Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions frontend/public/components/__tests__/resource-quota.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,61 @@ describe('Check applied cluster quota table columns by ResourceUsageRow', () =>
expect(screen.getByText('2')).toBeVisible();
});
});

describe('Check memory resource humanization in ResourceUsageRow', () => {
const memoryQuota = {
apiVersion: 'v1',
kind: 'ResourceQuota',
metadata: { name: 'example', namespace: 'example' },
spec: { hard: { 'requests.memory': '2147483648' } },
status: {
hard: { 'requests.memory': '2147483648' },
used: { 'requests.memory': '1073741824' },
},
};

it('displays memory values in human-readable format (GiB)', () => {
renderWithProviders(
<table>
<tbody>
<ResourceUsageRow resourceType={'requests.memory'} quota={memoryQuota} />
</tbody>
</table>,
);

// Verify the resource type
expect(screen.getByText('requests.memory')).toBeVisible();

// Verify memory values are humanized (1 GiB used, 2 GiB limit)
expect(screen.getByText('1 GiB')).toBeVisible();
expect(screen.getByText('2 GiB')).toBeVisible();
});

const storageQuota = {
apiVersion: 'v1',
kind: 'ResourceQuota',
metadata: { name: 'example', namespace: 'example' },
spec: { hard: { 'requests.storage': '10737418240' } },
status: {
hard: { 'requests.storage': '10737418240' },
used: { 'requests.storage': '5368709120' },
},
};

it('displays storage values in human-readable format (GiB)', () => {
renderWithProviders(
<table>
<tbody>
<ResourceUsageRow resourceType={'requests.storage'} quota={storageQuota} />
</tbody>
</table>,
);

// Verify the resource type
expect(screen.getByText('requests.storage')).toBeVisible();

// Verify storage values are humanized (5 GiB used, 10 GiB limit)
expect(screen.getByText('5 GiB')).toBeVisible();
expect(screen.getByText('10 GiB')).toBeVisible();
});
});
28 changes: 22 additions & 6 deletions frontend/public/components/resource-quota.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { SectionHeading } from './utils/headings';
import { navFactory } from './utils/horizontal-nav';
import { ResourceLink } from './utils/resource-link';
import { ResourceSummary } from './utils/details-page';
import { convertToBaseValue } from './utils/units';
import { convertToBaseValue, humanizeBinaryBytes } from './utils/units';
import { FieldLevelHelp } from './utils/field-level-help';
import { useAccessReview } from './utils/rbac';
import { LabelList } from './utils/label-list';
Expand Down Expand Up @@ -82,6 +82,22 @@ const getQuotaResourceTypes = (quota) => {
return _.keys(specHard).sort();
};

