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
2 changes: 2 additions & 0 deletions locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"No Upload URL found {{configError}}": "No Upload URL found {{configError}}",
"No username set, see operating system documentation for the default username.": "No username set, see operating system documentation for the default username.",
"No valid results found in ConfigMap": "No valid results found in ConfigMap",
"No virtualmachine networks found": "No virtualmachine networks found",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent casing: virtualmachine should be VirtualMachine.

This entry uses virtualmachine (lowercase) while the rest of the file consistently uses VirtualMachine (PascalCase) for the resource name—see lines 1291–1293 for similar "No ... found" patterns.

🔧 Suggested fix
-  "No virtualmachine networks found": "No virtualmachine networks found",
+  "No VirtualMachine networks found": "No VirtualMachine networks found",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachine networks found": "No VirtualMachine networks found",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/en/plugin__kubevirt-plugin.json` at line 1290, The localization
key/value "No virtualmachine networks found" uses incorrect casing; update it to
"No VirtualMachine networks found" to match the project's PascalCase convention
for the resource name (match other entries like "No VirtualMachine templates
found"); ensure both the JSON key and value (if identical) are changed
consistently so lookup and display remain correct.

"No VirtualMachineClusterInstanceType found": "No VirtualMachineClusterInstanceType found",
"No VirtualMachineInstanceTypes found": "No VirtualMachineInstanceTypes found",
"No VirtualMachines found": "No VirtualMachines found",
Expand Down Expand Up @@ -2127,6 +2128,7 @@
"VirtualMachine name field is mandatory": "VirtualMachine name field is mandatory",
"VirtualMachine name not valid": "VirtualMachine name not valid",
"VirtualMachine name: {{vmName}}": "VirtualMachine name: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "VirtualMachine statuses",
"VirtualMachine storage": "VirtualMachine storage",
"VirtualMachine Templates": "VirtualMachine Templates",
Expand Down
2 changes: 2 additions & 0 deletions locales/es/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,7 @@
"No Upload URL found {{configError}}": "No se encontró URL de carga {{configError}}",
"No username set, see operating system documentation for the default username.": "Sin nombre de usuario establecido, consulte la documentación del sistema operativo para conocer el nombre de usuario predeterminado.",
"No valid results found in ConfigMap": "No se encontraron resultados válidos en ConfigMap",
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachineClusterInstanceType found": "No se encontró ningún VirtualMachineClusterInstanceType",
"No VirtualMachineInstanceTypes found": "No se encontraron VirtualMachineInstanceTypes",
"No VirtualMachines found": "No se encontraron VirtualMachines",
Expand Down Expand Up @@ -2148,6 +2149,7 @@
"VirtualMachine name field is mandatory": "El campo de nombre de VirtualMachine es obligatorio.",
"VirtualMachine name not valid": "El nombre de VirtualMachine no es válido.",
"VirtualMachine name: {{vmName}}": "Nombre de VirtualMachine: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "Estados de VirtualMachine",
"VirtualMachine storage": "Almacenamiento de VirtualMachine",
"VirtualMachine Templates": "Plantillas de VirtualMachine",
Expand Down
2 changes: 2 additions & 0 deletions locales/fr/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,7 @@
"No Upload URL found {{configError}}": "Aucune URL de téléchargement trouvée {{configError}}",
"No username set, see operating system documentation for the default username.": "Aucun nom d'utilisateur défini, consultez la documentation du système d'exploitation pour le nom d'utilisateur par défaut.",
"No valid results found in ConfigMap": "Aucun résultat valide trouvé dans ConfigMap",
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachineClusterInstanceType found": "Aucun VirtualMachineClusterInstanceType trouvé",
"No VirtualMachineInstanceTypes found": "Aucun VirtualMachineInstanceTypes trouvé",
"No VirtualMachines found": "Aucune machine virtuelle trouvée",
Expand Down Expand Up @@ -2148,6 +2149,7 @@
"VirtualMachine name field is mandatory": "Le champ Nom de la machine virtuelle est obligatoire",
"VirtualMachine name not valid": "Nom de la machine virtuelle non valide",
"VirtualMachine name: {{vmName}}": "Nom de la machine virtuelle\u00a0: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "États des machines virtuelles",
"VirtualMachine storage": "Stockage de machine virtuelle",
"VirtualMachine Templates": "Modèles de machines virtuelles",
Expand Down
2 changes: 2 additions & 0 deletions locales/ja/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,7 @@
"No Upload URL found {{configError}}": "アップロード URL が見つかりません {{configError}}",
"No username set, see operating system documentation for the default username.": "ユーザー名が設定されていません。デフォルトのユーザー名については、オペレーティングシステムのドキュメントを参照してください。",
"No valid results found in ConfigMap": "ConfigMap 内に有効な結果が見つかりません",
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachineClusterInstanceType found": "VirtualMachineClusterInstanceType が見つかりません",
"No VirtualMachineInstanceTypes found": "VirtualMachineInstanceType が見つかりません",
"No VirtualMachines found": "VirtualMachine が見つかりません",
Expand Down Expand Up @@ -2119,6 +2120,7 @@
"VirtualMachine name field is mandatory": "VirtualMachine 名フィールドは必須です",
"VirtualMachine name not valid": "VirtualMachine 名が無効です",
"VirtualMachine name: {{vmName}}": "仮想マシン名: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "VirtualMachine のステータス",
"VirtualMachine storage": "VirtualMachine ストレージ",
"VirtualMachine Templates": "VirtualMachine テンプレート",
Expand Down
2 changes: 2 additions & 0 deletions locales/ko/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,7 @@
"No Upload URL found {{configError}}": "업로드 URL을 찾을 수 없음 {{configError}}",
"No username set, see operating system documentation for the default username.": "사용자 이름이 설정되지 않았습니다. 기본 사용자 이름은 운영 체제 설명서를 참조하세요.",
"No valid results found in ConfigMap": "ConfigMap에서 유효한 결과를 찾을 수 없음",
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachineClusterInstanceType found": "VirtualMachineClusterInstanceType을 찾을 수 없음",
"No VirtualMachineInstanceTypes found": "VirtualMachineInstanceTypes를 찾을 수 없음",
"No VirtualMachines found": "VirtualMachines를 찾을 수 없음",
Expand Down Expand Up @@ -2119,6 +2120,7 @@
"VirtualMachine name field is mandatory": "VirtualMachine 이름 필드는 필수입니다",
"VirtualMachine name not valid": "VirtualMachine 이름이 유효하지 않음",
"VirtualMachine name: {{vmName}}": "VirtualMachine 이름: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "VirtualMachine 상태",
"VirtualMachine storage": "VirtualMachine 스토리지",
"VirtualMachine Templates": "VirtualMachine 템플릿",
Expand Down
2 changes: 2 additions & 0 deletions locales/zh/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,7 @@
"No Upload URL found {{configError}}": "找到上传 URL {{configError}}",
"No username set, see operating system documentation for the default username.": "没有用户名设置,请参阅操作系统文档了解默认用户名。",
"No valid results found in ConfigMap": "ConfigMap 中没有找到有效结果",
"No virtualmachine networks found": "No virtualmachine networks found",
"No VirtualMachineClusterInstanceType found": "没有找到 VirtualMachineClusterInstanceType",
"No VirtualMachineInstanceTypes found": "没有找到 VirtualMachineInstanceTypes",
"No VirtualMachines found": "没有找到虚拟机",
Expand Down Expand Up @@ -2119,6 +2120,7 @@
"VirtualMachine name field is mandatory": "VirtualMachine 名称字段是必需的",
"VirtualMachine name not valid": "VirtualMachine 名称无效",
"VirtualMachine name: {{vmName}}": "VirtualMachine 名称: {{vmName}}",
"VirtualMachine networks": "VirtualMachine networks",
"VirtualMachine statuses": "VirtualMachine 状态",
"VirtualMachine storage": "VirtualMachine 存储",
"VirtualMachine Templates": "VirtualMachine 模板",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, useState } from 'react';
import React, { FC, useEffect, useRef, useState } from 'react';

