Skip to content
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

[RHOAIENG-2977] Artifacts table #2744

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class ExperimentsTable {
findFilterTextField() {
return this.findContainer()
.findByTestId('experiment-table-toolbar')
.findByTestId('run-table-toolbar-filter-text-field');
.findByTestId('pipeline-filter-text-field');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class PipelineRunFilterBar extends PipelineFilterBar {
}

findExperimentInput() {
return cy.findByTestId('run-table-toolbar-filter-text-field').find('#experiment-search-input');
return cy.findByTestId('pipeline-filter-text-field').find('#experiment-search-input');
}

findPipelineVersionSelect() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,7 @@ class PipelineRunJobTable extends PipelineRunsTable {
}

findFilterTextField() {
return cy
.findByTestId('schedules-table-toolbar')
.findByTestId('run-table-toolbar-filter-text-field');
return cy.findByTestId('schedules-table-toolbar').findByTestId('pipeline-filter-text-field');
}

findExperimentFilterSelect() {
Expand Down
29 changes: 25 additions & 4 deletions frontend/src/components/table/TableBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ type Props<DataType> = {
tooltip?: string;
};
getColumnSort?: GetColumnSort;
disableItemCount?: boolean;
} & EitherNotBoth<
{ disableRowRenderSupport?: boolean },
{ tbodyProps?: TbodyProps & { ref?: React.Ref<HTMLTableSectionElement> } }
> &
Omit<TableProps, 'ref' | 'data'> &
Pick<PaginationProps, 'itemCount' | 'onPerPageSelect' | 'onSetPage' | 'page' | 'perPage'>;
Pick<
PaginationProps,
| 'itemCount'
| 'onPerPageSelect'
| 'onSetPage'
| 'page'
| 'perPage'
| 'perPageOptions'
| 'toggleTemplate'
| 'onNextClick'
| 'onPreviousClick'
>;

export const MIN_PAGE_SIZE = 10;

Expand Down Expand Up @@ -87,26 +99,35 @@ const TableBase = <T,>({
tbodyProps,
perPage = 10,
page = 1,
perPageOptions = defaultPerPageOptions,
onSetPage,
onNextClick,
onPreviousClick,
onPerPageSelect,
getColumnSort,
itemCount = 0,
loading,
toggleTemplate,
disableItemCount = false,
...props
}: Props<T>): React.ReactElement => {
const selectAllRef = React.useRef(null);
const showPagination = enablePagination && itemCount > MIN_PAGE_SIZE;
const showPagination = enablePagination;

const pagination = (variant: 'top' | 'bottom') => (
<Pagination
isCompact={enablePagination === 'compact'}
itemCount={itemCount}
{...(!disableItemCount && { itemCount })}
perPage={perPage}
page={page}
onSetPage={onSetPage}
onNextClick={onNextClick}
onPreviousClick={onPreviousClick}
onPerPageSelect={onPerPageSelect}
toggleTemplate={toggleTemplate}
variant={variant}
widgetId="table-pagination"
perPageOptions={defaultPerPageOptions}
perPageOptions={perPageOptions}
titles={{
paginationAriaLabel: `${variant} pagination`,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,44 @@ type FilterOptionRenders = {
label?: string;
};

type PipelineFilterBarProps<Options extends FilterOptions> = {
type ToolbarFilterProps<T extends string> = React.ComponentProps<typeof ToolbarGroup> & {
children?: React.ReactNode;
filterOptions: { [key in Options]?: string };
filterOptionRenders: Record<Options, (props: FilterOptionRenders) => React.ReactNode>;
filterData: Record<Options, string | { label: string; value: string } | undefined>;
onFilterUpdate: (filterType: Options, value?: string | { label: string; value: string }) => void;
filterOptions: { [key in T]?: string };
filterOptionRenders: Record<T, (props: FilterOptionRenders) => React.ReactNode>;
filterData: Record<T, string | { label: string; value: string } | undefined>;
onFilterUpdate: (filterType: T, value?: string | { label: string; value: string }) => void;
onClearFilters: () => void;
testId?: string;
};

export type FilterProps = Pick<
React.ComponentProps<typeof PipelineFilterBar>,
React.ComponentProps<typeof FilterToolbar>,
'filterData' | 'onFilterUpdate' | 'onClearFilters'
>;

const PipelineFilterBar = <Options extends FilterOptions>({
export function FilterToolbar<T extends string>({
filterOptions,
filterOptionRenders,
filterData,
onFilterUpdate,
onClearFilters,
children,
...props
}: PipelineFilterBarProps<Options>): React.JSX.Element => {
const keys = Object.keys(filterOptions) as Array<Options>;
testId = 'filter-toolbar',
...toolbarGroupProps
}: ToolbarFilterProps<T>): React.JSX.Element {
const keys = Object.keys(filterOptions) as Array<T>;
const [open, setOpen] = React.useState(false);
const [currentFilterType, setCurrentFilterType] = React.useState<Options>(keys[0]);
const isToolbarChip = (v: unknown): v is ToolbarChip & { key: Options } =>
const [currentFilterType, setCurrentFilterType] = React.useState<T>(keys[0]);
const isToolbarChip = (v: unknown): v is ToolbarChip & { key: T } =>
!!v && Object.keys(v as ToolbarChip).every((k) => ['key', 'node'].includes(k));

return (
<>
<ToolbarGroup variant="filter-group" data-testid="pipeline-filter-toolbar" {...props}>
<ToolbarGroup variant="filter-group" data-testid={testId} {...toolbarGroupProps}>
<ToolbarItem>
<Dropdown
toggle={
<DropdownToggle id="pipeline-filter-toggle-button" onToggle={() => setOpen(!open)}>
<DropdownToggle id={`${testId}-toggle-button`} onToggle={() => setOpen(!open)}>
<>
<FilterIcon /> {filterOptions[currentFilterType]}
</>
Expand All @@ -60,7 +62,8 @@ const PipelineFilterBar = <Options extends FilterOptions>({
isOpen={open}
dropdownItems={keys.map((filterKey) => (
<DropdownItem
key={filterKey.toString()}
key={filterKey}
id={filterKey}
onClick={() => {
setOpen(false);
setCurrentFilterType(filterKey);
Expand All @@ -69,12 +72,12 @@ const PipelineFilterBar = <Options extends FilterOptions>({
{filterOptions[filterKey]}
</DropdownItem>
))}
data-testid="pipeline-filter-dropdown"
data-testid={`${testId}-dropdown`}
/>
</ToolbarItem>
<ToolbarFilter
categoryName="Filters"
data-testid="run-table-toolbar-filter-text-field"
data-testid={`${testId}-text-field`}
variant="search-filter"
chips={keys
.map<ToolbarChip | null>((filterKey) => {
Expand All @@ -100,7 +103,7 @@ const PipelineFilterBar = <Options extends FilterOptions>({
.filter(isToolbarChip)}
deleteChip={(_, chip) => {
if (isToolbarChip(chip)) {
onFilterUpdate(chip.key);
onFilterUpdate(chip.key, '');
}
}}
deleteChipGroup={() => onClearFilters()}
Expand All @@ -117,6 +120,10 @@ const PipelineFilterBar = <Options extends FilterOptions>({
{children}
</>
);
};
}

const PipelineFilterBar = <Options extends FilterOptions>(
props: ToolbarFilterProps<Options>,
): React.JSX.Element => <FilterToolbar {...props} testId="pipeline-filter" />;

export default PipelineFilterBar;
61 changes: 61 additions & 0 deletions frontend/src/concepts/pipelines/context/MlmdListContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

interface MlmdOrderBy {
field: string;
direction: 'asc' | 'desc';
}

interface MlmdListContextProps {
filterQuery: string | undefined;
pageToken: string | undefined;
maxResultSize: number;
orderBy: MlmdOrderBy | undefined;
setFilterQuery: (filterQuery: string | undefined) => void;
setPageToken: (pageToken: string | undefined) => void;
setMaxResultSize: (maxResultSize: number) => void;
setOrderBy: (orderBy: MlmdOrderBy | undefined) => void;
}

const MlmdListContext = React.createContext({} as MlmdListContextProps);

export const MlmdListContextProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
Gkrumbach07 marked this conversation as resolved.
Show resolved Hide resolved
const [filterQuery, setFilterQuery] = React.useState<string>();
const [pageToken, setPageToken] = React.useState<string>();
const [maxResultSize, setMaxResultSize] = React.useState(10);
const [orderBy, setOrderBy] = React.useState<MlmdOrderBy>();
const value = React.useMemo(
() => ({
filterQuery,
pageToken,
maxResultSize,
orderBy,
setFilterQuery,
setPageToken,
setMaxResultSize,
setOrderBy,
}),
[filterQuery, maxResultSize, orderBy, pageToken],
);

return <MlmdListContext.Provider value={value}>{children}</MlmdListContext.Provider>;
};

export const useMlmdListContext = (nextPageToken?: string): MlmdListContextProps => {
// https://github.com/patternfly/patternfly-react/issues/10312
// Force disabled state to pagination when there is no nextPageToken
React.useEffect(() => {
const paginationNextButtons = document.querySelectorAll('button[aria-label="Go to next page"]');
jpuzz0 marked this conversation as resolved.
Show resolved Hide resolved

if (paginationNextButtons.length > 0) {
paginationNextButtons.forEach((button) => {
if (!nextPageToken) {
button.setAttribute('disabled', '');
} else {
button.removeAttribute('disabled');
}
});
}
}, [nextPageToken]);

return React.useContext(MlmdListContext);
};
1 change: 1 addition & 0 deletions frontend/src/concepts/pipelines/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export {
ViewServerModal,
PipelineServerTimedOut,
} from './PipelinesContext';
export * from './MlmdListContext';
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ import React from 'react';
import {
Bullseye,
EmptyState,
EmptyStateBody,
EmptyStateVariant,
EmptyStateHeader,
EmptyStateIcon,
EmptyStateVariant,
EmptyStateBody,
Spinner,
} from '@patternfly/react-core';
import { ExclamationCircleIcon, PlusCircleIcon } from '@patternfly/react-icons';

import { useMlmdListContext } from '~/concepts/pipelines/context';
import { useGetArtifactsList } from './useGetArtifactsList';
import { ArtifactsTable } from './ArtifactsTable';

export const ArtifactsListTable: React.FC = () => {
const [artifacts, isArtifactsLoaded, artifactsError] = useGetArtifactsList();
export const ArtifactsList: React.FC = () => {
const { filterQuery } = useMlmdListContext();
const [artifactsResponse, isArtifactsLoaded, artifactsError] = useGetArtifactsList();
const { artifacts, nextPageToken } = artifactsResponse || {};
const filterQueryRef = React.useRef(filterQuery);

if (artifactsError) {
return (
Expand All @@ -39,7 +44,7 @@ export const ArtifactsListTable: React.FC = () => {
);
}

if (!artifacts?.length) {
if (!artifacts?.length && !filterQuery && filterQueryRef.current === filterQuery) {
return (
<EmptyState data-testid="artifacts-list-empty-state" variant={EmptyStateVariant.lg}>
<EmptyStateHeader
Expand All @@ -55,5 +60,11 @@ export const ArtifactsListTable: React.FC = () => {
);
}

return <></>;
return (
<ArtifactsTable
artifacts={artifacts}
nextPageToken={nextPageToken}
isLoaded={isArtifactsLoaded}
/>
);
};
Loading
Loading