Skip to content

Commit

Permalink
Merge pull request #2971 from Hyperkid123/inventory-filtering
Browse files Browse the repository at this point in the history
Add pagination and sorting to inventory POC
  • Loading branch information
Hyperkid123 authored Nov 7, 2024
2 parents e793f9e + e20fe70 commit 7998534
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 34 deletions.
32 changes: 32 additions & 0 deletions src/inventoryPoc/FilterToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { HostApiOptions } from './api';
import { Toolbar, ToolbarContent, ToolbarItem } from '@patternfly/react-core/dist/dynamic/components/Toolbar';
import { Pagination } from '@patternfly/react-core';

export type FilterToolbarProps = HostApiOptions & {
onSetPage: (page: number) => void;
onPerPageSelect: (perPage: number) => void;
};

const FilterToolbar = (props: FilterToolbarProps) => {
const { page, perPage, onSetPage, onPerPageSelect } = props;
return (
<>
<Toolbar>
<ToolbarContent>
<ToolbarItem>
<Pagination
onPerPageSelect={(_e, newPerPage) => onPerPageSelect(newPerPage)}
onSetPage={(_e, newPage) => onSetPage(newPage)}
page={page}
perPage={perPage}
perPageOptions={[5, 10, 20, 50, 100].map((i) => ({ title: `${i}`, value: i }))}
/>
</ToolbarItem>
</ToolbarContent>
</Toolbar>
</>
);
};