import { modelToGroupVersionKind, NamespaceModel } from '@kubevirt-ui-ext/kubevirt-api/console';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
Expand All @@ -23,27 +23,56 @@ const ExpandableProjectList: FC<ExpandableProjectListProps> = ({
}) => {
const { t } = useKubevirtTranslation();
const [expand, setExpand] = useState(false);
const isFirstRun = useRef(true);

if (!loaded) return <Skeleton />;

if (isEmpty(projectNames)) return <span>{emptyMessage ?? t('No projects matched')}</span>;

return (
<>
{projectNames.slice(0, expand ? projectNames.length : maxItems).map((project) => (
<ResourceLink
groupVersionKind={modelToGroupVersionKind(NamespaceModel)}
key={project}
name={project}
/>
))}
{projectNames.length > maxItems && (
<Button onClick={() => setExpand(!expand)} variant={ButtonVariant.link}>
{expand ? t('Show less') : t('+{{num}} more', { num: projectNames.length - maxItems })}
</Button>
// Notify virtualized table to re-measure after expand/collapse so row overlap is avoided
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
const id = requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.dispatchEvent(new Event('resize'));
});
});
return () => cancelAnimationFrame(id);
}, [expand]);
Comment on lines +28 to +40
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx | head -50

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 1911


🏁 Script executed:

