From 7ed344215ca7c0222a7f4718fcca5a186a58427b Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Thu, 30 Sep 2021 13:08:22 -0400 Subject: [PATCH] Addressed PR feedback --- .../components/resource-quota.spec.tsx | 51 +++++ .../console-shared/src/constants/common.ts | 1 - frontend/public/actions/features.ts | 8 - .../public/components/default-resource.tsx | 74 +++++-- frontend/public/components/factory/table.tsx | 7 - frontend/public/components/resource-quota.jsx | 180 +++++++++--------- 6 files changed, 202 insertions(+), 119 deletions(-) diff --git a/frontend/__tests__/components/resource-quota.spec.tsx b/frontend/__tests__/components/resource-quota.spec.tsx index 4386c8b59b32..47cbaf41b2d9 100644 --- a/frontend/__tests__/components/resource-quota.spec.tsx +++ b/frontend/__tests__/components/resource-quota.spec.tsx @@ -98,3 +98,54 @@ describe('Check cluster quota table columns by ResourceUsageRow', () => { 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..394bcea366c6 100644 --- a/frontend/public/components/default-resource.tsx +++ b/frontend/public/components/default-resource.tsx @@ -72,10 +72,12 @@ export const DetailsForKind = ( ); }; -export const DefaultList: React.FC = (props) => { +export const DefaultList: React.FC = ( + props, +) => { const { t } = useTranslation(); - const { kinds } = props; + const { kinds, namespace } = props; const TableHeader = () => { return [ @@ -104,16 +106,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 +116,42 @@ export const DefaultList: React.FC = (props) = - {resourceNamespace ? ( - + {obj.metadata.namespace ? ( + + ) : ( + t('public~None') + )} + + + + + + + + + ); + }; + + const TableRowForACRQ: React.FC> = ({ obj, customData }) => { + const kind = referenceFor(obj) || customData.kind; + const menuActions = [...Kebab.getExtensionsActionsForKind(kindObj(kind)), ...common]; + + return ( + <> + + + + + {customData.namespace ? ( + ) : ( t('public~None') )} @@ -157,7 +182,26 @@ export const DefaultList: React.FC = (props) = [kinds], ); - return ( + const customDataForACRQ = React.useMemo( + () => ({ + kind: kinds[0], + namespace, + }), + [kinds, namespace], + ); + + const appliedClusterQuotaReference = referenceForModel(AppliedClusterResourceQuotaModel); + const isACRQ = kinds[0] === appliedClusterQuotaReference; + return isACRQ ? ( + + ) : (
; }; -export type TableRowForKindProps = K8sResourceKind & { namespace?: string }; - DefaultDetailsPage.displayName = 'DefaultDetailsPage'; diff --git a/frontend/public/components/factory/table.tsx b/frontend/public/components/factory/table.tsx index 554d0c10066d..b6ca45e29235 100644 --- a/frontend/public/components/factory/table.tsx +++ b/frontend/public/components/factory/table.tsx @@ -270,7 +270,6 @@ const VirtualBody: React.FC = (props) => { scrollTop, width, getRowProps, - namespace, } = props; const cellMeasurementCache = new CellMeasurerCache({ @@ -284,7 +283,6 @@ const VirtualBody: React.FC = (props) => { obj: data[index], columns, customData, - namespace, }; // do not render non visible elements (this excludes overscan) @@ -333,7 +331,6 @@ export type RowFunctionArgs = { obj: T; columns: any[]; customData?: C; - namespace?: string; }; export type VirtualBodyProps = { @@ -348,7 +345,6 @@ export type VirtualBodyProps = { width: number; expand: boolean; getRowProps?: (obj: D) => Partial>; - namespace?: string; }; type HeaderFunc = (componentProps: ComponentProps) => any[]; @@ -419,7 +415,6 @@ export const Table: React.FC = ({ expand, label, mock, - namespace, selectedResourcesForKind, 'aria-label': ariaLabel, virtualize = true, @@ -575,7 +570,6 @@ export const Table: React.FC = ({ width={width} expand={expand} getRowProps={getRowProps} - namespace={namespace} /> )} @@ -678,7 +672,6 @@ export type TableProps = Partial> & { expand?: boolean; scrollElement?: HTMLElement | (() => HTMLElement); getRowProps?: VirtualBodyProps['getRowProps']; - namespace?: string; }; export type ComponentProps = { diff --git a/frontend/public/components/resource-quota.jsx b/frontend/public/components/resource-quota.jsx index 09bd1cfddabd..a090c3b93a57 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,19 +407,17 @@ const Details_ = ({ obj: rq, flags }) => { ); }; -const Details = connectToFlags(FLAGS.CAN_LIST_CLUSTER_QUOTA)(Details_); - -const ResourceQuotaTableRow_ = ({ obj: rq, flags, namespace }) => { +const ResourceQuotaTableRow = ({ obj: rq, customData }) => { const { t } = useTranslation(); return ( <> { )} - + ); }; -const ResourceQuotaTableRow = connectToFlags(FLAGS.CAN_LIST_CLUSTER_QUOTA)(ResourceQuotaTableRow_); - export const ResourceQuotasList = (props) => { const { t } = useTranslation(); const ResourceQuotaTableHeader = () => { @@ -461,6 +470,7 @@ export const ResourceQuotasList = (props) => { Header={ResourceQuotaTableHeader} Row={ResourceQuotaTableRow} virtualize + customData={{ namespace: props.namespace }} /> ); }; @@ -475,78 +485,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 (