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

// 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>
);
}