diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index 37dd55ead..d402e8d44 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -82,8 +82,8 @@ "Peer connection": "Peer connection", " {{ peerConnectedCount }} Connected": " {{ peerConnectedCount }} Connected", "Total applications": "Total applications", - " {{ protectedAppSetsCount }} protected apps": " {{ protectedAppSetsCount }} protected apps", - " {{ appsWithIssues }} of {{ protectedAppSetsCount }} apps with issues": " {{ appsWithIssues }} of {{ protectedAppSetsCount }} apps with issues", + " {{ protectedAppCount }} protected apps": " {{ protectedAppCount }} protected apps", + " {{ appsWithIssues }} of {{ protectedAppCount }} apps with issues": " {{ appsWithIssues }} of {{ protectedAppCount }} apps with issues", "Current value: ": "Current value: ", "Max value: ": "Max value: ", "Min value: ": "Min value: ", diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/argo-application-set.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx similarity index 87% rename from packages/mco/components/mco-dashboard/data-policy/cluster-app-card/argo-application-set.tsx rename to packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx index 33fc273ad..b552f58d9 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/argo-application-set.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { DRPC_STATUS } from '@odf/mco/constants'; -import { PlacementInfo, ProtectedAppSetsMap } from '@odf/mco/types'; +import { PlacementInfo, ProtectedAppsMap } from '@odf/mco/types'; import { getDRStatus } from '@odf/mco/utils'; import { utcDateTimeFormatter } from '@odf/shared/details-page/datetime'; import { fromNow } from '@odf/shared/details-page/datetime'; @@ -47,10 +47,12 @@ const getCurrentActivity = ( } }; -export const ActivitySection: React.FC = ({ selectedAppSet }) => { +export const ActivitySection: React.FC = ({ + selectedApplication, +}) => { const { t } = useCustomTranslation(); - const placementInfo: PlacementInfo = selectedAppSet?.placementInfo?.[0]; + const placementInfo: PlacementInfo = selectedApplication?.placementInfo?.[0]; const currentStatus = placementInfo?.status; const failoverCluster = placementInfo?.failoverCluster; const preferredCluster = placementInfo?.preferredCluster; @@ -71,11 +73,13 @@ export const ActivitySection: React.FC = ({ selectedAppSet }) => { ); }; -export const SnapshotSection: React.FC = ({ selectedAppSet }) => { +export const SnapshotSection: React.FC = ({ + selectedApplication, +}) => { const { t } = useCustomTranslation(); const [lastSyncTime, setLastSyncTime] = React.useState('N/A'); const lastGroupSyncTime = - selectedAppSet?.placementInfo?.[0]?.lastGroupSyncTime; + selectedApplication?.placementInfo?.[0]?.lastGroupSyncTime; const clearSetIntervalId = React.useRef(); const updateSyncTime = React.useCallback(() => { if (!!lastGroupSyncTime) { @@ -108,6 +112,6 @@ export const SnapshotSection: React.FC = ({ selectedAppSet }) => { }; type CommonProps = { - selectedAppSet: ProtectedAppSetsMap; + selectedApplication: ProtectedAppsMap; lastSyncTimeData?: PrometheusResponse; }; diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.tsx index a05ae7ac5..48ff41060 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.tsx @@ -5,9 +5,9 @@ import { applicationDetails, } from '@odf/mco/constants'; import { - DrClusterAppsMap, - AppSetObj, - ProtectedAppSetsMap, + DRClusterAppsMap, + ApplicationObj, + ProtectedAppsMap, ACMManagedClusterViewKind, ProtectedPVCData, MirrorPeerKind, @@ -38,14 +38,14 @@ import { } from '../../../../models'; import { getProtectedPVCFromVRG, - filterPVCDataUsingAppsets, + filterPVCDataUsingApps, } from '../../../../utils'; import { getLastSyncPerClusterQuery } from '../../queries'; import { CSVStatusesContext, DRResourcesContext, } from '../dr-dashboard-context'; -import { ActivitySection, SnapshotSection } from './argo-application-set'; +import { ActivitySection, SnapshotSection } from './application'; import { HealthSection, PeerConnectionSection, @@ -113,26 +113,26 @@ export const ClusterWiseCard: React.FC = ({ export const AppWiseCard: React.FC = ({ protectedPVCData, - selectedAppSet, + selectedApplication, }) => { return ( - + - + @@ -141,7 +141,7 @@ export const AppWiseCard: React.FC = ({ export const ClusterAppCard: React.FC = () => { const [cluster, setCluster] = React.useState(); - const [appSet, setAppSet] = React.useState({ + const [application, setApplication] = React.useState({ namespace: undefined, name: ALL_APPS, }); @@ -170,39 +170,39 @@ export const ClusterAppCard: React.FC = () => { const allLoaded = loaded && !csvLoading && !lastSyncTimeLoading && mcvsLoaded; const anyError = lastSyncTimeError || csvError || loadError || mcvsLoadError; - const selectedAppSet: ProtectedAppSetsMap = React.useMemo(() => { - const { name, namespace } = appSet || {}; + const selectedApplication: ProtectedAppsMap = React.useMemo(() => { + const { name, namespace } = application || {}; return !!namespace && name !== ALL_APPS - ? drClusterAppsMap[cluster]?.protectedAppSets?.find( - (protectedAppSet) => - protectedAppSet?.appName === name && - protectedAppSet?.appNamespace === namespace + ? drClusterAppsMap[cluster]?.protectedApps?.find( + (protectedApp) => + protectedApp?.appName === name && + protectedApp?.appNamespace === namespace ) : undefined; - }, [appSet, drClusterAppsMap, cluster]); + }, [application, drClusterAppsMap, cluster]); const protectedPVCData: ProtectedPVCData[] = React.useMemo(() => { const pvcsData = (mcvsLoaded && !mcvsLoadError && getProtectedPVCFromVRG(mcvs)) || []; - const protectedAppSets = !!selectedAppSet - ? [selectedAppSet] - : drClusterAppsMap[cluster]?.protectedAppSets; - return filterPVCDataUsingAppsets(pvcsData, protectedAppSets); + const protectedApps = !!selectedApplication + ? [selectedApplication] + : drClusterAppsMap[cluster]?.protectedApps; + return filterPVCDataUsingApps(pvcsData, protectedApps); }, [ drClusterAppsMap, - selectedAppSet, + selectedApplication, cluster, mcvs, mcvsLoaded, mcvsLoadError, ]); - const apiVersion = `${selectedAppSet?.appKind?.toLowerCase()}.${ - selectedAppSet?.appAPIVersion?.split('/')[0] + const apiVersion = `${selectedApplication?.appKind?.toLowerCase()}.${ + selectedApplication?.appAPIVersion?.split('/')[0] }`; const applicationDetailsPath = applicationDetails - .replace(':namespace', appSet.namespace) - .replace(':name', appSet.name) + + .replace(':namespace', application.namespace) + .replace(':name', application.name) + '?apiVersion=' + apiVersion; @@ -215,17 +215,14 @@ export const ClusterAppCard: React.FC = () => { - {!!appSet.namespace ? ( - - {appSet.name} + {!!application.namespace ? ( + + {application.name} ) : ( cluster @@ -234,7 +231,7 @@ export const ClusterAppCard: React.FC = () => { - {!appSet.namespace && appSet.name === ALL_APPS ? ( + {!application.namespace && application.name === ALL_APPS ? ( { ) : ( )} @@ -268,10 +265,10 @@ type ClusterWiseCardProps = { lastSyncTimeData: PrometheusResponse; protectedPVCData: ProtectedPVCData[]; csvData: PrometheusResponse; - clusterResources: DrClusterAppsMap; + clusterResources: DRClusterAppsMap; }; type AppWiseCardProps = { protectedPVCData: ProtectedPVCData[]; - selectedAppSet: ProtectedAppSetsMap; + selectedApplication: ProtectedAppsMap; }; diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx index d5e3673d8..808ea1da8 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx @@ -16,7 +16,7 @@ import { OBJECT_NAME, MANAGED_CLUSTER_CONDITION_AVAILABLE, } from '@odf/mco/constants'; -import { DrClusterAppsMap, PlacementInfo } from '@odf/mco/types'; +import { DRClusterAppsMap, PlacementInfo } from '@odf/mco/types'; import { getVolumeReplicationHealth, ValidateManagedClusterCondition, @@ -178,10 +178,10 @@ export const ApplicationsSection: React.FC = ({ const appsWithIssues = React.useMemo( () => - clusterResources[clusterName]?.protectedAppSets?.reduce( - (acc, protectedAppSetsMap) => { + clusterResources[clusterName]?.protectedApps?.reduce( + (acc, protectedAppsMap) => { const placementInfo: PlacementInfo = - protectedAppSetsMap?.placementInfo?.[0]; + protectedAppsMap?.placementInfo?.[0]; const hasIssue = !!lastSyncTimeData?.data?.result?.find( (item: PrometheusResult) => item?.metric?.[OBJECT_NAMESPACE] === @@ -192,6 +192,7 @@ export const ApplicationsSection: React.FC = ({ placementInfo?.syncInterval )[0] !== VOLUME_REPLICATION_HEALTH.HEALTHY ); + return hasIssue ? acc + 1 : acc; }, 0 @@ -199,22 +200,22 @@ export const ApplicationsSection: React.FC = ({ [clusterResources, clusterName, lastSyncTimeData] ); - const totalAppSetsCount = clusterResources[clusterName]?.totalAppSetsCount; - const protectedAppSetsCount = - clusterResources[clusterName]?.protectedAppSets?.length; + const totalAppSetsCount = clusterResources[clusterName]?.totalAppCount; + const protectedAppCount = + clusterResources[clusterName]?.protectedApps?.length; return (
{totalAppSetsCount || 0} {t('Total applications')} - {t(' {{ protectedAppSetsCount }} protected apps', { - protectedAppSetsCount, + {t(' {{ protectedAppCount }} protected apps', { + protectedAppCount, })} {t( - ' {{ appsWithIssues }} of {{ protectedAppSetsCount }} apps with issues', - { appsWithIssues, protectedAppSetsCount } + ' {{ appsWithIssues }} of {{ protectedAppCount }} apps with issues', + { appsWithIssues, protectedAppCount } )}
@@ -354,7 +355,7 @@ type OperatorsHealthPopUpProps = { }; type HealthSectionProps = { - clusterResources: DrClusterAppsMap; + clusterResources: DRClusterAppsMap; csvData: PrometheusResponse; clusterName: string; }; @@ -364,7 +365,7 @@ type PeerConnectionSectionProps = { }; type ApplicationsSectionProps = { - clusterResources: DrClusterAppsMap; + clusterResources: DRClusterAppsMap; clusterName: string; lastSyncTimeData: PrometheusResponse; }; diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/common.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/common.tsx index 0f9df0969..97c2188da 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/common.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/common.tsx @@ -10,9 +10,9 @@ import { VOLUME_REPLICATION_HEALTH, } from '@odf/mco/constants'; import { - DrClusterAppsMap, - ProtectedAppSetsMap, - AppSetObj, + DRClusterAppsMap, + ProtectedAppsMap, + ApplicationObj, ProtectedPVCData, PlacementInfo, } from '@odf/mco/types'; @@ -61,7 +61,7 @@ export const StatusText: React.FC = ({ children }) => { export const VolumeSummarySection: React.FC = ({ protectedPVCData, - selectedAppSet, + selectedApplication, }) => { const { t } = useCustomTranslation(); const [summary, setSummary] = React.useState({ @@ -73,7 +73,8 @@ export const VolumeSummarySection: React.FC = ({ const updateSummary = React.useCallback(() => { const volumeHealth = { critical: 0, warning: 0, healthy: 0 }; - const placementInfo: PlacementInfo = selectedAppSet?.placementInfo?.[0]; + const placementInfo: PlacementInfo = + selectedApplication?.placementInfo?.[0]; protectedPVCData?.forEach((pvcData) => { const pvcLastSyncTime = pvcData?.lastSyncTime; const health = getVolumeReplicationHealth( @@ -82,7 +83,7 @@ export const VolumeSummarySection: React.FC = ({ : LEAST_SECONDS_IN_PROMETHEUS, pvcData?.schedulingInterval )[0]; - if (!!selectedAppSet) { + if (!!selectedApplication) { pvcData?.drpcName === placementInfo?.drpcName && pvcData?.drpcNamespace === placementInfo?.drpcNamespace && volumeHealth[health]++; @@ -91,7 +92,7 @@ export const VolumeSummarySection: React.FC = ({ } }); setSummary(volumeHealth); - }, [selectedAppSet, protectedPVCData, setSummary]); + }, [selectedApplication, protectedPVCData, setSummary]); React.useEffect(() => { updateSummary(); @@ -151,7 +152,7 @@ const ClusterDropdown: React.FC> = ({ clusterResources, clusterName, setCluster, - setAppSet, + setApplication, className, }) => { const { t } = useCustomTranslation(); @@ -182,7 +183,7 @@ const ClusterDropdown: React.FC> = ({ selection: string ) => { setCluster(selection); - setAppSet({ + setApplication({ namespace: undefined, name: ALL_APPS, }); @@ -226,23 +227,23 @@ const ClusterDropdown: React.FC> = ({ const AppDropdown: React.FC> = ({ clusterResources, clusterName, - appSet, - setAppSet, + application, + setApplication, className, }) => { const { t } = useCustomTranslation(); const [isOpen, setIsOpen] = React.useState(false); const menuRef = React.useRef(null); - const { name, namespace } = appSet; + const { name, namespace } = application; const selected = !namespace ? ALL_APPS_ITEM_ID : `${namespace}%#%${name}`; const options: AppOptions = React.useMemo( () => - clusterResources[clusterName]?.protectedAppSets?.reduce( - (acc, protectedAppSet) => { - const appName = protectedAppSet?.appName; - const appNs = protectedAppSet?.appNamespace; + clusterResources[clusterName]?.protectedApps?.reduce( + (acc, protectedApp) => { + const appName = protectedApp?.appName; + const appNs = protectedApp?.appNamespace; if (!acc.hasOwnProperty(appNs)) acc[appNs] = [appName]; else acc[appNs].push(appName); return acc; @@ -261,7 +262,7 @@ const AppDropdown: React.FC> = ({ itemId: string ) => { const [itemNamespace, itemName] = getNSAndNameFromId(itemId); - setAppSet({ namespace: itemNamespace, name: itemName }); + setApplication({ namespace: itemNamespace, name: itemName }); setIsOpen(false); }; @@ -316,9 +317,9 @@ const AppDropdown: React.FC> = ({ export const ClusterAppDropdown: React.FC = ({ clusterResources, clusterName, - appSet, + application, setCluster, - setAppSet, + setApplication, }) => { return ( @@ -327,7 +328,7 @@ export const ClusterAppDropdown: React.FC = ({ clusterResources={clusterResources} clusterName={clusterName} setCluster={setCluster} - setAppSet={setAppSet} + setApplication={setApplication} className="mco-cluster-app__dropdown--padding" /> @@ -335,8 +336,8 @@ export const ClusterAppDropdown: React.FC = ({ @@ -345,7 +346,7 @@ export const ClusterAppDropdown: React.FC = ({ export const ProtectedPVCsSection: React.FC = ({ protectedPVCData, - selectedAppSet, + selectedApplication, }) => { const { t } = useCustomTranslation(); const clearSetIntervalId = React.useRef(); @@ -353,7 +354,7 @@ export const ProtectedPVCsSection: React.FC = ({ const [protectedPVCsCount, pvcsWithIssueCount] = protectedPVC; const updateProtectedPVC = React.useCallback(() => { - const placementInfo = selectedAppSet?.placementInfo?.[0]; + const placementInfo = selectedApplication?.placementInfo?.[0]; const issueCount = protectedPVCData?.reduce((acc, protectedPVCItem) => { const pvcLastSyncTime = protectedPVCItem?.lastSyncTime; @@ -364,7 +365,7 @@ export const ProtectedPVCsSection: React.FC = ({ protectedPVCItem?.schedulingInterval )[0]; - (!!selectedAppSet + (!!selectedApplication ? protectedPVCItem?.drpcName === placementInfo?.drpcName && protectedPVCItem?.drpcNamespace === placementInfo?.drpcNamespace && replicationHealth !== VOLUME_REPLICATION_HEALTH.HEALTHY @@ -373,7 +374,7 @@ export const ProtectedPVCsSection: React.FC = ({ return acc; }, 0) || 0; setProtectedPVC([protectedPVCData?.length || 0, issueCount]); - }, [selectedAppSet, protectedPVCData, setProtectedPVC]); + }, [selectedApplication, protectedPVCData, setProtectedPVC]); React.useEffect(() => { updateProtectedPVC(); @@ -397,23 +398,23 @@ export const ProtectedPVCsSection: React.FC = ({ type ProtectedPVCsSectionProps = { protectedPVCData: ProtectedPVCData[]; - selectedAppSet?: ProtectedAppSetsMap; + selectedApplication?: ProtectedAppsMap; }; type VolumeSummarySectionProps = { protectedPVCData: ProtectedPVCData[]; - selectedAppSet?: ProtectedAppSetsMap; + selectedApplication?: ProtectedAppsMap; }; type ClusterAppDropdownProps = { - clusterResources: DrClusterAppsMap; + clusterResources: DRClusterAppsMap; clusterName: string; - appSet: { + application: { name: string; namespace: string; }; setCluster: React.Dispatch>; - setAppSet: React.Dispatch>; + setApplication: React.Dispatch>; className?: string; }; diff --git a/packages/mco/components/mco-dashboard/data-policy/dr-dashboard-context.tsx b/packages/mco/components/mco-dashboard/data-policy/dr-dashboard-context.tsx index d7e6e2201..a281636ab 100644 --- a/packages/mco/components/mco-dashboard/data-policy/dr-dashboard-context.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/dr-dashboard-context.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; +import { DRClusterAppsMap } from '@odf/mco/types'; import { PrometheusResponse } from '@openshift-console/dynamic-plugin-sdk'; -import { DrClusterAppsMap } from '../../../types'; export const CSVStatusesContext = React.createContext( {} as CSVStatusesContextType @@ -17,7 +17,7 @@ type CSVStatusesContextType = { }; type DRResourcesContextType = { - drClusterAppsMap: DrClusterAppsMap; + drClusterAppsMap: DRClusterAppsMap; loaded: boolean; loadError: any; }; diff --git a/packages/mco/components/mco-dashboard/data-policy/dr-dashboard.tsx b/packages/mco/components/mco-dashboard/data-policy/dr-dashboard.tsx index a3b010d5a..b3aef3b7d 100644 --- a/packages/mco/components/mco-dashboard/data-policy/dr-dashboard.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/dr-dashboard.tsx @@ -1,51 +1,24 @@ import * as React from 'react'; +import { ACMManagedClusterKind, DRClusterAppsMap } from '@odf/mco/types'; import { useCustomPrometheusPoll } from '@odf/shared/hooks/custom-prometheus-poll'; -import { getName, getNamespace } from '@odf/shared/selectors'; -import * as _ from 'lodash-es'; +import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { Grid, GridItem } from '@patternfly/react-core'; +import { ACM_ENDPOINT, HUB_CLUSTER_NAME } from '../../../constants'; import { - ACM_ENDPOINT, - APPLICATION_TYPE, - DRPC_STATUS, - HUB_CLUSTER_NAME, -} from '../../../constants'; -import { - DisasterRecoveryResourceKind, - useArgoApplicationSetResourceWatch, + getManagedClusterResourceObj, useDisasterRecoveryResourceWatch, } from '../../../hooks'; -import { - DRClusterKind, - ACMManagedClusterKind, - DrClusterAppsMap, -} from '../../../types'; -import { - findDRType, - findDeploymentClusters, - getProtectedPVCsFromDRPC, - getRemoteNamespaceFromAppSet, -} from '../../../utils'; import { StorageDashboard, STATUS_QUERIES } from '../queries'; import { AlertsCard } from './alert-card/alert-card'; import { ClusterAppCard } from './cluster-app-card/cluster-app-card'; import { CSVStatusesContext, DRResourcesContext } from './dr-dashboard-context'; +import { useApplicationSetParser } from './parsers/applicationset-parser'; +import { useSubscriptionParser } from './parsers/subscription-parser'; import { StatusCard } from './status-card/status-card'; import { SummaryCard } from './summary-card/summary-card'; import '../mco-dashboard.scss'; import '../../../style.scss'; -const getApplicationSetResources = ( - drResources: DisasterRecoveryResourceKind, - drLoaded: boolean, - drLoadError: any -) => ({ - drResources: { - data: drResources, - loaded: drLoaded, - loadError: drLoadError, - }, -}); - const UpperSection: React.FC = () => ( @@ -63,7 +36,30 @@ const UpperSection: React.FC = () => ( ); -export const DRDashboard: React.FC = () => { +const aggregateAppsMap = ( + clusterAppsList: DRClusterAppsMap[] +): DRClusterAppsMap => + clusterAppsList.reduce((acc, clusterAppsMap) => { + Object.keys(clusterAppsMap).forEach((clusterName) => { + const { managedCluster, totalAppCount, protectedApps } = + clusterAppsMap[clusterName]; + + if (!acc.hasOwnProperty(clusterName)) { + acc[clusterName] = { + managedCluster: managedCluster, + totalAppCount: totalAppCount, + protectedApps: protectedApps, + }; + } else { + acc[clusterName].totalAppCount += totalAppCount; + acc[clusterName].protectedApps = + acc[clusterName].protectedApps.concat(protectedApps); + } + }); + return acc; + }, {}); + +const DRDashboard: React.FC = () => { const [csvData, csvError, csvLoading] = useCustomPrometheusPoll({ endpoint: 'api/v1/query' as any, query: STATUS_QUERIES[StorageDashboard.CSV_STATUS_ALL_WHITELISTED], @@ -73,94 +69,44 @@ export const DRDashboard: React.FC = () => { const [drResources, drLoaded, drLoadError] = useDisasterRecoveryResourceWatch(); - const [argoApplicationSetResources, loaded, loadError] = - useArgoApplicationSetResourceWatch( - getApplicationSetResources(drResources, drLoaded, drLoadError) + + const [managedClusters, managedClusterLoaded, managedClusterLoadError] = + useK8sWatchResource( + getManagedClusterResourceObj() + ); + + const [subscriptions, subscriptionLoaded, subscriptionLoadError] = + useSubscriptionParser( + drResources, + drLoaded, + drLoadError, + managedClusters, + managedClusterLoaded, + managedClusterLoadError ); - const drClusters: DRClusterKind[] = drResources?.drClusters; - const managedClusters: ACMManagedClusterKind[] = - argoApplicationSetResources?.managedClusters; - const formattedArgoAppSetResources = - argoApplicationSetResources?.formattedResources; + const [applicationSets, applicationSetLoaded, applicationSetLoadError] = + useApplicationSetParser( + drResources, + drLoaded, + drLoadError, + managedClusters, + managedClusterLoaded, + managedClusterLoadError + ); - const drClusterAppsMap: DrClusterAppsMap = React.useMemo(() => { - if (loaded && !loadError) { - // DRCluster to its ManagedCluster mapping - const drClusterAppsMap: DrClusterAppsMap = drClusters.reduce( - (acc, drCluster) => { - acc[getName(drCluster)] = { - managedCluster: managedClusters.find( - (managedCluster) => getName(managedCluster) === getName(drCluster) - ), - totalAppSetsCount: 0, - protectedAppSets: [], - }; - return acc; - }, - {} as DrClusterAppsMap - ); + const loaded = applicationSetLoaded && subscriptionLoaded; + const loadError = applicationSetLoadError || subscriptionLoadError; - // DRCluster to its ApplicationSets (total and protected) mapping - formattedArgoAppSetResources.forEach((argoApplicationSetResource) => { - const { application } = argoApplicationSetResource || {}; - const { - drClusters: currentDrClusters, - drPlacementControl, - drPolicy, - placementDecision, - } = argoApplicationSetResource?.placements?.[0] || {}; - const deploymentClusters = findDeploymentClusters( - placementDecision, - drPlacementControl - ); - deploymentClusters.forEach((decisionCluster) => { - if (drClusterAppsMap.hasOwnProperty(decisionCluster)) { - drClusterAppsMap[decisionCluster].totalAppSetsCount = - drClusterAppsMap[decisionCluster].totalAppSetsCount + 1; - if (!_.isEmpty(drPlacementControl)) { - drClusterAppsMap[decisionCluster].protectedAppSets.push({ - appName: getName(application), - appNamespace: getNamespace(application), - appKind: application?.kind, - appAPIVersion: application?.apiVersion, - appType: APPLICATION_TYPE.APPSET, - placementInfo: [ - { - deploymentClusterName: decisionCluster, - drpcName: getName(drPlacementControl), - drpcNamespace: getNamespace(drPlacementControl), - protectedPVCs: getProtectedPVCsFromDRPC(drPlacementControl), - replicationType: findDRType(currentDrClusters), - syncInterval: drPolicy?.spec?.schedulingInterval, - workloadNamespace: - getRemoteNamespaceFromAppSet(application), - failoverCluster: drPlacementControl?.spec?.failoverCluster, - preferredCluster: - drPlacementControl?.spec?.preferredCluster, - lastGroupSyncTime: - drPlacementControl?.status?.lastGroupSyncTime, - status: drPlacementControl?.status?.phase as DRPC_STATUS, - }, - ], - }); - } - } - }); - }); - return drClusterAppsMap; + const aggregatedAppsMap = React.useMemo(() => { + if (!!loaded && !loadError) { + return aggregateAppsMap([subscriptions, applicationSets]); } return {}; - }, [ - drClusters, - managedClusters, - formattedArgoAppSetResources, - loaded, - loadError, - ]); + }, [applicationSets, subscriptions, loaded, loadError]); const dRResourcesContext = { - drClusterAppsMap, + drClusterAppsMap: aggregatedAppsMap, loaded, loadError, }; diff --git a/packages/mco/components/mco-dashboard/data-policy/parsers/applicationset-parser.tsx b/packages/mco/components/mco-dashboard/data-policy/parsers/applicationset-parser.tsx new file mode 100644 index 000000000..1b14a3770 --- /dev/null +++ b/packages/mco/components/mco-dashboard/data-policy/parsers/applicationset-parser.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import { APPLICATION_TYPE, DRPC_STATUS } from '@odf/mco/constants'; +import { + DisasterRecoveryResourceKind, + getApplicationSetResourceObj, + getPlacementDecisionsResourceObj, + getPlacementResourceObj, + useArgoApplicationSetResourceWatch, +} from '@odf/mco/hooks'; +import { + ACMManagedClusterKind, + DRClusterAppsMap, + DRClusterKind, +} from '@odf/mco/types'; +import { + findDRType, + findDeploymentClusters, + getProtectedPVCsFromDRPC, + getRemoteNamespaceFromAppSet, +} from '@odf/mco/utils'; +import { getName, getNamespace } from '@odf/shared/selectors'; +import * as _ from 'lodash-es'; + +const getApplicationSetResources = ( + managedClusters: ACMManagedClusterKind[], + managedClusterLoaded: boolean, + managedClusterLoadError: any, + drResources: DisasterRecoveryResourceKind, + drLoaded: boolean, + drLoadError: any +) => ({ + resources: { + applications: getApplicationSetResourceObj(), + placements: getPlacementResourceObj(), + placementDecisions: getPlacementDecisionsResourceObj(), + }, + drResources: { + data: drResources, + loaded: drLoaded, + loadError: drLoadError, + }, + overrides: { + managedClusters: { + data: managedClusters, + loaded: managedClusterLoaded, + loadError: managedClusterLoadError, + }, + }, +}); + +export const useApplicationSetParser: UseApplicationSetParser = ( + drResources, + drLoaded, + drLoadError, + managedClusters, + managedClusterLoaded, + managedClusterLoadError +) => { + const [argoApplicationSetResources, loaded, loadError] = + useArgoApplicationSetResourceWatch( + getApplicationSetResources( + managedClusters, + managedClusterLoaded, + managedClusterLoadError, + drResources, + drLoaded, + drLoadError + ) + ); + + const drClusters: DRClusterKind[] = drResources?.drClusters; + const formattedArgoAppSetResources = + argoApplicationSetResources?.formattedResources; + + const drClusterAppsMap: DRClusterAppsMap = React.useMemo(() => { + if (loaded && !loadError) { + const drClusterAppsMap: DRClusterAppsMap = drClusters.reduce( + (acc, drCluster) => { + const clusterName = getName(drCluster); + acc[clusterName] = { + managedCluster: managedClusters.find( + (managedCluster) => getName(managedCluster) === clusterName + ), + totalAppCount: 0, + protectedApps: [], + }; + return acc; + }, + {} as DRClusterAppsMap + ); + + // DRCluster to its ApplicationSets (total and protected) mapping + formattedArgoAppSetResources.forEach((argoApplicationSetResource) => { + const { application } = argoApplicationSetResource || {}; + const { + drClusters: currentDRClusters, + drPlacementControl, + drPolicy, + placementDecision, + } = argoApplicationSetResource.placements?.[0] || {}; + const deploymentClusters = findDeploymentClusters( + placementDecision, + drPlacementControl + ); + deploymentClusters.forEach((decisionCluster) => { + if (drClusterAppsMap.hasOwnProperty(decisionCluster)) { + drClusterAppsMap[decisionCluster].totalAppCount = + drClusterAppsMap[decisionCluster].totalAppCount + 1; + if (!_.isEmpty(drPlacementControl)) { + drClusterAppsMap[decisionCluster].protectedApps.push({ + appName: getName(application), + appNamespace: getNamespace(application), + appKind: application?.kind, + appAPIVersion: application?.apiVersion, + appType: APPLICATION_TYPE.APPSET, + placementInfo: [ + { + deploymentClusterName: decisionCluster, + drpcName: getName(drPlacementControl), + drpcNamespace: getNamespace(drPlacementControl), + protectedPVCs: getProtectedPVCsFromDRPC(drPlacementControl), + replicationType: findDRType(currentDRClusters), + syncInterval: drPolicy?.spec?.schedulingInterval, + workloadNamespace: + getRemoteNamespaceFromAppSet(application), + failoverCluster: drPlacementControl?.spec?.failoverCluster, + preferredCluster: + drPlacementControl?.spec?.preferredCluster, + lastGroupSyncTime: + drPlacementControl?.status?.lastGroupSyncTime, + status: drPlacementControl?.status?.phase as DRPC_STATUS, + }, + ], + }); + } + } + }); + }); + return drClusterAppsMap; + } + return {}; + }, [ + drClusters, + formattedArgoAppSetResources, + managedClusters, + loaded, + loadError, + ]); + + return [drClusterAppsMap, loaded, loadError]; +}; + +type UseApplicationSetParserResult = [DRClusterAppsMap, boolean, any]; + +type UseApplicationSetParser = ( + drResources: DisasterRecoveryResourceKind, + drLoaded: boolean, + drLoadError: any, + managedClusters: ACMManagedClusterKind[], + managedClusterLoaded: boolean, + managedClusterLoadError: any +) => UseApplicationSetParserResult; diff --git a/packages/mco/components/mco-dashboard/data-policy/parsers/subscription-parser.tsx b/packages/mco/components/mco-dashboard/data-policy/parsers/subscription-parser.tsx new file mode 100644 index 000000000..adc56a053 --- /dev/null +++ b/packages/mco/components/mco-dashboard/data-policy/parsers/subscription-parser.tsx @@ -0,0 +1,191 @@ +import * as React from 'react'; +import { APPLICATION_TYPE, DRPC_STATUS } from '@odf/mco/constants'; +import { + DisasterRecoveryResourceKind, + SubscriptionGroupType, + useSubscriptionResourceWatch, +} from '@odf/mco/hooks'; +import { ACMPlacementModel } from '@odf/mco/models'; +import { + ACMManagedClusterKind, + ACMPlacementRuleKind, + DRClusterAppsMap, + DRClusterKind, + PlacementInfo, + ProtectedAppsMap, +} from '@odf/mco/types'; +import { + findDRType, + getClustersFromDecisions, + getProtectedPVCsFromDRPC, +} from '@odf/mco/utils'; +import { ApplicationKind } from '@odf/shared'; +import { getName, getNamespace } from '@odf/shared/selectors'; +import * as _ from 'lodash-es'; + +const createPlacementInfoList = ( + subscriptionGroupsList: SubscriptionGroupType[], + clusterName: string, + applicationNamespace: string +): PlacementInfo[] => { + const placementInfoList: PlacementInfo[] = []; + + subscriptionGroupsList.forEach((subscriptionGroup) => { + const { drInfo } = subscriptionGroup; + if (!_.isEmpty(drInfo)) { + const { + drPlacementControl, + drPolicy, + drClusters: currentDRClusters, + } = drInfo; + + const placementInfo = { + deploymentClusterName: clusterName, + drpcName: getName(drPlacementControl), + drpcNamespace: getNamespace(drPlacementControl), + protectedPVCs: getProtectedPVCsFromDRPC(drPlacementControl), + replicationType: findDRType(currentDRClusters), + syncInterval: drPolicy?.spec?.schedulingInterval, + workloadNamespace: applicationNamespace, + failoverCluster: drPlacementControl?.spec?.failoverCluster, + preferredCluster: drPlacementControl?.spec?.preferredCluster, + lastGroupSyncTime: drPlacementControl?.status?.lastGroupSyncTime, + status: drPlacementControl?.status?.phase as DRPC_STATUS, + }; + + placementInfoList.push(placementInfo); + } + }); + + return placementInfoList; +}; + +const createProtectedAppMap = ( + application: ApplicationKind, + clusterName: string, + subscriptionGroupsList: SubscriptionGroupType[] +): ProtectedAppsMap => { + const applicationNamespace = getNamespace(application); + const protectedApp: ProtectedAppsMap = { + appName: getName(application), + appNamespace: applicationNamespace, + appKind: application?.kind, + appAPIVersion: application?.apiVersion, + appType: APPLICATION_TYPE.SUBSCRIPTION, + placementInfo: createPlacementInfoList( + subscriptionGroupsList, + clusterName, + applicationNamespace + ), + }; + + return protectedApp; +}; + +const createClusterWiseSubscriptionGroupsMap = ( + subscriptionGroupInfo: SubscriptionGroupType[] +): ClusterWiseSubscriptionGroupsMap => { + const clusterWiseSubscriptionGroups: ClusterWiseSubscriptionGroupsMap = {}; + + subscriptionGroupInfo?.forEach((subscriptionGroup) => { + const deploymentClusters: string[] = + subscriptionGroup.placement?.kind === ACMPlacementModel.kind + ? getClustersFromDecisions(subscriptionGroup.placementDecision) + : getClustersFromDecisions( + subscriptionGroup.placement as ACMPlacementRuleKind + ); + + deploymentClusters?.forEach((decisionCluster) => { + clusterWiseSubscriptionGroups[decisionCluster] = + clusterWiseSubscriptionGroups[decisionCluster] || []; + clusterWiseSubscriptionGroups[decisionCluster].push(subscriptionGroup); + }); + }); + + return clusterWiseSubscriptionGroups; +}; + +export const useSubscriptionParser: UseSubscriptionParser = ( + drResources, + drLoaded, + drLoadError, + managedClusters, + managedClusterLoaded, + managedClusterLoadError +) => { + const [subscriptionResources, subsResourceLoaded, subsResourceLoadError] = + useSubscriptionResourceWatch({ + drResources: { + data: drResources, + loaded: drLoaded, + loadError: drLoadError, + }, + }); + + const loaded = subsResourceLoaded && managedClusterLoaded; + const loadError = subsResourceLoadError || managedClusterLoadError; + const drClusters: DRClusterKind[] = drResources?.drClusters; + const drClusterAppsMap: DRClusterAppsMap = React.useMemo(() => { + if (loaded && !loadError) { + const drClusterAppsMap: DRClusterAppsMap = drClusters.reduce( + (acc, drCluster) => { + const clusterName = getName(drCluster); + acc[clusterName] = { + totalAppCount: 0, + protectedApps: [], + managedCluster: managedClusters.find( + (managedCluster) => getName(managedCluster) === clusterName + ), + }; + return acc; + }, + {} as DRClusterAppsMap + ); + + subscriptionResources.forEach((subscriptionResource) => { + const { application, subscriptionGroupInfo } = + subscriptionResource || {}; + + const clusterWiseSubscriptionGroups = + createClusterWiseSubscriptionGroupsMap(subscriptionGroupInfo); + + Object.entries(clusterWiseSubscriptionGroups).forEach( + ([clusterName, subscriptionGroupsList]) => { + if (clusterName in drClusterAppsMap) { + drClusterAppsMap[clusterName].totalAppCount += 1; + const protectedApp = createProtectedAppMap( + application, + clusterName, + subscriptionGroupsList + ); + + if (!!protectedApp.placementInfo.length) { + drClusterAppsMap[clusterName].protectedApps.push(protectedApp); + } + } + } + ); + }); + return drClusterAppsMap; + } + + return {}; + }, [subscriptionResources, managedClusters, drClusters, loaded, loadError]); + + return [drClusterAppsMap, loaded, loadError]; +}; + +type UseSubscriptionParserResult = [DRClusterAppsMap, boolean, any]; + +type UseSubscriptionParser = ( + drResources: DisasterRecoveryResourceKind, + drLoaded: boolean, + drLoadError: any, + managedClusters: ACMManagedClusterKind[], + managedClusterLoaded: boolean, + managedClusterLoadError: any +) => UseSubscriptionParserResult; + +type ClusterWiseSubscriptionGroupsMap = { + [clusterName: string]: SubscriptionGroupType[]; +}; diff --git a/packages/mco/components/mco-dashboard/data-policy/status-card/status-card.tsx b/packages/mco/components/mco-dashboard/data-policy/status-card/status-card.tsx index e234789b0..a78f13966 100644 --- a/packages/mco/components/mco-dashboard/data-policy/status-card/status-card.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/status-card/status-card.tsx @@ -30,7 +30,7 @@ import { TextVariants, Text, } from '@patternfly/react-core'; -import { DrClusterAppsMap } from '../../../../types'; +import { DRClusterAppsMap } from '../../../../types'; import { CSVStatusesContext, DRResourcesContext, @@ -65,7 +65,7 @@ const getDRCombinedStatus = ( const getClustersOperatorHealth = ( csvManagedData: PrometheusResponse, - drClusterAppsMap: DrClusterAppsMap + drClusterAppsMap: DRClusterAppsMap ) => { const drClusters = Object.keys(drClusterAppsMap); const clusterWiseHealth: ClusterWiseHealth = diff --git a/packages/mco/components/mco-dashboard/data-policy/summary-card/summary-card.tsx b/packages/mco/components/mco-dashboard/data-policy/summary-card/summary-card.tsx index a2f2a8c2d..f6ac170b6 100644 --- a/packages/mco/components/mco-dashboard/data-policy/summary-card/summary-card.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/summary-card/summary-card.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { MANAGED_CLUSTER_CONDITION_AVAILABLE } from '@odf/mco/constants'; -import { DrClusterAppsMap } from '@odf/mco/types'; +import { DRClusterAppsMap } from '@odf/mco/types'; import { ValidateManagedClusterCondition } from '@odf/mco/utils'; import HealthItem from '@odf/shared/dashboards/status-card/HealthItem'; import { FieldLevelHelp } from '@odf/shared/generic'; @@ -31,7 +31,7 @@ type SummaryMap = { }; const getClusterSummary = ( - drClusterAppsMap: DrClusterAppsMap, + drClusterAppsMap: DRClusterAppsMap, loaded: boolean, loadError: any ): SummaryMap => { @@ -45,10 +45,10 @@ const getClusterSummary = ( drClusters?.forEach((cluster) => { summaryMap.applications.totalCount = summaryMap.applications.totalCount + - drClusterAppsMap[cluster].totalAppSetsCount; + drClusterAppsMap[cluster].totalAppCount; summaryMap.applications.protectedCount = summaryMap.applications.protectedCount + - drClusterAppsMap[cluster].protectedAppSets.length; + drClusterAppsMap[cluster].protectedApps.length; const isClusterHealthy = ValidateManagedClusterCondition( drClusterAppsMap[cluster]?.managedCluster, MANAGED_CLUSTER_CONDITION_AVAILABLE diff --git a/packages/mco/components/modals/app-manage-policies/parsers/subscription-parser.tsx b/packages/mco/components/modals/app-manage-policies/parsers/subscription-parser.tsx index 514dd3e89..97afb08e6 100644 --- a/packages/mco/components/modals/app-manage-policies/parsers/subscription-parser.tsx +++ b/packages/mco/components/modals/app-manage-policies/parsers/subscription-parser.tsx @@ -7,6 +7,10 @@ import { getDRPolicyResourceObj, useSubscriptionResourceWatch, useDisasterRecoveryResourceWatch, + getPlacementResourceObj, + getPlacementDecisionsResourceObj, + getSubscriptionResourceObj, + getPlacementRuleResourceObj, } from '@odf/mco/hooks'; import { ACMPlacementModel } from '@odf/mco/models'; import { @@ -48,16 +52,37 @@ const getDRResources = (namespace: string) => ({ const getSubscriptionResources = ( appResource: ApplicationKind, + namespace: string, drResources: DisasterRecoveryResourceKind, drLoaded: boolean, drLoadError: any ) => ({ + resources: { + subscriptions: getSubscriptionResourceObj({ + namespace, + }), + placementRules: getPlacementRuleResourceObj({ + namespace, + }), + placements: getPlacementResourceObj({ + namespace, + }), + placementDecisions: getPlacementDecisionsResourceObj({ + namespace, + }), + }, drResources: { data: drResources, loaded: drLoaded, loadError: drLoadError, }, - application: appResource, + overrides: { + applications: { + data: appResource, + loaded: true, + loadError: '', + }, + }, }); export const SubscriptionParser: React.FC = ({ @@ -69,25 +94,27 @@ export const SubscriptionParser: React.FC = ({ const [drResources, drLoaded, drLoadError] = useDisasterRecoveryResourceWatch( getDRResources(getNamespace(application)) ); - const [subscriptionResources, loaded, loadError] = + const [subscriptionResourceList, loaded, loadError] = useSubscriptionResourceWatch( - getSubscriptionResources(application, drResources, drLoaded, drLoadError) + getSubscriptionResources( + application, + getNamespace(application), + drResources, + drLoaded, + drLoadError + ) ); const formattedDRResources = drResources?.formattedResources; + const subscriptionResources = subscriptionResourceList?.[0]; const applicationInfo: ApplicationInfoType = React.useMemo(() => { let applicationInfo: ApplicationInfoType = {}; if (loaded && !loadError) { const unProtectedPlacements: PlacementType[] = []; const drPolicyToDPRCMap: DRPolicyToDRPCMap = {}; - subscriptionResources.forEach( - ({ - placement, - drClusters, - drPlacementControl, - drPolicy, - placementDecision, - }) => { + subscriptionResources?.subscriptionGroupInfo?.forEach( + ({ placement, placementDecision, drInfo }) => { + const { drClusters, drPlacementControl, drPolicy } = drInfo || {}; const deploymentClusters: string[] = placement.kind === ACMPlacementModel.kind ? getClustersFromDecisions(placementDecision) diff --git a/packages/mco/constants/dashboard.ts b/packages/mco/constants/dashboard.ts index 47ac4979d..db8cd5926 100644 --- a/packages/mco/constants/dashboard.ts +++ b/packages/mco/constants/dashboard.ts @@ -1,4 +1,4 @@ -export const ALL_APPS = 'All ApplicationSet'; +export const ALL_APPS = 'All Application'; export const ALL_APPS_ITEM_ID = 'all-applications-itemid'; // Volume replication threshold diff --git a/packages/mco/hooks/argo-application-set.ts b/packages/mco/hooks/argo-application-set.ts index 9a04f35d1..e18fa83a3 100644 --- a/packages/mco/hooks/argo-application-set.ts +++ b/packages/mco/hooks/argo-application-set.ts @@ -184,7 +184,9 @@ type WatchResources = { applications?: WatchK8sResultsObject; placements?: WatchK8sResultsObject; placementDecisions?: WatchK8sResultsObject; - managedClusters?: WatchK8sResultsObject; + managedClusters?: WatchK8sResultsObject< + ACMManagedClusterKind | ACMManagedClusterKind[] + >; }; drResources?: { data: DisasterRecoveryResourceKind; diff --git a/packages/mco/hooks/subscription.ts b/packages/mco/hooks/subscription.ts index 57babc3c8..8714a26b8 100644 --- a/packages/mco/hooks/subscription.ts +++ b/packages/mco/hooks/subscription.ts @@ -1,8 +1,17 @@ import * as React from 'react'; import { getName, getNamespace } from '@odf/shared/selectors'; import { ApplicationKind } from '@odf/shared/types'; -import { useK8sWatchResources } from '@openshift-console/dynamic-plugin-sdk'; -import { ACMPlacementModel, ACMPlacementRuleModel } from '../models'; +import { + WatchK8sResource, + WatchK8sResultsObject, + useK8sWatchResources, +} from '@openshift-console/dynamic-plugin-sdk'; +import * as _ from 'lodash-es'; +import { + ACMPlacementModel, + ACMPlacementRuleModel, + ACMSubscriptionModel, +} from '../models'; import { ACMPlacementDecisionKind, ACMPlacementKind, @@ -25,43 +34,100 @@ import { DisasterRecoveryResourceKind, } from './disaster-recovery'; import { + getApplicationResourceObj, getPlacementDecisionsResourceObj, getPlacementResourceObj, getPlacementRuleResourceObj, getSubscriptionResourceObj, } from './mco-resources'; -const getResources = (namespace: string) => ({ - placements: getPlacementResourceObj({ namespace }), - placementDecisions: getPlacementDecisionsResourceObj({ namespace }), - placementRules: getPlacementRuleResourceObj({ namespace }), - subscriptions: getSubscriptionResourceObj({ namespace }), +const getResources = () => ({ + applications: getApplicationResourceObj(), + placements: getPlacementResourceObj(), + placementDecisions: getPlacementDecisionsResourceObj(), + placementRules: getPlacementRuleResourceObj(), + subscriptions: getSubscriptionResourceObj(), }); -const getPlacementMap = (placements: ACMPlacementType[]) => { - return ( - placements.reduce( - (arr, placement) => ({ - ...arr, +const appFilter = (application: ApplicationKind) => + application?.spec?.componentKinds?.some( + (componentKind) => + componentKind.group === ACMSubscriptionModel.apiGroup && + componentKind.kind === ACMSubscriptionModel.kind + ); + +const getNamespaceWiseApplications = ( + applications: ApplicationKind[] +): NamespaceWiseMapping => + applications.reduce( + (acc, application) => + appFilter(application) + ? { + ...acc, + [getNamespace(application)]: [ + ...(acc[getNamespace(application)] || []), + application, + ], + } + : acc, + {} + ); + +const getNamespaceWiseSubscriptions = ( + subscriptions: ACMSubscriptionKind[] +): NamespaceWiseMapping => + subscriptions.reduce( + (acc, subscription) => + isPlacementModel(subscription) + ? { + ...acc, + [getNamespace(subscription)]: [ + ...(acc[getNamespace(subscription)] || []), + subscription, + ], + } + : acc, + {} + ); + +const getNamespaceWisePlacementDecisions = ( + placementDecisions: ACMPlacementDecisionKind[] +): NamespaceWiseMapping => + placementDecisions.reduce( + (acc, placementDecision) => ({ + ...acc, + [getNamespace(placementDecision)]: [ + ...(acc[getNamespace(placementDecision)] || []), + placementDecision, + ], + }), + {} as any + ); + +const getNamespaceWisePlacements = ( + placements: ACMPlacementType[] +): NamespaceToNameMapping => + placements?.reduce( + (acc, placement) => ({ + ...acc, + [getNamespace(placement)]: { + ...(acc[getNamespace(placement)] || []), [getName(placement)]: placement, - }), - {} - ) || {} + }, + }), + {} ); -}; -const getPlacements = ( +const generateSubscriptionGroupInfo = ( application: ApplicationKind, subscriptions: ACMSubscriptionKind[], - placements: ACMPlacementKind[], - placementRules: ACMPlacementRuleKind[], + placementMap: ACMPlacementMap, + placementRuleMap: ACMPlacementMap, placementDecisions: ACMPlacementDecisionKind[], drResources: DisasterRecoveryFormatted[] -): ApplicationDeploymentInfo[] => { - const placementRuleMap = getPlacementMap(placementRules); - const placementMap = getPlacementMap(placements); +): SubscriptionGroupType[] => { const placementToAppDeploymentMap: PlacementToAppDeploymentMap = {}; - subscriptions.forEach((subscription) => { + subscriptions?.forEach((subscription) => { // applying subscription filter from application if ( isPlacementModel(subscription) && @@ -95,12 +161,23 @@ const getPlacements = ( ) : null; placementToAppDeploymentMap[placementUniqueId] = { + subscriptions: [subscription], placement, placementDecision, - drClusters: drResource?.drClusters, - drPolicy: drResource?.drPolicy, - drPlacementControl: drResource?.drPlacementControls?.[0], + ...(!_.isEmpty(drResource) + ? { + drInfo: { + drClusters: drResource.drClusters, + drPolicy: drResource.drPolicy, + drPlacementControl: drResource.drPlacementControls?.[0], + }, + } + : {}), }; + } else { + placementToAppDeploymentMap[placementUniqueId].subscriptions.push( + subscription + ); } } }); @@ -108,14 +185,19 @@ const getPlacements = ( return Object.values(placementToAppDeploymentMap) || []; }; -export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ({ - application, - drResources, -}) => { +export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ( + resource +) => { const response = useK8sWatchResources( - getResources(getNamespace(application)) + resource?.resources || getResources() ); + const { + data: applications, + loaded: applicationsLoaded, + loadError: applicationsLoadError, + } = response?.applications || resource?.overrides?.applications || {}; + const { data: placements, loaded: placementsLoaded, @@ -144,9 +226,10 @@ export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ({ data: drResourceList, loaded: drLoaded, loadError: drLoadError, - } = drResources || {}; + } = resource?.drResources || {}; const loaded = + applicationsLoaded && placementsLoaded && placementDecisionsLoaded && placementRulesLoaded && @@ -154,6 +237,7 @@ export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ({ drLoaded; const loadError = + applicationsLoadError || placementsLoadError || placementDecisionsLoadError || placementRulesLoadError || @@ -161,21 +245,43 @@ export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ({ drLoadError; return React.useMemo(() => { - const subscriptionResources: SubscriptionResourceKind = - loaded && !loadError - ? getPlacements( + const applicationDeploymentInfo: ApplicationDeploymentInfo[] = []; + if (loaded && !loadError) { + const applicationList = Array.isArray(applications) + ? applications + : [applications]; + const namespaceToApplicationMap = + getNamespaceWiseApplications(applicationList); + const namespaceToSubscriptionMap = + getNamespaceWiseSubscriptions(subscriptions); + const namespaceToPlacementRuleMap = + getNamespaceWisePlacements(placementRules); + const namespaceToPlacementMap = getNamespaceWisePlacements(placements); + const namespaceToPlacementDecisionMap = + getNamespaceWisePlacementDecisions(placementDecisions); + Object.keys(namespaceToApplicationMap).forEach((namespace) => { + namespaceToApplicationMap[namespace].forEach((application) => { + const subscriptionGroupInfo = generateSubscriptionGroupInfo( application, - subscriptions, - placements, - placementRules, - placementDecisions, + namespaceToSubscriptionMap?.[namespace] as ACMSubscriptionKind[], + namespaceToPlacementMap?.[namespace] || {}, + namespaceToPlacementRuleMap?.[namespace] || {}, + namespaceToPlacementDecisionMap?.[ + namespace + ] as ACMPlacementDecisionKind[], drResourceList?.formattedResources - ) - : []; + ); - return [subscriptionResources, loaded, loadError]; + applicationDeploymentInfo.push({ + application, + subscriptionGroupInfo, + }); + }); + }); + } + return [applicationDeploymentInfo, loaded, loadError]; }, [ - application, + applications, placements, placementDecisions, placementRules, @@ -187,10 +293,11 @@ export const useSubscriptionResourceWatch: UseSubscriptionResourceWatch = ({ }; type PlacementToAppDeploymentMap = { - [uniqueName: string]: ApplicationDeploymentInfo; + [uniqueName: string]: SubscriptionGroupType; }; type WatchResourceType = { + applications?: ApplicationKind | ApplicationKind[]; placements: ACMPlacementKind[]; placementDecisions: ACMPlacementDecisionKind[]; placementRules: ACMPlacementRuleKind[]; @@ -198,24 +305,58 @@ type WatchResourceType = { }; type WatchResources = { + resources?: { + applications?: WatchK8sResource; + placements: WatchK8sResource; + placementDecisions: WatchK8sResource; + placementRules: WatchK8sResource; + subscriptions: WatchK8sResource; + }; drResources?: { data: DisasterRecoveryResourceKind; loaded: boolean; loadError: any; }; - application: ApplicationKind; + overrides?: { + applications?: WatchK8sResultsObject; + }; }; -type ApplicationDeploymentInfo = { - placement: ACMPlacementType; - placementDecision?: ACMPlacementDecisionKind; +type DRInfoType = { drPlacementControl?: DRPlacementControlKind; drPolicy?: DRPolicyKind; drClusters?: DRClusterKind[]; }; +type ApplicationDeploymentInfo = { + application: ApplicationKind; + subscriptionGroupInfo: SubscriptionGroupType[]; +}; + type SubscriptionResourceKind = ApplicationDeploymentInfo[]; type UseSubscriptionResourceWatch = ( resource?: WatchResources ) => [SubscriptionResourceKind, boolean, any]; + +type NamespaceWiseMapping = { + [namespace in string]: + | ApplicationKind[] + | ACMSubscriptionKind[] + | ACMPlacementDecisionKind[]; +}; + +type ACMPlacementMap = { + [name in string]: ACMPlacementRuleKind | ACMPlacementKind; +}; + +type NamespaceToNameMapping = { + [namespace in string]: ACMPlacementMap; +}; + +export type SubscriptionGroupType = { + subscriptions: ACMSubscriptionKind[]; + placement?: ACMPlacementType; + placementDecision?: ACMPlacementDecisionKind; + drInfo?: DRInfoType; +}; diff --git a/packages/mco/types/dashboard.ts b/packages/mco/types/dashboard.ts index 215c1d7d4..748db924d 100644 --- a/packages/mco/types/dashboard.ts +++ b/packages/mco/types/dashboard.ts @@ -15,7 +15,7 @@ export type PlacementInfo = Partial<{ protectedPVCs: string[]; }>; -export type ProtectedAppSetsMap = { +export type ProtectedAppsMap = { appName: string; appNamespace: string; appKind: string; @@ -24,16 +24,16 @@ export type ProtectedAppSetsMap = { placementInfo: PlacementInfo[]; }; -export type AppSetObj = { +export type ApplicationObj = { namespace: string; name: string; }; -export type DrClusterAppsMap = { +export type DRClusterAppsMap = { [drClusterName: string]: { managedCluster: ACMManagedClusterKind; - totalAppSetsCount: number; - protectedAppSets: ProtectedAppSetsMap[]; + totalAppCount: number; + protectedApps: ProtectedAppsMap[]; }; }; diff --git a/packages/mco/utils/disaster-recovery.tsx b/packages/mco/utils/disaster-recovery.tsx index 28a9992fc..8225b91a6 100644 --- a/packages/mco/utils/disaster-recovery.tsx +++ b/packages/mco/utils/disaster-recovery.tsx @@ -60,7 +60,7 @@ import { ACMManagedClusterViewKind, DRVolumeReplicationGroupKind, ProtectedPVCData, - ProtectedAppSetsMap, + ProtectedAppsMap, ACMPlacementDecisionKind, ACMPlacementKind, MirrorPeerKind, @@ -495,18 +495,19 @@ export const getProtectedPVCFromVRG = ( }, []); }; -export const filterPVCDataUsingAppsets = ( +export const filterPVCDataUsingApps = ( pvcsData: ProtectedPVCData[], - protectedAppsets: ProtectedAppSetsMap[] + protectedApps: ProtectedAppsMap[] ) => pvcsData?.filter( (pvcData) => - !!protectedAppsets?.find((appSet) => { - const placementInfo = appSet?.placementInfo?.[0]; - const result = - placementInfo?.drpcName === pvcData?.drpcName && - placementInfo?.drpcNamespace === pvcData?.drpcNamespace; - return result; + !!protectedApps?.find((application) => { + return application.placementInfo?.map((placementInfo) => { + const result = + placementInfo.drpcName === pvcData.drpcName && + placementInfo.drpcNamespace === pvcData.drpcNamespace; + return result; + }); }) );