diff --git a/frontend/src/concepts/distributedWorkloads/DistributedWorkloadsContext.tsx b/frontend/src/concepts/distributedWorkloads/DistributedWorkloadsContext.tsx index a9ff3abfd4..15cc6575a5 100644 --- a/frontend/src/concepts/distributedWorkloads/DistributedWorkloadsContext.tsx +++ b/frontend/src/concepts/distributedWorkloads/DistributedWorkloadsContext.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Bullseye, Alert, Spinner } from '@patternfly/react-core'; import { ClusterQueueKind, LocalQueueKind, WorkloadKind } from '~/k8sTypes'; import { FetchStateObject } from '~/types'; -import { DEFAULT_LIST_FETCH_STATE, DEFAULT_VALUE_FETCH_STATE } from '~/utilities/const'; +import { DEFAULT_LIST_FETCH_STATE } from '~/utilities/const'; import { SupportedArea, conditionalArea } from '~/concepts/areas'; import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject'; import { ProjectsContext, byName } from '~/concepts/projects/ProjectsContext'; @@ -20,13 +20,14 @@ import useLocalQueues from './useLocalQueues'; import useWorkloads from './useWorkloads'; type DistributedWorkloadsContextType = { - clusterQueue: FetchStateObject; + clusterQueues: FetchStateObject; localQueues: FetchStateObject; workloads: FetchStateObject; projectCurrentMetrics: DWProjectCurrentMetrics; refreshAllData: () => void; namespace: string; projectDisplayName: string; + cqExists: boolean; }; type DistributedWorkloadsContextProviderProps = { @@ -35,13 +36,14 @@ type DistributedWorkloadsContextProviderProps = { }; export const DistributedWorkloadsContext = React.createContext({ - clusterQueue: DEFAULT_VALUE_FETCH_STATE, + clusterQueues: DEFAULT_LIST_FETCH_STATE, localQueues: DEFAULT_LIST_FETCH_STATE, workloads: DEFAULT_LIST_FETCH_STATE, projectCurrentMetrics: DEFAULT_DW_PROJECT_CURRENT_METRICS, refreshAllData: () => undefined, namespace: '', projectDisplayName: '', + cqExists: false, }); export const DistributedWorkloadsContextProvider = @@ -59,17 +61,23 @@ export const DistributedWorkloadsContextProvider = // TODO mturley implement lazy loading, let the context consumers tell us what data they need and make the other ones throw a NotReadyError - const clusterQueues = useMakeFetchObject(useClusterQueues(refreshRate)); - // We only support one ClusterQueue, but if the user has created multiple we use the first one with resourceGroups - const clusterQueue: FetchStateObject = { - ...clusterQueues, - data: clusterQueues.data.find((cq) => cq.spec.resourceGroups?.length), - }; - const localQueues = useMakeFetchObject( useLocalQueues(namespace, refreshRate), ); + const allClusterQueues = useMakeFetchObject(useClusterQueues(refreshRate)); + + const validCQExists = allClusterQueues.data.some((cq) => cq.spec.resourceGroups?.length); + + const clusterQueues = { + ...allClusterQueues, + data: allClusterQueues.data.filter( + (cq) => + localQueues.data.some((lq) => lq.spec.clusterQueue === cq.metadata?.name) && + cq.spec.resourceGroups?.length, + ), + }; + const workloads = useMakeFetchObject(useWorkloads(namespace, refreshRate)); const projectCurrentMetrics = useDWProjectCurrentMetrics( @@ -115,13 +123,14 @@ export const DistributedWorkloadsContextProvider = return ( {children} diff --git a/frontend/src/concepts/distributedWorkloads/__tests__/utils.spec.ts b/frontend/src/concepts/distributedWorkloads/__tests__/utils.spec.ts index 0924e63951..c3d10f4f76 100644 --- a/frontend/src/concepts/distributedWorkloads/__tests__/utils.spec.ts +++ b/frontend/src/concepts/distributedWorkloads/__tests__/utils.spec.ts @@ -200,10 +200,20 @@ describe('getQueueRequestedResources', () => { describe('getTotalSharedQuota', () => { it('correctly parses and adds up total resources from clusterQueue resourceGroups', () => { - const mockClusterQueue = mockClusterQueueK8sResource({}); - expect(getTotalSharedQuota(mockClusterQueue)).toEqual({ + const mockClusterQueues = [mockClusterQueueK8sResource({})]; + expect(getTotalSharedQuota(mockClusterQueues)).toEqual({ cpuCoresRequested: 100, memoryBytesRequested: 68719476736, } satisfies WorkloadRequestedResources); }); + it('correctley parses and adds up total resources from multiple clusterQueues', () => { + const mockClusterQueues = [ + mockClusterQueueK8sResource({ name: 'test-clusterqueue-1' }), + mockClusterQueueK8sResource({ name: 'test-clusterqueue-2' }), + ]; + expect(getTotalSharedQuota(mockClusterQueues)).toEqual({ + cpuCoresRequested: 200, + memoryBytesRequested: 137438953472, + } satisfies WorkloadRequestedResources); + }); }); diff --git a/frontend/src/concepts/distributedWorkloads/utils.tsx b/frontend/src/concepts/distributedWorkloads/utils.tsx index 7816d8fedd..e2a90297ca 100644 --- a/frontend/src/concepts/distributedWorkloads/utils.tsx +++ b/frontend/src/concepts/distributedWorkloads/utils.tsx @@ -241,22 +241,24 @@ export const getQueueRequestedResources = ( }; export const getTotalSharedQuota = ( - clusterQueue?: ClusterQueueKind, + clusterQueues?: ClusterQueueKind[], ): WorkloadRequestedResources => { const sumFromResourceGroups = (units: UnitOption[], attribute: ContainerResourceAttributes) => - (clusterQueue?.spec.resourceGroups || []).reduce( - (resourceGroupsTotal, resourceGroup) => - resourceGroupsTotal + - resourceGroup.flavors.reduce((flavorsTotal, flavor) => { - const [value, unit] = convertToUnit( - String(flavor.resources.find(({ name }) => name === attribute)?.nominalQuota || 0), - units, - '', - ); - return unit.unit === '' ? flavorsTotal + value : flavorsTotal; - }, 0), - 0, - ); + (clusterQueues || []) + .flatMap((clusterQueue) => clusterQueue.spec.resourceGroups || []) + .reduce( + (resourceGroupsTotal, resourceGroup) => + resourceGroupsTotal + + resourceGroup.flavors.reduce((flavorsTotal, flavor) => { + const [value, unit] = convertToUnit( + String(flavor.resources.find(({ name }) => name === attribute)?.nominalQuota || 0), + units, + '', + ); + return unit.unit === '' ? flavorsTotal + value : flavorsTotal; + }, 0), + 0, + ); return { cpuCoresRequested: sumFromResourceGroups(CPU_UNITS, ContainerResourceAttributes.CPU), memoryBytesRequested: sumFromResourceGroups( diff --git a/frontend/src/pages/distributedWorkloads/global/GlobalDistributedWorkloadsTabs.tsx b/frontend/src/pages/distributedWorkloads/global/GlobalDistributedWorkloadsTabs.tsx index acc1c6ad21..4c04bec3f1 100644 --- a/frontend/src/pages/distributedWorkloads/global/GlobalDistributedWorkloadsTabs.tsx +++ b/frontend/src/pages/distributedWorkloads/global/GlobalDistributedWorkloadsTabs.tsx @@ -33,8 +33,8 @@ const GlobalDistributedWorkloadsTabs: React.FC id === activeTabId); const { namespace } = useParams<{ namespace: string }>(); - const { clusterQueue, localQueues } = React.useContext(DistributedWorkloadsContext); - const requiredFetches = [clusterQueue, localQueues]; + const { clusterQueues, localQueues, cqExists } = React.useContext(DistributedWorkloadsContext); + const requiredFetches = [clusterQueues, localQueues]; const error = requiredFetches.find((f) => !!f.error)?.error; const loaded = requiredFetches.every((f) => f.loaded); @@ -51,9 +51,9 @@ const GlobalDistributedWorkloadsTabs: React.FC; } - if (!clusterQueue.data || localQueues.data.length === 0) { - const nonAdmin = !clusterQueue.data; - const title = `Configure the ${!clusterQueue.data ? 'cluster queue' : 'project queue'}`; + if (clusterQueues.data.length === 0 || localQueues.data.length === 0) { + const nonAdmin = !cqExists; + const title = `Configure the ${!cqExists ? 'cluster queue' : 'project queue'}`; const message = nonAdmin ? 'Ask your cluster admin to configure the cluster queue.' : 'Configure the queue for this project, or select a different project.'; diff --git a/frontend/src/pages/distributedWorkloads/global/projectMetrics/sections/RequestedResources.tsx b/frontend/src/pages/distributedWorkloads/global/projectMetrics/sections/RequestedResources.tsx index 770bb26260..1eea2352b2 100644 --- a/frontend/src/pages/distributedWorkloads/global/projectMetrics/sections/RequestedResources.tsx +++ b/frontend/src/pages/distributedWorkloads/global/projectMetrics/sections/RequestedResources.tsx @@ -11,8 +11,8 @@ import { LoadingState } from '~/pages/distributedWorkloads/components/LoadingSta import { RequestedResourcesBulletChart } from './RequestedResourcesBulletChart'; export const RequestedResources: React.FC = () => { - const { localQueues, clusterQueue } = React.useContext(DistributedWorkloadsContext); - const requiredFetches = [localQueues, clusterQueue]; + const { localQueues, clusterQueues } = React.useContext(DistributedWorkloadsContext); + const requiredFetches = [localQueues, clusterQueues]; const error = requiredFetches.find((f) => !!f.error)?.error; const loaded = requiredFetches.every((f) => f.loaded); @@ -25,8 +25,8 @@ export const RequestedResources: React.FC = () => { } const requestedByThisProject = getQueueRequestedResources(localQueues.data); - const requestedByAllProjects = getQueueRequestedResources([clusterQueue.data]); - const totalSharedQuota = getTotalSharedQuota(clusterQueue.data); + const requestedByAllProjects = getQueueRequestedResources(clusterQueues.data); + const totalSharedQuota = getTotalSharedQuota(clusterQueues.data); return (