Skip to content

Commit 238f606

Browse files
authored
Merge pull request #84 from ceph/custom-filter
2 parents 9a03741 + 9d6cacc commit 238f606

File tree

3 files changed

+166
-12
lines changed

3 files changed

+166
-12
lines changed

src/components/RunList/index.tsx

+145-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { useMemo } from 'react';
1+
import { SetStateAction, useMemo } from 'react';
2+
import { useState } from "react";
23
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
4+
import Grid from '@mui/material/Grid';
5+
import Button from '@mui/material/Button';
36
import type {
47
DecodedValueMap,
58
QueryParamConfigMap,
@@ -9,11 +12,13 @@ import { useDebounce } from "usehooks-ts";
912
import {
1013
useMaterialReactTable,
1114
MaterialReactTable,
15+
MRT_TableHeadCellFilterContainer,
1216
type MRT_ColumnDef,
1317
type MRT_PaginationState,
1418
type MRT_Updater,
1519
type MRT_ColumnFiltersState,
1620
type MRT_TableOptions,
21+
type MRT_TableInstance,
1722
} from 'material-react-table';
1823
import { type Theme } from "@mui/material/styles";
1924
import { parse } from "date-fns";
@@ -31,6 +36,10 @@ import {
3136
RunStatuses,
3237
} from "../../lib/paddles.d";
3338
import useDefaultTableOptions from "../../lib/table";
39+
import Typography from '@mui/material/Typography';
40+
import Box from '@mui/material/Box';
41+
import Badge from '@mui/material/Badge';
42+
import Menu from '@mui/material/Menu';
3443

3544

3645
const DEFAULT_PAGE_SIZE = 25;
@@ -45,7 +54,6 @@ const _columns: MRT_ColumnDef<Run>[] = [
4554
header: "link",
4655
maxSize: 12,
4756
enableColumnFilter: false,
48-
enableColumnActions: false,
4957
Cell: ({ row }) => {
5058
return (
5159
<IconLink to={`/runs/${row.original.name}`}>
@@ -62,14 +70,14 @@ const _columns: MRT_ColumnDef<Run>[] = [
6270
return row.original.status.replace("finished ", "");
6371
},
6472
filterSelectOptions: Object.values(RunStatuses),
65-
size: 40,
66-
enableColumnActions: false,
73+
maxSize: 25,
6774
},
6875
{
6976
accessorKey: "user",
7077
header: "user",
7178
maxSize: 45,
7279
enableColumnActions: false,
80+
enableColumnFilter: false,
7381
},
7482
{
7583
id: "scheduled",
@@ -81,23 +89,23 @@ const _columns: MRT_ColumnDef<Run>[] = [
8189
const date_: string[] = row.original.scheduled.split(" ");
8290
return <div> {date_[0]} <br /> {date_[1]} </div>
8391
},
84-
size: 50,
92+
size: 35,
8593
},
8694
{
8795
id: "started",
8896
header: "started",
8997
accessorFn: (row: Run) => formatDate(row.started),
9098
enableColumnFilter: false,
9199
sortingFn: "datetime",
92-
size: 125,
100+
size: 35,
93101
},
94102
{
95103
id: "posted",
96104
header: "updated",
97105
accessorFn: (row: Run) => formatDate(row.posted),
98106
enableColumnFilter: false,
99107
sortingFn: "datetime",
100-
size: 125,
108+
maxSize: 35,
101109
},
102110
{
103111
id: "runtime",
@@ -115,16 +123,16 @@ const _columns: MRT_ColumnDef<Run>[] = [
115123
{
116124
accessorKey: "suite",
117125
header: "suite",
118-
size: 70,
126+
size: 50,
119127
},
120128
{
121129
accessorKey: "branch",
122130
header: "branch",
123-
maxSize: 75,
131+
maxSize: 70,
124132
},
125133
{
126134
accessorKey: "machine_type",
127-
header: "machine_type",
135+
header: "machine type",
128136
size: 30,
129137
},
130138
{
@@ -200,6 +208,9 @@ type RunListProps = {
200208
}
201209

202210
export default function RunList(props: RunListProps) {
211+
const [openFilterMenu, setOpenFilterMenu] = useState<boolean>(false);
212+
const [dropMenuAnchorEl, setDropMenuAnchor] = useState<null | HTMLElement>(null);
213+
203214
const { params, setter, tableOptions } = props;
204215
const options = useDefaultTableOptions<Run>();
205216
const debouncedParams = useDebounce(params, 500);
@@ -220,6 +231,15 @@ export default function RunList(props: RunListProps) {
220231
pageIndex: params.page || 0,
221232
pageSize: params.pageSize || DEFAULT_PAGE_SIZE,
222233
};
234+
const toggleFilterMenu = (event: { currentTarget: SetStateAction<HTMLElement | null>; }) => {
235+
if (dropMenuAnchorEl) {
236+
setDropMenuAnchor(null);
237+
setOpenFilterMenu(false);
238+
} else {
239+
setDropMenuAnchor(event.currentTarget);
240+
setOpenFilterMenu(true);
241+
}
242+
}
223243
const onColumnFiltersChange = (updater: MRT_Updater<MRT_ColumnFiltersState>) => {
224244
if ( ! ( updater instanceof Function ) ) return;
225245
const result: RunListParams = {pageSize: pagination.pageSize};
@@ -267,12 +287,19 @@ export default function RunList(props: RunListProps) {
267287
data: data,
268288
manualPagination: true,
269289
manualFiltering: true,
290+
enableColumnActions: false,
270291
onPaginationChange,
271292
rowCount: Infinity,
272293
muiPaginationProps: {
273294
showLastButton: false,
274295
},
275296
onColumnFiltersChange,
297+
columnFilterDisplayMode: 'custom',
298+
enableColumnFilters: false,
299+
muiFilterTextFieldProps: ({ column }) => ({
300+
label: column.columnDef.header,
301+
placeholder: '',
302+
}),
276303
initialState: {
277304
...options.initialState,
278305
columnVisibility: {
@@ -297,8 +324,115 @@ export default function RunList(props: RunListProps) {
297324
if ( category ) return { className: category };
298325
return {};
299326
},
327+
renderTopToolbarCustomActions: ({ table }) => (
328+
<Box sx={{ padding: '4px' }}>
329+
<Badge
330+
color="primary"
331+
overlap="circular"
332+
badgeContent={table.getState().columnFilters.reduce((count, filter) => (filter.value ? count + 1 : count), 0)}
333+
>
334+
<Button
335+
id="filter-button"
336+
onClick={toggleFilterMenu}
337+
>
338+
Filters
339+
</Button>
340+
</Badge>
341+
</Box>
342+
),
300343
...tableOptions,
301344
});
345+
302346
if (query.isError) return null;
303-
return <MaterialReactTable table={table} />
347+
return (
348+
<div>
349+
<div>
350+
<Typography variant="body2" gutterBottom color="gray" textAlign={"right"}>
351+
{ table.getState().columnFilters.map((column) => {
352+
let filterValue = column.value;
353+
if (column.id == "scheduled") {
354+
const parsedDate = new Date(column.value as string);
355+
filterValue = (parsedDate.toISOString().split('T')[0])
356+
}
357+
return (column.value ? `${column.id}: '${filterValue}' ` : "")
358+
} )}
359+
</Typography>
360+
<Menu
361+
id="filter-menu"
362+
anchorEl={dropMenuAnchorEl}
363+
open={openFilterMenu}
364+
onClose={toggleFilterMenu}
365+
MenuListProps={{
366+
'aria-labelledby': 'filter-button',
367+
}}
368+
>
369+
<FilterMenu isOpen={openFilterMenu} table={table} />
370+
</Menu>
371+
</div>
372+
<MaterialReactTable table={table} />
373+
</div>
374+
)
375+
}
376+
377+
378+
// FilterMenu
379+
380+
type FilterMenuProps = {
381+
isOpen: boolean;
382+
table: MRT_TableInstance<Run>;
383+
};
384+
385+
386+
const FILTER_SECTIONS = ["run", "build", "result"]
387+
const FILTER_SECTIONS_COLUMNS = [
388+
["scheduled", "suite", "machine_type", "user"],
389+
["branch", "sha1"],
390+
["status"],
391+
]
392+
393+
function FilterMenu({ isOpen, table}: FilterMenuProps) {
394+
if (!isOpen) {
395+
return null;
396+
}
397+
398+
return (
399+
<Box
400+
sx={{
401+
padding: '1em',
402+
margin: '0px 0.5em',
403+
border: '2px dashed grey',
404+
borderRadius: '8px',
405+
}}
406+
>
407+
{FILTER_SECTIONS_COLUMNS.map((_, sectionIndex) => (
408+
<Box
409+
key={`section-${sectionIndex}`}
410+
sx={{
411+
marginBottom: '1em',
412+
marginLeft: '0.5em',
413+
}}
414+
>
415+
<Typography variant="body2" gutterBottom color="gray">
416+
Filter by {FILTER_SECTIONS[sectionIndex]} details...
417+
</Typography>
418+
<Grid container spacing={1} alignItems="center">
419+
{table.getLeafHeaders().map((header) => {
420+
if (FILTER_SECTIONS_COLUMNS[sectionIndex].includes(header.id)) {
421+
return (
422+
<Grid item xs={2} key={header.id} marginLeft={"1.2em"}>
423+
<MRT_TableHeadCellFilterContainer
424+
header={header}
425+
table={table}
426+
style={{ backgroundColor: 'transparent', width: '100%' }}
427+
/>
428+
</Grid>
429+
);
430+
}
431+
return null;
432+
})}
433+
</Grid>
434+
</Box>
435+
))}
436+
</Box>
437+
)
304438
}

src/lib/paddles.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,19 @@ function getURL(endpoint: string, params?: GetURLParams) {
5656
}
5757

5858
function useRuns(params: GetURLParams): UseQueryResult<Run[]> {
59+
let baseUrl = "/runs/";
5960
const params_ = { ...params };
6061
if (params_.pageSize) {
6162
params_.count = params.pageSize;
6263
delete params_.pageSize;
6364
}
64-
const url = getURL("/runs/", params);
65+
if (params_.machine_type) {
66+
// this is to handle difference in machine_type filter
67+
// in two endpoints /node/?machine_type=xyz and /runs/machine_type/xyz
68+
baseUrl += "machine_type/" + params_.machine_type + "/";
69+
delete params_.machine_type;
70+
}
71+
const url = getURL(baseUrl, params);
6572
const query = useQuery(["runs", { url }], {
6673
select: (data: Run[]) =>
6774
data.map((item) => {

src/lib/table.ts

+13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ export default function useDefaultTableOptions<TData extends MRT_RowData>(): Par
2424
mrtTheme: {
2525
baseBackgroundColor: theme.palette.background.default,
2626
},
27+
muiTableHeadCellProps: {
28+
sx: {
29+
'& .Mui-TableHeadCell-Content': {
30+
fontSize: "0.8em",
31+
},
32+
'& .MuiTableSortLabel-root': {
33+
display: "none",
34+
},
35+
'&:hover .MuiTableSortLabel-root': {
36+
display: "block",
37+
},
38+
},
39+
},
2740
muiTableBodyCellProps: {
2841
sx: {
2942
color: "black",

0 commit comments

Comments
 (0)