const isBinaryResourceType = (resourceType) => {
// Resource types that should be displayed as binary bytes (e.g., GiB, MiB)
return (
resourceType.includes('memory') ||
resourceType.includes('storage') ||
resourceType.includes('ephemeral-storage')
);
Comment on lines +85 to +91

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and inspect the relevant section with line numbers.
git ls-files 'frontend/public/components/resource-quota.jsx'
wc -l frontend/public/components/resource-quota.jsx
sed -n '1,180p' frontend/public/components/resource-quota.jsx | cat -n

# Find where the helper is used and any related quota formatting logic.
rg -n "isBinaryResourceType|humanize|resourceType" frontend/public/components frontend -g '!**/node_modules/**'

Repository: openshift/console

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the resource quota tests around memory/storage handling.
sed -n '180,260p' frontend/public/components/__tests__/resource-quota.spec.tsx | cat -n

# Inspect the byte humanization helper to understand what happens to count values.
sed -n '330,430p' frontend/public/components/utils/units.js | cat -n

# Look for any resource quota keys that contain "storage" but are not byte quantities.
rg -n "\bstorage\b|storageclass\.storage\.k8s\.io|ephemeral-storage" frontend/public/components frontend/e2e -g '!**/node_modules/**'

Repository: openshift/console

Length of output: 17646


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the humanization functions used by resource-quota.jsx.
sed -n '140,230p' frontend/public/components/utils/units.js | cat -n

# Check the Kubernetes quota docs mention the storage-class-specific PVC quota key.
python3 - <<'PY'
import urllib.request, re
url = "https://kubernetes.io/docs/concepts/policy/resource-quotas/"
html = urllib.request.urlopen(url, timeout=20).read().decode("utf-8", "replace")
for pat in [
    r"storageclass\.storage\.k8s\.io/persistentvolumeclaims",
    r"<storage-class-name>\.storageclass\.storage\.k8s\.io/persistentvolumeclaims",
]:
    m = re.search(pat, html)
    print(pat, "=>", bool(m))
PY

Repository: openshift/console

Length of output: 5826


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the remaining unit-conversion internals.
sed -n '1,140p' frontend/public/components/utils/units.js | cat -n
sed -n '140,260p' frontend/public/components/utils/units.js | cat -n

# Search for resource-quota tests that exercise non-byte quota keys.
rg -n "persistentvolumeclaims|pods|services|configmaps|secrets|count/" frontend/public/components/__tests__/resource-quota.spec.tsx frontend/public/components/resource-quota.jsx frontend/public/components -g '!**/node_modules/**'

Repository: openshift/console

Length of output: 29271


Match only explicit binary quota keys. includes('storage') also matches count-based keys like storageclass.storage.k8s.io/persistentvolumeclaims, so their values get byte formatting. Use an allowlist of the known byte-based resource keys instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/public/components/resource-quota.jsx` around lines 85 - 91, Update
isBinaryResourceType to use an allowlist of explicit binary quota resource keys,
rather than substring checks for “memory” or “storage.” Ensure only the known
byte-based keys are classified as binary, while count-based keys such as
storageclass quota resources remain non-binary.

Source: MCP tools

};

const formatResourceValue = (value, resourceType) => {
if (value === null || value === undefined) {
return DASH;
}
return isBinaryResourceType(resourceType) ? humanizeBinaryBytes(value).string : value;
};
Comment on lines +94 to +99

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve numeric zero values.

!value converts a legitimate 0 usage into DASH. The existing ACRQ fixture supplies numeric zero at Line 156 and expects 0 at Line 183, so this change causes that rendering/test to fail. Check explicitly for undefined, null, or an empty string instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/public/components/resource-quota.jsx` around lines 94 - 99, Update
formatResourceValue so numeric zero is preserved rather than converted to DASH.
Replace the broad falsy check with an explicit check for undefined, null, or an
empty string, while leaving binary-resource formatting and other values
unchanged.


export const getACRQResourceUsage = (quota, resourceType, namespace) => {
let used;
if (namespace) {
Expand Down Expand Up @@ -190,9 +206,9 @@ export const ResourceUsageRow = ({ quota, resourceType, namespace = undefined })
<Td visibility={['hidden', 'visibleOnMd']} className="co-resource-quota-icon">
<UsageIcon percent={percent.namespace} />
</Td>
<Td>{used.namespace}</Td>
<Td>{totalUsed}</Td>
<Td>{max}</Td>
<Td>{formatResourceValue(used.namespace, resourceType)}</Td>
<Td>{formatResourceValue(totalUsed, resourceType)}</Td>
<Td>{formatResourceValue(max, resourceType)}</Td>
</Tr>
);
}
Expand All @@ -204,8 +220,8 @@ export const ResourceUsageRow = ({ quota, resourceType, namespace = undefined })
<Td visibility={['hidden', 'visibleOnMd']} className="co-resource-quota-icon">
<UsageIcon percent={percent} />
</Td>
<Td>{used}</Td>
<Td>{max}</Td>
<Td>{formatResourceValue(used, resourceType)}</Td>
<Td>{formatResourceValue(max, resourceType)}</Td>
</Tr>
);
};
Expand Down