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

Refactor Antd Table columns #7772

Merged
merged 13 commits into from
May 14, 2024
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Toasts are shown until WEBKNOSSOS is running in the active browser tab again. Also, the content of most toasts that show errors or warnings is printed to the browser's console. [#7741](https://github.com/scalableminds/webknossos/pull/7741)
- Improved UI speed when editing the description of an annotation. [#7769](https://github.com/scalableminds/webknossos/pull/7769)
- Updated dataset animations to use the new meshing API. Animitation now support ad-hoc meshes and mappings. [#7692](https://github.com/scalableminds/webknossos/pull/7692)
- Slightly refactored the `<FixExpandleTable/>`component to use columns as props. [#7772](https://github.com/scalableminds/webknossos/pull/7772)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say that the changelog entry is not necessary as these changes are not user facing. But feel free to keep this :)



### Fixed
Expand Down
344 changes: 174 additions & 170 deletions frontend/javascripts/admin/task/task_list_view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link } from "react-router-dom";
import { PropTypes } from "@scalableminds/prop-types";
import { Table, Tag, Spin, Button, Input, Modal, Card, Alert, App } from "antd";
import { Tag, Spin, Button, Input, Modal, Card, Alert, App, TableProps } from "antd";
import {
CheckCircleOutlined,
ClockCircleOutlined,
Expand Down Expand Up @@ -39,7 +39,6 @@ import messages from "messages";
import FixedExpandableTable from "components/fixed_expandable_table";
import UserSelectionComponent from "admin/user/user_selection_component";

const { Column } = Table;
const { Search, TextArea } = Input;

type Props = {
Expand Down Expand Up @@ -237,6 +236,176 @@ function TaskListView({ initialFieldValues }: Props) {
const marginRight = {
marginRight: 20,
};

const columns: TableProps["columns"] = [
{
title: "ID",
dataIndex: "id",
key: "id",
sorter: Utils.localeCompareBy<APITask>((task) => task.id),
className: "monospace-id",
width: 100,
},
{
title: "Project",
dataIndex: "projectName",
key: "projectName",
width: 130,
sorter: Utils.localeCompareBy<APITask>((task) => task.projectName),
render: (projectName: string) => <a href={`/projects#${projectName}`}>{projectName}</a>,
},
{
title: "Type",
dataIndex: "type",
key: "type",
width: 200,
sorter: Utils.localeCompareBy<APITask>((task) => task.type.summary),
render: (taskType: APITaskType) => (
<a href={`/taskTypes#${taskType.id}`}>{taskType.summary}</a>
),
},
{
title: "Dataset",
dataIndex: "dataSet",
key: "dataSet",
sorter: Utils.localeCompareBy<APITask>((task) => task.dataSet),
},
{
title: "Stats",
dataIndex: "status",
key: "status",
render: (status: APITask["status"], task: APITask) => (
<div className="nowrap">
<span title="Pending Instances">
<PlayCircleOutlined className="icon-margin-right" />
{status.pending}
</span>
<br />
<span title="Active Instances">
<ForkOutlined className="icon-margin-right" />
{status.active}
</span>
<br />
<span title="Finished Instances">
<CheckCircleOutlined className="icon-margin-right" />
{status.finished}
</span>
<br />
<span title="Annotation Time">
<ClockCircleOutlined className="icon-margin-right" />
{formatSeconds((task.tracingTime || 0) / 1000)}
</span>
</div>
),
filters: [
{
text: "Has Pending Instances",
value: "pending",
},
{
text: "Has Active Instances",
value: "active",
},
{
text: "Has Finished Instances",
value: "finished",
},
],
onFilter: (key: any, task: APITask) => task.status[key as unknown as keyof TaskStatus] > 0,
},
{
title: "Edit Position / Bounding Box",
dataIndex: "editPosition",
key: "editPosition",
width: 150,
render: (_, task: APITask) => (
<div className="nowrap">
{formatTuple(task.editPosition)} <br />
<span>{formatTuple(task.boundingBoxVec6)}</span>
</div>
),
},
{
title: "Experience",
dataIndex: "neededExperience",
key: "neededExperience",
sorter: Utils.localeCompareBy<APITask>((task) => task.neededExperience.domain),
width: 250,
render: (neededExperience: APITask["neededExperience"]) =>
neededExperience.domain !== "" || neededExperience.value > 0 ? (
<Tag>
{neededExperience.domain} : {neededExperience.value}
</Tag>
) : null,
},
{
title: "Creation Date",
dataIndex: "created",
key: "created",
width: 200,
sorter: Utils.compareBy<APITask>((task) => task.created),
render: (created: APITask["created"]) => <FormattedDate timestamp={created} />,
defaultSortOrder: "descend",
},
{
title: "Action",
key: "actions",
width: 170,
fixed: "right",
render: (_unused, task: APITask) => (
<>
{task.status.finished > 0 ? (
<div>
<a
href={`/annotations/CompoundTask/${task.id}`}
title="View all Finished Annotations"
>
<EyeOutlined className="icon-margin-right" />
View
</a>
</div>
) : null}
<div>
<a href={`/tasks/${task.id}/edit`} title="Edit Task">
,
<EditOutlined className="icon-margin-right" />
Edit
</a>
</div>
{task.status.pending > 0 ? (
<div>
<LinkButton onClick={_.partial(assignTaskToUser, task)}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Here is an example that causes the rendering to crash as the current code shadows the lodash variable

<UserAddOutlined className="icon-margin-right" />
Manually Assign to User
</LinkButton>
</div>
) : null}
{task.status.finished > 0 ? (
<div>
<AsyncLink
href="#"
onClick={() => {
const includesVolumeData = task.type.tracingType !== "skeleton";
return downloadAnnotationAPI(task.id, "CompoundTask", includesVolumeData);
}}
title="Download all Finished Annotations"
icon={<DownloadOutlined className="icon-margin-right" />}
>
Download
</AsyncLink>
</div>
) : null}
<div>
<LinkButton onClick={_.partial(deleteTask, task)}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Here as well btw :)

<DeleteOutlined className="icon-margin-right" />
Delete
</LinkButton>
</div>
</>
),
},
];

return (
<div className="container">
<div className="pull-right">
Expand Down Expand Up @@ -323,6 +492,7 @@ function TaskListView({ initialFieldValues }: Props) {
<FixedExpandableTable
dataSource={getFilteredTasks()}
rowKey="id"
columns={columns}
pagination={{
defaultPageSize: 50,
}}
Expand All @@ -336,174 +506,8 @@ function TaskListView({ initialFieldValues }: Props) {
locale={{
emptyText: renderPlaceholder(),
}}
>
<Column
title="ID"
dataIndex="id"
key="id"
sorter={Utils.localeCompareBy<APITask>((task) => task.id)}
className="monospace-id"
width={100}
/>
<Column
title="Project"
dataIndex="projectName"
key="projectName"
width={130}
sorter={Utils.localeCompareBy<APITask>((task) => task.projectName)}
render={(projectName: string) => <a href={`/projects#${projectName}`}>{projectName}</a>}
/>
<Column
title="Type"
dataIndex="type"
key="type"
width={200}
sorter={Utils.localeCompareBy<APITask>((task) => task.type.summary)}
render={(taskType: APITaskType) => (
<a href={`/taskTypes#${taskType.id}`}>{taskType.summary}</a>
)}
/>
<Column
title="Dataset"
dataIndex="dataSet"
key="dataSet"
sorter={Utils.localeCompareBy<APITask>((task) => task.dataSet)}
/>
<Column
title="Stats"
dataIndex="status"
key="status"
render={(status, task: APITask) => (
<div className="nowrap">
<span title="Pending Instances">
<PlayCircleOutlined className="icon-margin-right" />
{status.pending}
</span>
<br />
<span title="Active Instances">
<ForkOutlined className="icon-margin-right" />
{status.active}
</span>
<br />
<span title="Finished Instances">
<CheckCircleOutlined className="icon-margin-right" />
{status.finished}
</span>
<br />
<span title="Annotation Time">
<ClockCircleOutlined className="icon-margin-right" />
{formatSeconds((task.tracingTime || 0) / 1000)}
</span>
</div>
)}
filters={[
{
text: "Has Pending Instances",
value: "pending",
},
{
text: "Has Active Instances",
value: "active",
},
{
text: "Has Finished Instances",
value: "finished",
},
]}
onFilter={(key, task: APITask) => task.status[key as unknown as keyof TaskStatus] > 0}
/>
<Column
title="Edit Position / Bounding Box"
dataIndex="editPosition"
key="editPosition"
width={150}
render={(__, task: APITask) => (
<div className="nowrap">
{formatTuple(task.editPosition)} <br />
<span>{formatTuple(task.boundingBoxVec6)}</span>
</div>
)}
/>
<Column
title="Experience"
dataIndex="neededExperience"
key="neededExperience"
sorter={Utils.localeCompareBy<APITask>((task) => task.neededExperience.domain)}
width={250}
render={(neededExperience) =>
neededExperience.domain !== "" || neededExperience.value > 0 ? (
<Tag>
{neededExperience.domain} : {neededExperience.value}
</Tag>
) : null
}
/>
<Column
title="Creation Date"
dataIndex="created"
key="created"
width={200}
sorter={Utils.compareBy<APITask>((task) => task.created)}
render={(created) => <FormattedDate timestamp={created} />}
defaultSortOrder={"descend"}
/>
<Column
title="Action"
key="actions"
width={170}
fixed="right"
render={(__, task: APITask) => (
Copy link
Contributor

Choose a reason for hiding this comment

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

The previous code seems to have used two underscores to avoid the shadowing. Imo using _unused is more explicit and easier to spot than using two underscores to avoid the name shadowing. Therefore, the _unused approach is more explicit and therefore imo more preferable.

<>
{task.status.finished > 0 ? (
<div>
<a
href={`/annotations/CompoundTask/${task.id}`}
title="View all Finished Annotations"
>
<EyeOutlined className="icon-margin-right" />
View
</a>
</div>
) : null}
<div>
<a href={`/tasks/${task.id}/edit`} title="Edit Task">
<EditOutlined className="icon-margin-right" />
Edit
</a>
</div>
{task.status.pending > 0 ? (
<div>
<LinkButton onClick={_.partial(assignTaskToUser, task)}>
<UserAddOutlined className="icon-margin-right" />
Manually Assign to User
</LinkButton>
</div>
) : null}
{task.status.finished > 0 ? (
<div>
<AsyncLink
href="#"
onClick={() => {
const includesVolumeData = task.type.tracingType !== "skeleton";
return downloadAnnotationAPI(task.id, "CompoundTask", includesVolumeData);
}}
title="Download all Finished Annotations"
icon={<DownloadOutlined className="icon-margin-right" />}
>
Download
</AsyncLink>
</div>
) : null}
<div>
<LinkButton onClick={_.partial(deleteTask, task)}>
<DeleteOutlined className="icon-margin-right" />
Delete
</LinkButton>
</div>
</>
)}
/>
</FixedExpandableTable>
/>

{getAnonymousTaskLinkModal()}
</Spin>
</div>
Expand Down
Loading