You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
6.9 KiB
TypeScript
150 lines
6.9 KiB
TypeScript
// Copyright (C) 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { AnyAction } from 'redux';
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Title from 'antd/lib/typography/Title';
|
|
import Modal from 'antd/lib/modal';
|
|
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
|
|
import RadioButton from 'antd/lib/radio/radioButton';
|
|
import Description from 'antd/lib/descriptions';
|
|
import Rate from 'antd/lib/rate';
|
|
import { Row, Col } from 'antd/lib/grid';
|
|
|
|
import UserSelector, { User } from 'components/task-page/user-selector';
|
|
import { CombinedState, ReviewStatus } from 'reducers/interfaces';
|
|
import { switchSubmitReviewDialog } from 'actions/annotation-actions';
|
|
import { submitReviewAsync } from 'actions/review-actions';
|
|
import { clamp } from 'utils/math';
|
|
import { useHistory } from 'react-router';
|
|
|
|
function computeEstimatedQuality(reviewedStates: number, openedIssues: number): number {
|
|
if (reviewedStates === 0 && openedIssues === 0) {
|
|
return 5; // corner case
|
|
}
|
|
|
|
const K = 2; // means how many reviewed states are equivalent to one issue
|
|
const quality = reviewedStates / (reviewedStates + K * openedIssues);
|
|
return clamp(+(5 * quality).toPrecision(2), 0, 5);
|
|
}
|
|
|
|
export default function SubmitReviewModal(): JSX.Element | null {
|
|
const dispatch = useDispatch();
|
|
const history = useHistory();
|
|
const isVisible = useSelector((state: CombinedState): boolean => state.annotation.submitReviewDialogVisible);
|
|
const job = useSelector((state: CombinedState): any => state.annotation.job.instance);
|
|
const activeReview = useSelector((state: CombinedState): any => state.review.activeReview);
|
|
const reviewIsBeingSubmitted = useSelector((state: CombinedState): any => state.review.fetching.reviewId);
|
|
const numberOfIssues = useSelector((state: CombinedState): any => state.review.issues.length);
|
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
|
const numberOfNewIssues = activeReview ? activeReview.issues.length : 0;
|
|
const reviewedFrames = activeReview ? activeReview.reviewedFrames.length : 0;
|
|
const reviewedStates = activeReview ? activeReview.reviewedStates.length : 0;
|
|
|
|
const [reviewer, setReviewer] = useState<User | null>(job.reviewer ? job.reviewer : null);
|
|
const [reviewStatus, setReviewStatus] = useState<string>(ReviewStatus.ACCEPTED);
|
|
const [estimatedQuality, setEstimatedQuality] = useState<number>(0);
|
|
|
|
const close = (): AnyAction => dispatch(switchSubmitReviewDialog(false));
|
|
const submitReview = (): void => {
|
|
activeReview.estimatedQuality = estimatedQuality;
|
|
activeReview.status = reviewStatus;
|
|
if (reviewStatus === ReviewStatus.REVIEW_FURTHER) {
|
|
activeReview.reviewer = reviewer;
|
|
}
|
|
dispatch(submitReviewAsync(activeReview));
|
|
};
|
|
|
|
useEffect(() => {
|
|
setEstimatedQuality(computeEstimatedQuality(reviewedStates, numberOfNewIssues));
|
|
}, [reviewedStates, numberOfNewIssues]);
|
|
useEffect(() => {
|
|
if (!isSubmitting && activeReview && activeReview.id === reviewIsBeingSubmitted) {
|
|
setIsSubmitting(true);
|
|
} else if (isSubmitting && reviewIsBeingSubmitted === null) {
|
|
setIsSubmitting(false);
|
|
close();
|
|
history.push(`/tasks/${job.task.id}`);
|
|
}
|
|
}, [reviewIsBeingSubmitted, activeReview]);
|
|
|
|
if (!isVisible) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
className='cvat-submit-review-dialog'
|
|
visible={isVisible}
|
|
destroyOnClose
|
|
confirmLoading={isSubmitting}
|
|
onOk={submitReview}
|
|
onCancel={close}
|
|
okText='Submit'
|
|
width={650}
|
|
>
|
|
<Row justify='start'>
|
|
<Col>
|
|
<Title level={4}>Submitting your review</Title>
|
|
</Col>
|
|
</Row>
|
|
<Row justify='start'>
|
|
<Col span={12}>
|
|
<Description title='Review summary' layout='horizontal' column={1} size='small' bordered>
|
|
<Description.Item label='Estimated quality: '>{estimatedQuality}</Description.Item>
|
|
<Description.Item label='Issues: '>
|
|
<Text>{numberOfIssues}</Text>
|
|
{!!numberOfNewIssues && <Text strong>{` (+${numberOfNewIssues})`}</Text>}
|
|
</Description.Item>
|
|
<Description.Item label='Reviewed frames '>{reviewedFrames}</Description.Item>
|
|
<Description.Item label='Reviewed objects: '>{reviewedStates}</Description.Item>
|
|
</Description>
|
|
</Col>
|
|
<Col span={11} offset={1}>
|
|
<Row>
|
|
<Col>
|
|
<Radio.Group
|
|
value={reviewStatus}
|
|
onChange={(event: RadioChangeEvent) => {
|
|
if (typeof event.target.value !== 'undefined') {
|
|
setReviewStatus(event.target.value);
|
|
}
|
|
}}
|
|
>
|
|
<RadioButton value={ReviewStatus.ACCEPTED}>Accept</RadioButton>
|
|
<RadioButton value={ReviewStatus.REVIEW_FURTHER}>Review next</RadioButton>
|
|
<RadioButton value={ReviewStatus.REJECTED}>Reject</RadioButton>
|
|
</Radio.Group>
|
|
{reviewStatus === ReviewStatus.REVIEW_FURTHER && (
|
|
<Row align='middle' justify='start'>
|
|
<Col>
|
|
<Text type='secondary'>Reviewer: </Text>
|
|
</Col>
|
|
<Col offset={1}>
|
|
<UserSelector value={reviewer} onSelect={setReviewer} />
|
|
</Col>
|
|
</Row>
|
|
)}
|
|
<Row justify='center' align='middle'>
|
|
<Col>
|
|
<Rate
|
|
value={Math.round(estimatedQuality)}
|
|
onChange={(value: number | undefined) => {
|
|
if (typeof value !== 'undefined') {
|
|
setEstimatedQuality(value);
|
|
}
|
|
}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
</Row>
|
|
</Modal>
|
|
);
|
|
}
|