rg "CNV-79276" --type ts --type tsx

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 98


🏁 Script executed:

rg "CNV-79276"

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 53


🏁 Script executed:

# Check if there are any comments or references to issues in the ExpandableProjectList file
cat src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx | grep -i "cny\|cnv\|issue\|jira\|bug"

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 53


🏁 Script executed:

# Check if there's a constants file for this component
ls -la src/utils/components/ExpandableProjectList/

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 326


🏁 Script executed:

cat src/utils/components/ExpandableProjectList/constants.ts

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 103


🏁 Script executed:

# Check if there are any other references to this pattern or related issues
rg "requestAnimationFrame" src/utils/components/ExpandableProjectList/ -A 5 -B 2

Repository: kubevirt-ui/kubevirt-plugin

Length of output: 920


Cancel both animation-frame callbacks in the resize workaround.

Line 35 schedules a second requestAnimationFrame, but line 39 cancels only the first handle. The inner callback can still fire after rapid toggle/unmount and dispatch a stale global resize event.

Add a variable to capture the nested animation frame ID and cancel it in the cleanup function:

🔧 Proposed fix
   // Notify virtualized table to re-measure after expand/collapse so row overlap is avoided
   useEffect(() => {
     if (isFirstRun.current) {
       isFirstRun.current = false;
       return;
     }
+    let nestedId: number | undefined;
     const id = requestAnimationFrame(() => {
-      requestAnimationFrame(() => {
+      nestedId = requestAnimationFrame(() => {
         window.dispatchEvent(new Event('resize'));
       });
     });
-    return () => cancelAnimationFrame(id);
+    return () => {
+      cancelAnimationFrame(id);
+      if (nestedId !== undefined) cancelAnimationFrame(nestedId);
+    };
   }, [expand]);

If this is a workaround for a tracked issue, update the comment to reference it per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Notify virtualized table to re-measure after expand/collapse so row overlap is avoided
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
const id = requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.dispatchEvent(new Event('resize'));
});
});
return () => cancelAnimationFrame(id);
}, [expand]);
// Notify virtualized table to re-measure after expand/collapse so row overlap is avoided
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
return;
}
let nestedId: number | undefined;
const id = requestAnimationFrame(() => {
nestedId = requestAnimationFrame(() => {
window.dispatchEvent(new Event('resize'));
});
});
return () => {
cancelAnimationFrame(id);
if (nestedId !== undefined) cancelAnimationFrame(nestedId);
};
}, [expand]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/components/ExpandableProjectList/ExpandableProjectList.tsx` around
lines 28 - 40, The cleanup only cancels the outer requestAnimationFrame handle;
capture the nested rAF id as well and cancel it to avoid the inner callback
dispatching a stale 'resize' after rapid toggles/unmount. Update the effect in
ExpandableProjectList: declare a second variable (e.g., innerId) that stores the
inner requestAnimationFrame returned inside the outer callback, and call
cancelAnimationFrame on both id and innerId in the returned cleanup; keep the
isFirstRun, expand dependency and window.dispatchEvent usage unchanged and, if
this is a workaround, add a comment referencing the tracked issue.


if (!loaded) {
return (
<div style={{ alignItems: 'center', display: 'flex', minHeight: '48px' }}>
<Skeleton />
</div>
);
}

const cellContent = (
<div style={{ minHeight: '48px' }}>
{isEmpty(projectNames) ? (
<span>{emptyMessage ?? t('No projects matched')}</span>
) : (
<>
{projectNames.slice(0, expand ? projectNames.length : maxItems).map((project) => (
<ResourceLink
groupVersionKind={modelToGroupVersionKind(NamespaceModel)}
key={project}
name={project}
/>
))}
{projectNames.length > maxItems && (
<Button onClick={() => setExpand(!expand)} variant={ButtonVariant.link}>
{expand
? t('Show less')
: t('+{{num}} more', { num: projectNames.length - maxItems })}
</Button>
)}
</>
)}
</>
</div>
);

return cellContent;
};

export default ExpandableProjectList;
47 changes: 28 additions & 19 deletions src/views/vmnetworks/list/VMNetworkList.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,56 @@
import React, { FC } from 'react';

import KubevirtTable from '@kubevirt-utils/components/KubevirtTable/KubevirtTable';
import StateHandler from '@kubevirt-utils/components/StateHandler/StateHandler';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getName } from '@kubevirt-utils/resources/shared';
import { ClusterUserDefinedNetworkKind } from '@kubevirt-utils/resources/udn/types';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import {
ListPageBody,
ListPageFilter,
useListPageFilter,
VirtualizedTable,
} from '@openshift-console/dynamic-plugin-sdk';

import useVMNetworks from '../hooks/useVMNetworks';

import LocalnetEmptyState from './components/LocalnetEmptyState/LocalnetEmptyState';
import VMNetworkRow from './components/VMNetworkRow';
import useVMNetworkColumns from './hooks/useVMNetworkColumns';
import useVMNetworkTableColumns from './hooks/useVMNetworkTableColumns';

type VMNetworkListProps = {
onCreate: () => void;
};

const VMNetworkList: FC<VMNetworkListProps> = ({ onCreate }) => {
const { t } = useKubevirtTranslation();
const [vmNetworks, loaded, error] = useVMNetworks();
const [data, filteredData, onFilterChange] = useListPageFilter(vmNetworks);
const columns = useVMNetworkColumns();
const columns = useVMNetworkTableColumns();

let body: React.ReactNode = null;
if (isEmpty(data)) {
body = <LocalnetEmptyState onCreate={onCreate} />;
} else {
body = (
<ListPageBody>
<ListPageFilter data={data} loaded={loaded} onFilterChange={onFilterChange} />
<KubevirtTable<ClusterUserDefinedNetworkKind>
ariaLabel={t('VirtualMachine networks')}
columns={columns}
data={filteredData}
getRowId={(row) => getName(row) ?? ''}
loaded={loaded}
loadError={error}
noFilteredDataEmptyMsg={t('No virtualmachine networks found')}
unfilteredData={data}
Comment on lines +38 to +45
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize VM network labels to avoid typo and i18n key drift.

Line 44 uses "No virtualmachine networks found" (missing space), and Line 38/44 introduce inconsistent wording variants. This will surface awkward UI copy and duplicate translation keys.

💡 Suggested fix
-          ariaLabel={t('VirtualMachine networks')}
+          ariaLabel={t('Virtual machine networks')}
...
-          noFilteredDataEmptyMsg={t('No virtualmachine networks found')}
+          noFilteredDataEmptyMsg={t('No virtual machine networks found')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ariaLabel={t('VirtualMachine networks')}
columns={columns}
data={filteredData}
getRowId={(row) => getName(row) ?? ''}
loaded={loaded}
loadError={error}
noFilteredDataEmptyMsg={t('No virtualmachine networks found')}
unfilteredData={data}
ariaLabel={t('Virtual machine networks')}
columns={columns}
data={filteredData}
getRowId={(row) => getName(row) ?? ''}
loaded={loaded}
loadError={error}
noFilteredDataEmptyMsg={t('No virtual machine networks found')}
unfilteredData={data}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/vmnetworks/list/VMNetworkList.tsx` around lines 38 - 45, Normalize
the VM network UI copy to a single consistent phrase and i18n key: replace the
inconsistent "VirtualMachine networks"/"virtualmachine networks" strings passed
to t(...) in VMNetworkList (props like ariaLabel and noFilteredDataEmptyMsg)
with a standardized phrase such as "Virtual machine networks" and "No virtual
machine networks found"; update the corresponding translation keys/entries to
match the new strings and ensure getRowId/getName usage remains unchanged.

