diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index d402e8d44..401d56d72 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -70,6 +70,11 @@ "Unknown": "Unknown", "Snapshot": "Snapshot", "Last replicated on: {{ lastSyncTime }}": "Last replicated on: {{ lastSyncTime }}", + "Subscription": "Subscription", + "Snapshot last replicated on": "Snapshot last replicated on", + "Subscription details": "Subscription details", + "Application: ": "Application: ", + "Type: {{type}}": "Type: {{type}}", "Operator health": "Operator health", "Operators are responsible for maintaining and reconciling the state of the cluster.": "Operators are responsible for maintaining and reconciling the state of the cluster.", "Degraded": "Degraded", @@ -1175,7 +1180,6 @@ "Deployment": "Deployment", "Infrastructure": "Infrastructure", "Infrastructures": "Infrastructures", - "Subscription": "Subscription", "Subscriptions": "Subscriptions", "Project": "Project", "Show password": "Show password", diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx index b552f58d9..741a50c40 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/application.tsx @@ -8,17 +8,49 @@ 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'; -import { URL_POLL_DEFAULT_DELAY } from '@odf/shared/hooks/custom-prometheus-poll/use-url-poll'; +import { useScheduler } from '@odf/shared/hooks'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; +import { getPageRange } from '@odf/shared/utils'; import { PrometheusResponse, + RowProps, StatusIconAndText, + TableColumn, + TableData, + VirtualizedTable, + useActiveColumns, } from '@openshift-console/dynamic-plugin-sdk'; import { TFunction } from 'i18next'; -import { Text } from '@patternfly/react-core'; +import { + Pagination, + PaginationVariant, + Text, + TextVariants, +} from '@patternfly/react-core'; +import { sortable } from '@patternfly/react-table'; import { StatusText } from './common'; -const getCurrentActivity = ( +const INITIAL_PAGE_NUMBER = 1; +const COUNT_PER_PAGE_NUMBER = 5; + +const subscriptionTableColumnProps = [ + { + id: 'name', + }, + { + id: 'activity', + }, + { + id: 'lastSnapshotSyncTime', + }, +]; + +const formatSyncTimeWithRPO = (lastSnapshotSyncTime: string, rpo: string) => { + const dateTime = utcDateTimeFormatter.format(new Date(lastSnapshotSyncTime)); + return `${dateTime} (${rpo})`; +}; + +export const getCurrentActivity = ( currentStatus: string, failoverCluster: string, preferredCluster: string, @@ -47,6 +79,34 @@ const getCurrentActivity = ( } }; +const getSubscriptionRow = ( + placementInfoList: PlacementInfo[] = [] +): SubscriptionRowProps[] => { + const _getRowProps = ( + subscriptions: string[], + { + status, + lastGroupSyncTime, + failoverCluster, + preferredCluster, + }: PlacementInfo + ): SubscriptionRowProps[] => + subscriptions?.map((subscriptionName) => ({ + name: subscriptionName, + activity: status, + lastSnapshotSyncTime: lastGroupSyncTime, + failoverCluster: failoverCluster, + preferredCluster: preferredCluster, + })); + return placementInfoList.reduce( + (acc, placementInfo) => [ + ...acc, + ..._getRowProps(placementInfo?.subscriptions, placementInfo), + ], + [] + ); +}; + export const ActivitySection: React.FC = ({ selectedApplication, }) => { @@ -80,24 +140,17 @@ export const SnapshotSection: React.FC = ({ const [lastSyncTime, setLastSyncTime] = React.useState('N/A'); const lastGroupSyncTime = selectedApplication?.placementInfo?.[0]?.lastGroupSyncTime; - const clearSetIntervalId = React.useRef(); const updateSyncTime = React.useCallback(() => { if (!!lastGroupSyncTime) { - const dateTime = utcDateTimeFormatter.format(new Date(lastGroupSyncTime)); - setLastSyncTime(`${dateTime} (${fromNow(lastGroupSyncTime)})`); + setLastSyncTime( + formatSyncTimeWithRPO(lastGroupSyncTime, fromNow(lastGroupSyncTime)) + ); } else { setLastSyncTime('N/A'); } }, [lastGroupSyncTime]); - React.useEffect(() => { - updateSyncTime(); - clearSetIntervalId.current = setInterval( - updateSyncTime, - URL_POLL_DEFAULT_DELAY - ); - return () => clearInterval(clearSetIntervalId.current); - }, [updateSyncTime]); + useScheduler(updateSyncTime); return (
@@ -111,7 +164,177 @@ export const SnapshotSection: React.FC = ({ ); }; +const SubscriptionRow: React.FC< + RowProps +> = ({ + obj: { + name, + activity, + lastSnapshotSyncTime, + failoverCluster, + preferredCluster, + }, + rowData: subsWiseRPO, + activeColumnIDs, +}) => { + const { t } = useCustomTranslation(); + const lastSyncTimeWithRPO = !!lastSnapshotSyncTime + ? formatSyncTimeWithRPO(lastSnapshotSyncTime, subsWiseRPO[name]) + : 'N/A'; + + return ( + <> + + {name} + + + {getCurrentActivity(activity, failoverCluster, preferredCluster, t)} + + + {lastSyncTimeWithRPO} + + + ); +}; + +export const SubscriptionSection: React.FC = ({ + selectedApplication, +}) => { + const { t } = useCustomTranslation(); + const subsCount = selectedApplication?.placementInfo?.reduce( + (acc, placement) => acc + placement.subscriptions?.length || 0, + 0 + ); + return ( +
+ {subsCount} + {t('Subscription')} +
+ ); +}; + +export const SubscriptionDetailsTable: React.FC = + ({ selectedApplication }) => { + const { placementInfo } = selectedApplication; + const { t } = useCustomTranslation(); + const [subsWiseRPO, setSubsWiseRPO] = + React.useState({}); + const [page, setPage] = React.useState(INITIAL_PAGE_NUMBER); + const [perPage, setPerPage] = React.useState(COUNT_PER_PAGE_NUMBER); + const subscriptionsTableColumns = React.useMemo< + TableColumn[] + >( + () => [ + { + title: t('Name'), + sort: 'name', + transforms: [sortable], + id: subscriptionTableColumnProps[0].id, + }, + { + title: t('Activity'), + sort: 'activity', + transforms: [sortable], + id: subscriptionTableColumnProps[1].id, + }, + { + title: t('Snapshot last replicated on'), + sort: 'lastSnapshotSyncTime', + transforms: [sortable], + id: subscriptionTableColumnProps[2].id, + }, + ], + [t] + ); + const [columns] = useActiveColumns({ + columns: subscriptionsTableColumns, + showNamespaceOverride: false, + columnManagementID: null, + }); + const [subscriptionRows, numberOfRows]: [SubscriptionRowProps[], number] = + React.useMemo(() => { + const [start, end] = getPageRange(page, perPage); + const subscriptionRowList = getSubscriptionRow(placementInfo); + return [ + subscriptionRowList.slice(start, end), + subscriptionRowList.length, + ]; + }, [placementInfo, page, perPage]); + const updatedRPO = React.useCallback(() => { + const rpoMap = subscriptionRows.reduce((acc, row) => { + const { name, lastSnapshotSyncTime } = row; + acc[name] = !!lastSnapshotSyncTime ? fromNow(lastSnapshotSyncTime) : ''; + return acc; + }, {}); + setSubsWiseRPO(rpoMap); + }, [subscriptionRows, setSubsWiseRPO]); + + useScheduler(updatedRPO); + + return ( +
+ {t('Subscription details')} +
+ + setPage(newPage)} + onPerPageSelect={(_event, newPerPage, newPage) => { + setPerPage(newPerPage); + setPage(newPage); + }} + /> +
+
+ ); + }; + type CommonProps = { selectedApplication: ProtectedAppsMap; lastSyncTimeData?: PrometheusResponse; }; + +type SubscriptionSectionProps = { + selectedApplication: ProtectedAppsMap; +}; + +type SubscriptionDetailsTableProps = { + selectedApplication: ProtectedAppsMap; +}; + +type SubscriptionRowProps = { + name: string; + activity: string; + lastSnapshotSyncTime: string; + failoverCluster: string; + preferredCluster: string; +}; + +type SubscriptionWiseRPOMap = { + [subscriptionName: string]: string; +}; diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.scss b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.scss index c243f0c34..c12190fcc 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.scss +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster-app-card.scss @@ -20,6 +20,10 @@ &__dropdown--padding { padding-right: var(--pf-global--spacer--xs); } + &__dropdownHeight { + max-height: 30rem; + overflow-y: auto; + } &__text--padding-left { padding-left: var(--pf-global--spacer--sm); } @@ -57,3 +61,8 @@ .co-utilization-card__item-summary .bold { font-weight: bold; } + +.mco-cluster-app__subs-table--width { + min-width: 10rem; + max-width: 55rem; +} 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 48ff41060..04a08c6bb 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 @@ -3,6 +3,7 @@ import { HUB_CLUSTER_NAME, ALL_APPS, applicationDetails, + APPLICATION_TYPE, } from '@odf/mco/constants'; import { DRClusterAppsMap, @@ -13,6 +14,7 @@ import { MirrorPeerKind, } from '@odf/mco/types'; import { getClusterNamesFromMirrorPeers } from '@odf/mco/utils'; +import { useCustomTranslation } from '@odf/shared'; import { DataUnavailableError } from '@odf/shared/generic/Error'; import { useCustomPrometheusPoll, @@ -31,6 +33,7 @@ import { CardTitle, Grid, GridItem, + Text, } from '@patternfly/react-core'; import { ACMManagedClusterViewModel, @@ -45,7 +48,12 @@ import { CSVStatusesContext, DRResourcesContext, } from '../dr-dashboard-context'; -import { ActivitySection, SnapshotSection } from './application'; +import { + ActivitySection, + SnapshotSection, + SubscriptionDetailsTable, + SubscriptionSection, +} from './application'; import { HealthSection, PeerConnectionSection, @@ -59,7 +67,63 @@ import { } from './common'; import './cluster-app-card.scss'; -export const ClusterWiseCard: React.FC = ({ +const ApplicationSetAppCard: React.FC = ({ + protectedPVCData, + selectedApplication, +}) => { + return ( + + + + + + + + + + + + + + + ); +}; + +const SubscriptionSetAppCard: React.FC = ({ + protectedPVCData, + selectedApplication, +}) => { + return ( + + + + + + + + + + + + + + + ); +}; + +const ClusterWiseCard: React.FC = ({ clusterName, lastSyncTimeData, protectedPVCData, @@ -111,31 +175,48 @@ export const ClusterWiseCard: React.FC = ({ ); }; -export const AppWiseCard: React.FC = ({ - protectedPVCData, - selectedApplication, +const AppWiseCard: React.FC = (props) => { + switch (props?.selectedApplication?.appType) { + case APPLICATION_TYPE.APPSET: + return ; + case APPLICATION_TYPE.SUBSCRIPTION: + return ; + default: + return <>; + } +}; + +const ClusterAppCardTitle: React.FC = ({ + app, + appKind, + appAPIVersion, + appType, + cluster, }) => { - return ( - - - - - - - - - - - - - - + const { t } = useCustomTranslation(); + const apiVersion = `${appKind?.toLowerCase()}.${ + appAPIVersion?.split('/')[0] + }`; + const applicationDetailsPath = + applicationDetails + .replace(':namespace', app.namespace) + .replace(':name', app.name) + + '?apiVersion=' + + apiVersion; + return !!app.namespace ? ( +
+ + {t('Application: ')} + + {app.name} + + + + {t('Type: {{type}}', { type: appType })} + +
+ ) : ( +
{cluster}
); }; @@ -196,15 +277,6 @@ export const ClusterAppCard: React.FC = () => { mcvsLoaded, mcvsLoadError, ]); - const apiVersion = `${selectedApplication?.appKind?.toLowerCase()}.${ - selectedApplication?.appAPIVersion?.split('/')[0] - }`; - const applicationDetailsPath = - applicationDetails - .replace(':namespace', application.namespace) - .replace(':name', application.name) + - '?apiVersion=' + - apiVersion; return ( @@ -219,14 +291,14 @@ export const ClusterAppCard: React.FC = () => { setCluster={setCluster} setApplication={setApplication} /> - - {!!application.namespace ? ( - - {application.name} - - ) : ( - cluster - )} + +
@@ -272,3 +344,11 @@ type AppWiseCardProps = { protectedPVCData: ProtectedPVCData[]; selectedApplication: ProtectedAppsMap; }; + +type ClusterAppCardTitleProps = { + app: ApplicationObj; + cluster: string; + appKind: string; + appAPIVersion: string; + appType: APPLICATION_TYPE; +}; 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 808ea1da8..679a3aeba 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 } from '@odf/mco/types'; import { getVolumeReplicationHealth, ValidateManagedClusterCondition, @@ -179,18 +179,19 @@ export const ApplicationsSection: React.FC = ({ const appsWithIssues = React.useMemo( () => clusterResources[clusterName]?.protectedApps?.reduce( - (acc, protectedAppsMap) => { - const placementInfo: PlacementInfo = - protectedAppsMap?.placementInfo?.[0]; - const hasIssue = !!lastSyncTimeData?.data?.result?.find( - (item: PrometheusResult) => - item?.metric?.[OBJECT_NAMESPACE] === - placementInfo?.drpcNamespace && - item?.metric?.[OBJECT_NAME] === placementInfo?.drpcName && - getVolumeReplicationHealth( - Number(item?.value[1]) || 0, - placementInfo?.syncInterval - )[0] !== VOLUME_REPLICATION_HEALTH.HEALTHY + (acc, protectedAppMap) => { + const hasIssue = !!protectedAppMap.placementInfo?.find( + (placementInfo) => + !!lastSyncTimeData?.data?.result?.find( + (item: PrometheusResult) => + item.metric?.[OBJECT_NAMESPACE] === + placementInfo.drpcNamespace && + item.metric?.[OBJECT_NAME] === placementInfo.drpcName && + getVolumeReplicationHealth( + Number(item.value[1]) || 0, + placementInfo.syncInterval + )[0] !== VOLUME_REPLICATION_HEALTH.HEALTHY + ) ); return hasIssue ? acc + 1 : acc; 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 97c2188da..47c39e95c 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 @@ -14,11 +14,13 @@ import { ProtectedAppsMap, ApplicationObj, ProtectedPVCData, - PlacementInfo, } from '@odf/mco/types'; -import { getVolumeReplicationHealth } from '@odf/mco/utils'; +import { + filterPVCDataUsingApp, + getVolumeReplicationHealth, +} from '@odf/mco/utils'; import { getTimeDifferenceInSeconds } from '@odf/shared/details-page/datetime'; -import { URL_POLL_DEFAULT_DELAY } from '@odf/shared/hooks/custom-prometheus-poll/use-url-poll'; +import { useScheduler } from '@odf/shared/hooks'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; import { Select as SelectNext, @@ -69,12 +71,9 @@ export const VolumeSummarySection: React.FC = ({ warning: 0, healthy: 0, }); - const clearSetIntervalId = React.useRef(); const updateSummary = React.useCallback(() => { const volumeHealth = { critical: 0, warning: 0, healthy: 0 }; - const placementInfo: PlacementInfo = - selectedApplication?.placementInfo?.[0]; protectedPVCData?.forEach((pvcData) => { const pvcLastSyncTime = pvcData?.lastSyncTime; const health = getVolumeReplicationHealth( @@ -83,25 +82,15 @@ export const VolumeSummarySection: React.FC = ({ : LEAST_SECONDS_IN_PROMETHEUS, pvcData?.schedulingInterval )[0]; - if (!!selectedApplication) { - pvcData?.drpcName === placementInfo?.drpcName && - pvcData?.drpcNamespace === placementInfo?.drpcNamespace && - volumeHealth[health]++; - } else { - volumeHealth[health]++; - } + !!selectedApplication + ? filterPVCDataUsingApp(pvcData, selectedApplication) && + volumeHealth[health]++ + : volumeHealth[health]++; }); setSummary(volumeHealth); }, [selectedApplication, protectedPVCData, setSummary]); - React.useEffect(() => { - updateSummary(); - clearSetIntervalId.current = setInterval( - updateSummary, - URL_POLL_DEFAULT_DELAY - ); - return () => clearInterval(clearSetIntervalId.current); - }, [updateSummary]); + useScheduler(updateSummary); return (
@@ -287,29 +276,30 @@ const AppDropdown: React.FC> = ({ onSelect={onSelect} onOpenChange={(isOpenFlag) => setIsOpen(isOpenFlag)} toggle={toggle} - className={className} > - - - - {ALL_APPS} - - - - {Object.keys(options)?.map((appNS: string) => ( - +
+ - {options[appNS]?.map((appName: string) => ( - - {appName} - - ))} + + {ALL_APPS} + - ))} + {Object.keys(options)?.map((appNS: string) => ( + + + {options[appNS]?.map((appName: string) => ( + + {appName} + + ))} + + + ))} +
); }; @@ -338,6 +328,7 @@ export const ClusterAppDropdown: React.FC = ({ clusterName={clusterName} application={application} setApplication={setApplication} + className="mco-cluster-app__dropdownHeight" /> @@ -349,12 +340,10 @@ export const ProtectedPVCsSection: React.FC = ({ selectedApplication, }) => { const { t } = useCustomTranslation(); - const clearSetIntervalId = React.useRef(); const [protectedPVC, setProtectedPVC] = React.useState([0, 0]); const [protectedPVCsCount, pvcsWithIssueCount] = protectedPVC; const updateProtectedPVC = React.useCallback(() => { - const placementInfo = selectedApplication?.placementInfo?.[0]; const issueCount = protectedPVCData?.reduce((acc, protectedPVCItem) => { const pvcLastSyncTime = protectedPVCItem?.lastSyncTime; @@ -366,8 +355,7 @@ export const ProtectedPVCsSection: React.FC = ({ )[0]; (!!selectedApplication - ? protectedPVCItem?.drpcName === placementInfo?.drpcName && - protectedPVCItem?.drpcNamespace === placementInfo?.drpcNamespace && + ? !!filterPVCDataUsingApp(protectedPVCItem, selectedApplication) && replicationHealth !== VOLUME_REPLICATION_HEALTH.HEALTHY : replicationHealth !== VOLUME_REPLICATION_HEALTH.HEALTHY) && acc++; @@ -376,14 +364,7 @@ export const ProtectedPVCsSection: React.FC = ({ setProtectedPVC([protectedPVCData?.length || 0, issueCount]); }, [selectedApplication, protectedPVCData, setProtectedPVC]); - React.useEffect(() => { - updateProtectedPVC(); - clearSetIntervalId.current = setInterval( - updateProtectedPVC, - URL_POLL_DEFAULT_DELAY - ); - return () => clearInterval(clearSetIntervalId.current); - }, [updateProtectedPVC]); + useScheduler(updateProtectedPVC); return (
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 index adc56a053..b5c0a2134 100644 --- a/packages/mco/components/mco-dashboard/data-policy/parsers/subscription-parser.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/parsers/subscription-parser.tsx @@ -39,7 +39,7 @@ const createPlacementInfoList = ( drClusters: currentDRClusters, } = drInfo; - const placementInfo = { + const placementInfo: PlacementInfo = { deploymentClusterName: clusterName, drpcName: getName(drPlacementControl), drpcNamespace: getNamespace(drPlacementControl), @@ -51,6 +51,9 @@ const createPlacementInfoList = ( preferredCluster: drPlacementControl?.spec?.preferredCluster, lastGroupSyncTime: drPlacementControl?.status?.lastGroupSyncTime, status: drPlacementControl?.status?.phase as DRPC_STATUS, + subscriptions: subscriptionGroup?.subscriptions?.map((subs) => + getName(subs) + ), }; placementInfoList.push(placementInfo); diff --git a/packages/mco/components/modals/app-manage-policies/policy-list-view.tsx b/packages/mco/components/modals/app-manage-policies/policy-list-view.tsx index 1749fc645..6b83f4d35 100644 --- a/packages/mco/components/modals/app-manage-policies/policy-list-view.tsx +++ b/packages/mco/components/modals/app-manage-policies/policy-list-view.tsx @@ -3,6 +3,7 @@ import { ActionDropdown } from '@odf/shared/dropdown/action-dropdown'; import { ModalBody } from '@odf/shared/modals/Modal'; import { getName } from '@odf/shared/selectors'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; +import { getPageRange } from '@odf/shared/utils'; import { getErrorMessage } from '@odf/shared/utils'; import { global_palette_blue_300 as blueInfoColor } from '@patternfly/react-tokens/dist/js/global_palette_blue_300'; import { @@ -39,12 +40,6 @@ import './style.scss'; const INITIAL_PAGE_NUMBER = 1; const COUNT_PER_PAGE_NUMBER = 4; -const getRange = (currentPage: number, perPage: number) => { - const indexOfLastRow = currentPage * perPage; - const indexOfFirstRow = indexOfLastRow - perPage; - return [indexOfFirstRow, indexOfLastRow]; -}; - const filterPolicies = (dataPolicyInfo: DataPolicyType[], searchText: string) => dataPolicyInfo.filter((policy) => getName(policy).toLowerCase().includes(searchText) @@ -130,7 +125,7 @@ export const PolicyListView: React.FC = ({ const [page, setPage] = React.useState(INITIAL_PAGE_NUMBER); const [perPage, setPerPage] = React.useState(COUNT_PER_PAGE_NUMBER); const [searchText, onSearchChange] = React.useState(''); - const [start, end] = getRange(page, perPage); + const [start, end] = getPageRange(page, perPage); const policies = filterPolicies(dataPolicyInfo, searchText) || []; const paginatedPolicies = policies.slice(start, end); const unFilteredAssignedPolicyLength = dataPolicyInfo.length; diff --git a/packages/mco/types/acm.ts b/packages/mco/types/acm.ts index 020391fa3..badbada16 100644 --- a/packages/mco/types/acm.ts +++ b/packages/mco/types/acm.ts @@ -130,9 +130,10 @@ export type PlacementToDrpcMap = { export type ACMManagedClusterViewKind = K8sResourceCommon & { spec?: { scope: { - resource: string; + resource?: string; name: string; namespace?: string; + kind: string; }; }; status?: { diff --git a/packages/mco/types/dashboard.ts b/packages/mco/types/dashboard.ts index 748db924d..b5218222d 100644 --- a/packages/mco/types/dashboard.ts +++ b/packages/mco/types/dashboard.ts @@ -13,6 +13,8 @@ export type PlacementInfo = Partial<{ lastGroupSyncTime: string; status: DRPC_STATUS; protectedPVCs: string[]; + // Only applicable for Subscription type + subscriptions?: string[]; }>; export type ProtectedAppsMap = { @@ -21,6 +23,8 @@ export type ProtectedAppsMap = { appKind: string; appAPIVersion: string; appType: APPLICATION_TYPE; + // ToDo: refactor this PlacementInfo type to + // make more flxibile between different app types placementInfo: PlacementInfo[]; }; diff --git a/packages/mco/utils/disaster-recovery.tsx b/packages/mco/utils/disaster-recovery.tsx index 8225b91a6..a4e98178d 100644 --- a/packages/mco/utils/disaster-recovery.tsx +++ b/packages/mco/utils/disaster-recovery.tsx @@ -471,7 +471,7 @@ export const findAppsUsingDRPolicy = ( const filterMulticlusterView = (mcvs: ACMManagedClusterViewKind[]) => mcvs?.filter( - (mcv) => mcv?.spec?.scope?.resource === DRVolumeReplicationGroup.kind + (mcv) => mcv?.spec?.scope?.kind === DRVolumeReplicationGroup.kind ); export const getProtectedPVCFromVRG = ( @@ -495,20 +495,26 @@ export const getProtectedPVCFromVRG = ( }, []); }; +export const filterPVCDataUsingApp = ( + pvcData: ProtectedPVCData, + protectedApp: ProtectedAppsMap +) => + protectedApp?.placementInfo?.find((placementInfo) => { + const result = + placementInfo.drpcName === pvcData?.drpcName && + placementInfo.drpcNamespace === pvcData?.drpcNamespace; + return result; + }); + export const filterPVCDataUsingApps = ( pvcsData: ProtectedPVCData[], protectedApps: ProtectedAppsMap[] ) => pvcsData?.filter( (pvcData) => - !!protectedApps?.find((application) => { - return application.placementInfo?.map((placementInfo) => { - const result = - placementInfo.drpcName === pvcData.drpcName && - placementInfo.drpcNamespace === pvcData.drpcNamespace; - return result; - }); - }) + !!protectedApps?.find( + (protectedApp) => !!filterPVCDataUsingApp(pvcData, protectedApp) + ) ); export const filterDRAlerts = (alert: Alert) => diff --git a/packages/shared/src/hooks/index.ts b/packages/shared/src/hooks/index.ts index bdeadee6d..a393fda08 100644 --- a/packages/shared/src/hooks/index.ts +++ b/packages/shared/src/hooks/index.ts @@ -7,3 +7,4 @@ export * from './select-list'; export * from './sort-list'; export * from './use-fetch-csv'; export * from './useK8sList'; +export * from './scheduler'; diff --git a/packages/shared/src/hooks/scheduler.ts b/packages/shared/src/hooks/scheduler.ts new file mode 100644 index 000000000..8f3602b36 --- /dev/null +++ b/packages/shared/src/hooks/scheduler.ts @@ -0,0 +1,24 @@ +import * as React from 'react'; +import { URL_POLL_DEFAULT_DELAY } from './custom-prometheus-poll/use-url-poll'; + +export const useScheduler: UseSchedulerProps = ( + schedulerFunction: SchedulerFunction, + syncTime?: number +) => { + const clearSetIntervalId = React.useRef(); + React.useEffect(() => { + schedulerFunction(); + clearSetIntervalId.current = setInterval( + schedulerFunction, + syncTime || URL_POLL_DEFAULT_DELAY + ); + return () => clearInterval(clearSetIntervalId.current); + }, [schedulerFunction, syncTime]); +}; + +type SchedulerFunction = () => void; + +type UseSchedulerProps = ( + schedulerFunction: SchedulerFunction, + syncTime?: number +) => void; diff --git a/packages/shared/src/utils/common.ts b/packages/shared/src/utils/common.ts index 2c6bb6f60..6c642cd38 100644 --- a/packages/shared/src/utils/common.ts +++ b/packages/shared/src/utils/common.ts @@ -146,3 +146,9 @@ export const getValidatedProp = (error: boolean) => error ? 'error' : 'default'; export const isAbortError = (err): boolean => err?.name === 'AbortError'; + +export const getPageRange = (currentPage: number, perPage: number) => { + const indexOfLastRow = currentPage * perPage; + const indexOfFirstRow = indexOfLastRow - perPage; + return [indexOfFirstRow, indexOfLastRow]; +};