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

Feat: add path filter to tree details #564

Merged
merged 2 commits into from
Nov 22, 2024
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
7 changes: 7 additions & 0 deletions backend/kernelCI_app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,11 @@ class FilterParams:
"gte": ["gte", ">="],
"lt": ["lt", "<"],
"lte": ["lte", "<="],
"like": ["like", "LIKE"],
}

string_like_filters = ["test.path"]

def __init__(self, request):
self.filters = []
self.create_filters_from_req(request)
Expand All @@ -129,6 +132,10 @@ def create_filters_from_req(self, request):
self.add_filter(field, value, "in")
continue

if filter_term in self.string_like_filters:
self.add_filter(filter_term, request.GET.get(k), "like")
continue

match = self.filter_reg.match(filter_term)
if match:
field = match.group(1)
Expand Down
3 changes: 3 additions & 0 deletions backend/kernelCI_app/views/treeCommitsHistory.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ def __treat_unknown_filter(self, table_field, op, value_name, filter):
self.field_values[value_name] = filter['value']
if op == "IN":
clause += f" = ANY(%({value_name})s)"
elif op == "LIKE":
self.field_values[value_name] = f"%{filter['value']}%"
clause += f" {op} %({value_name})s"
else:
clause += f" {op} %({value_name})s"

Expand Down
10 changes: 10 additions & 0 deletions backend/kernelCI_app/views/treeDetailsSlowView.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self):
self.filterTreeDetailsCompiler = set()
self.filterArchitecture = set()
self.filterHardware = set()
self.filterPath = ""
self.filter_handlers = {
"boot.status": self.__handle_boot_status,
"boot.duration": self.__handle_boot_duration,
Expand All @@ -36,6 +37,7 @@ def __init__(self):
"compiler": self.__handle_compiler,
"architecture": self.__handle_architecture,
"test.hardware": self.__handle_hardware,
"test.path": self.__handle_path,
}

self.testHistory = []
Expand Down Expand Up @@ -95,6 +97,9 @@ def __handle_architecture(self, current_filter):
def __handle_hardware(self, current_filter):
self.filterHardware.add(current_filter["value"])

def __handle_path(self, current_filter):
self.filterPath = current_filter["value"]

def __processFilters(self, request):
try:
filter_params = FilterParams(request)
Expand Down Expand Up @@ -470,8 +475,13 @@ def get(self, request, commit_hash: str | None):
) = currentRowData

self.hardwareUsed.add(testEnvironmentCompatible)

