提交 56893f84 编写于 作者: K klakhov

added analytics page, empty quality page

上级 c1614ceb
......@@ -723,29 +723,28 @@ export class Task extends Session {
if (Array.isArray(initialData.jobs)) {
for (const job of initialData.jobs) {
if (job.type === JobType.NORMAL) {
const jobInstance = new Job({
url: job.url,
id: job.id,
assignee: job.assignee,
state: job.state,
stage: job.stage,
start_frame: job.start_frame,
stop_frame: job.stop_frame,
// following fields also returned when doing API request /jobs/<id>
// here we know them from task and append to constructor
task_id: data.id,
project_id: data.project_id,
labels: data.labels,
bug_tracker: data.bug_tracker,
mode: data.mode,
dimension: data.dimension,
data_compressed_chunk_type: data.data_compressed_chunk_type,
data_chunk_size: data.data_chunk_size,
});
data.jobs.push(jobInstance);
}
const jobInstance = new Job({
url: job.url,
id: job.id,
assignee: job.assignee,
state: job.state,
stage: job.stage,
type: job.type,
start_frame: job.start_frame,
stop_frame: job.stop_frame,
// following fields also returned when doing API request /jobs/<id>
// here we know them from task and append to constructor
task_id: data.id,
project_id: data.project_id,
labels: data.labels,
bug_tracker: data.bug_tracker,
mode: data.mode,
dimension: data.dimension,
data_compressed_chunk_type: data.data_compressed_chunk_type,
data_chunk_size: data.data_chunk_size,
});
data.jobs.push(jobInstance);
}
}
......
......@@ -35,6 +35,7 @@ export enum Actions {
MOVE_TASK_TO_PROJECT = 'move_task_to_project',
OPEN_BUG_TRACKER = 'open_bug_tracker',
BACKUP_TASK = 'backup_task',
VIEW_ANALYTICS = 'view_analytics',
}
function ActionsMenuComponent(props: Props): JSX.Element {
......@@ -107,10 +108,18 @@ function ActionsMenuComponent(props: Props): JSX.Element {
</Menu.Item>
), 40]);
menuItems.push([(
<Menu.Item
key={Actions.VIEW_ANALYTICS}
>
View analytics
</Menu.Item>
), 50]);
if (projectID === null) {
menuItems.push([(
<Menu.Item key={Actions.MOVE_TASK_TO_PROJECT}>Move to project</Menu.Item>
), 50]);
), 60]);
}
menuItems.push([(
......@@ -118,7 +127,7 @@ function ActionsMenuComponent(props: Props): JSX.Element {
<Menu.Divider />
<Menu.Item key={Actions.DELETE_TASK}>Delete</Menu.Item>
</React.Fragment>
), 60]);
), 70]);
menuItems.push(
...plugins.map(({ component: Component, weight }, index) => {
......
......@@ -74,6 +74,7 @@ import EmailVerificationSentPage from './email-confirmation-pages/email-verifica
import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation';
import CreateModelPage from './create-model-page/create-model-page';
import CreateJobPage from './create-job-page/create-job-page';
import TaskAnalyticsPage from './task-analytics-page/task-analytics-page';
interface CVATAppProps {
loadFormats: () => void;
......@@ -461,6 +462,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Route exact path='/tasks' component={TasksPageContainer} />
<Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageComponent} />
<Route exact path='/tasks/:id/analytics' component={TaskAnalyticsPage} />
<Route exact path='/tasks/:id/jobs/create' component={CreateJobPage} />
<Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
<Route exact path='/jobs' component={JobsPageComponent} />
......
// Copyright (C) 2023 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { Link } from 'react-router-dom';
import Text from 'antd/lib/typography/Text';
import Empty from 'antd/lib/empty';
import { Col, Row } from 'antd/lib/grid';
interface Props {
taskId: number,
}
function EmptyQualityComponent(props: Props): JSX.Element {
const { taskId } = props;
return (
<div className='cvat-task-quality-page cvat-task-quality-page-empty'>
<Empty description={(
<>
<Row justify='center' align='middle'>
<Col>
<Text strong>No Ground truth job created yet ...</Text>
</Col>
</Row>
<Row justify='center' align='middle'>
<Col>
<Text type='secondary'>To start viewing quality data</Text>
</Col>
</Row>
<Row justify='center' align='middle'>
<Col>
<Link to={`/tasks/${taskId}/jobs/create`}>create a new one</Link>
</Col>
</Row>
</>
)}
/>
</div>
);
}
export default React.memo(EmptyQualityComponent);
// Copyright (C) 2023 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { Job, JobType, Task } from 'cvat-core-wrapper';
import EmptyQuality from './empty-quality';
interface Props {
task: Task,
}
function TaskQualityComponent(props: Props): JSX.Element {
const { task } = props;
const hasGTJob = task.jobs.some((job: Job) => job.type === JobType.GROUND_TRUTH);
if (!hasGTJob) {
return (<EmptyQuality taskId={task.id} />);
}
return (
<div className='cvat-task-quality-page'>hello</div>
);
}
export default React.memo(TaskQualityComponent);
// Copyright (C) 2023 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
@import '../../base.scss';
.cvat-analytics-inner {
background: $background-color-1;
min-height: $grid-unit-size * 95;
padding: $grid-unit-size * 2;
.ant-tabs {
height: 100%;
}
}
.cvat-task-quality-page-empty {
display: flex;
justify-content: center;
align-items: center;
height: $grid-unit-size * 68;
}
// Copyright (C) 2023 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useEffect, useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Tabs from 'antd/lib/tabs';
import { useHistory, useParams } from 'react-router';
import Text from 'antd/lib/typography/Text';
import Spin from 'antd/lib/spin';
import Button from 'antd/lib/button';
import { Task } from 'reducers';
import { notification } from 'antd';
import { getCore } from 'cvat-core-wrapper';
import { LeftOutlined } from '@ant-design/icons/lib/icons';
import TaskQualityComponent from './quality/task-quality-component';
const core = getCore();
function TaskAnalyticsPage(): JSX.Element {
const [fetchingTask, setFetchingTask] = useState(true);
const [taskInstance, setTaskInstance] = useState<Task | null>(null);
const history = useHistory();
const id = +useParams<{ id: string }>().id;
useEffect((): void => {
if (Number.isInteger(id)) {
core.tasks.get({ id })
.then(([task]: Task[]) => {
if (task) {
setTaskInstance(task);
}
}).catch((error: Error) => {
notification.error({
message: 'Could not fetch requested task from the server',
description: error.toString(),
});
}).finally(() => {
setFetchingTask(false);
});
} else {
notification.error({
message: 'Could not receive the requested task from the server',
description: `Requested task id "${id}" is not valid`,
});
setFetchingTask(false);
}
}, []);
return (
<div className='cvat-task-analytics-page'>
{
fetchingTask ? (
<div className='cvat-create-job-loding'>
<Spin size='large' className='cvat-spinner' />
</div>
) : (
<Row
justify='center'
align='top'
className='cvat-analytics-wrapper'
>
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-task-top-bar'>
<Button
className='cvat-back-to-task-button'
onClick={() => history.push(`/tasks/${taskInstance.id}`)}
type='link'
size='large'
>
<LeftOutlined />
Back to task
</Button>
</Col>
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-analytics-inner'>
<Tabs type='card'>
<Tabs.TabPane
tab={(
<span>
<Text>Quality</Text>
</span>
)}
key='quality'
>
<TaskQualityComponent task={taskInstance} />
</Tabs.TabPane>
</Tabs>
</Col>
</Row>
)
}
</div>
);
}
export default React.memo(TaskAnalyticsPage);
......@@ -15,7 +15,7 @@ import Text from 'antd/lib/typography/Text';
import moment from 'moment';
import copy from 'copy-to-clipboard';
import { Task, Job } from 'cvat-core-wrapper';
import { Task, Job, JobType } from 'cvat-core-wrapper';
import { JobStage } from 'reducers';
import CVATTooltip from 'components/common/cvat-tooltip';
import UserSelector, { User } from './user-selector';
......@@ -89,7 +89,9 @@ function JobListComponent(props: Props): JSX.Element {
} = props;
const history = useHistory();
const { jobs, id: taskId } = taskInstance;
const { id: taskId } = taskInstance;
let { jobs } = taskInstance;
jobs = jobs.filter((job: Job) => job.type === JobType.NORMAL);
function sorter(path: string) {
return (obj1: any, obj2: any): number => {
......
......@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router';
import { Row, Col } from 'antd/lib/grid';
import { LeftOutlined, MoreOutlined } from '@ant-design/icons';
......@@ -21,6 +21,10 @@ export default function DetailsComponent(props: DetailsComponentProps): JSX.Elem
const history = useHistory();
const onViewAnalytics = useCallback(() => {
history.push(`/tasks/${taskInstance.id}/analytics`);
}, [history]);
return (
<Row className='cvat-task-top-bar' justify='space-between' align='middle'>
<Col>
......@@ -47,7 +51,13 @@ export default function DetailsComponent(props: DetailsComponentProps): JSX.Elem
)}
</Col>
<Col>
<Dropdown overlay={<ActionsMenuContainer taskInstance={taskInstance} />}>
<Dropdown overlay={(
<ActionsMenuContainer
taskInstance={taskInstance}
onViewAnalytics={onViewAnalytics}
/>
)}
>
<Button size='middle' className='cvat-task-page-actions-button'>
<Text className='cvat-text-color'>Actions</Text>
<MoreOutlined className='cvat-menu-icon' />
......
......@@ -144,6 +144,10 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
const { taskInstance, history } = this.props;
const { id } = taskInstance;
const onViewAnalytics = (): void => {
history.push(`/tasks/${taskInstance.id}/analytics`);
};
return (
<Col span={4}>
<Row justify='end'>
......@@ -164,7 +168,13 @@ class TaskItemComponent extends React.PureComponent<TaskItemProps & RouteCompone
</Col>
</Row>
<Row justify='end'>
<Dropdown overlay={<ActionsMenuContainer taskInstance={taskInstance} />}>
<Dropdown overlay={(
<ActionsMenuContainer
taskInstance={taskInstance}
onViewAnalytics={onViewAnalytics}
/>
)}
>
<Col className='cvat-item-open-task-actions'>
<Text className='cvat-text-color'>Actions</Text>
<MoreOutlined className='cvat-menu-icon' />
......
......@@ -20,6 +20,7 @@ import { importActions } from 'actions/import-actions';
interface OwnProps {
taskInstance: any;
onViewAnalytics: () => void;
}
interface StateToProps {
......@@ -87,8 +88,8 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps):
deleteTask,
openRunModelWindow,
openMoveTaskToProjectWindow,
onViewAnalytics,
} = props;
const onClickMenu = (params: MenuInfo): void | JSX.Element => {
const [action] = params.keyPath;
if (action === Actions.EXPORT_TASK_DATASET) {
......@@ -105,6 +106,8 @@ function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps):
openMoveTaskToProjectWindow(taskInstance.id);
} else if (action === Actions.LOAD_TASK_ANNO) {
showImportModal(taskInstance);
} else if (action === Actions.VIEW_ANALYTICS) {
onViewAnalytics();
}
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册