Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,21 @@ import { ResourceFilters, ResourceMetadata, GetDataViewRows } from './types';
import { useConsoleDataViewData } from './useConsoleDataViewData';
import { useConsoleDataViewFilters } from './useConsoleDataViewFilters';

export type ConsoleDataViewProps<TData, TCustomRowData, TFilters> = {
export const initialFiltersDefault: ResourceFilters = { name: '', label: '' };

export type ConsoleDataViewProps<
TData,
TCustomRowData = any,
TFilters extends ResourceFilters = ResourceFilters
> = {
label?: string;
data: TData[];
loaded: boolean;
loadError?: any;
columns: TableColumn<TData>[];
columnLayout?: ColumnLayout;
columnManagementID?: string;
initialFilters: TFilters;
initialFilters?: TFilters;
Copy link
Member

Choose a reason for hiding this comment

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

does this work to remove the useMemo in line 109?

Suggested change
initialFilters?: TFilters;
initialFilters?: TFilters = initialFilters;

additionalFilterNodes?: React.ReactNode[];
/**
* By default, `TData` is assumed to be assignable to `K8sResourceCommon` type.
Expand Down Expand Up @@ -100,12 +106,17 @@ export const ConsoleDataView = <
}: ConsoleDataViewProps<TData, TCustomRowData, TFilters>) => {
const { t } = useTranslation();

const initialFiltersOrDefault = React.useMemo(
() => initialFilters ?? (initialFiltersDefault as TFilters),
[initialFilters],
);
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

🧩 Analysis chain

Type assertion may be unsafe for extended filter types.

The cast initialFiltersDefault as TFilters on line 110 assumes that TFilters only contains the name and label fields. If a consumer uses ConsoleDataView with a filter type that extends ResourceFilters with additional required fields, this assertion will compile but produce an incomplete object at runtime, potentially causing bugs.

Consider a safer approach that preserves type safety:

  const initialFiltersOrDefault = React.useMemo(
-    () => initialFilters ?? (initialFiltersDefault as TFilters),
+    () => initialFilters ?? ({ ...initialFiltersDefault } as TFilters),
    [initialFilters],
  );

Better yet, require consumers with custom filter types to always provide initialFilters, or document that TFilters must not have additional required fields beyond ResourceFilters:

// Option 1: Make initialFilters required when TFilters has extra fields
export type ConsoleDataViewProps<
  TData,
  TCustomRowData = any,
  TFilters extends ResourceFilters = ResourceFilters
> = {
  // ... other props
  initialFilters: TFilters extends ResourceFilters 
    ? TFilters | undefined 
    : TFilters;
  // ... rest
};

// Option 2: Add JSDoc warning
/**
 * @param initialFilters - Optional. When omitted, defaults to { name: '', label: '' }.
 * If TFilters extends ResourceFilters with additional fields, initialFilters must be provided.
 */

Run the following to check if any consumers are using extended filter types:


🏁 Script executed:

#!/bin/bash
# Search for ConsoleDataView usage with custom filter types that might extend ResourceFilters
rg -nP --type=tsx --type=ts 'ConsoleDataView<[^>]+,\s*[^>]+,\s*\{[^}]*\}>' -A5 -B5

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Fix the search - ripgrep doesn't need type flags for ts/tsx, just use -g patterns
# First, let's look at the ConsoleDataView.tsx file to understand the types
cat frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx | head -150

Length of output: 4468


🏁 Script executed:

#!/bin/bash
# Find ResourceFilters definition
rg -n "type ResourceFilters|interface ResourceFilters" -A5

Length of output: 498


🏁 Script executed:

#!/bin/bash
# Search for ConsoleDataView usage in tsx/ts files (corrected syntax)
rg -n "ConsoleDataView<" -g "*.tsx" -g "*.ts" -A3 -B3

Length of output: 19853


🏁 Script executed:

#!/bin/bash
# Find all custom filter type definitions mentioned in ConsoleDataView usages
rg -n "type PodFilters|type HelmReleaseFilters|type TemplateInstanceFilters|type NodeFilters|type ReceiverFilters|type BindingFilters|type RelatedObjectsFilters|type ClusterOperatorFilters" -A8

Length of output: 5619


🏁 Script executed:

#!/bin/bash
# Also search for interface definitions of these filters
rg -n "interface PodFilters|interface HelmReleaseFilters|interface TemplateInstanceFilters|interface NodeFilters|interface ReceiverFilters|interface BindingFilters|interface RelatedObjectsFilters|interface ClusterOperatorFilters" -A8

Length of output: 908


🏁 Script executed:

#!/bin/bash
# Check if major consumers (PodList, HelmReleaseList, NodesPage) pass initialFilters
rg -n "ConsoleDataView<PodKind|HelmRelease|NodeRowItem" -A20 | grep -E "initialFilters|ConsoleDataView<"

Length of output: 1011


🏁 Script executed:

#!/bin/bash
# Check the useConsoleDataViewFilters function to see if it accesses extended filter fields
cat frontend/packages/console-app/src/components/data-view/useConsoleDataViewFilters.ts | head -100

Length of output: 2378


The type assertion issue is confirmed as a legitimate type safety concern.

The verification reveals that while current consumers (HelmReleaseList, PodList, NodesPage) all explicitly provide initialFilters, thereby avoiding runtime exposure to incomplete objects, the type assertion as TFilters on line 110 is genuinely unsafe. It bypasses TypeScript's type checking and creates a false sense of security.

Custom filter types like PodFilters, HelmReleaseFilters, NodeFilters, and others extend ResourceFilters with additional required fields (status, roles, architecture, etc.). If a future consumer relies on the default initialization without providing initialFilters, or if code later accesses extended filter fields from the default, an incomplete object would be passed through, potentially causing bugs.

Fix the type assertion to preserve type safety:

  const initialFiltersOrDefault = React.useMemo(
-    () => initialFilters ?? (initialFiltersDefault as TFilters),
+    () => initialFilters ?? ({ ...initialFiltersDefault } as TFilters),
    [initialFilters],
  );

Alternatively, make initialFilters required when TFilters has additional fields, or add clear documentation that custom filter types must always provide initialFilters:

/**
 * @param initialFilters - Required when TFilters extends ResourceFilters with additional fields.
 * If omitted, only { name: '', label: '' } is provided as default.
 */
🤖 Prompt for AI Agents
In frontend/packages/console-app/src/components/data-view/ConsoleDataView.tsx
around lines 109 to 112, remove the unsafe type assertion "as TFilters" and
preserve type safety by making the default value a Partial of the generic
instead of pretending it's a full TFilters: change the prop types so
initialFiltersDefault is Partial<TFilters> (or ResourceFilters) and compute
initialFiltersOrDefault as React.useMemo(() => initialFilters ??
(initialFiltersDefault as Partial<TFilters>), [initialFilters,
initialFiltersDefault]) (i.e. stop asserting the default is a full TFilters),
then add a short JSDoc on the component props stating that callers must supply
initialFilters when their TFilters extend ResourceFilters with additional
required fields (or alternatively make initialFilters required for those flavors
via a conditional generic in props).


const { filters, onSetFilters, clearAllFilters, filteredData } = useConsoleDataViewFilters<
TData,
TFilters
>({
data,
initialFilters,
initialFilters: initialFiltersOrDefault,
getObjectMetadata,
matchesAdditionalFilters,
});
Expand Down Expand Up @@ -253,5 +264,3 @@ export const actionsCellProps = {
hasLeftBorder: true,
isActionCell: true,
};

export const initialFiltersDefault = { name: '', label: '' };
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type ConsoleDataViewRow = DataViewTd[];
/**
* Maps Console `RowProps` data to DataView compatible format.
*/
export type GetDataViewRows<TData, TCustomRowData> = (
export type GetDataViewRows<TData, TCustomRowData = any> = (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using any instead of unknown for compatibility with existing RowProps signature

RowProps<D, R extends any = {}>

data: RowProps<TData, TCustomRowData>[],
columns: ConsoleDataViewColumn<TData>[],
) => ConsoleDataViewRow[];
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,11 @@ const NodeList: React.FC<NodeListProps> = ({
[],
);

const initialFilters = React.useMemo<NodeFilters>(
() => ({ ...initialFiltersDefault, status: [], roles: [], architecture: [] }),
[],
);

// Create stable filter nodes with stable option references to prevent filter resets
const additionalFilterNodes = React.useMemo<React.ReactNode[]>(
() => [
Expand Down Expand Up @@ -622,9 +627,7 @@ const NodeList: React.FC<NodeListProps> = ({
columns={columns}
columnLayout={columnLayout}
columnManagementID={columnManagementID}
initialFilters={
{ ...initialFiltersDefault, status: [], roles: [], architecture: [] } as NodeFilters
}
initialFilters={initialFilters}
additionalFilterNodes={additionalFilterNodes}
matchesAdditionalFilters={matchesAdditionalFilters}
getDataViewRows={(rowData, tableColumns) =>
Expand Down
4 changes: 1 addition & 3 deletions frontend/packages/console-app/src/components/pdb/PDBList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
actionsCellProps,
cellIsStickyProps,
getNameCellProps,
initialFiltersDefault,
ConsoleDataView,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { GetDataViewRows } from '@console/app/src/components/data-view/types';
Expand All @@ -31,7 +30,7 @@ export const tableColumnInfo = [
{ id: '' },
];

const getDataViewRows: GetDataViewRows<PodDisruptionBudgetKind, undefined> = (data, columns) => {
const getDataViewRows: GetDataViewRows<PodDisruptionBudgetKind> = (data, columns) => {
return data.map(({ obj: pdb }) => {
const { name, namespace } = pdb.metadata;
const resourceKind = referenceForModel(PodDisruptionBudgetModel);
Expand Down Expand Up @@ -155,7 +154,6 @@ const PodDisruptionBudgetList: React.FCC<PodDisruptionBudgetsListProps> = ({
data={data}
loaded={loaded}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
ConsoleDataView,
initialFiltersDefault,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { ConsoleDataView } from '@console/app/src/components/data-view/ConsoleDataView';
import { TableProps } from '@console/internal/components/factory';
import { LoadingBox } from '@console/internal/components/utils';
import { K8sResourceKind } from '@console/internal/module/k8s';
Expand All @@ -22,7 +19,6 @@ const HelmReleaseResourcesList: React.FC<TableProps> = (props) => {
loaded={props.loaded}
label={t('helm-plugin~Resources')}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement
data-test="helm-resources-list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const HelmReleaseResourceStatus: React.FC<HelmReleaseResourceStatusProps>
);
};

export const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
export const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj: resource }) => {
const kind = referenceFor(resource);
const rowCells = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getDataViewRows, HelmReleaseResourceStatus } from '../HelmReleaseResour

configure({ testIdAttribute: 'data-test' });

let testData: RowProps<K8sResourceKind, undefined>[];
let testData: RowProps<K8sResourceKind>[];
let testColumns: TableColumn<K8sResourceKind>[];

describe('getDataViewRows', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
ConsoleDataView,
initialFiltersDefault,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { ConsoleDataView } from '@console/app/src/components/data-view/ConsoleDataView';
import { TableProps } from '@console/internal/components/factory';
import { LoadingBox } from '@console/internal/components/utils';
import { K8sResourceKind } from '@console/internal/module/k8s';
Expand All @@ -22,7 +19,6 @@ const HelmChartRepositoryList: React.FC<TableProps> = (props) => {
loaded={props.loaded}
label={t('helm-plugin~HelmChartRepositories')}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement
data-test="helm-chart-repositories-list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DisabledCell: React.FC<{ disabled?: boolean }> = ({ disabled }) => {
return <>{disabled ? t('helm-plugin~True') : t('helm-plugin~False')}</>;
};

export const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
export const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj }) => {
const objReference = referenceFor(obj);
const context = { [objReference]: obj };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ const HelmReleaseList: React.FC = () => {
}));
}, []);

const initialFilters = React.useMemo(() => ({ ...initialFiltersDefault, status: [] }), []);

const additionalFilterNodes = React.useMemo<React.ReactNode[]>(
() => [
<DataViewCheckboxFilter
Expand Down Expand Up @@ -262,7 +264,7 @@ const HelmReleaseList: React.FC = () => {
loaded={isLoaded}
loadError={secretsLoadError || loadError}
columns={columns}
initialFilters={{ ...initialFiltersDefault, status: [] }}
initialFilters={initialFilters}
additionalFilterNodes={additionalFilterNodes}
getObjectMetadata={getObjectMetadata}
matchesAdditionalFilters={matchesAdditionalFilters}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
ConsoleDataView,
initialFiltersDefault,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { ConsoleDataView } from '@console/app/src/components/data-view/ConsoleDataView';
import { TableProps } from '@console/internal/components/factory';
import { LoadingBox } from '@console/internal/components/utils';
import { K8sResourceKind } from '@console/internal/module/k8s';
Expand All @@ -22,7 +19,6 @@ const ProjectHelmChartRepositoryList: React.FC<TableProps> = (props) => {
loaded={props.loaded}
label={t('helm-plugin~HelmChartRepositories')}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement
data-test="project-helm-chart-repositories-list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const DisabledCell: React.FC<{ disabled?: boolean }> = ({ disabled }) => {
return <>{disabled ? t('helm-plugin~True') : t('helm-plugin~False')}</>;
};

export const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
export const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj }) => {
const objReference = referenceFor(obj);
const context = { [objReference]: obj };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
ConsoleDataView,
initialFiltersDefault,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { ConsoleDataView } from '@console/app/src/components/data-view/ConsoleDataView';
import { TableProps } from '@console/internal/components/factory';
import { LoadingBox } from '@console/internal/components/utils';
import { K8sResourceKind } from '@console/internal/module/k8s';
Expand All @@ -22,7 +19,6 @@ const RepositoriesList: React.FC<TableProps> = (props) => {
loaded={props.loaded}
label={t('helm-plugin~HelmChartRepositories')}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement
data-test="repositories-list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const DisabledCell: React.FC<{ disabled?: boolean }> = ({ disabled }) => {
return <>{disabled ? t('helm-plugin~True') : t('helm-plugin~False')}</>;
};

export const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
export const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj }) => {
const objReference = referenceFor(obj);
const context = { [objReference]: obj };
Expand Down
6 changes: 4 additions & 2 deletions frontend/public/components/RBAC/bindings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const bindingType = (binding: BindingKind) => {
return binding.metadata.namespace ? 'namespace' : 'cluster';
};

const getDataViewRows: GetDataViewRows<BindingKind, undefined> = (data, columns) => {
const getDataViewRows: GetDataViewRows<BindingKind> = (data, columns) => {
return data.map(({ obj: binding }) => {
const rowCells = {
[tableColumnInfo[0].id]: {
Expand Down Expand Up @@ -266,6 +266,8 @@ export const BindingsList: React.FCC<BindingsListTableProps> = (props) => {
return options;
}, [hasCRBindings, t]);

const initialFilters = React.useMemo(() => ({ ...initialFiltersDefault, 'role-kind': [] }), []);

const additionalFilterNodes = React.useMemo<React.ReactNode[]>(
() => [
<DataViewCheckboxFilter
Expand Down Expand Up @@ -318,7 +320,7 @@ export const BindingsList: React.FCC<BindingsListTableProps> = (props) => {
loaded={loaded}
label={t('public~RoleBindings')}
columns={columns}
initialFilters={{ ...initialFiltersDefault, 'role-kind': [] }}
initialFilters={initialFilters}
additionalFilterNodes={additionalFilterNodes}
matchesAdditionalFilters={matchesAdditionalFilters}
getDataViewRows={getDataViewRows}
Expand Down
5 changes: 3 additions & 2 deletions frontend/public/components/RBAC/role.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const BindingsListComponent = (props) => {
loaded={loaded}
label={t('public~RoleBindings')}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getBindingsDataViewRows}
hideColumnManagement={true}
/>
Expand Down Expand Up @@ -446,6 +445,8 @@ const RolesList = (props) => {
const columns = useRolesColumns();
const roleFilterOptions = useRoleFilterOptions();

const initialFilters = React.useMemo(() => ({ ...initialFiltersDefault, 'role-kind': [] }), []);

const additionalFilterNodes = React.useMemo(
() => [
<DataViewCheckboxFilter
Expand Down Expand Up @@ -480,7 +481,7 @@ const RolesList = (props) => {
label={t('public~Roles')}
columns={columns}
getDataViewRows={getDataViewRows}
initialFilters={{ ...initialFiltersDefault, 'role-kind': [] }}
initialFilters={initialFilters}
additionalFilterNodes={additionalFilterNodes}
matchesAdditionalFilters={matchesAdditionalFilters}
hideColumnManagement={true}
Expand Down
4 changes: 1 addition & 3 deletions frontend/public/components/alert-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { ListPage } from './factory/list-page';
import {
cellIsStickyProps,
getNameCellProps,
initialFiltersDefault,
ConsoleDataView,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { GetDataViewRows } from '@console/app/src/components/data-view/types';
Expand Down Expand Up @@ -124,7 +123,7 @@ const tableColumnInfo = [
{ id: 'nodeSelector' },
];

const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj: alertManager }) => {
const { metadata, spec } = alertManager;

Expand Down Expand Up @@ -230,7 +229,6 @@ const AlertManagersList: React.FCC<AlertManagersListProps> = ({ data, loaded, ..
data={data}
loaded={loaded}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement={true}
/>
Expand Down
4 changes: 1 addition & 3 deletions frontend/public/components/build-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
actionsCellProps,
cellIsStickyProps,
getNameCellProps,
initialFiltersDefault,
ConsoleDataView,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { GetDataViewRows } from '@console/app/src/components/data-view/types';
Expand Down Expand Up @@ -134,7 +133,7 @@ const tableColumnInfo = [
{ id: 'actions' },
];

const getDataViewRows: GetDataViewRows<BuildConfig, undefined> = (data, columns) => {
const getDataViewRows: GetDataViewRows<BuildConfig> = (data, columns) => {
return data.map(({ obj }) => {
const { name, namespace } = obj.metadata;
const latestBuild = obj?.latestBuild;
Expand Down Expand Up @@ -328,7 +327,6 @@ export const BuildConfigsList: React.FCC<BuildConfigsListProps> = ({ data, loade
data={buildResource}
loaded={loaded}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement={true}
/>
Expand Down
4 changes: 1 addition & 3 deletions frontend/public/components/build.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
actionsCellProps,
cellIsStickyProps,
getNameCellProps,
initialFiltersDefault,
ConsoleDataView,
} from '@console/app/src/components/data-view/ConsoleDataView';
import { GetDataViewRows } from '@console/app/src/components/data-view/types';
Expand Down Expand Up @@ -358,7 +357,7 @@ const tableColumnInfo = [
{ id: 'actions' },
];

const getDataViewRows: GetDataViewRows<K8sResourceKind, undefined> = (data, columns) => {
const getDataViewRows: GetDataViewRows<K8sResourceKind> = (data, columns) => {
return data.map(({ obj }) => {
const { name, namespace } = obj.metadata;
const kindReference = referenceFor(obj);
Expand Down Expand Up @@ -466,7 +465,6 @@ export const BuildsList: React.FCC<BuildsListProps> = ({ data, loaded, ...props
data={data}
loaded={loaded}
columns={columns}
initialFilters={initialFiltersDefault}
getDataViewRows={getDataViewRows}
hideColumnManagement={true}
/>
Expand Down
Loading