From 7c01a2ad55f0c785b83347375e9d45b6b15f3b2c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 25 Nov 2021 00:07:35 +0300 Subject: [PATCH 1/6] Added resolved issues filter --- cvat-ui/src/actions/review-actions.ts | 4 ++++ .../review/issues-aggregator.tsx | 6 +++-- .../objects-side-bar/issues-list.tsx | 22 +++++++++++++++++-- .../annotation-page/canvas/canvas-wrapper.tsx | 10 ++++++--- cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/review-reducer.ts | 8 +++++++ 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cvat-ui/src/actions/review-actions.ts b/cvat-ui/src/actions/review-actions.ts index 2f90d6720599..46e95092bc63 100644 --- a/cvat-ui/src/actions/review-actions.ts +++ b/cvat-ui/src/actions/review-actions.ts @@ -29,6 +29,7 @@ export enum ReviewActionTypes { SUBMIT_REVIEW_SUCCESS = 'SUBMIT_REVIEW_SUCCESS', SUBMIT_REVIEW_FAILED = 'SUBMIT_REVIEW_FAILED', SWITCH_ISSUES_HIDDEN_FLAG = 'SWITCH_ISSUES_HIDDEN_FLAG', + SWITCH_ISSUES_HIDDEN_RESOLVED_FLAG = 'SWITCH_ISSUES_HIDDEN_RESOLVED_FLAG', } export const reviewActions = { @@ -58,6 +59,9 @@ export const reviewActions = { submitReviewSuccess: () => createAction(ReviewActionTypes.SUBMIT_REVIEW_SUCCESS), submitReviewFailed: (error: any) => createAction(ReviewActionTypes.SUBMIT_REVIEW_FAILED, { error }), switchIssuesHiddenFlag: (hidden: boolean) => createAction(ReviewActionTypes.SWITCH_ISSUES_HIDDEN_FLAG, { hidden }), + switchIssuesHiddenResolvedFlag: (hidden: boolean) => ( + createAction(ReviewActionTypes.SWITCH_ISSUES_HIDDEN_RESOLVED_FLAG, { hidden }) + ), }; export type ReviewActions = ActionUnion; diff --git a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx index 484e39e27db4..e990a0894180 100644 --- a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx +++ b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -29,10 +29,11 @@ export default function IssueAggregatorComponent(): JSX.Element | null { const dispatch = useDispatch(); const [expandedIssue, setExpandedIssue] = useState(null); const frameIssues = useSelector((state: CombinedState): any[] => state.review.frameIssues); - const canvasInstance = useSelector((state: CombinedState): Canvas => state.annotation.canvas.instance); + const canvasInstance = useSelector((state: CombinedState): Canvas => state.annotation.canvas.instance as Canvas); const canvasIsReady = useSelector((state: CombinedState): boolean => state.annotation.canvas.ready); const newIssuePosition = useSelector((state: CombinedState): number[] | null => state.review.newIssuePosition); const issuesHidden = useSelector((state: CombinedState): any => state.review.issuesHidden); + const issuesResolvedHidden = useSelector((state: CombinedState): any => state.review.issuesResolvedHidden); const issueFetching = useSelector((state: CombinedState): number | null => state.review.fetching.issueId); const issueLabels: JSX.Element[] = []; const issueDialogs: JSX.Element[] = []; @@ -81,6 +82,7 @@ export default function IssueAggregatorComponent(): JSX.Element | null { const { geometry } = canvasInstance; for (const issue of frameIssues) { if (issuesHidden) break; + if (issuesResolvedHidden && issue.resolvedDate !== null) continue; const issueResolved = !!issue.resolver; const offset = 15; const translated = issue.position.map((coord: number): number => coord + geometry.offset); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx index 1504cd630298..32f4f6c00489 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined, + CheckCircleFilled, CheckCircleOutlined, } from '@ant-design/icons'; import Alert from 'antd/lib/alert'; import { Row, Col } from 'antd/lib/grid'; @@ -22,6 +23,7 @@ export default function LabelsListComponent(): JSX.Element { const issues = useSelector((state: CombinedState): any[] => state.review.issues); const activeReview = useSelector((state: CombinedState): any => state.review.activeReview); const issuesHidden = useSelector((state: CombinedState): any => state.review.issuesHidden); + const issuesResolvedHidden = useSelector((state: CombinedState): any => state.review.issuesResolvedHidden); const combinedIssues = activeReview ? issues.concat(activeReview.issues) : issues; const frames = combinedIssues.map((issue: any): number => issue.frame).sort((a: number, b: number) => +a - +b); const nearestLeft = frames.filter((_frame: number): boolean => _frame < frame).reverse()[0]; @@ -62,8 +64,8 @@ export default function LabelsListComponent(): JSX.Element { - - + + {issuesHidden ? ( + + + { issuesResolvedHidden ? ( + dispatch(reviewActions.switchIssuesHiddenResolvedFlag(false))} + /> + ) : ( + dispatch(reviewActions.switchIssuesHiddenResolvedFlag(true))} + /> + + )} + +
diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx index 59b4af02d99c..59e9ca96efb7 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -167,16 +167,20 @@ function mapStateToProps(state: CombinedState): StateToProps { opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, }, }, - review: { frameIssues, issuesHidden }, + review: { frameIssues, issuesHidden, issuesResolvedHidden }, shortcuts: { keyMap }, } = state; + const issues = frameIssues.filter((issue) => ( + !issuesHidden && [Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) && + (issue.resolvedDate !== null && issuesResolvedHidden) + )); + return { sidebarCollapsed, canvasInstance, jobInstance, - frameIssues: - issuesHidden || ![Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) ? null : frameIssues, + frameIssues: issues, frameData, frameAngle: frameAngles[frame - jobInstance.startFrame], frameFetching, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 00b1d3bc4674..f22a29fe66f3 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -675,6 +675,7 @@ export interface ReviewState { activeReview: any | null; newIssuePosition: number[] | null; issuesHidden: boolean; + issuesResolvedHidden: boolean; fetching: { reviewId: number | null; issueId: number | null; diff --git a/cvat-ui/src/reducers/review-reducer.ts b/cvat-ui/src/reducers/review-reducer.ts index 9d3d800bffb1..29c51eb18bf8 100644 --- a/cvat-ui/src/reducers/review-reducer.ts +++ b/cvat-ui/src/reducers/review-reducer.ts @@ -16,6 +16,7 @@ const defaultState: ReviewState = { activeReview: null, // not saved on the server newIssuePosition: null, issuesHidden: false, + issuesResolvedHidden: false, fetching: { reviewId: null, issueId: null, @@ -175,6 +176,13 @@ export default function (state: ReviewState = defaultState, action: any): Review issuesHidden: hidden, }; } + case ReviewActionTypes.SWITCH_ISSUES_HIDDEN_RESOLVED_FLAG: { + const { hidden } = action.payload; + return { + ...state, + issuesResolvedHidden: hidden, + }; + } case AnnotationActionTypes.CLOSE_JOB: case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState }; From f167868afcfc9e4c8081b9d9345b2b361061931f Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 26 Nov 2021 12:26:37 +0300 Subject: [PATCH 2/6] Fixed filtering condition --- .../src/components/annotation-page/review/issues-aggregator.tsx | 2 +- .../src/containers/annotation-page/canvas/canvas-wrapper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx index e990a0894180..4c300935f55f 100644 --- a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx +++ b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx @@ -82,7 +82,7 @@ export default function IssueAggregatorComponent(): JSX.Element | null { const { geometry } = canvasInstance; for (const issue of frameIssues) { if (issuesHidden) break; - if (issuesResolvedHidden && issue.resolvedDate !== null) continue; + if (issuesResolvedHidden && !!issue.resolvedDate) continue; const issueResolved = !!issue.resolver; const offset = 15; const translated = issue.position.map((coord: number): number => coord + geometry.offset); diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx index 59e9ca96efb7..be20412c375f 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -173,7 +173,7 @@ function mapStateToProps(state: CombinedState): StateToProps { const issues = frameIssues.filter((issue) => ( !issuesHidden && [Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) && - (issue.resolvedDate !== null && issuesResolvedHidden) + !(!!issue.resolvedDate && issuesResolvedHidden) )); return { From 26778f1f82e48f3d8c8c082e8b46250af79a0430 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 26 Nov 2021 13:18:29 +0300 Subject: [PATCH 3/6] Added issue deleting --- cvat-core/src/issue.js | 25 +++++++++++++- cvat-core/src/review.js | 10 +++++- cvat-core/src/server-proxy.js | 11 ++++++ cvat-ui/src/actions/review-actions.ts | 31 +++++++++++++++++ .../annotation-page/review/issue-dialog.tsx | 34 +++++++++++++++++-- cvat-ui/src/reducers/interfaces.ts | 2 ++ cvat-ui/src/reducers/notifications-reducer.ts | 16 +++++++++ cvat-ui/src/reducers/review-reducer.ts | 11 ++++++ 8 files changed, 136 insertions(+), 4 deletions(-) diff --git a/cvat-core/src/issue.js b/cvat-core/src/issue.js index e18ae3ed3d06..4b00769b5013 100644 --- a/cvat-core/src/issue.js +++ b/cvat-core/src/issue.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -241,6 +241,22 @@ class Issue { return result; } + /** + * The method deletes the issue + * Deletes local or server-saved issues + * @method delete + * @memberof module:API.cvat.classes.Issue + * @readonly + * @instance + * @async + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ArgumentError} + */ + async delete() { + await PluginRegistry.apiWrapper.call(this, Issue.prototype.delete); + } + serialize() { const { comments } = this; const data = { @@ -332,4 +348,11 @@ Issue.prototype.reopen.implementation = async function () { } }; +Issue.prototype.delete.implementation = async function () { + const { id } = this; + if (id >= 0) { + await serverProxy.issues.delete(id); + } +}; + module.exports = Issue; diff --git a/cvat-core/src/review.js b/cvat-core/src/review.js index db9491e4f8fb..320fc200511a 100644 --- a/cvat-core/src/review.js +++ b/cvat-core/src/review.js @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2020-2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -250,6 +250,10 @@ class Review { return result; } + async deleteIssue(issueId) { + await PluginRegistry.apiWrapper.call(this, Review.prototype.deleteIssue, issueId); + } + /** * Method submits local review to the server * @method submit @@ -394,4 +398,8 @@ Review.prototype.submit.implementation = async function () { } }; +Review.prototype.deleteIssue.implementation = function (issueId) { + this.__internal.issue_set = this.__internal.issue_set.filter((issue) => issue.id !== issueId); +}; + module.exports = Review; diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 9e6a846d72d4..d289f25f24f9 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -743,6 +743,16 @@ return response.data; } + async function deleteIssue(issueID) { + const { backendAPI } = config; + + try { + await Axios.delete(`${backendAPI}/issues/${issueID}`); + } catch (errorData) { + throw generateError(errorData); + } + } + async function saveJob(id, jobData) { const { backendAPI } = config; @@ -1403,6 +1413,7 @@ issues: { value: Object.freeze({ update: updateIssue, + delete: deleteIssue, }), writable: false, }, diff --git a/cvat-ui/src/actions/review-actions.ts b/cvat-ui/src/actions/review-actions.ts index 46e95092bc63..893f09ebd132 100644 --- a/cvat-ui/src/actions/review-actions.ts +++ b/cvat-ui/src/actions/review-actions.ts @@ -25,6 +25,9 @@ export enum ReviewActionTypes { COMMENT_ISSUE = 'COMMENT_ISSUE', COMMENT_ISSUE_SUCCESS = 'COMMENT_ISSUE_SUCCESS', COMMENT_ISSUE_FAILED = 'COMMENT_ISSUE_FAILED', + REMOVE_ISSUE_SUCCESS = 'REMOVE_ISSUE_SUCCESS', + REMOVE_ISSUE_FAILED = 'REMOVE_ISSUE_FAILED', + SWITCH_REMOVE_ISSUE_DIALOG = 'SWITCH_REMOVE_ISSUE_DIALOG', SUBMIT_REVIEW = 'SUBMIT_REVIEW', SUBMIT_REVIEW_SUCCESS = 'SUBMIT_REVIEW_SUCCESS', SUBMIT_REVIEW_FAILED = 'SUBMIT_REVIEW_FAILED', @@ -58,6 +61,10 @@ export const reviewActions = { submitReview: (reviewId: number) => createAction(ReviewActionTypes.SUBMIT_REVIEW, { reviewId }), submitReviewSuccess: () => createAction(ReviewActionTypes.SUBMIT_REVIEW_SUCCESS), submitReviewFailed: (error: any) => createAction(ReviewActionTypes.SUBMIT_REVIEW_FAILED, { error }), + removeIssueSuccess: (issueId: number, frame: number) => ( + createAction(ReviewActionTypes.REMOVE_ISSUE_SUCCESS, { issueId, frame }) + ), + removeIssueFailed: (error: any) => createAction(ReviewActionTypes.REMOVE_ISSUE_FAILED, { error }), switchIssuesHiddenFlag: (hidden: boolean) => createAction(ReviewActionTypes.SWITCH_ISSUES_HIDDEN_FLAG, { hidden }), switchIssuesHiddenResolvedFlag: (hidden: boolean) => ( createAction(ReviewActionTypes.SWITCH_ISSUES_HIDDEN_RESOLVED_FLAG, { hidden }) @@ -208,3 +215,27 @@ export const submitReviewAsync = (review: any): ThunkAction => async (dispatch, dispatch(reviewActions.submitReviewFailed(error)); } }; + +export const deleteIssueAsync = (id: number): ThunkAction => async (dispatch, getState) => { + const state = getState(); + const { + review: { frameIssues, activeReview }, + annotation: { + player: { + frame: { number: frameNumber }, + }, + }, + } = state; + + try { + const [issue] = frameIssues.filter((_issue: any): boolean => _issue.id === id); + await issue.delete(); + if (activeReview !== null) { + await activeReview.deleteIssue(id); + await activeReview.toLocalStorage(); + } + dispatch(reviewActions.removeIssueSuccess(id, frameNumber)); + } catch (error) { + dispatch(reviewActions.removeIssueFailed(error)); + } +}; diff --git a/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx b/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx index 27b89772ced7..1db0e931ffdd 100644 --- a/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx +++ b/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx @@ -2,8 +2,15 @@ // // SPDX-License-Identifier: MIT -import React, { useState, useEffect, useRef } from 'react'; +import React, { + useState, + useEffect, + useRef, + useCallback, +} from 'react'; import ReactDOM from 'react-dom'; +import { useDispatch } from 'react-redux'; +import Modal from 'antd/lib/modal'; import { Row, Col } from 'antd/lib/grid'; import { CloseOutlined } from '@ant-design/icons'; import Comment from 'antd/lib/comment'; @@ -13,6 +20,7 @@ import Button from 'antd/lib/button'; import Input from 'antd/lib/input'; import moment from 'moment'; import CVATTooltip from 'components/common/cvat-tooltip'; +import { deleteIssueAsync } from 'actions/review-actions'; interface Props { id: number; @@ -32,6 +40,7 @@ interface Props { export default function IssueDialog(props: Props): JSX.Element { const ref = useRef(null); const [currentText, setCurrentText] = useState(''); + const dispatch = useDispatch(); const { comments, id, @@ -55,6 +64,22 @@ export default function IssueDialog(props: Props): JSX.Element { } }, [resolved]); + const onDeleteIssue = useCallback((): void => { + Modal.confirm({ + title: `The issue #${id} will be deleted.`, + className: 'cvat-modal-confirm-remove-issue', + onOk: () => { + collapse(); + dispatch(deleteIssueAsync(id)); + }, + okButtonProps: { + type: 'primary', + danger: true, + }, + okText: 'Delete', + }); + }, []); + const lines = comments.map( (_comment: any): JSX.Element => { const created = _comment.createdDate ? moment(_comment.createdDate) : moment(moment.now()); @@ -118,7 +143,12 @@ export default function IssueDialog(props: Props): JSX.Element { /> - + + + + {currentText.length ? (