if (
(
self.filterPath != ""
and (self.filterPath not in path)
)
or (
len(self.filterHardware) > 0
and (testEnvironmentCompatible not in self.filterHardware)
)
Expand Down
46 changes: 30 additions & 16 deletions dashboard/src/components/BootsTable/BootsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ interface IBootsTable {
filter: TestsTableFilter;
getRowLink: (testId: TestHistory['id']) => LinkProps;
onClickFilter: (newFilter: TestsTableFilter) => void;
updatePathFilter: (pathFilter: string) => void;
}

const TableCellComponent = ({
Expand Down Expand Up @@ -196,6 +197,7 @@ export function BootsTable({
filter,
getRowLink,
onClickFilter,
updatePathFilter,
}: IBootsTable): JSX.Element {
const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState<PaginationState>({
Expand Down Expand Up @@ -311,20 +313,40 @@ 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<HTMLInputElement>) =>
table.setGlobalFilter(String(e.target.value)),
[table],
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value !== undefined) {
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));
},
[table, updatePathFilter],
);

const groupHeaders = table.getHeaderGroups()[0]?.headers;
const tableHeaders = useMemo((): JSX.Element[] => {
return groupHeaders.map(header => {
const headerComponent = header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext());
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
<TableHead key={header.id} className="border-b px-2 font-bold">
{header.id === 'path' ? (
<div className="flex items-center">
{headerComponent}
{/* TODO: add startingValue with the currentPathFilter from the diffFilter param, same for TestsTable */}
<DebounceInput
debouncedSideEffect={onSearchChange}
className="w-50 font-normal"
type="text"
placeholder={intl.formatMessage({ id: 'global.search' })}
/>
</div>
) : (
headerComponent
)}
</TableHead>
);
});
Expand Down Expand Up @@ -416,15 +438,7 @@ export function BootsTable({
navigationLogsActions={navigationLogsActions}
onOpenChange={onOpenChange}
>
<div className="flex justify-between">
<TableStatusFilter filters={filters} onClickTest={onClickFilter} />
<DebounceInput
debouncedSideEffect={onSearchChange}
className="w-50"
type="text"
placeholder={intl.formatMessage({ id: 'global.search' })}
/>
</div>
<TableStatusFilter filters={filters} onClickTest={onClickFilter} />
<BaseTable headerComponents={tableHeaders}>
<TableBody>{tableRows}</TableBody>
</BaseTable>
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/Cards/HardwareUsed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const HardwareUsed = ({ hardwareUsed, title }: IHardwareUsed): JSX.Element => {
return (
<BaseCard
title={title}
className="mb-0"
content={
<div className="flex flex-row flex-wrap gap-4 p-4">
{hardwareSorted}
Expand Down
12 changes: 7 additions & 5 deletions dashboard/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const TabsComponent = ({
key={tab.name}
value={tab.name}
>
<FormattedMessage id={tab.name} />{' '}
<FormattedMessage id={tab.name} />
<div className="pl-2">{tab.rightElement}</div>
</TabsTrigger>
)),
Expand All @@ -63,10 +63,12 @@ const TabsComponent = ({
defaultValue={defaultTab}
className="w-full"
>
<TabsList className="w-full justify-start bg-transparent">
{tabsTrigger}
</TabsList>
<div className="border-t border-darkGray py-6">{filterListElement}</div>
<div className="sticky top-16 z-[5] rounded-md bg-lightGray pb-6 pt-12">
Copy link
Collaborator

Choose a reason for hiding this comment

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

try swapping rounded-md with drop-shadow

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

drop-shadow gets a bit weird when nothing else in the page has shadows, but we might try something else another time

<TabsList className="w-full justify-start rounded-none border-b border-darkGray bg-transparent">
{tabsTrigger}
</TabsList>
{filterListElement && <div className="pt-6">{filterListElement}</div>}
</div>

{tabsContent}
</Tabs>
Expand Down
45 changes: 29 additions & 16 deletions dashboard/src/components/TestsTable/TestsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ export interface ITestsTable {
columns?: ColumnDef<TPathTests>[];
innerColumns?: ColumnDef<TIndividualTest>[];
getRowLink: (testId: TestHistory['id']) => LinkProps;
updatePathFilter?: (pathFilter: string) => void;
}

export function TestsTable({
testHistory,
onClickFilter,
filter,
getRowLink,
columns = defaultColumns,
innerColumns = defaultInnerColumns,
getRowLink,
updatePathFilter,
}: ITestsTable): JSX.Element {
const [sorting, setSorting] = useState<SortingState>([]);
const [expanded, setExpanded] = useState<ExpandedState>({});
Expand Down Expand Up @@ -253,20 +255,39 @@ 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<HTMLInputElement>) =>
table.setGlobalFilter(String(e.target.value)),
[table],
(e: React.ChangeEvent<HTMLInputElement>) => {
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));
},
[table, updatePathFilter],
);

const groupHeaders = table.getHeaderGroups()[0]?.headers;
const tableHeaders = useMemo((): JSX.Element[] => {
return groupHeaders.map(header => {
const headerComponent = header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext());
return (
<TableHead key={header.id} className="border-b px-2 font-bold">
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.id === 'path_group' ? (
<div className="flex items-center">
{headerComponent}
<DebounceInput
Copy link
Collaborator

Choose a reason for hiding this comment

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

Review about this comment

debouncedSideEffect={onSearchChange}
className="w-50 font-normal"
type="text"
placeholder={intl.formatMessage({ id: 'global.search' })}
/>
</div>
) : (
headerComponent
)}
</TableHead>
);
});
Expand Down Expand Up @@ -316,15 +337,7 @@ export function TestsTable({

return (
<div className="flex flex-col gap-6 pb-4">
<div className="flex justify-between">
<TableStatusFilter filters={filters} onClickTest={onClickFilter} />
<DebounceInput
debouncedSideEffect={onSearchChange}
className="w-50"
type="text"
placeholder={intl.formatMessage({ id: 'global.search' })}
/>
</div>
<TableStatusFilter filters={filters} onClickTest={onClickFilter} />
<BaseTable headerComponents={tableHeaders}>
<TableBody>{tableRows}</TableBody>
</BaseTable>
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-slate-100 p-1 text-slate-500 dark:bg-slate-800 dark:text-slate-400',
'inline-flex h-10 items-center justify-center rounded-md bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400',
className,
)}
{...props}
Expand Down Expand Up @@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300',
'ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300',
className,
)}
{...props}
Expand Down
16 changes: 16 additions & 0 deletions dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => {

const navigate = useNavigate({ from: '/tree/$treeId/' });

const updatePathFilter = useCallback(
(pathFilter: string) => {
navigate({
search: previousSearch => ({
...previousSearch,
diffFilter: {
...previousSearch.diffFilter,
path: pathFilter === '' ? undefined : { [pathFilter]: true },
},
}),
});
},
[navigate],
);

const onClickFilter = useCallback(
(newFilter: TestsTableFilter): void => {
navigate({
Expand Down Expand Up @@ -165,6 +180,7 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => {
onClickFilter={onClickFilter}
testHistory={data.bootHistory}
getRowLink={getRowLink}
updatePathFilter={updatePathFilter}
/>
</div>
);
Expand Down
16 changes: 16 additions & 0 deletions dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => {

const navigate = useNavigate({ from: '/tree/$treeId' });

const updatePathFilter = useCallback(
(pathFilter: string) => {
navigate({
search: previousSearch => ({
...previousSearch,
diffFilter: {
...previousSearch.diffFilter,
path: pathFilter === '' ? undefined : { [pathFilter]: true },
},
}),
});
},
[navigate],
);

const getRowLink = useCallback(
(bootId: string): LinkProps => {
return {
Expand Down Expand Up @@ -170,6 +185,7 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => {
onClickFilter={onClickFilter}
filter={tableFilter.testsTable}
getRowLink={getRowLink}
updatePathFilter={updatePathFilter}
/>
</div>
);
Expand Down
13 changes: 9 additions & 4 deletions dashboard/src/pages/TreeDetails/TreeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ function TreeDetails(): JSX.Element {
}, [isBuildTab, testsIsLoading, buildIsLoading]);

const filterListElement = useMemo(
() => <TreeDetailsFilterList filter={diffFilter} />,
() =>
Object.keys(diffFilter).length !== 0 ? (
<TreeDetailsFilterList filter={diffFilter} />
) : undefined,
[diffFilter],
);

Expand Down Expand Up @@ -267,9 +270,11 @@ function TreeDetails(): JSX.Element {
/>
</div>
</QuerySwitcher>
<div className="relative mt-10 flex flex-col pb-2">
<div className="absolute right-0 top-[-16px]">
<TreeDetailsFilter paramFilter={diffFilter} treeUrl={treeUrl} />
<div className="flex flex-col pb-2">
<div className="sticky top-[4.5rem] z-10">
<div className="absolute right-0 top-2 py-4">
<TreeDetailsFilter paramFilter={diffFilter} treeUrl={treeUrl} />
</div>
</div>
<TreeDetailsTab
treeDetailsData={treeDetailsData}
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const filterFieldMap = {
'test.duration_[gte]': 'testDurationMin',
'test.duration_[lte]': 'testDurationMax',
'test.hardware': 'hardware',
'test.path': 'path',
} as const satisfies Record<TRequestFiltersValues, TFilterKeys>;

export const mapFilterToReq = (
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/pages/TreeDetails/TreeDetailsFilterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const TreeDetailsFilterList = ({

if (typeof fieldSection === 'object') {
delete fieldSection[value];
if (Object.keys(fieldSection).length === 0) {
delete newFilter[field];
}
} else {
delete newFilter[field];
}
Expand Down
Loading
Loading