export default FilterToolbar;
8 changes: 7 additions & 1 deletion src/inventoryPoc/InventoryColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ export class BaseInventoryColumn {
private columnId: string;
private title: ReactNode;
private columnData: BaseColumnData;
private sortable?: boolean = false;

constructor(columnId: string, title: ReactNode, { columnData }: { columnData: BaseColumnData }) {
constructor(columnId: string, title: ReactNode, { columnData }: { columnData: BaseColumnData }, { sortable }: { sortable?: boolean } = {}) {
this.columnId = columnId;
this.title = title;
this.columnData = columnData;
this.sortable = sortable;
}

getColumnId(): string {
Expand All @@ -42,6 +44,10 @@ export class BaseInventoryColumn {
return this.columnData;
}

getSortable(): boolean {
return !!this.sortable;
}

setColumnData(columnData: BaseColumnData): void {
this.columnData = columnData;
}
Expand Down
124 changes: 96 additions & 28 deletions src/inventoryPoc/ModularInventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { DateFormat } from '@redhat-cloud-services/frontend-components/DateForma
import SecurityIcon from '@patternfly/react-icons/dist/dynamic/icons/security-icon';
import TagIcon from '@patternfly/react-icons/dist/dynamic/icons/tag-icon';

import { AdvisorSystem, Host, getHostCVEs, getHostInsights, getHostPatch, getHostTags, getHosts } from './api';
import { AdvisorSystem, Host, HostApiOptions, getHostCVEs, getHostInsights, getHostPatch, getHostTags, getHosts } from './api';
import { Checkbox } from '@patternfly/react-core/dist/dynamic/components/Checkbox';
import { Icon } from '@patternfly/react-core/dist/dynamic/components/Icon';
import { Skeleton } from '@patternfly/react-core/dist/dynamic/components/Skeleton';
import ShieldIcon from '@patternfly/react-icons/dist/dynamic/icons/shield-alt-icon';
import BugIcon from '@patternfly/react-icons/dist/dynamic/icons/bug-icon';
import CogIcon from '@patternfly/react-icons/dist/dynamic/icons/cog-icon';
import { Toolbar, ToolbarContent, ToolbarItem } from '@patternfly/react-core/dist/dynamic/components/Toolbar';
import FilterToolbar from './FilterToolbar';

function createRows(
columns: {
Expand Down Expand Up @@ -110,14 +111,40 @@ function useColumnData(columns: InventoryColumn[]) {
return res;
}

const ModularInventory = ({ columns }: { columns: Omit<InventoryColumn, 'isReady' | 'isAsync' | 'observeReady'>[] }) => {
const ModularInventory = ({
columns,
onSort,
sortBy,
sortDirection,
}: {
sortBy: number;
sortDirection: 'asc' | 'desc';
onSort: (index: number, direction: 'asc' | 'desc') => void;
columns: Omit<InventoryColumn, 'isReady' | 'isAsync' | 'observeReady'>[];
}) => {
const [allData] = useColumnData(columns as InventoryColumn[]);
return (
<Table>
<Thead>
<Tr>
{columns.map((column) => (
<Th key={column.getColumnId()}>{column.getTitle()}</Th>
{columns.map((column, idx) => (
<Th
sort={
column.getSortable()
? {
columnIndex: idx,
sortBy: {
index: sortBy,
direction: sortDirection,
},
onSort: (_e, index, direction) => onSort(index, direction),
}
: undefined
}
key={column.getColumnId()}
>
{column.getTitle()}
</Th>
))}
</Tr>
</Thead>
Expand All @@ -136,19 +163,20 @@ const ModularInventory = ({ columns }: { columns: Omit<InventoryColumn, 'isReady

const columnIds = [
'id',
'name',
'display_name',
'all-cves',
'cves',
'tags',
'os',
'lastCheckIn',
'updated',
'criticalCves',
'importantCves',
'moderateCves',
'lowCves',
'recommendations',
'installAbleAdvisories',
];

const ColumnEnabler = ({
enabledColumns,
handleCheckboxChange,
Expand Down Expand Up @@ -272,19 +300,31 @@ const columnsRegistry: {
},

id: (hosts: Host[]) => {
return new BaseInventoryColumn('id', 'System ID', {
columnData: hosts.map((host) => host.id),
});
return new BaseInventoryColumn(
'id',
'System ID',
{
columnData: hosts.map((host) => host.id),
},
{ sortable: true }
);
},

name: (hosts: Host[]) => {
return new BaseInventoryColumn('name', 'System Name', {
columnData: hosts.map((host) => (
<a key={host.id} href="#">
{host.display_name}
</a>
)),
});
display_name: (hosts: Host[]) => {
return new BaseInventoryColumn(
'display_name',
'System Name',
{
columnData: hosts.map((host) => (
<a key={host.id} href="#">
{host.display_name}
</a>
)),
},
{
sortable: true,
}
);
},

'all-cves': (_e, cvePromises: ReturnType<typeof getHostCVEs>[]) => {
Expand Down Expand Up @@ -363,14 +403,21 @@ const columnsRegistry: {
});
},

lastCheckIn: (hosts: Host[]) => {
return new BaseInventoryColumn('lastCheckIn', 'Last check-in', {
columnData: hosts.map((host) =>
host.per_reporter_staleness.puptoo?.last_check_in ? (
<DateFormat key={host.id} date={host.per_reporter_staleness.puptoo?.last_check_in} />
) : null
),
});
updated: (hosts: Host[]) => {
return new BaseInventoryColumn(
'updated',
'Last check-in',
{
columnData: hosts.map((host) =>
host.per_reporter_staleness.puptoo?.last_check_in ? (
<DateFormat key={host.id} date={host.per_reporter_staleness.puptoo?.last_check_in} />
) : null
),
},
{
sortable: true,
}
);
},
};

Expand Down Expand Up @@ -414,19 +461,40 @@ const ModularInventoryRoute = () => {
}, [hosts, enabledColumns]);

async function initData() {
const response = await getHosts();
const response = await getHosts(filterState);
setHosts(response.results);
getHostTags(response.results[0].insights_id);
}
const [filterState, setFilterState] = useState<HostApiOptions>({ page: 1, perPage: 20, orderBy: 'updated', orderHow: 'DESC' });

useEffect(() => {
initData();
}, []);
}, [JSON.stringify(filterState)]);

const onPerPageSelect = (perPage: number) => {
setFilterState((prev) => ({ ...prev, perPage }));
};
const onSetPage = (page: number) => {
setFilterState((prev) => ({ ...prev, page }));
};

return (
<div className="pf-v5-u-p-md">
<ColumnEnabler enabledColumns={enabledColumns} handleCheckboxChange={handleCheckboxChange} />
<ModularInventory columns={cols} />
<FilterToolbar onPerPageSelect={onPerPageSelect} onSetPage={onSetPage} {...filterState} />
<ModularInventory
sortBy={filterState.orderBy ? columnIds.indexOf(filterState.orderBy) ?? 0 : 0}
sortDirection={filterState.orderHow?.toLocaleLowerCase() as 'asc' | 'desc'}
onSort={(index, direction) => {
console.log(index, direction);
setFilterState((prev) => ({
...prev,
orderBy: (columnIds[index] as any) ?? 'updated',
orderHow: (direction.toUpperCase() as any) ?? 'DESC',
}));
}}
columns={cols}
/>
</div>
);
};
Expand Down
12 changes: 7 additions & 5 deletions src/inventoryPoc/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ export type Host = {
};
};

export const getHosts = async () => {
export type HostApiOptions = { page?: number; perPage?: number; orderBy?: 'updated' | 'display_name' | 'id'; orderHow?: 'ASC' | 'DESC' };

export const getHosts = async ({ orderBy = 'updated', orderHow = 'DESC', page = 1, perPage = 20 }: HostApiOptions) => {
const response = await axios.get<{ results: Host[] }>('/api/inventory/v1/hosts', {
params: {
page: 1,
per_page: 20,
order_by: 'updated',
order_how: 'DESC',
page,
per_page: perPage,
order_by: orderBy,
order_how: orderHow,
'fields[system_profile]': ['operating_system'],
},
});
Expand Down

0 comments on commit 7998534

Please sign in to comment.