diff --git a/frontend/__tests__/components/resource-quota.spec.tsx b/frontend/__tests__/components/resource-quota.spec.tsx index 4386c8b59b32..976cc854d496 100644 --- a/frontend/__tests__/components/resource-quota.spec.tsx +++ b/frontend/__tests__/components/resource-quota.spec.tsx @@ -98,3 +98,83 @@ describe('Check cluster quota table columns by ResourceUsageRow', () => { expect(col3.text()).toBe('2'); }); }); + +describe('Check cluster quota table columns by ResourceUsageRow', () => { + let wrapper: ShallowWrapper; + const quota = { + apiVersion: 'quota.openshift.io/v1', + kind: 'ClusterResourceQuota', + metadata: { name: 'example' }, + spec: { quota: { hard: { 'limits.cpu': 2 } } }, + status: { total: { hard: { 'limits.cpu': 2 }, used: { 'limits.cpu': 1 } } }, + }; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('renders ResourceUsageRow for each columns', () => { + const col0 = wrapper.childAt(0); + expect(col0.text()).toBe('limits.cpu'); + + const col1 = wrapper.childAt(1); + expect(col1.find('.co-resource-quota-icon').exists()).toBe(true); + + const col2 = wrapper.childAt(2); + expect(col2.text()).toBe('1'); + + const col3 = wrapper.childAt(3); + expect(col3.text()).toBe('2'); + }); +}); + +describe('Check applied cluster quota table columns by ResourceUsageRow', () => { + let wrapper: ShallowWrapper; + const quota = { + apiVersion: 'quota.openshift.io/v1', + kind: 'AppliedClusterResourceQuota', + metadata: { name: 'example' }, + spec: { quota: { hard: { 'limits.cpu': 2 } } }, + status: { + namespaces: [ + { + namespace: 'test-namespace', + status: { used: { 'limits.cpu': 0 }, total: { 'limits.cpu': 2 } }, + }, + { + namespace: 'test-namespace2', + status: { used: { 'limits.cpu': 1 }, total: { 'limits.cpu': 2 } }, + }, + ], + total: { hard: { 'limits.cpu': 2 }, used: { 'limits.cpu': 1 } }, + }, + }; + + beforeEach(() => { + wrapper = shallow( + , + ); + }); + + it('renders ResourceUsageRow for each columns', () => { + const col0 = wrapper.childAt(0); + expect(col0.text()).toBe('limits.cpu'); + + const col1 = wrapper.childAt(1); + expect(col1.find('.co-resource-quota-icon').exists()).toBe(true); + + const col2 = wrapper.childAt(2); + expect(col2.text()).toBe('0'); + + const col3 = wrapper.childAt(3); + expect(col3.text()).toBe('1'); + + const col4 = wrapper.childAt(4); + expect(col4.text()).toBe('2'); + }); +}); diff --git a/frontend/packages/console-shared/src/constants/common.ts b/frontend/packages/console-shared/src/constants/common.ts index 96ef9e6110cb..58df54d8c364 100644 --- a/frontend/packages/console-shared/src/constants/common.ts +++ b/frontend/packages/console-shared/src/constants/common.ts @@ -65,7 +65,6 @@ export enum FLAGS { CAN_LIST_NODE = 'CAN_LIST_NODE', CAN_LIST_PV = 'CAN_LIST_PV', CAN_LIST_CRD = 'CAN_LIST_CRD', - CAN_LIST_CLUSTER_QUOTA = 'CAN_LIST_CLUSTER_QUOTA', CAN_LIST_CHARGEBACK_REPORTS = 'CAN_LIST_CHARGEBACK_REPORTS', CAN_LIST_USERS = 'CAN_LIST_USERS', CAN_LIST_GROUPS = 'CAN_LIST_GROUPS', diff --git a/frontend/public/actions/features.ts b/frontend/public/actions/features.ts index 154ed4d49938..62eb4441dad9 100644 --- a/frontend/public/actions/features.ts +++ b/frontend/public/actions/features.ts @@ -122,14 +122,6 @@ const ssarChecks = [ verb: 'list', }, }, - { - flag: FLAGS.CAN_LIST_CLUSTER_QUOTA, - resourceAttributes: { - group: 'quota.openshift.io', - resource: 'clusterresourcequota', - verb: 'list', - }, - }, { // TODO: Move into OLM plugin flag: FLAGS.CAN_LIST_OPERATOR_GROUP, diff --git a/frontend/public/components/default-resource.tsx b/frontend/public/components/default-resource.tsx index 880855adf791..23594a5f62e3 100644 --- a/frontend/public/components/default-resource.tsx +++ b/frontend/public/components/default-resource.tsx @@ -104,16 +104,9 @@ export const DefaultList: React.FC = (props) = ]; }; - const TableRowForKind: React.FC> = ({ - obj, - customData, - namespace, - }) => { + const TableRowForKind: React.FC> = ({ obj, customData }) => { const kind = referenceFor(obj) || customData.kind; const menuActions = [...Kebab.getExtensionsActionsForKind(kindObj(kind)), ...common]; - const appliedClusterQuotaReference = referenceForModel(AppliedClusterResourceQuotaModel); - const resourceNamespace = - kind === appliedClusterQuotaReference ? namespace : obj.metadata.namespace; return ( <> @@ -121,12 +114,12 @@ export const DefaultList: React.FC = (props) = - {resourceNamespace ? ( - + {obj.metadata.namespace ? ( + ) : ( t('public~None') )} @@ -141,6 +134,32 @@ export const DefaultList: React.FC = (props) = ); }; + const TableRowForACRQ: React.FC> = ({ + obj, + customData, + namespace, + }) => { + const kind = referenceFor(obj) || customData.kind; + const menuActions = [...Kebab.getExtensionsActionsForKind(kindObj(kind)), ...common]; + + return ( + <> + + + + + {namespace ? : t('public~None')} + + + + + + + + + ); + }; + const getAriaLabel = (item) => { const model = modelFor(item); // API discovery happens asynchronously. Avoid runtime errors if the model hasn't loaded. @@ -157,13 +176,15 @@ export const DefaultList: React.FC = (props) = [kinds], ); + const appliedClusterQuotaReference = referenceForModel(AppliedClusterResourceQuotaModel); + return ( ); @@ -186,6 +207,6 @@ export const DefaultDetailsPage: React.FC; }; -export type TableRowForKindProps = K8sResourceKind & { namespace?: string }; +export type TableRowForACRQProps = K8sResourceKind & { namespace?: string }; DefaultDetailsPage.displayName = 'DefaultDetailsPage'; diff --git a/frontend/public/components/resource-quota.jsx b/frontend/public/components/resource-quota.jsx index 09bd1cfddabd..93c74197ff6e 100644 --- a/frontend/public/components/resource-quota.jsx +++ b/frontend/public/components/resource-quota.jsx @@ -12,7 +12,6 @@ import { import { useTranslation } from 'react-i18next'; import { FLAGS, YellowExclamationTriangleIcon } from '@console/shared'; -import { LAST_NAMESPACE_NAME_LOCAL_STORAGE_KEY } from '@console/shared/src/constants'; import { DetailsPage, MultiListPage, Table, TableData } from './factory'; import { Kebab, @@ -51,12 +50,14 @@ const resourceQuotaReference = referenceForModel(ResourceQuotaModel); const clusterQuotaReference = referenceForModel(ClusterResourceQuotaModel); const appliedClusterQuotaReference = referenceForModel(AppliedClusterResourceQuotaModel); -const quotaKind = (quota, canListClusterQuota) => { +const quotaKind = (quota) => { if (quota.metadata.namespace) { return resourceQuotaReference; } - return canListClusterQuota ? clusterQuotaReference : appliedClusterQuotaReference; + return quota.kind === 'ClusterResourceQuota' + ? clusterQuotaReference + : appliedClusterQuotaReference; }; const quotaActions = (quota) => @@ -70,12 +71,11 @@ export const getQuotaResourceTypes = (quota) => { return _.keys(specHard).sort(); }; -const getResourceUsage = (quota, resourceType, isACRQ = false) => { +const getResourceUsage = (quota, resourceType, namespace, isACRQ = false) => { const isCluster = isClusterQuota(quota); const statusPath = isCluster ? ['status', 'total', 'hard'] : ['status', 'hard']; const specPath = isCluster ? ['spec', 'quota', 'hard'] : ['spec', 'hard']; const usedPath = isCluster ? ['status', 'total', 'used'] : ['status', 'used']; - let totalUsedPath; let totalUsed; let used; @@ -84,11 +84,11 @@ const getResourceUsage = (quota, resourceType, isACRQ = false) => { // "used" is the data for the current namespace and "totalUsed" is the cluster-scoped data if (isACRQ) { used = _.get(quota, ['status', 'namespaces']); - const currentNamespace = sessionStorage.getItem(LAST_NAMESPACE_NAME_LOCAL_STORAGE_KEY); - used = used.filter((ns) => ns.namespace === currentNamespace); - used = _.get(used[0], ['status', 'used', resourceType]); - totalUsedPath = ['status', 'total', 'used']; - totalUsed = _.get(quota, [...totalUsedPath, resourceType]); + if (namespace !== undefined) { + used = used.filter((ns) => ns.namespace === namespace); + used = _.get(used[0], ['status', 'used', resourceType]); + } + totalUsed = _.get(quota, ['status', 'total', 'used', resourceType]); } else { used = _.get(quota, [...usedPath, resourceType]); } @@ -123,8 +123,18 @@ export const UsageIcon = ({ percent }) => { return usageIcon; }; -export const ResourceUsageRow = ({ quota, resourceType, isACRQ = false }) => { - const { used, totalUsed, max, percent } = getResourceUsage(quota, resourceType, isACRQ); +export const ResourceUsageRow = ({ + quota, + resourceType, + namespace = undefined, + isACRQ = false, +}) => { + const { used, totalUsed, max, percent } = getResourceUsage( + quota, + resourceType, + namespace, + isACRQ, + ); if (isACRQ) { return (
@@ -299,13 +309,14 @@ export const hasComputeResources = (resourceTypes) => { return _.intersection(resourceTypes, chartResourceTypes).length > 0; }; -const Details_ = ({ obj: rq, flags }) => { +const Details = ({ obj: rq, match }) => { const { t } = useTranslation(); const resourceTypes = getQuotaResourceTypes(rq); const showChartRow = hasComputeResources(resourceTypes); const scopes = _.get(rq, ['spec', 'scopes']); - const kind = quotaKind(rq, flags[FLAGS.CAN_LIST_CLUSTER_QUOTA]); + const kind = quotaKind(rq); const isACRQ = kind === appliedClusterQuotaReference; + const namespace = match?.params?.ns; let text; switch (kind) { case appliedClusterQuotaReference: @@ -381,7 +392,13 @@ const Details_ = ({ obj: rq, flags }) => {
{resourceTypes.map((type) => ( - + ))}
@@ -390,20 +407,16 @@ const Details_ = ({ obj: rq, flags }) => { ); }; -const Details = connectToFlags(FLAGS.CAN_LIST_CLUSTER_QUOTA)(Details_); - -const ResourceQuotaTableRow_ = ({ obj: rq, flags, namespace }) => { +const ResourceQuotaTableRow = ({ obj: rq, namespace }) => { const { t } = useTranslation(); return ( <> @@ -419,18 +432,12 @@ const ResourceQuotaTableRow_ = ({ obj: rq, flags, namespace }) => { )} - + ); }; -const ResourceQuotaTableRow = connectToFlags(FLAGS.CAN_LIST_CLUSTER_QUOTA)(ResourceQuotaTableRow_); - export const ResourceQuotasList = (props) => { const { t } = useTranslation(); const ResourceQuotaTableHeader = () => { @@ -475,78 +482,74 @@ export const quotaType = (quota) => { // Split each resource quota into one row per subject export const flatten = (resources) => _.flatMap(resources, (resource) => _.compact(resource.data)); -export const ResourceQuotasPage = connectToFlags( - FLAGS.OPENSHIFT, - FLAGS.CAN_LIST_CLUSTER_QUOTA, -)(({ namespace, flags, mock, showTitle }) => { - const { t } = useTranslation(); - const resources = [{ kind: 'ResourceQuota', namespaced: true }]; - let rowFilters = null; +export const ResourceQuotasPage = connectToFlags(FLAGS.OPENSHIFT)( + ({ namespace, flags, mock, showTitle }) => { + const { t } = useTranslation(); + const resources = [{ kind: 'ResourceQuota', namespaced: true }]; + let rowFilters = null; - if (flagPending(flags[FLAGS.OPENSHIFT]) || flagPending(flags[FLAGS.CAN_LIST_CLUSTER_QUOTA])) { - return ; - } - if (flags[FLAGS.OPENSHIFT]) { - if (flags[FLAGS.CAN_LIST_CLUSTER_QUOTA]) { + if (flagPending(flags[FLAGS.OPENSHIFT])) { + return ; + } + if (flags[FLAGS.OPENSHIFT]) { resources.push({ kind: referenceForModel(ClusterResourceQuotaModel), namespaced: false, optional: true, }); - } else { resources.push({ kind: referenceForModel(AppliedClusterResourceQuotaModel), namespaced: true, namespace, optional: true, }); - } - rowFilters = [ - { - filterGroupName: t('public~Role'), - type: 'role-kind', - reducer: quotaType, - items: [ - { - id: 'cluster', - title: t('public~Cluster-wide {{resource}}', { - resource: t(ResourceQuotaModel.labelPluralKey), - }), - }, - { - id: 'namespace', - title: t('public~Namespace {{resource}}', { - resource: t(ResourceQuotaModel.labelPluralKey), - }), - }, - ], - }, - ]; - } - const createNS = namespace || 'default'; - const accessReview = { - model: ResourceQuotaModel, - namespace: createNS, - }; - return ( - - ); -}); + rowFilters = [ + { + filterGroupName: t('public~Role'), + type: 'role-kind', + reducer: quotaType, + items: [ + { + id: 'cluster', + title: t('public~Cluster-wide {{resource}}', { + resource: t(ResourceQuotaModel.labelPluralKey), + }), + }, + { + id: 'namespace', + title: t('public~Namespace {{resource}}', { + resource: t(ResourceQuotaModel.labelPluralKey), + }), + }, + ], + }, + ]; + } + const createNS = namespace || 'default'; + const accessReview = { + model: ResourceQuotaModel, + namespace: createNS, + }; + return ( + + ); + }, +); export const ResourceQuotasDetailsPage = (props) => { return (