-
Notifications
You must be signed in to change notification settings - Fork 2
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
Refactor system table filter and sorter #518
Changes from all commits
c07f0cc
6e2ced9
5bee0b1
b4947b2
2914cda
2a5817b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,10 @@ import { SystemModel } from "../../models"; | |
import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; | ||
import { PrivateIcon, useUser } from ".."; | ||
import { generateLeaderboardURL } from "../../utils"; | ||
import type { FilterValue, SorterResult } from "antd/es/table/interface"; | ||
import type { TablePaginationConfig } from "antd/es/table"; | ||
import { FilterUpdate, SystemFilter } from "./SystemFilter"; | ||
import { TaskCategory } from "../../clients/openapi"; | ||
const { Text } = Typography; | ||
|
||
interface Props { | ||
|
@@ -30,6 +34,9 @@ interface Props { | |
setSelectedSystemIDs: React.Dispatch<React.SetStateAction<string[]>>; | ||
onActiveSystemChange: (ids: string[]) => void; | ||
showEditDrawer: (systemIDToEdit: string) => void; | ||
onFilterChange: (value: FilterUpdate) => void; | ||
filterValue: SystemFilter; | ||
taskCategories: TaskCategory[]; | ||
} | ||
|
||
export function SystemTableContent({ | ||
|
@@ -44,6 +51,9 @@ export function SystemTableContent({ | |
setSelectedSystemIDs, | ||
onActiveSystemChange, | ||
showEditDrawer, | ||
onFilterChange, | ||
filterValue, | ||
taskCategories, | ||
}: Props) { | ||
const { userInfo } = useUser(); | ||
const metricColumns: ColumnsType<SystemModel> = metricNames.map((metric) => ({ | ||
|
@@ -52,6 +62,7 @@ export function SystemTableContent({ | |
width: 135, | ||
ellipsis: true, | ||
align: "center", | ||
sorter: true, | ||
})); | ||
|
||
function showSystemAnalysis(systemID: string) { | ||
|
@@ -69,6 +80,11 @@ export function SystemTableContent({ | |
} | ||
} | ||
} | ||
const taskFilterList = taskCategories.flatMap((category) => { | ||
return category.tasks.map((task) => { | ||
return { text: task.name, value: task.name }; | ||
}); | ||
}); | ||
|
||
const columns: ColumnsType<SystemModel> = [ | ||
{ | ||
|
@@ -100,6 +116,9 @@ export function SystemTableContent({ | |
fixed: "left", | ||
title: "Task", | ||
render: (value) => <Tag style={{ whiteSpace: "normal" }}>{value}</Tag>, | ||
filters: taskFilterList, | ||
filterMultiple: false, | ||
filteredValue: filterValue.task ? [filterValue.task] : null, | ||
}, | ||
{ | ||
dataIndex: "dataset_name", | ||
|
@@ -132,6 +151,13 @@ export function SystemTableContent({ | |
title: "Dataset Split", | ||
fixed: "left", | ||
align: "center", | ||
filterMultiple: false, | ||
filters: [ | ||
{ text: "train", value: "train" }, | ||
{ text: "validation", value: "validation" }, | ||
{ text: "test", value: "test" }, | ||
], | ||
filteredValue: filterValue.split ? [filterValue.split] : null, | ||
}, | ||
{ | ||
dataIndex: "source_language", | ||
|
@@ -158,6 +184,7 @@ export function SystemTableContent({ | |
render: (_, record) => record.created_at.format("MM/DD/YYYY HH:mm"), | ||
width: 130, | ||
align: "center", | ||
sorter: true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the sorters also need to be controlled by |
||
}, | ||
{ | ||
dataIndex: "system_tags", | ||
|
@@ -220,6 +247,50 @@ export function SystemTableContent({ | |
}, | ||
}; | ||
|
||
const handleTableChange = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of implementing all the table change logic in one function, I think it'll be more readable if it is implemented for each column where the filter is defined so you don't have to write these if-else statements to match filter names. There does not seem to be an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lyuyangh, I try to use the
Therefore, in PR #528 , I still keep the |
||
_pagination: TablePaginationConfig, | ||
filters: Record<string, FilterValue | null>, | ||
sorter: SorterResult<SystemModel> | SorterResult<SystemModel>[] | ||
) => { | ||
// Handle filter change | ||
for (const k in filters) { | ||
if ( | ||
k === "dataset.split" && | ||
filters[k]?.toString() !== filterValue.split | ||
) { | ||
onFilterChange({ split: filters[k]?.toString() }); | ||
} else if (k === "task" && filters[k]?.toString() !== filterValue.task) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now the filters only allow one filtered value, so In PR #528 , I try to separate the |
||
onFilterChange({ task: filters[k]?.toString() }); | ||
} | ||
} | ||
// Handle sorter change | ||
// Only one sorted column allowed now | ||
if (!(sorter instanceof Array)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It took me a while to understand how sort columns and directions are determined. I think something like this would be easier to read:
I put the default sorter before the more complex sorter logic. The if statement also handles null checks so we don't have to do it in the else block. I think you added |
||
const sortFieldName = | ||
sorter.column === undefined | ||
? undefined | ||
: sorter.column.title === "Created At" | ||
? "created_at" | ||
: sorter.column.title?.toString(); | ||
if ( | ||
sortFieldName !== undefined && | ||
(sortFieldName !== filterValue.sortField || | ||
!sorter.order?.toString().startsWith(filterValue.sortDir)) | ||
) { | ||
onFilterChange({ | ||
sortField: sortFieldName, | ||
sortDir: sorter.order?.toString() === "descend" ? "desc" : "asc", | ||
}); | ||
} else if ( | ||
sortFieldName === undefined && | ||
filterValue.sortField !== "created_at" | ||
) { | ||
// Default sorted column | ||
onFilterChange({ sortField: "created_at", sortDir: "desc" }); | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Table | ||
|
@@ -237,6 +308,7 @@ export function SystemTableContent({ | |
onChange: (newPage, newPageSize) => | ||
onPageChange(newPage - 1, newPageSize), | ||
}} | ||
onChange={handleTableChange} | ||
sticky={false} | ||
loading={loading} | ||
scroll={{ x: 100 }} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,13 +10,8 @@ import { | |
Popconfirm, | ||
Radio, | ||
} from "antd"; | ||
import { TaskSelect } from ".."; | ||
import { TaskCategory } from "../../clients/openapi"; | ||
import { | ||
ArrowDownOutlined, | ||
ArrowUpOutlined, | ||
WarningOutlined, | ||
} from "@ant-design/icons"; | ||
import { WarningOutlined } from "@ant-design/icons"; | ||
import { SystemModel } from "../../models"; | ||
import { LoginState, useUser } from "../useUser"; | ||
import { backendClient, parseBackendError } from "../../clients"; | ||
|
@@ -195,6 +190,24 @@ export function SystemTableTools({ | |
); | ||
} | ||
|
||
const handleSearch = (query: string) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this sends out a request even if the query is incorrect. Also, it sends requests for each character when the user is entering the first part of the query (e.g. "dataset:"). This will increase the load of the backend and it also makes the frontend less responsive because the requests are in sequence. |
||
let systemQuery = undefined; | ||
let datasetQuery = undefined; | ||
const segments = query.split(";"); | ||
segments.forEach((seg) => { | ||
const components = seg.split(":"); | ||
const property = components[0].trim(); | ||
if (property === "system" && components.length > 1) { | ||
systemQuery = | ||
components[1]?.trim().length > 0 ? components[1].trim() : undefined; | ||
} else if (property === "dataset" && components.length > 0) { | ||
datasetQuery = | ||
components[1]?.trim().length > 0 ? components[1].trim() : undefined; | ||
} | ||
}); | ||
onChange({ name: systemQuery, dataset: datasetQuery }); | ||
}; | ||
|
||
return ( | ||
<div style={{ width: "100%" }}> | ||
<Space style={{ width: "fit-content", float: "left" }}> | ||
|
@@ -205,55 +218,13 @@ export function SystemTableTools({ | |
</Space> | ||
<Space style={{ width: "fit-content", float: "right" }}> | ||
{mineVsAllSystemsToggle} | ||
<Select | ||
options={["test", "validation", "train", "all"].map((opt) => ({ | ||
value: opt, | ||
label: opt, | ||
}))} | ||
value={value.split || undefined} | ||
placeholder="Dataset split" | ||
onChange={(value) => onChange({ split: value })} | ||
style={{ minWidth: "120px" }} | ||
/> | ||
<TaskSelect | ||
taskCategories={taskCategories} | ||
allowClear | ||
value={value.task || undefined} | ||
onChange={(value) => onChange({ task: value || "" })} | ||
placeholder="All Tasks" | ||
style={{ minWidth: "150px" }} | ||
/> | ||
<div style={{ display: "flex", flexDirection: "row" }}> | ||
<Select | ||
options={[ | ||
...metricOptions.map((opt) => ({ value: opt, label: opt })), | ||
{ value: "created_at", label: "Created At" }, | ||
]} | ||
value={value.sortField} | ||
onChange={(value) => onChange({ sortField: value })} | ||
style={{ minWidth: "120px" }} | ||
<Tooltip title="Search by system name and/or dataset name. Query format is 'system: target name; dataset: target name'."> | ||
<Input.Search | ||
placeholder="Search by system/dataset name" | ||
onChange={(e) => handleSearch(e.target.value)} | ||
style={{ minWidth: "300px" }} | ||
/> | ||
<Tooltip title="Click to change sort direction"> | ||
<Button | ||
icon={ | ||
value.sortDir === "asc" ? ( | ||
<ArrowUpOutlined /> | ||
) : ( | ||
<ArrowDownOutlined /> | ||
) | ||
} | ||
onClick={() => | ||
onChange({ sortDir: value.sortDir === "asc" ? "desc" : "asc" }) | ||
} | ||
/> | ||
</Tooltip> | ||
</div> | ||
|
||
<Input.Search | ||
placeholder="Search by system name" | ||
value={value.name} | ||
onChange={(e) => onChange({ name: e.target.value })} | ||
/> | ||
</Tooltip> | ||
|
||
<Select | ||
mode="tags" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these would require scanning the entire database which may not scale. Could you please confirm?