/>
</ListPageBody>
);
}

return (
<StateHandler error={error} hasData={!!data} loaded={loaded} showSkeletonLoading withBullseye>
{isEmpty(data) ? (
<LocalnetEmptyState onCreate={onCreate} />
) : (
<ListPageBody>
<ListPageFilter data={data} loaded={loaded} onFilterChange={onFilterChange} />
<VirtualizedTable<ClusterUserDefinedNetworkKind>
columns={columns}
data={filteredData}
loaded={loaded}
loadError={error}
Row={VMNetworkRow}
unfilteredData={data}
/>
</ListPageBody>
)}
{body}
</StateHandler>
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/views/vmnetworks/list/hooks/useVMNetworkColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const sortUDNByMTU =
return direction === 'asc' ? result : -result;
};

const useVMNetworkColumns = (): { id: string; title: string }[] => {
const useVMNetworkColumns = (): [TableColumn<ClusterUserDefinedNetworkKind>[], boolean] => {
const { t } = useKubevirtTranslation();

const columns: TableColumn<ClusterUserDefinedNetworkKind>[] = useMemo(
Expand Down Expand Up @@ -56,13 +56,13 @@ const useVMNetworkColumns = (): { id: string; title: string }[] => {
[t],
);

const [activeColumns] = useActiveColumns<ClusterUserDefinedNetworkKind>({
const [activeColumns, userSettingsLoaded] = useActiveColumns<ClusterUserDefinedNetworkKind>({
columnManagementID: 'VMNetworkList',
columns,
showNamespaceOverride: false,
});

return activeColumns;
return [activeColumns, userSettingsLoaded];
};

export default useVMNetworkColumns;
70 changes: 70 additions & 0 deletions src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom-v5-compat';
Copy link
Member

@galkremer1 galkremer1 Mar 6, 2026

Choose a reason for hiding this comment

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

Simpler fix available

The root cause diagnosis is correct, but the solution can be simplified.

Issues with this PR

The ExpandableProjectList change is unnecessary and should be reverted.
KubevirtTable renders all rows in the DOM — there is no virtualizer to notify.
The window.dispatchEvent(new Event('resize')) workaround was needed for
VirtualizedTable, which this PR already removes. It also has a bug: the inner
requestAnimationFrame is not cancelled on cleanup.

useVMNetworkColumns.tsx becomes dead code but is not deleted.

Cleaner approach

Same number of files (VMNetworkList, new hook, deleted hook, 6 locales), but no changes to ExpandableProjectList.tsx:

  1. Replace useVMNetworkColumns.tsx with useVMNetworkTableColumns.tsx
    column config in ColumnConfig[] format for KubevirtTable, with explicit
    pf-m-width-* props to preserve column widths. VLAN ID sort fixed to numeric.
  2. Update VMNetworkList.tsx — swap VirtualizedTableKubevirtTable.
  3. Delete useVMNetworkColumns.tsx.
  4. Add "No virtual machine networks found" to all 6 locale files (en, es, fr, ja, ko, zh).
  5. Zero changes to ExpandableProjectList.tsx.

AI prompt to generate this fix

In kubevirt-ui/kubevirt-plugin, fix the row-overlap bug on the VM Networks
page (VMNetworkList.tsx).

  1. Create src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx
    returning ColumnConfig<ClusterUserDefinedNetworkKind>[] for KubevirtTable.
    Columns: Name (link), Connected projects (<MatchedProjects>), Physical
    network name, VLAN ID (numeric sort), MTU (numeric sort), Actions (kebab).
    Add pf-m-width-* props to pin column widths (20 / 25 / 20 / 15 / 15 for
    the five data columns; pf-v6-c-table__action for actions).
  2. Update VMNetworkList.tsx — replace VirtualizedTable with KubevirtTable,
    use the new hook, add ariaLabel, getRowId, and noFilteredDataEmptyMsg.
  3. Delete useVMNetworkColumns.tsx (dead code).
  4. Add "No virtual machine networks found" to
    locales/en/plugin__kubevirt-plugin.json.
  5. Make no changes to ExpandableProjectList.tsx.

Copy link
Member

Choose a reason for hiding this comment

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

Also - please note that while it is not part of the Jira - I think it's work adding a loading state to the table.

Copy link
Member

Choose a reason for hiding this comment

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


import { ColumnConfig } from '@kubevirt-utils/hooks/useDataViewTableSort/types';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { getName } from '@kubevirt-utils/resources/shared';
import { getLocalnet, getMTU, getVLANID } from '@kubevirt-utils/resources/udn/selectors';
import { ClusterUserDefinedNetworkKind } from '@kubevirt-utils/resources/udn/types';
import { NO_DATA_DASH } from '@kubevirt-utils/resources/vm/utils/constants';

import VMNetworkActions from '../../actions/VMNetworkActions';
import { VM_NETWORKS_PATH } from '../../constants';
import MatchedProjects from '../components/MatchedProjects';

const useVMNetworkTableColumns = (): ColumnConfig<ClusterUserDefinedNetworkKind>[] => {
const { t } = useKubevirtTranslation();

return useMemo(
(): ColumnConfig<ClusterUserDefinedNetworkKind>[] => [
{
getValue: (row) => getName(row) ?? '',
key: 'name',
label: t('Name'),
renderCell: (row) => {
const name = getName(row);
return <Link to={`${VM_NETWORKS_PATH}/${name}`}>{name}</Link>;
},
sortable: true,
},
{
key: 'connected-projects',
label: t('Connected projects'),
renderCell: (row) => <MatchedProjects obj={row} />,
},
{
getValue: (row) => getLocalnet(row)?.physicalNetworkName ?? '',
key: 'physicalNetworkName',
label: t('Physical network name'),
renderCell: (row) => getLocalnet(row)?.physicalNetworkName || NO_DATA_DASH,
sortable: true,
},
{
getValue: (row) => String(getVLANID(row) ?? ''),
key: 'vlanID',
label: t('VLAN ID'),
renderCell: (row) => getVLANID(row) ?? NO_DATA_DASH,
sortable: true,
Comment on lines +43 to +47
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix VLAN sorting to be numeric, not lexicographic.

Line 43 converts VLAN ID to string, which can misorder sortable results.

🔧 Suggested fix
-        getValue: (row) => String(getVLANID(row) ?? ''),
+        getValue: (row) => getVLANID(row) ?? -1,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getValue: (row) => String(getVLANID(row) ?? ''),
key: 'vlanID',
label: t('VLAN ID'),
renderCell: (row) => getVLANID(row) ?? NO_DATA_DASH,
sortable: true,
getValue: (row) => getVLANID(row) ?? -1,
key: 'vlanID',
label: t('VLAN ID'),
renderCell: (row) => getVLANID(row) ?? NO_DATA_DASH,
sortable: true,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/views/vmnetworks/list/hooks/useVMNetworkTableColumns.tsx` around lines 43
- 47, The VLAN column is being sorted lexicographically because getValue
currently returns a string; change getValue in the vlanID column to return a
numeric value instead (e.g. Number(getVLANID(row)) when present, or
null/undefined when absent) so sorting is numeric; keep renderCell as-is
(getVLANID(row) ?? NO_DATA_DASH) and reference the column key 'vlanID',
getValue, getVLANID, and renderCell when making the change.

},
{
getValue: (row) => getMTU(row) ?? 0,
key: 'mtu',
label: t('MTU'),
renderCell: (row) => {
const mtu = getMTU(row);
return mtu ?? <div className="pf-v6-u-text-color-subtle">{t('Not available')}</div>;
},
sortable: true,
},
{
key: 'actions',
label: '',
props: { className: 'pf-v6-c-table__action' },
renderCell: (row) => <VMNetworkActions obj={row} />,
},
],
[t],
);
};

export default useVMNetworkTableColumns;