// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT import React, { useEffect, useState } from 'react'; import { RouteComponentProps } from 'react-router'; import { withRouter } from 'react-router-dom'; import { Row, Col } from 'antd/lib/grid'; import { LoadingOutlined, QuestionCircleOutlined, CopyOutlined } from '@ant-design/icons'; import { ColumnFilterItem } from 'antd/lib/table/interface'; import Table from 'antd/lib/table'; import Button from 'antd/lib/button'; import Text from 'antd/lib/typography/Text'; import moment from 'moment'; import copy from 'copy-to-clipboard'; import CVATTooltip from 'components/common/cvat-tooltip'; import UserSelector, { User } from './user-selector'; interface Props { taskInstance: any; onJobUpdate(jobInstance: any): void; } function ReviewSummaryComponent({ jobInstance }: { jobInstance: any }): JSX.Element { const [summary, setSummary] = useState | null>(null); const [error, setError] = useState(null); useEffect(() => { setError(null); jobInstance .reviewsSummary() .then((_summary: Record) => { setSummary(_summary); }) .catch((_error: any) => { // eslint-disable-next-line console.log(_error); setError(_error); }); }, []); if (!summary) { if (error) { if (error.toString().includes('403')) { return

You do not have permissions

; } return

Could not fetch, check console output

; } return ( <>

Loading..

); } return (
Reviews {summary.reviews}
Average quality {Number.parseFloat(summary.average_estimated_quality).toFixed(2)}
Unsolved issues {summary.issues_unsolved}
Resolved issues {summary.issues_resolved}
); } function JobListComponent(props: Props & RouteComponentProps): JSX.Element { const { taskInstance, onJobUpdate, history: { push }, } = props; const { jobs, id: taskId } = taskInstance; function sorter(path: string) { return (obj1: any, obj2: any): number => { let currentObj1 = obj1; let currentObj2 = obj2; let field1: string | null = null; let field2: string | null = null; for (const pathSegment of path.split('.')) { field1 = currentObj1 && pathSegment in currentObj1 ? currentObj1[pathSegment] : null; field2 = currentObj2 && pathSegment in currentObj2 ? currentObj2[pathSegment] : null; currentObj1 = currentObj1 && pathSegment in currentObj1 ? currentObj1[pathSegment] : null; currentObj2 = currentObj2 && pathSegment in currentObj2 ? currentObj2[pathSegment] : null; } if (field1 && field2) { return field1.localeCompare(field2); } if (field1 === null) { return 1; } return -1; }; } function collectUsers(path: string): ColumnFilterItem[] { return Array.from( new Set( jobs.map((job: any) => { if (job[path] === null) { return null; } return job[path].username; }), ), ).map((value: string | null) => ({ text: value || 'Is Empty', value: value || false })); } const columns = [ { title: 'Job', dataIndex: 'job', key: 'job', render: (id: number): JSX.Element => (
), }, { title: 'Frames', dataIndex: 'frames', key: 'frames', className: 'cvat-text-color cvat-job-item-frames', }, { title: 'Status', dataIndex: 'status', key: 'status', className: 'cvat-job-item-status', render: (jobInstance: any): JSX.Element => { const { status } = jobInstance; let progressColor = null; if (status === 'completed') { progressColor = 'cvat-job-completed-color'; } else if (status === 'validation') { progressColor = 'cvat-job-validation-color'; } else { progressColor = 'cvat-job-annotation-color'; } return ( {status} }> ); }, sorter: sorter('status.status'), filters: [ { text: 'annotation', value: 'annotation' }, { text: 'validation', value: 'validation' }, { text: 'completed', value: 'completed' }, ], onFilter: (value: string | number | boolean, record: any) => record.status.status === value, }, { title: 'Started on', dataIndex: 'started', key: 'started', className: 'cvat-text-color', }, { title: 'Duration', dataIndex: 'duration', key: 'duration', className: 'cvat-text-color', }, { title: 'Assignee', dataIndex: 'assignee', key: 'assignee', className: 'cvat-job-item-assignee', render: (jobInstance: any): JSX.Element => ( { // eslint-disable-next-line jobInstance.assignee = value; onJobUpdate(jobInstance); }} /> ), sorter: sorter('assignee.assignee.username'), filters: collectUsers('assignee'), onFilter: (value: string | number | boolean, record: any) => (record.assignee.assignee?.username || false) === value, }, { title: 'Reviewer', dataIndex: 'reviewer', key: 'reviewer', className: 'cvat-job-item-reviewer', render: (jobInstance: any): JSX.Element => ( { // eslint-disable-next-line jobInstance.reviewer = value; onJobUpdate(jobInstance); }} /> ), sorter: sorter('reviewer.reviewer.username'), filters: collectUsers('reviewer'), onFilter: (value: string | number | boolean, record: any) => (record.reviewer.reviewer?.username || false) === value, }, ]; let completed = 0; const data = jobs.reduce((acc: any[], job: any) => { if (job.status === 'completed') { completed++; } const created = moment(props.taskInstance.createdDate); const now = moment(moment.now()); acc.push({ key: job.id, job: job.id, frames: `${job.startFrame}-${job.stopFrame}`, status: job, started: `${created.format('MMMM Do YYYY HH:MM')}`, duration: `${moment.duration(now.diff(created)).humanize()}`, assignee: job, reviewer: job, }); return acc; }, []); return (
Jobs {`${completed} of ${data.length} jobs`} 'cvat-task-jobs-table-row'} columns={columns} dataSource={data} size='small' /> ); } export default withRouter(JobListComponent);