[dashboard] Fix resource quota table on Tenant page (#1934)

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does

- Fix `Used` column to show correct data
- Removes table from `Info` page
- Add patch to let flatMap work with jsonpath

<img width="1800" height="940" alt="resource quota table example"
src="https://github.com/user-attachments/assets/421d1d32-fdc8-4269-838f-b02db0ed6712"
/>


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[dashboard] fix resource quota table on tenant details page
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed resource quota data fetching for tenant resources to use correct
namespace references from status fields.

* **Refactor**
* Refined dashboard layout for resource quota displays and improved
custom field resolution with enhanced dynamic key mapping for complex
data structures.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Andrei Kvapil 2026-02-02 09:57:46 +01:00 committed by GitHub
commit b558489d22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 23 deletions

View file

@ -174,27 +174,6 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
}),
)
}
if kind == "Info" {
rightColStack = append(rightColStack,
antdFlexVertical("resource-quotas-block", 4, []any{
antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{
"fontSize": float64(20),
"marginBottom": float64(12),
}),
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "resource-quotas-table",
"baseprefix": "/openapi-ui",
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-resource-quotas",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas",
"pathToItems": []any{`items`},
},
},
}),
)
}
if kind == "Tenant" {
rightColStack = append(rightColStack,
antdFlexVertical("resource-quotas-block", 4, []any{
@ -209,7 +188,7 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
"baseprefix": "/openapi-ui",
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-resource-quotas",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{reqsJsonPath[0]['.status.namespace']}/resourcequotas",
"pathToItems": []any{`items`},
},
},

View file

@ -194,7 +194,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createFlatMapColumn("Data", ".spec.hard"),
createStringColumn("Resource", "_flatMapData_Key"),
createStringColumn("Hard", "_flatMapData_Value"),
createStringColumn("Used", ".status.used['{_flatMapData_Key}']"),
createStringColumn("Used", ".status.used[_flatMapData_Key]"),
}),
// Factory ingress details rules

View file

@ -0,0 +1,90 @@
diff --git a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts
index 87a0f12..fb2e1cc 100644
--- a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts
+++ b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts
@@ -134,22 +134,6 @@ export const prepare = ({
// impossible in k8s
return {}
})
- if (customFields.length > 0) {
- dataSource = dataSource.map((el: TJSON) => {
- const newFieldsForComplexJsonPath: Record<string, TJSON> = {}
- customFields.forEach(({ dataIndex, jsonPath }) => {
- const jpQueryResult = jp.query(el, `$${jsonPath}`)
- newFieldsForComplexJsonPath[dataIndex] =
- Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult
- })
- if (typeof el === 'object') {
- return { ...el, ...newFieldsForComplexJsonPath }
- }
- // impossible in k8s
- return { ...newFieldsForComplexJsonPath }
- })
- }
-
// Handle flatMap: expand rows for map objects
// Process all flatMap columns sequentially
if (flatMapColumns.length > 0 && dataSource) {
@@ -204,6 +188,62 @@ export const prepare = ({
currentDataSource = expandedDataSource
})
dataSource = currentDataSource
+ }
+
+ if (customFields.length > 0) {
+ dataSource = dataSource.map((el: TJSON) => {
+ const newFieldsForComplexJsonPath: Record<string, TJSON> = {}
+ customFields.forEach(({ dataIndex, jsonPath }) => {
+ let fieldValue: TJSON = null
+ let handled = false
+
+ const flatMapMatch = jsonPath.match(/^(.*)\[(_flatMap[^\]]+_Key)\](.*)$/)
+ if (flatMapMatch && el && typeof el === 'object' && !Array.isArray(el)) {
+ const basePath = flatMapMatch[1]
+ const keyField = flatMapMatch[2]
+ const tailPath = flatMapMatch[3]
+ const keyValue = (el as Record<string, unknown>)[keyField]
+ if (keyValue !== null && keyValue !== undefined) {
+ const baseResult = jp.query(el, `$${basePath}`)[0]
+ if (baseResult && typeof baseResult === 'object' && !Array.isArray(baseResult)) {
+ const baseValue = (baseResult as Record<string, unknown>)[String(keyValue)]
+ if (tailPath) {
+ const normalizedTailPath =
+ tailPath.startsWith('.') || tailPath.startsWith('[') ? tailPath : `.${tailPath}`
+ const tailResult = jp.query(baseValue, `$${normalizedTailPath}`)
+ fieldValue = Array.isArray(tailResult) && tailResult.length === 1 ? tailResult[0] : tailResult
+ } else {
+ fieldValue = baseValue as TJSON
+ }
+ handled = true
+ }
+ }
+ }
+
+ if (!handled) {
+ let resolvedJsonPath = jsonPath
+ if (el && typeof el === 'object' && !Array.isArray(el)) {
+ resolvedJsonPath = jsonPath.replace(/\[(_flatMap[^\]]+_Key)\]/g, (match, keyField) => {
+ const keyValue = (el as Record<string, unknown>)[keyField]
+ if (keyValue === null || keyValue === undefined) {
+ return match
+ }
+ const escaped = String(keyValue).replace(/'/g, "\\'")
+ return `['${escaped}']`
+ })
+ }
+ const jpQueryResult = jp.query(el, `$${resolvedJsonPath}`)
+ fieldValue = Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult
+ }
+
+ newFieldsForComplexJsonPath[dataIndex] = fieldValue
+ })
+ if (typeof el === 'object') {
+ return { ...el, ...newFieldsForComplexJsonPath }
+ }
+ // impossible in k8s
+ return { ...newFieldsForComplexJsonPath }
+ })
}
} else {
dataSource = dataItems.map((el: TJSON) => {