-
Notifications
You must be signed in to change notification settings - Fork 694
CONSOLE-4667: Add ResourceDataView component and related code #15375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
openshift-merge-bot
merged 7 commits into
openshift:main
from
vojtechszocs:resource-data-view
Sep 12, 2025
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3d4fdc6
Add ResourceDataView component and related code
vojtechszocs e8b7638
Use StatusBox to wrap ResourceDataView render output
vojtechszocs a23bea5
Address review comments
vojtechszocs dd0edd2
Make sort optional
vojtechszocs 5ba41f2
Apply sorting fix changes as per Robb's testing
vojtechszocs 18589bb
Address label filter related review comments
vojtechszocs 7af299a
Update i18n files
vojtechszocs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
frontend/packages/console-app/src/components/data-view/DataViewLabelFilter.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import * as React from 'react'; | ||
| import { ToolbarFilter } from '@patternfly/react-core'; | ||
| import * as _ from 'lodash'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { useSearchParams } from 'react-router-dom-v5-compat'; | ||
| import { K8sResourceCommon } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; | ||
| import AutocompleteInput from '@console/internal/components/autocomplete'; | ||
|
|
||
| type DataViewLabelFilterProps<TData> = { | ||
| data: TData[]; | ||
| title: string; | ||
| filterId: string; | ||
| onChange?: (key: string, selectedValues: string) => void; | ||
| showToolbarItem?: boolean; | ||
| }; | ||
|
|
||
| export const DataViewLabelFilter = <TData extends K8sResourceCommon = K8sResourceCommon>({ | ||
| data, | ||
| title, | ||
| filterId, | ||
| onChange, | ||
| showToolbarItem, | ||
| }: DataViewLabelFilterProps<TData>) => { | ||
| const { t } = useTranslation(); | ||
|
|
||
| const [searchParams] = useSearchParams(); | ||
| const [labelInputText, setLabelInputText] = React.useState(''); | ||
| const labelSelection = searchParams.get(filterId)?.split(',') ?? []; | ||
|
|
||
| const applyLabelFilters = (values: string[]) => { | ||
| setLabelInputText(''); | ||
| onChange?.(filterId, values.join(',')); | ||
| }; | ||
|
|
||
| return ( | ||
| <ToolbarFilter | ||
| categoryName={title} | ||
| labels={labelSelection} | ||
| showToolbarItem={showToolbarItem} | ||
| deleteLabel={(_category, label: string) => { | ||
| setLabelInputText(''); | ||
| applyLabelFilters(_.difference(labelSelection, [label])); | ||
| }} | ||
| deleteLabelGroup={() => { | ||
| setLabelInputText(''); | ||
| applyLabelFilters([]); | ||
| }} | ||
| > | ||
| <div className="pf-v6-c-input-group co-filter-group"> | ||
| <AutocompleteInput | ||
| color="purple" | ||
| onSuggestionSelect={(selected) => { | ||
| applyLabelFilters(_.uniq([...labelSelection, selected])); | ||
| }} | ||
| showSuggestions | ||
| textValue={labelInputText} | ||
| setTextValue={setLabelInputText} | ||
| placeholder={t('public~Filter by label')} | ||
| data={data} | ||
| /> | ||
| </div> | ||
| </ToolbarFilter> | ||
| ); | ||
| }; |
208 changes: 208 additions & 0 deletions
208
frontend/packages/console-app/src/components/data-view/ResourceDataView.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import * as React from 'react'; | ||
| import { | ||
| ResponsiveAction, | ||
| ResponsiveActions, | ||
| SkeletonTableBody, | ||
| } from '@patternfly/react-component-groups'; | ||
| import { Bullseye, Pagination, Tooltip } from '@patternfly/react-core'; | ||
| import { | ||
| DataView, | ||
| DataViewState, | ||
| DataViewTable, | ||
| DataViewTextFilter, | ||
| DataViewToolbar, | ||
| } from '@patternfly/react-data-view'; | ||
| import DataViewFilters from '@patternfly/react-data-view/dist/cjs/DataViewFilters'; | ||
| import { ColumnsIcon } from '@patternfly/react-icons'; | ||
| import { InnerScrollContainer, Tbody, Td, Tr } from '@patternfly/react-table'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { | ||
| ColumnLayout, | ||
| K8sResourceCommon, | ||
| } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; | ||
| import { createColumnManagementModal } from '@console/internal/components/modals'; | ||
| import { TableColumn } from '@console/internal/module/k8s'; | ||
| import { EmptyBox } from '@console/shared/src/components/empty-state/EmptyBox'; | ||
| import { StatusBox } from '@console/shared/src/components/status/StatusBox'; | ||
| import { DataViewLabelFilter } from './DataViewLabelFilter'; | ||
| import { ResourceFilters, GetDataViewRows } from './types'; | ||
| import { useResourceDataViewData } from './useResourceDataViewData'; | ||
| import { useResourceDataViewFilters } from './useResourceDataViewFilters'; | ||
|
|
||
| export type ResourceDataViewProps<TData, TCustomRowData, TFilters> = { | ||
| label?: string; | ||
| data: TData[]; | ||
| loaded: boolean; | ||
| loadError?: any; | ||
| columns: TableColumn<TData>[]; | ||
| columnLayout?: ColumnLayout; | ||
| columnManagementID?: string; | ||
| initialFilters: TFilters; | ||
| additionalFilterNodes?: React.ReactNode[]; | ||
| matchesAdditionalFilters?: (resource: TData, filters: TFilters) => boolean; | ||
| getDataViewRows: GetDataViewRows<TData, TCustomRowData>; | ||
| customRowData?: TCustomRowData; | ||
| showNamespaceOverride?: boolean; | ||
| hideNameLabelFilters?: boolean; | ||
| hideLabelFilter?: boolean; | ||
| hideColumnManagement?: boolean; | ||
| mock?: boolean; | ||
| }; | ||
|
|
||
| /** | ||
| * Console DataView component based on PatternFly DataView. | ||
| */ | ||
| export const ResourceDataView = < | ||
| TData extends K8sResourceCommon = K8sResourceCommon, | ||
| TCustomRowData = any, | ||
| TFilters extends ResourceFilters = ResourceFilters | ||
| >({ | ||
| label, | ||
| data, | ||
| loaded, | ||
| loadError, | ||
| columns, | ||
| columnLayout, | ||
| columnManagementID, | ||
| initialFilters, | ||
| additionalFilterNodes, | ||
| matchesAdditionalFilters, | ||
| getDataViewRows, | ||
| customRowData, | ||
| showNamespaceOverride, | ||
| hideNameLabelFilters, | ||
| hideLabelFilter, | ||
| hideColumnManagement, | ||
| mock, | ||
| }: ResourceDataViewProps<TData, TCustomRowData, TFilters>) => { | ||
| const { t } = useTranslation(); | ||
|
|
||
| const { filters, onSetFilters, clearAllFilters, filteredData } = useResourceDataViewFilters< | ||
| TData, | ||
| TFilters | ||
| >({ | ||
| data, | ||
| initialFilters, | ||
| matchesAdditionalFilters, | ||
| }); | ||
|
|
||
| const { dataViewColumns, dataViewRows, pagination } = useResourceDataViewData< | ||
| TData, | ||
| TCustomRowData | ||
| >({ | ||
| columns, | ||
| filteredData, | ||
| getDataViewRows, | ||
| showNamespaceOverride, | ||
| columnManagementID, | ||
| customRowData, | ||
| }); | ||
|
|
||
| const bodyLoading = React.useMemo( | ||
| () => <SkeletonTableBody rowsCount={5} columnsCount={dataViewColumns.length} />, | ||
| [dataViewColumns.length], | ||
| ); | ||
|
|
||
| const bodyEmpty = React.useMemo( | ||
| () => ( | ||
| <Tbody> | ||
| <Tr> | ||
| <Td colSpan={dataViewColumns.length}> | ||
| <Bullseye> | ||
| {label ? t('public~No {{label}} found', { label }) : t('public~None found')} | ||
| </Bullseye> | ||
| </Td> | ||
| </Tr> | ||
| </Tbody> | ||
| ), | ||
| [t, dataViewColumns.length, label], | ||
| ); | ||
|
|
||
| const activeState = React.useMemo(() => { | ||
| if (!loaded) { | ||
| return DataViewState.loading; | ||
| } | ||
| if (filteredData.length === 0) { | ||
| return DataViewState.empty; | ||
| } | ||
| return undefined; | ||
| }, [filteredData.length, loaded]); | ||
|
|
||
| const dataViewFilterNodes = React.useMemo<React.ReactNode[]>(() => { | ||
| const basicFilters: React.ReactNode[] = []; | ||
|
|
||
| if (!hideNameLabelFilters) { | ||
| basicFilters.push(<DataViewTextFilter key="name" filterId="name" title={t('public~Name')} />); | ||
| } | ||
|
|
||
| if (!hideNameLabelFilters && !hideLabelFilter) { | ||
| basicFilters.push( | ||
| <DataViewLabelFilter key="label" filterId="label" title={t('public~Label')} data={data} />, | ||
| ); | ||
| } | ||
|
|
||
| return additionalFilterNodes?.length > 0 | ||
| ? [...additionalFilterNodes, ...basicFilters] | ||
| : basicFilters; | ||
|
|
||
| // Can't use data in the deps array as it will recompute the filters and will cause the selected category to reset | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [additionalFilterNodes, t]); | ||
|
|
||
| return mock ? ( | ||
| <EmptyBox label={label} /> | ||
| ) : ( | ||
| <StatusBox | ||
| label={label} | ||
| data={data} | ||
| loaded={loaded} | ||
| loadError={loadError} | ||
| skeleton={<div className="loading-skeleton--table" />} | ||
| > | ||
| <DataView activeState={activeState}> | ||
| <DataViewToolbar | ||
| filters={ | ||
| dataViewFilterNodes.length > 0 && ( | ||
| <DataViewFilters values={filters} onChange={(_e, values) => onSetFilters(values)}> | ||
| {dataViewFilterNodes} | ||
| </DataViewFilters> | ||
| ) | ||
| } | ||
| clearAllFilters={clearAllFilters} | ||
| actions={ | ||
| !hideColumnManagement && ( | ||
| <ResponsiveActions breakpoint="lg"> | ||
| <ResponsiveAction | ||
| isPersistent | ||
| variant="plain" | ||
| onClick={() => | ||
| createColumnManagementModal({ | ||
| columnLayout, | ||
| noLimit: true, | ||
| }) | ||
| } | ||
| aria-label={t('public~Column management')} | ||
| data-test="manage-columns" | ||
| > | ||
| <Tooltip content={t('public~Manage columns')} trigger="mouseenter"> | ||
| <ColumnsIcon /> | ||
| </Tooltip> | ||
| </ResponsiveAction> | ||
| </ResponsiveActions> | ||
| ) | ||
| } | ||
| pagination={<Pagination itemCount={filteredData.length} {...pagination} />} | ||
| /> | ||
| <InnerScrollContainer> | ||
| <DataViewTable | ||
| columns={dataViewColumns} | ||
| rows={dataViewRows} | ||
| bodyStates={{ empty: bodyEmpty, loading: bodyLoading }} | ||
| gridBreakPoint="" | ||
| variant="compact" | ||
| /> | ||
| </InnerScrollContainer> | ||
| </DataView> | ||
| </StatusBox> | ||
| ); | ||
| }; | ||
29 changes: 29 additions & 0 deletions
29
frontend/packages/console-app/src/components/data-view/types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { DataViewTh, DataViewTd } from '@patternfly/react-data-view'; | ||
| import { SortByDirection } from '@patternfly/react-table'; | ||
| import { | ||
| K8sResourceCommon, | ||
| RowProps, | ||
| } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; | ||
|
|
||
| export type ResourceFilters = { | ||
| name: string; | ||
| label: string; | ||
| }; | ||
|
|
||
| export type ResourceDataViewColumn< | ||
| TData extends K8sResourceCommon = K8sResourceCommon | ||
| > = DataViewTh & { | ||
| id: string; | ||
| title: string; | ||
| sortFunction?: string | ((filteredData: TData[], sortDirection: SortByDirection) => TData[]); | ||
| }; | ||
|
|
||
| export type ResourceDataViewRow = DataViewTd[]; | ||
|
|
||
| /** | ||
| * Maps Console `RowProps` data to DataView compatible format. | ||
| */ | ||
| export type GetDataViewRows<TData, TCustomRowData> = ( | ||
| data: RowProps<TData, TCustomRowData>[], | ||
| columns: ResourceDataViewColumn<TData>[], | ||
| ) => ResourceDataViewRow[]; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we add unit test for the new component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yapei I think we can skip adding unit tests for this component at this point in time.
The main goal of this PR was to refactor original DataView PoC code from #14897 and #15057 without putting this component into any use. This is the initial step towards using PatternFly DataView code in Console.
Any immediate issues found by @rhamilto via CronTab plugin testing should be covered in this PR. Once this PR merges, the next step should be putting this component into use on select Console data tables and resolve any other issues we find along the way.
Once we're confident about the API (props) for this component, we can add unit tests for it.