From 9fc879ad5cf1ba591fb4e86769b81e2ab69e1f4a Mon Sep 17 00:00:00 2001 From: Marcelo Robert Santos Date: Wed, 27 Nov 2024 11:55:49 -0300 Subject: [PATCH] feat: full path filter Splits path filter between boots and tests; Adds filtering to hardwareDetails; Syncs filter listing and input field; Fixes boot commit history graph not using the path filter. Closes #578 and Closes #582 --- backend/kernelCI_app/utils.py | 2 +- .../kernelCI_app/views/hardwareDetailsView.py | 8 +++++++- .../kernelCI_app/views/treeDetailsSlowView.py | 19 +++++++++++++++---- .../src/components/BootsTable/BootsTable.tsx | 18 +++++++++++------- .../src/components/TestsTable/TestsTable.tsx | 13 +++++++++---- .../pages/TreeDetails/Tabs/Boots/BootsTab.tsx | 8 ++++++-- .../pages/TreeDetails/Tabs/Tests/TestsTab.tsx | 8 ++++++-- .../pages/TreeDetails/TreeDetailsFilter.tsx | 3 ++- .../hardwareDetails/HardwareDetailsFilter.tsx | 2 ++ .../hardwareDetails/Tabs/Boots/BootsTab.tsx | 8 ++++++-- .../Tabs/Tests/HardwareDetailsTestsTable.tsx | 2 ++ .../hardwareDetails/Tabs/Tests/TestsTab.tsx | 9 ++++++--- .../src/types/hardware/hardwareDetails.ts | 6 ++++++ dashboard/src/types/tree/TreeDetails.tsx | 6 ++++-- dashboard/src/utils/filters.ts | 1 + 15 files changed, 84 insertions(+), 29 deletions(-) diff --git a/backend/kernelCI_app/utils.py b/backend/kernelCI_app/utils.py index 02a19f8e..787a2144 100644 --- a/backend/kernelCI_app/utils.py +++ b/backend/kernelCI_app/utils.py @@ -109,7 +109,7 @@ class FilterParams: "like": ["like", "LIKE"], } - string_like_filters = ["test.path"] + string_like_filters = ["boot.path", "test.path"] def __init__(self, request): self.filters = [] diff --git a/backend/kernelCI_app/views/hardwareDetailsView.py b/backend/kernelCI_app/views/hardwareDetailsView.py index 799f2ae4..4553ae3b 100644 --- a/backend/kernelCI_app/views/hardwareDetailsView.py +++ b/backend/kernelCI_app/views/hardwareDetailsView.py @@ -168,7 +168,13 @@ def pass_in_filters(self, data, filters): field = currentFilter.get("field") value = currentFilter.get("value") - if data[field] not in value: + if ( + field == "path" + and value[0] not in data[field] + ): + return False + + if (data[field] not in value): return False return True diff --git a/backend/kernelCI_app/views/treeDetailsSlowView.py b/backend/kernelCI_app/views/treeDetailsSlowView.py index 0ad99420..ca06ea94 100644 --- a/backend/kernelCI_app/views/treeDetailsSlowView.py +++ b/backend/kernelCI_app/views/treeDetailsSlowView.py @@ -27,7 +27,8 @@ def __init__(self): self.filterTreeDetailsCompiler = set() self.filterArchitecture = set() self.filterHardware = set() - self.filterPath = "" + self.filterTestPath = "" + self.filterBootPath = "" self.filter_handlers = { "boot.status": self.__handle_boot_status, "boot.duration": self.__handle_boot_duration, @@ -38,6 +39,7 @@ def __init__(self): "architecture": self.__handle_architecture, "test.hardware": self.__handle_hardware, "test.path": self.__handle_path, + "boot.path": self.__handle_path, } self.testHistory = [] @@ -98,7 +100,10 @@ def __handle_hardware(self, current_filter): self.filterHardware.add(current_filter["value"]) def __handle_path(self, current_filter): - self.filterPath = current_filter["value"] + if current_filter["field"] == "boot.path": + self.filterBootPath = current_filter["value"] + else: + self.filterTestPath = current_filter["value"] def __processFilters(self, request): try: @@ -478,8 +483,14 @@ def get(self, request, commit_hash: str | None): if ( ( - self.filterPath != "" - and (self.filterPath not in path) + path.startswith("boot") + and self.filterBootPath != "" + and (self.filterBootPath not in path) + ) + or ( + not path.startswith("boot") + and self.filterTestPath != "" + and (self.filterTestPath not in path) ) or ( len(self.filterHardware) > 0 diff --git a/dashboard/src/components/BootsTable/BootsTable.tsx b/dashboard/src/components/BootsTable/BootsTable.tsx index 1271c93c..cb45f8fa 100644 --- a/dashboard/src/components/BootsTable/BootsTable.tsx +++ b/dashboard/src/components/BootsTable/BootsTable.tsx @@ -118,7 +118,8 @@ interface IBootsTable { filter: TestsTableFilter; getRowLink: (testId: TestHistory['id']) => LinkProps; onClickFilter: (newFilter: TestsTableFilter) => void; - updatePathFilter: (pathFilter: string) => void; + updatePathFilter?: (pathFilter: string) => void; + currentPathFilter?: string; } const TableCellComponent = ({ @@ -192,12 +193,14 @@ const TableRowComponent = ({ const TableCellMemoized = memo(TableCellComponent); const TableRowMemoized = memo(TableRowComponent); +// TODO: would be useful if the navigation happened within the table, so the parent component would only be required to pass the navigation url instead of the whole function for the update and the currentPath diffFilter (boots/tests Table) export function BootsTable({ testHistory, filter, getRowLink, onClickFilter, updatePathFilter, + currentPathFilter, }: IBootsTable): JSX.Element { const [sorting, setSorting] = useState([]); const [pagination, setPagination] = useState({ @@ -313,14 +316,14 @@ export function BootsTable({ ?.setFilterValue(filter !== 'all' ? filter : undefined); }, [filter, table]); - // TODO: there should be a filtering for the frontend before the backend AND that filtering should consider the individual tests inside each test "batch" (the data from individualTestsTables), not only the rows of the external table const onSearchChange = useCallback( (e: React.ChangeEvent) => { - if (e.target.value !== undefined) { + if (e.target.value !== undefined && updatePathFilter) { updatePathFilter(e.target.value); } - // TODO: only use the frontend filtering when the backend filter function is undefined (like in BuildDetails page) - table.setGlobalFilter(String(e.target.value)); + if (updatePathFilter === undefined) { + table.setGlobalFilter(String(e.target.value)); + } }, [table, updatePathFilter], ); @@ -342,9 +345,10 @@ export function BootsTable({ {header.id === 'path' ? (
{headerComponent} - {/* TODO: add startingValue with the currentPathFilter from the diffFilter param, same for TestsTable */} ); }); - }, [groupHeaders, intl, onSearchChange, sorting]); + }, [currentPathFilter, groupHeaders, intl, onSearchChange, sorting]); const modelRows = table.getRowModel().rows; diff --git a/dashboard/src/components/TestsTable/TestsTable.tsx b/dashboard/src/components/TestsTable/TestsTable.tsx index f51d4a2c..937ca232 100644 --- a/dashboard/src/components/TestsTable/TestsTable.tsx +++ b/dashboard/src/components/TestsTable/TestsTable.tsx @@ -48,8 +48,10 @@ export interface ITestsTable { innerColumns?: ColumnDef[]; getRowLink: (testId: TestHistory['id']) => LinkProps; updatePathFilter?: (pathFilter: string) => void; + currentPathFilter?: string; } +// TODO: would be useful if the navigation happened within the table, so the parent component would only be required to pass the navigation url instead of the whole function for the update and the currentPath diffFilter (boots/tests Table) export function TestsTable({ testHistory, onClickFilter, @@ -58,6 +60,7 @@ export function TestsTable({ innerColumns = defaultInnerColumns, getRowLink, updatePathFilter, + currentPathFilter, }: ITestsTable): JSX.Element { const [sorting, setSorting] = useState([]); const [expanded, setExpanded] = useState({}); @@ -255,14 +258,14 @@ export function TestsTable({ [filterCount, intl, filter], ); - // TODO: there should be a filtering for the frontend before the backend AND that filtering should consider the individual tests inside each test "batch" (the data from individualTestsTables), not only the rows of the external table const onSearchChange = useCallback( (e: React.ChangeEvent) => { if (e.target.value !== undefined && updatePathFilter) { updatePathFilter(e.target.value); } - // TODO: remove this frontend filter when the hardwareDetails backend filtering gets in place - table.setGlobalFilter(String(e.target.value)); + if (updatePathFilter === undefined) { + table.setGlobalFilter(String(e.target.value)); + } }, [table, updatePathFilter], ); @@ -285,7 +288,9 @@ export function TestsTable({
{headerComponent} ); }); - }, [groupHeaders, intl, onSearchChange, sorting]); + }, [currentPathFilter, groupHeaders, intl, onSearchChange, sorting]); const modelRows = table.getRowModel().rows; const tableRows = useMemo((): JSX.Element[] | JSX.Element => { diff --git a/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx index 08256731..8ead9a13 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx @@ -32,9 +32,12 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { const { treeId } = useParams({ from: '/tree/$treeId/', }); - const { tableFilter } = useSearch({ + const { tableFilter, diffFilter } = useSearch({ from: '/tree/$treeId/', }); + const currentPathFilter = diffFilter.bootPath + ? Object.keys(diffFilter.bootPath)[0] + : undefined; const navigate = useNavigate({ from: '/tree/$treeId/' }); @@ -45,7 +48,7 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { ...previousSearch, diffFilter: { ...previousSearch.diffFilter, - path: pathFilter === '' ? undefined : { [pathFilter]: true }, + bootPath: pathFilter === '' ? undefined : { [pathFilter]: true }, }, }), }); @@ -181,6 +184,7 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { testHistory={data.bootHistory} getRowLink={getRowLink} updatePathFilter={updatePathFilter} + currentPathFilter={currentPathFilter} />
); diff --git a/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx index 2c112f70..aaa7b168 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx @@ -38,9 +38,12 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { filter: reqFilter, }); - const { tableFilter } = useSearch({ + const { tableFilter, diffFilter } = useSearch({ from: '/tree/$treeId/', }); + const currentPathFilter = diffFilter.testPath + ? Object.keys(diffFilter.testPath)[0] + : undefined; const navigate = useNavigate({ from: '/tree/$treeId' }); @@ -51,7 +54,7 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { ...previousSearch, diffFilter: { ...previousSearch.diffFilter, - path: pathFilter === '' ? undefined : { [pathFilter]: true }, + testPath: pathFilter === '' ? undefined : { [pathFilter]: true }, }, }), }); @@ -186,6 +189,7 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { filter={tableFilter.testsTable} getRowLink={getRowLink} updatePathFilter={updatePathFilter} + currentPathFilter={currentPathFilter} />
); diff --git a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx index 6bacaa71..5dd7c183 100644 --- a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx +++ b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx @@ -47,7 +47,8 @@ const filterFieldMap = { 'test.duration_[gte]': 'testDurationMin', 'test.duration_[lte]': 'testDurationMax', 'test.hardware': 'hardware', - 'test.path': 'path', + 'test.path': 'testPath', + 'boot.path': 'bootPath', } as const satisfies Record; export const mapFilterToReq = ( diff --git a/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx b/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx index 1b6c1e59..548bf434 100644 --- a/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx +++ b/dashboard/src/pages/hardwareDetails/HardwareDetailsFilter.tsx @@ -45,6 +45,8 @@ const filterFieldMap = { 'test.status': 'testStatus', 'test.duration_[gte]': 'testDurationMin', 'test.duration_[lte]': 'testDurationMax', + 'test.path': 'testPath', + 'boot.path': 'bootPath', } as const satisfies Record; export const mapFilterToReq = ( diff --git a/dashboard/src/pages/hardwareDetails/Tabs/Boots/BootsTab.tsx b/dashboard/src/pages/hardwareDetails/Tabs/Boots/BootsTab.tsx index cfd40b06..796b7f5c 100644 --- a/dashboard/src/pages/hardwareDetails/Tabs/Boots/BootsTab.tsx +++ b/dashboard/src/pages/hardwareDetails/Tabs/Boots/BootsTab.tsx @@ -145,9 +145,12 @@ const ErrorsSummary = ({ export const MemoizedErrorsSummary = memo(ErrorsSummary); const BootsTab = ({ boots, hardwareId }: TBootsTab): JSX.Element => { - const { tableFilter } = useSearch({ + const { tableFilter, diffFilter } = useSearch({ from: '/hardware/$hardwareId', }); + const currentPathFilter = diffFilter.bootPath + ? Object.keys(diffFilter.bootPath)[0] + : undefined; const getRowLink = useCallback( (bootId: string): LinkProps => ({ @@ -170,7 +173,7 @@ const BootsTab = ({ boots, hardwareId }: TBootsTab): JSX.Element => { ...previousSearch, diffFilter: { ...previousSearch.diffFilter, - path: pathFilter === '' ? undefined : { [pathFilter]: true }, + bootPath: pathFilter === '' ? undefined : { [pathFilter]: true }, }, }), }); @@ -245,6 +248,7 @@ const BootsTab = ({ boots, hardwareId }: TBootsTab): JSX.Element => { testHistory={boots.history} onClickFilter={onClickFilter} updatePathFilter={updatePathFilter} + currentPathFilter={currentPathFilter} /> ); diff --git a/dashboard/src/pages/hardwareDetails/Tabs/Tests/HardwareDetailsTestsTable.tsx b/dashboard/src/pages/hardwareDetails/Tabs/Tests/HardwareDetailsTestsTable.tsx index 2ec7d885..f1865173 100644 --- a/dashboard/src/pages/hardwareDetails/Tabs/Tests/HardwareDetailsTestsTable.tsx +++ b/dashboard/src/pages/hardwareDetails/Tabs/Tests/HardwareDetailsTestsTable.tsx @@ -94,6 +94,7 @@ const HardwareDetailsTestTable = ({ testHistory, hardwareId, updatePathFilter, + currentPathFilter, }: IHardwareDetailsTestTable): JSX.Element => { const getRowLink = useCallback( (bootId: string): LinkProps => ({ @@ -115,6 +116,7 @@ const HardwareDetailsTestTable = ({ innerColumns={innerColumns} getRowLink={getRowLink} updatePathFilter={updatePathFilter} + currentPathFilter={currentPathFilter} /> ); }; diff --git a/dashboard/src/pages/hardwareDetails/Tabs/Tests/TestsTab.tsx b/dashboard/src/pages/hardwareDetails/Tabs/Tests/TestsTab.tsx index 9ac9c174..299e5394 100644 --- a/dashboard/src/pages/hardwareDetails/Tabs/Tests/TestsTab.tsx +++ b/dashboard/src/pages/hardwareDetails/Tabs/Tests/TestsTab.tsx @@ -27,13 +27,15 @@ interface TTestsTab { } const TestsTab = ({ tests, hardwareId }: TTestsTab): JSX.Element => { - const { tableFilter } = useSearch({ + const { tableFilter, diffFilter } = useSearch({ from: '/hardware/$hardwareId', }); + const currentPathFilter = diffFilter.testPath + ? Object.keys(diffFilter.testPath)[0] + : undefined; const navigate = useNavigate({ from: '/hardware/$hardwareId' }); - // TODO: move inside the same hook for the tables, passing navigate, in order to not repeat the same code in each tab of each monitor const updatePathFilter = useCallback( (pathFilter: string) => { navigate({ @@ -41,7 +43,7 @@ const TestsTab = ({ tests, hardwareId }: TTestsTab): JSX.Element => { ...previousSearch, diffFilter: { ...previousSearch.diffFilter, - path: pathFilter === '' ? undefined : { [pathFilter]: true }, + testPath: pathFilter === '' ? undefined : { [pathFilter]: true }, }, }), }); @@ -116,6 +118,7 @@ const TestsTab = ({ tests, hardwareId }: TTestsTab): JSX.Element => { hardwareId={hardwareId} onClickFilter={onClickFilter} updatePathFilter={updatePathFilter} + currentPathFilter={currentPathFilter} /> ); diff --git a/dashboard/src/types/hardware/hardwareDetails.ts b/dashboard/src/types/hardware/hardwareDetails.ts index 3155c437..9c255aec 100644 --- a/dashboard/src/types/hardware/hardwareDetails.ts +++ b/dashboard/src/types/hardware/hardwareDetails.ts @@ -67,6 +67,8 @@ export const zFilterObjectsKeys = z.enum([ 'testStatus', 'trees', 'path', + 'bootPath', + 'testPath', ]); export const zFilterNumberKeys = z.enum([ 'buildDurationMin', @@ -98,6 +100,8 @@ export const zDiffFilter = z testDurationMax: zFilterNumberValue, trees: zFilterBoolValue, path: zFilterBoolValue, + bootPath: zFilterBoolValue, + testPath: zFilterBoolValue, } satisfies Record), z.record(z.never()), ]) @@ -132,6 +136,8 @@ const requestFilters = { 'boot.status', 'boot.duration_[gte]', 'boot.duration_[lte]', + 'test.path', + 'boot.path', ], } as const; diff --git a/dashboard/src/types/tree/TreeDetails.tsx b/dashboard/src/types/tree/TreeDetails.tsx index 45f7e426..4e064778 100644 --- a/dashboard/src/types/tree/TreeDetails.tsx +++ b/dashboard/src/types/tree/TreeDetails.tsx @@ -148,7 +148,8 @@ export const zFilterObjectsKeys = z.enum([ 'bootStatus', 'testStatus', 'hardware', - 'path', + 'testPath', + 'bootPath', ]); export const zFilterNumberKeys = z.enum([ 'buildDurationMin', @@ -173,7 +174,8 @@ export const zDiffFilter = z bootStatus: zFilterBoolValue, testStatus: zFilterBoolValue, hardware: zFilterBoolValue, - path: zFilterBoolValue, + testPath: zFilterBoolValue, + bootPath: zFilterBoolValue, buildDurationMax: zFilterNumberValue, buildDurationMin: zFilterNumberValue, bootDurationMin: zFilterNumberValue, diff --git a/dashboard/src/utils/filters.ts b/dashboard/src/utils/filters.ts index 83d93da3..28568e76 100644 --- a/dashboard/src/utils/filters.ts +++ b/dashboard/src/utils/filters.ts @@ -8,6 +8,7 @@ const requestFilters = { 'test.duration_[lte]', 'test.hardware', 'test.path', + 'boot.path', 'boot.status', 'boot.duration_[gte]', 'boot.duration_[lte]',