// Copyright (C) 2020-2022 Intel Corporation // Copyright (C) 2022-2023 CVAT.ai Corporation // // SPDX-License-Identifier: MIT import React from 'react'; import { RouteComponentProps } from 'react-router'; import { withRouter } from 'react-router-dom'; import Text from 'antd/lib/typography/Text'; import { Row, Col } from 'antd/lib/grid'; import Button from 'antd/lib/button'; import { LoadingOutlined, MoreOutlined } from '@ant-design/icons'; import Dropdown from 'antd/lib/dropdown'; import Progress from 'antd/lib/progress'; import Badge from 'antd/lib/badge'; import moment from 'moment'; import { Task, RQStatus } from 'cvat-core-wrapper'; import ActionsMenuContainer from 'containers/actions-menu/actions-menu'; import Preview from 'components/common/preview'; import { ActiveInference, PluginComponent } from 'reducers'; import AutomaticAnnotationProgress from './automatic-annotation-progress'; export interface TaskItemProps { taskInstance: any; deleted: boolean; activeInference: ActiveInference | null; ribbonPlugins: PluginComponent[]; cancelAutoAnnotation(): void; updateTaskInState(task: Task): void; } interface State { importingState: { state: RQStatus | null; message: string; progress: number; } | null; } class TaskItemComponent extends React.PureComponent { #isUnmounted: boolean; constructor(props: TaskItemProps & RouteComponentProps) { super(props); const { taskInstance } = props; this.#isUnmounted = false; this.state = { importingState: taskInstance.size > 0 ? null : { state: null, message: 'Request current progress', progress: 0, }, }; } public componentDidMount(): void { const { taskInstance, updateTaskInState } = this.props; const { importingState } = this.state; if (importingState !== null) { taskInstance.listenToCreate((state: RQStatus, progress: number, message: string) => { if (!this.#isUnmounted) { this.setState({ importingState: { message, progress: Math.floor(progress * 100), state, }, }); } }).then((createdTask: Task) => { if (!this.#isUnmounted) { this.setState({ importingState: null }); setTimeout(() => { const { taskInstance: currentTaskInstance } = this.props; if (currentTaskInstance.size !== createdTask.size) { // update state only if it was not updated anywhere else // for example in createTaskAsync updateTaskInState(createdTask); } }, 1000); } }); } } public componentWillUnmount(): void { this.#isUnmounted = true; } private renderPreview(): JSX.Element { const { taskInstance } = this.props; return ( ); } private renderDescription(): JSX.Element { // Task info const { taskInstance } = this.props; const { id } = taskInstance; const owner = taskInstance.owner ? taskInstance.owner.username : null; const updated = moment(taskInstance.updatedDate).fromNow(); const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); // Get and truncate a task name const name = `${taskInstance.name.substring(0, 70)}${taskInstance.name.length > 70 ? '...' : ''}`; return ( {`#${id}: `} {name}
{owner && ( <> {`Created ${owner ? `by ${owner}` : ''} on ${created}`}
)} {`Last updated ${updated}`} ); } private renderProgress(): JSX.Element { const { taskInstance, activeInference, cancelAutoAnnotation } = this.props; const { importingState } = this.state; if (importingState) { let textType: 'success' | 'danger' = 'success'; if (!!importingState.state && [RQStatus.FAILED, RQStatus.UNKNOWN].includes(importingState.state)) { textType = 'danger'; } return (
{`\u2022 ${importingState.message || importingState.state}`} { !!importingState.state && [RQStatus.QUEUED, RQStatus.STARTED] .includes(importingState.state) && }
); } // Count number of jobs and performed jobs const numOfJobs = taskInstance.progress.totalJobs; const numOfCompleted = taskInstance.progress.completedJobs; const numOfValidation = taskInstance.progress.validationJobs; const numOfAnnotation = taskInstance.progress.annotationJobs; // Progress appearance depends on number of jobs const jobsProgress = ((numOfCompleted + numOfValidation) * 100) / numOfJobs; return (
{ numOfCompleted > 0 && ( {`\u2022 ${numOfCompleted} done `} )} { numOfValidation > 0 && ( {`\u2022 ${numOfValidation} on review `} )} { numOfAnnotation > 0 && ( {`\u2022 ${numOfAnnotation} annotating `} )} {`\u2022 ${numOfJobs} total`}
); } private renderNavigation(): JSX.Element { const { importingState } = this.state; const { taskInstance, history } = this.props; const { id } = taskInstance; const onViewAnalytics = (): void => { history.push(`/tasks/${taskInstance.id}/analytics`); }; return ( )} > Actions ); } public render(): JSX.Element { const { deleted, ribbonPlugins } = this.props; const style = {}; if (deleted) { (style as any).pointerEvents = 'none'; (style as any).opacity = 0.5; } const ribbonItems = ribbonPlugins .filter((plugin) => plugin.data.shouldBeRendered(this.props, this.state)) .map((plugin) => ({ component: plugin.component, weight: plugin.data.weight })); return ( {ribbonItems.sort((item1, item2) => item1.weight - item2.weight) .map((item) => item.component).map((Component, index) => ( ))} )} > {this.renderPreview()} {this.renderDescription()} {this.renderProgress()} {this.renderNavigation()} ); } } export default withRouter(TaskItemComponent);