diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index ee0a0fb3df9e..ee22092a50c8 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -151,8 +151,6 @@ Canvas itself handles: `cvat_canvas_shape_merging`, `cvat_canvas_shape_drawing`, `cvat_canvas_shape_occluded` -- Drawn review ROIs have an id `cvat_canvas_issue_region_{issue.id}` -- Drawn review roi has the class `cvat_canvas_issue_region` - Drawn texts have the class `cvat_canvas_text` - Tags have the class `cvat_canvas_tag` - Canvas image has ID `cvat_canvas_image` diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 181fa15c195e..483ce0861ada 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -70,15 +70,6 @@ polyline.cvat_shape_drawing_opacity { stroke: white; } -.cvat_canvas_issue_region { - display: none; - stroke-width: 0; -} - -circle.cvat_canvas_issue_region { - opacity: 1 !important; -} - polyline.cvat_canvas_shape_grouping { @extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_opacity; diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index bc19a8381cd5..93f622d606e9 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -29,7 +29,6 @@ const CanvasVersion = pjson.version; interface Canvas { html(): HTMLDivElement; setup(frameData: any, objectStates: any[], zLayer?: number): void; - setupIssueRegions(issueRegions: Record): void; activate(clientID: number | null, attributeID?: number): void; rotate(rotationAngle: number): void; focus(clientID: number, padding?: number): void; @@ -76,10 +75,6 @@ class CanvasImpl implements Canvas { this.model.setup(frameData, objectStates, zLayer); } - public setupIssueRegions(issueRegions: Record): void { - this.model.setupIssueRegions(issueRegions); - } - public fitCanvas(): void { this.model.fitCanvas(this.view.html().clientWidth, this.view.html().clientHeight); } diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index dca3c7d888b0..ba02042c0be8 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -19,7 +19,6 @@ import { export interface CanvasController { readonly objects: any[]; - readonly issueRegions: Record; readonly zLayer: number | null; readonly focusData: FocusData; readonly activeElement: ActiveElement; @@ -123,10 +122,6 @@ export class CanvasControllerImpl implements CanvasController { return this.model.zLayer; } - public get issueRegions(): Record { - return this.model.issueRegions; - } - public get objects(): any[] { return this.model.objects; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 780cca672679..40135e571af3 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -119,7 +119,6 @@ export enum UpdateReasons { IMAGE_MOVED = 'image_moved', GRID_UPDATED = 'grid_updated', - ISSUE_REGIONS_UPDATED = 'issue_regions_updated', OBJECTS_UPDATED = 'objects_updated', SHAPE_ACTIVATED = 'shape_activated', SHAPE_FOCUSED = 'shape_focused', @@ -159,7 +158,6 @@ export enum Mode { export interface CanvasModel { readonly imageBitmap: boolean; readonly image: Image | null; - readonly issueRegions: Record; readonly objects: any[]; readonly zLayer: number | null; readonly gridSize: Size; @@ -180,7 +178,6 @@ export interface CanvasModel { move(topOffset: number, leftOffset: number): void; setup(frameData: any, objectStates: any[], zLayer: number): void; - setupIssueRegions(issueRegions: Record): void; activate(clientID: number | null, attributeID: number | null): void; rotate(rotationAngle: number): void; focus(clientID: number, padding: number): void; @@ -220,7 +217,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { gridSize: Size; left: number; objects: any[]; - issueRegions: Record; scale: number; top: number; zLayer: number | null; @@ -270,7 +266,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }, left: 0, objects: [], - issueRegions: {}, scale: 1, top: 0, zLayer: null, @@ -429,11 +424,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }); } - public setupIssueRegions(issueRegions: Record): void { - this.data.issueRegions = issueRegions; - this.notify(UpdateReasons.ISSUE_REGIONS_UPDATED); - } - public activate(clientID: number | null, attributeID: number | null): void { if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) { return; @@ -706,10 +696,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return this.data.image; } - public get issueRegions(): Record { - return { ...this.data.issueRegions }; - } - public get objects(): any[] { if (this.data.zLayer !== null) { return this.data.objects.filter((object: any): boolean => object.zOrder <= this.data.zLayer); diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 95e89b7d6aed..775f1ad9bcc9 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -68,10 +68,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private controller: CanvasController; private svgShapes: Record; private svgTexts: Record; - private issueRegionPattern_1: SVG.Pattern; - private issueRegionPattern_2: SVG.Pattern; private drawnStates: Record; - private drawnIssueRegions: Record; private geometry: Geometry; private drawHandler: DrawHandler; private editHandler: EditHandler; @@ -520,27 +517,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - // Transform all drawn issues region - for (const issueRegion of Object.values(this.drawnIssueRegions)) { - ((issueRegion as any) as SVG.Shape).attr('r', `${(consts.BASE_POINT_SIZE * 3) / this.geometry.scale}`); - ((issueRegion as any) as SVG.Shape).attr( - 'stroke-width', - `${consts.BASE_STROKE_WIDTH / this.geometry.scale}`, - ); - } - - // Transform patterns - for (const pattern of [this.issueRegionPattern_1, this.issueRegionPattern_2]) { - pattern.attr({ - width: consts.BASE_PATTERN_SIZE / this.geometry.scale, - height: consts.BASE_PATTERN_SIZE / this.geometry.scale, - }); - - pattern.children().forEach((element: SVG.Element): void => { - element.attr('stroke-width', consts.BASE_STROKE_WIDTH / this.geometry.scale); - }); - } - // Transform handlers this.drawHandler.transform(this.geometry); this.editHandler.transform(this.geometry); @@ -562,59 +538,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - private setupIssueRegions(issueRegions: Record): void { - for (const issueRegion of Object.keys(this.drawnIssueRegions)) { - if (!(issueRegion in issueRegions) || !+issueRegion) { - this.drawnIssueRegions[+issueRegion].remove(); - delete this.drawnIssueRegions[+issueRegion]; - } - } - - for (const issueRegion of Object.keys(issueRegions)) { - if (issueRegion in this.drawnIssueRegions) continue; - const points = this.translateToCanvas(issueRegions[+issueRegion]); - if (points.length === 2) { - this.drawnIssueRegions[+issueRegion] = this.adoptedContent - .circle((consts.BASE_POINT_SIZE * 3 * 2) / this.geometry.scale) - .center(points[0], points[1]) - .addClass('cvat_canvas_issue_region') - .attr({ - id: `cvat_canvas_issue_region_${issueRegion}`, - fill: 'url(#cvat_issue_region_pattern_1)', - }); - } else if (points.length === 4) { - const stringified = this.stringifyToCanvas([ - points[0], - points[1], - points[2], - points[1], - points[2], - points[3], - points[0], - points[3], - ]); - this.drawnIssueRegions[+issueRegion] = this.adoptedContent - .polygon(stringified) - .addClass('cvat_canvas_issue_region') - .attr({ - id: `cvat_canvas_issue_region_${issueRegion}`, - fill: 'url(#cvat_issue_region_pattern_1)', - 'stroke-width': `${consts.BASE_STROKE_WIDTH / this.geometry.scale}`, - }); - } else { - const stringified = this.stringifyToCanvas(points); - this.drawnIssueRegions[+issueRegion] = this.adoptedContent - .polygon(stringified) - .addClass('cvat_canvas_issue_region') - .attr({ - id: `cvat_canvas_issue_region_${issueRegion}`, - fill: 'url(#cvat_issue_region_pattern_1)', - 'stroke-width': `${consts.BASE_STROKE_WIDTH / this.geometry.scale}`, - }); - } - } - } - private setupObjects(states: any[]): void { const created = []; const updated = []; @@ -881,7 +804,6 @@ export class CanvasViewImpl implements CanvasView, Listener { this.svgShapes = {}; this.svgTexts = {}; this.drawnStates = {}; - this.drawnIssueRegions = {}; this.activeElement = { clientID: null, attributeID: null, @@ -915,28 +837,6 @@ export class CanvasViewImpl implements CanvasView, Listener { const gridDefs: SVGDefsElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'defs'); const gridRect: SVGRectElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - // Setup defs - const contentDefs = this.adoptedContent.defs(); - this.issueRegionPattern_1 = contentDefs - .pattern(consts.BASE_PATTERN_SIZE, consts.BASE_PATTERN_SIZE, (add): void => { - add.line(0, 0, 0, 10).stroke('red'); - }) - .attr({ - id: 'cvat_issue_region_pattern_1', - patternTransform: 'rotate(45)', - patternUnits: 'userSpaceOnUse', - }); - - this.issueRegionPattern_2 = contentDefs - .pattern(consts.BASE_PATTERN_SIZE, consts.BASE_PATTERN_SIZE, (add): void => { - add.line(0, 0, 0, 10).stroke('yellow'); - }) - .attr({ - id: 'cvat_issue_region_pattern_2', - patternTransform: 'rotate(45)', - patternUnits: 'userSpaceOnUse', - }); - // Setup loading animation this.loadingAnimation.setAttribute('id', 'cvat_canvas_loading_animation'); loadingCircle.setAttribute('id', 'cvat_canvas_loading_circle'); @@ -1185,8 +1085,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } const event: CustomEvent = new CustomEvent('canvas.setup'); this.canvas.dispatchEvent(event); - } else if (reason === UpdateReasons.ISSUE_REGIONS_UPDATED) { - this.setupIssueRegions(this.controller.issueRegions); } else if (reason === UpdateReasons.GRID_UPDATED) { const size: Size = this.geometry.grid; this.gridPattern.setAttribute('width', `${size.width}`); diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 63ed49b8c2b4..56f1455712f0 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -13,9 +13,6 @@ function build() { const Log = require('./log'); const ObjectState = require('./object-state'); const Statistics = require('./statistics'); - const Comment = require('./comment'); - const Issue = require('./issue'); - const Review = require('./review'); const { Job, Task } = require('./session'); const { Project } = require('./project'); const { Attribute, Label } = require('./labels'); @@ -726,9 +723,6 @@ function build() { Statistics, ObjectState, MLModel, - Comment, - Issue, - Review, }, }; diff --git a/cvat-core/src/comment.js b/cvat-core/src/comment.js deleted file mode 100644 index e8e18cb42c37..000000000000 --- a/cvat-core/src/comment.js +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const User = require('./user'); -const { ArgumentError } = require('./exceptions'); -const { negativeIDGenerator } = require('./common'); - -/** - * Class representing a single comment - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Comment { - constructor(initialData) { - const data = { - id: undefined, - message: undefined, - created_date: undefined, - updated_date: undefined, - removed: false, - author: undefined, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - if (data.author && !(data.author instanceof User)) data.author = new User(data.author); - - if (typeof id === 'undefined') { - data.id = negativeIDGenerator(); - } - if (typeof data.created_date === 'undefined') { - data.created_date = new Date().toISOString(); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * @name message - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - message: { - get: () => data.message, - set: (value) => { - if (!value.trim().length) { - throw new ArgumentError('Value must not be empty'); - } - data.message = value; - }, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name updatedDate - * @type {string} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ - updatedDate: { - get: () => data.updated_date, - }, - /** - * Instance of a user who has created the comment - * @name author - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Comment - * @readonly - * @instance - */ - author: { - get: () => data.author, - }, - /** - * @name removed - * @type {boolean} - * @memberof module:API.cvat.classes.Comment - * @instance - */ - removed: { - get: () => data.removed, - set: (value) => { - if (typeof value !== 'boolean') { - throw new ArgumentError('Value must be a boolean value'); - } - data.removed = value; - }, - }, - __internal: { - get: () => data, - }, - }), - ); - } - - serialize() { - const data = { - message: this.message, - }; - - if (this.id > 0) { - data.id = this.id; - } - if (this.createdDate) { - data.created_date = this.createdDate; - } - if (this.updatedDate) { - data.updated_date = this.updatedDate; - } - if (this.author) { - data.author = this.author.serialize(); - } - - return data; - } - - toJSON() { - const data = this.serialize(); - const { author, ...updated } = data; - return { - ...updated, - author_id: author ? author.id : undefined, - }; - } -} - -module.exports = Comment; diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 4ce6d80c08c6..7072093c1868 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -23,13 +23,11 @@ * @name TaskStatus * @memberof module:API.cvat.enums * @property {string} ANNOTATION 'annotation' - * @property {string} VALIDATION 'validation' * @property {string} COMPLETED 'completed' * @readonly */ const TaskStatus = Object.freeze({ ANNOTATION: 'annotation', - VALIDATION: 'validation', COMPLETED: 'completed', }); @@ -47,22 +45,6 @@ DIMENSION_3D: '3d', }); - /** - * Review statuses - * @enum {string} - * @name ReviewStatus - * @memberof module:API.cvat.enums - * @property {string} ACCEPTED 'accepted' - * @property {string} REJECTED 'rejected' - * @property {string} REVIEW_FURTHER 'review_further' - * @readonly - */ - const ReviewStatus = Object.freeze({ - ACCEPTED: 'accepted', - REJECTED: 'rejected', - REVIEW_FURTHER: 'review_further', - }); - /** * List of RQ statuses * @enum {string} @@ -336,7 +318,6 @@ module.exports = { ShareFileType, TaskStatus, - ReviewStatus, TaskMode, AttributeType, ObjectType, diff --git a/cvat-core/src/issue.js b/cvat-core/src/issue.js deleted file mode 100644 index e18ae3ed3d06..000000000000 --- a/cvat-core/src/issue.js +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const quickhull = require('quickhull'); - -const PluginRegistry = require('./plugins'); -const Comment = require('./comment'); -const User = require('./user'); -const { ArgumentError } = require('./exceptions'); -const { negativeIDGenerator } = require('./common'); -const serverProxy = require('./server-proxy'); - -/** - * Class representing a single issue - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Issue { - constructor(initialData) { - const data = { - id: undefined, - position: undefined, - comment_set: [], - frame: undefined, - created_date: undefined, - resolved_date: undefined, - owner: undefined, - resolver: undefined, - removed: false, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - if (data.owner && !(data.owner instanceof User)) data.owner = new User(data.owner); - if (data.resolver && !(data.resolver instanceof User)) data.resolver = new User(data.resolver); - - if (data.comment_set) { - data.comment_set = data.comment_set.map((comment) => new Comment(comment)); - } - - if (typeof data.id === 'undefined') { - data.id = negativeIDGenerator(); - } - if (typeof data.created_date === 'undefined') { - data.created_date = new Date().toISOString(); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * Region of interests of the issue - * @name position - * @type {number[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - position: { - get: () => data.position, - set: (value) => { - if (Array.isArray(value) || value.some((coord) => typeof coord !== 'number')) { - throw new ArgumentError(`Array of numbers is expected. Got ${value}`); - } - data.position = value; - }, - }, - /** - * List of comments attached to the issue - * @name comments - * @type {module:API.cvat.classes.Comment[]} - * @memberof module:API.cvat.classes.Issue - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - comments: { - get: () => data.comment_set.filter((comment) => !comment.removed), - }, - /** - * @name frame - * @type {integer} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - frame: { - get: () => data.frame, - }, - /** - * @name createdDate - * @type {string} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - createdDate: { - get: () => data.created_date, - }, - /** - * @name resolvedDate - * @type {string} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - resolvedDate: { - get: () => data.resolved_date, - }, - /** - * An instance of a user who has raised the issue - * @name owner - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - owner: { - get: () => data.owner, - }, - /** - * An instance of a user who has resolved the issue - * @name resolver - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Issue - * @readonly - * @instance - */ - resolver: { - get: () => data.resolver, - }, - /** - * @name removed - * @type {boolean} - * @memberof module:API.cvat.classes.Comment - * @instance - */ - removed: { - get: () => data.removed, - set: (value) => { - if (typeof value !== 'boolean') { - throw new ArgumentError('Value must be a boolean value'); - } - data.removed = value; - }, - }, - __internal: { - get: () => data, - }, - }), - ); - } - - static hull(coordinates) { - if (coordinates.length > 4) { - const points = coordinates.reduce((acc, coord, index, arr) => { - if (index % 2) acc.push({ x: arr[index - 1], y: coord }); - return acc; - }, []); - - return quickhull(points) - .map((point) => [point.x, point.y]) - .flat(); - } - - return coordinates; - } - - /** - * @typedef {Object} CommentData - * @property {number} [author] an ID of a user who has created the comment - * @property {string} message a comment message - * @global - */ - /** - * Method appends a comment to the issue - * For a new issue it saves comment locally, for a saved issue it saves comment on the server - * @method comment - * @memberof module:API.cvat.classes.Issue - * @param {CommentData} data - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async comment(data) { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.comment, data); - return result; - } - - /** - * The method resolves the issue - * New issues are resolved locally, server-saved issues are resolved on the server - * @method resolve - * @memberof module:API.cvat.classes.Issue - * @param {module:API.cvat.classes.User} user - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - async resolve(user) { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.resolve, user); - return result; - } - - /** - * The method resolves the issue - * New issues are reopened locally, server-saved issues are reopened on the server - * @method reopen - * @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 reopen() { - const result = await PluginRegistry.apiWrapper.call(this, Issue.prototype.reopen); - return result; - } - - serialize() { - const { comments } = this; - const data = { - position: this.position, - frame: this.frame, - comment_set: comments.map((comment) => comment.serialize()), - }; - - if (this.id > 0) { - data.id = this.id; - } - if (this.createdDate) { - data.created_date = this.createdDate; - } - if (this.resolvedDate) { - data.resolved_date = this.resolvedDate; - } - if (this.owner) { - data.owner = this.owner.toJSON(); - } - if (this.resolver) { - data.resolver = this.resolver.toJSON(); - } - - return data; - } - - toJSON() { - const data = this.serialize(); - const { owner, resolver, ...updated } = data; - return { - ...updated, - comment_set: this.comments.map((comment) => comment.toJSON()), - owner_id: owner ? owner.id : undefined, - resolver_id: resolver ? resolver.id : undefined, - }; - } -} - -Issue.prototype.comment.implementation = async function (data) { - if (typeof data !== 'object' || data === null) { - throw new ArgumentError(`The argument "data" must be a not null object. Got ${data}`); - } - if (typeof data.message !== 'string' || data.message.length < 1) { - throw new ArgumentError(`Comment message must be a not empty string. Got ${data.message}`); - } - if (!(data.author instanceof User)) { - throw new ArgumentError(`Author of the comment must a User instance. Got ${data.author}`); - } - - const comment = new Comment(data); - const { id } = this; - if (id >= 0) { - const jsonified = comment.toJSON(); - jsonified.issue = id; - const response = await serverProxy.comments.create(jsonified); - const savedComment = new Comment(response); - this.__internal.comment_set.push(savedComment); - } else { - this.__internal.comment_set.push(comment); - } -}; - -Issue.prototype.resolve.implementation = async function (user) { - if (!(user instanceof User)) { - throw new ArgumentError(`The argument "user" must be an instance of a User class. Got ${typeof user}`); - } - - const { id } = this; - if (id >= 0) { - const response = await serverProxy.issues.update(id, { resolver_id: user.id }); - this.__internal.resolved_date = response.resolved_date; - this.__internal.resolver = new User(response.resolver); - } else { - this.__internal.resolved_date = new Date().toISOString(); - this.__internal.resolver = user; - } -}; - -Issue.prototype.reopen.implementation = async function () { - const { id } = this; - if (id >= 0) { - const response = await serverProxy.issues.update(id, { resolver_id: null }); - this.__internal.resolved_date = response.resolved_date; - this.__internal.resolver = response.resolver; - } else { - this.__internal.resolved_date = null; - this.__internal.resolver = null; - } -}; - -module.exports = Issue; diff --git a/cvat-core/src/review.js b/cvat-core/src/review.js deleted file mode 100644 index db9491e4f8fb..000000000000 --- a/cvat-core/src/review.js +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -const store = require('store'); - -const PluginRegistry = require('./plugins'); -const Issue = require('./issue'); -const User = require('./user'); -const { ArgumentError, DataError } = require('./exceptions'); -const { ReviewStatus } = require('./enums'); -const { negativeIDGenerator } = require('./common'); -const serverProxy = require('./server-proxy'); - -/** - * Class representing a single review - * @memberof module:API.cvat.classes - * @hideconstructor - */ -class Review { - constructor(initialData) { - const data = { - id: undefined, - job: undefined, - issue_set: [], - estimated_quality: undefined, - status: undefined, - reviewer: undefined, - assignee: undefined, - reviewed_frames: undefined, - reviewed_states: undefined, - }; - - for (const property in data) { - if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { - data[property] = initialData[property]; - } - } - - if (data.reviewer && !(data.reviewer instanceof User)) data.reviewer = new User(data.reviewer); - if (data.assignee && !(data.assignee instanceof User)) data.assignee = new User(data.assignee); - - data.reviewed_frames = Array.isArray(data.reviewed_frames) ? new Set(data.reviewed_frames) : new Set(); - data.reviewed_states = Array.isArray(data.reviewed_states) ? new Set(data.reviewed_states) : new Set(); - if (data.issue_set) { - data.issue_set = data.issue_set.map((issue) => new Issue(issue)); - } - - if (typeof data.id === 'undefined') { - data.id = negativeIDGenerator(); - } - - Object.defineProperties( - this, - Object.freeze({ - /** - * @name id - * @type {integer} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - */ - id: { - get: () => data.id, - }, - /** - * An identifier of a job the review is attached to - * @name job - * @type {integer} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - */ - job: { - get: () => data.job, - }, - /** - * List of attached issues - * @name issues - * @type {number[]} - * @memberof module:API.cvat.classes.Review - * @instance - * @readonly - */ - issues: { - get: () => data.issue_set.filter((issue) => !issue.removed), - }, - /** - * Estimated quality of the review - * @name estimatedQuality - * @type {number} - * @memberof module:API.cvat.classes.Review - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - estimatedQuality: { - get: () => data.estimated_quality, - set: (value) => { - if (typeof value !== 'number' || value < 0 || value > 5) { - throw new ArgumentError(`Value must be a number in range [0, 5]. Got ${value}`); - } - data.estimated_quality = value; - }, - }, - /** - * @name status - * @type {module:API.cvat.enums.ReviewStatus} - * @memberof module:API.cvat.classes.Review - * @instance - * @readonly - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - status: { - get: () => data.status, - set: (status) => { - const type = ReviewStatus; - let valueInEnum = false; - for (const value in type) { - if (type[value] === status) { - valueInEnum = true; - break; - } - } - - if (!valueInEnum) { - throw new ArgumentError( - 'Value must be a value from the enumeration cvat.enums.ReviewStatus', - ); - } - - data.status = status; - }, - }, - /** - * An instance of a user who has done the review - * @name reviewer - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - reviewer: { - get: () => data.reviewer, - set: (reviewer) => { - if (!(reviewer instanceof User)) { - throw new ArgumentError(`Reviewer must be an instance of the User class. Got ${reviewer}`); - } - - data.reviewer = reviewer; - }, - }, - /** - * An instance of a user who was assigned for annotation before the review - * @name assignee - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - */ - assignee: { - get: () => data.assignee, - }, - /** - * A set of frames that have been visited during review - * @name reviewedFrames - * @type {number[]} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - */ - reviewedFrames: { - get: () => Array.from(data.reviewed_frames), - }, - /** - * A set of reviewed states (server IDs combined with frames) - * @name reviewedFrames - * @type {string[]} - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - */ - reviewedStates: { - get: () => Array.from(data.reviewed_states), - }, - __internal: { - get: () => data, - }, - }), - ); - } - - /** - * Method appends a frame to a set of reviewed frames - * Reviewed frames are saved only in local storage - * @method reviewFrame - * @memberof module:API.cvat.classes.Review - * @param {number} frame - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async reviewFrame(frame) { - const result = await PluginRegistry.apiWrapper.call(this, Review.prototype.reviewFrame, frame); - return result; - } - - /** - * Method appends a frame to a set of reviewed frames - * Reviewed states are saved only in local storage. They are used to automatic annotations quality assessment - * @method reviewStates - * @memberof module:API.cvat.classes.Review - * @param {string[]} stateIDs - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async reviewStates(stateIDs) { - const result = await PluginRegistry.apiWrapper.call(this, Review.prototype.reviewStates, stateIDs); - return result; - } - - /** - * @typedef {Object} IssueData - * @property {number} frame - * @property {number[]} position - * @property {number} owner - * @property {CommentData[]} comment_set - * @global - */ - /** - * Method adds a new issue to the review - * @method openIssue - * @memberof module:API.cvat.classes.Review - * @param {IssueData} data - * @returns {module:API.cvat.classes.Issue} - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ArgumentError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async openIssue(data) { - const result = await PluginRegistry.apiWrapper.call(this, Review.prototype.openIssue, data); - return result; - } - - /** - * Method submits local review to the server - * @method submit - * @memberof module:API.cvat.classes.Review - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.DataError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async submit() { - const result = await PluginRegistry.apiWrapper.call(this, Review.prototype.submit); - return result; - } - - serialize() { - const { issues, reviewedFrames, reviewedStates } = this; - const data = { - job: this.job, - issue_set: issues.map((issue) => issue.serialize()), - reviewed_frames: Array.from(reviewedFrames), - reviewed_states: Array.from(reviewedStates), - }; - - if (this.id > 0) { - data.id = this.id; - } - if (typeof this.estimatedQuality !== 'undefined') { - data.estimated_quality = this.estimatedQuality; - } - if (typeof this.status !== 'undefined') { - data.status = this.status; - } - if (this.reviewer) { - data.reviewer = this.reviewer.toJSON(); - } - if (this.assignee) { - data.reviewer = this.assignee.toJSON(); - } - - return data; - } - - toJSON() { - const data = this.serialize(); - const { - reviewer, - assignee, - reviewed_frames: reviewedFrames, - reviewed_states: reviewedStates, - ...updated - } = data; - - return { - ...updated, - issue_set: this.issues.map((issue) => issue.toJSON()), - reviewer_id: reviewer ? reviewer.id : undefined, - assignee_id: assignee ? assignee.id : undefined, - }; - } - - async toLocalStorage() { - const data = this.serialize(); - store.set(`job-${this.job}-review`, JSON.stringify(data)); - } -} - -Review.prototype.reviewFrame.implementation = function (frame) { - if (!Number.isInteger(frame)) { - throw new ArgumentError(`The argument "frame" is expected to be an integer. Got ${frame}`); - } - this.__internal.reviewed_frames.add(frame); -}; - -Review.prototype.reviewStates.implementation = function (stateIDs) { - if (!Array.isArray(stateIDs) || stateIDs.some((stateID) => typeof stateID !== 'string')) { - throw new ArgumentError(`The argument "stateIDs" is expected to be an array of string. Got ${stateIDs}`); - } - - stateIDs.forEach((stateID) => this.__internal.reviewed_states.add(stateID)); -}; - -Review.prototype.openIssue.implementation = async function (data) { - if (typeof data !== 'object' || data === null) { - throw new ArgumentError(`The argument "data" must be a not null object. Got ${data}`); - } - - if (typeof data.frame !== 'number') { - throw new ArgumentError(`Issue frame must be a number. Got ${data.frame}`); - } - - if (!(data.owner instanceof User)) { - throw new ArgumentError(`Issue owner must be a User instance. Got ${data.owner}`); - } - - if (!Array.isArray(data.position) || data.position.some((coord) => typeof coord !== 'number')) { - throw new ArgumentError(`Issue position must be an array of numbers. Got ${data.position}`); - } - - if (!Array.isArray(data.comment_set)) { - throw new ArgumentError(`Issue comment set must be an array. Got ${data.comment_set}`); - } - - const copied = { - frame: data.frame, - position: Issue.hull(data.position), - owner: data.owner, - comment_set: [], - }; - - const issue = new Issue(copied); - - for (const comment of data.comment_set) { - await issue.comment.implementation.call(issue, comment); - } - - this.__internal.issue_set.push(issue); - return issue; -}; - -Review.prototype.submit.implementation = async function () { - if (typeof this.estimatedQuality === 'undefined') { - throw new DataError('Estimated quality is expected to be a number. Got "undefined"'); - } - - if (typeof this.status === 'undefined') { - throw new DataError('Review status is expected to be a string. Got "undefined"'); - } - - if (this.id < 0) { - const data = this.toJSON(); - - const response = await serverProxy.jobs.reviews.create(data); - store.remove(`job-${this.job}-review`); - this.__internal.id = response.id; - this.__internal.issue_set = response.issue_set.map((issue) => new Issue(issue)); - this.__internal.estimated_quality = response.estimated_quality; - this.__internal.status = response.status; - - if (response.reviewer) this.__internal.reviewer = new User(response.reviewer); - if (response.assignee) this.__internal.assignee = new User(response.assignee); - } -}; - -module.exports = Review; diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index af3f746f585c..6fe124645966 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -614,90 +614,6 @@ return response.data; } - async function getJobReviews(jobID) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/jobs/${jobID}/reviews`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function createReview(data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.post(`${backendAPI}/reviews`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function getJobIssues(jobID) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.get(`${backendAPI}/jobs/${jobID}/issues`, { - proxy: config.proxy, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function createComment(data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.post(`${backendAPI}/comments`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - - async function updateIssue(issueID, data) { - const { backendAPI } = config; - - let response = null; - try { - response = await Axios.patch(`${backendAPI}/issues/${issueID}`, JSON.stringify(data), { - proxy: config.proxy, - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (errorData) { - throw generateError(errorData); - } - - return response.data; - } - async function saveJob(id, jobData) { const { backendAPI } = config; @@ -1182,11 +1098,6 @@ value: Object.freeze({ get: getJob, save: saveJob, - issues: getJobIssues, - reviews: { - get: getJobReviews, - create: createReview, - }, }), writable: false, }, @@ -1238,20 +1149,6 @@ writable: false, }, - issues: { - value: Object.freeze({ - update: updateIssue, - }), - writable: false, - }, - - comments: { - value: Object.freeze({ - create: createComment, - }), - writable: false, - }, - predictor: { value: Object.freeze({ status: predictorStatus, diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index a1136030fd9e..8ee7721222c0 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -14,8 +14,6 @@ const { TaskStatus } = require('./enums'); const { Label } = require('./labels'); const User = require('./user'); - const Issue = require('./issue'); - const Review = require('./review'); const { FieldUpdateTrigger } = require('./common'); function buildDuplicatedAPI(prototype) { @@ -728,7 +726,6 @@ const data = { id: undefined, assignee: null, - reviewer: null, status: undefined, start_frame: undefined, stop_frame: undefined, @@ -737,7 +734,6 @@ const updatedFields = new FieldUpdateTrigger({ assignee: false, - reviewer: false, status: false, }); @@ -754,7 +750,6 @@ } if (data.assignee) data.assignee = new User(data.assignee); - if (data.reviewer) data.reviewer = new User(data.reviewer); Object.defineProperties( this, @@ -787,24 +782,6 @@ data.assignee = assignee; }, }, - /** - * Instance of a user who is responsible for review - * @name reviewer - * @type {module:API.cvat.classes.User} - * @memberof module:API.cvat.classes.Job - * @instance - * @throws {module:API.cvat.exceptions.ArgumentError} - */ - reviewer: { - get: () => data.reviewer, - set: (reviewer) => { - if (reviewer !== null && !(reviewer instanceof User)) { - throw new ArgumentError('Value must be a user instance'); - } - updatedFields.reviewer = true; - data.reviewer = reviewer; - }, - }, /** * @name status * @type {module:API.cvat.enums.TaskStatus} @@ -931,64 +908,6 @@ const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.save); return result; } - - /** - * Method returns a list of issues for a job - * @method issues - * @memberof module:API.cvat.classes.Job - * @type {module:API.cvat.classes.Issue[]} - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async issues() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.issues); - return result; - } - - /** - * Method returns a list of reviews for a job - * @method reviews - * @type {module:API.cvat.classes.Review[]} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async reviews() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.reviews); - return result; - } - - /** - * /** - * @typedef {Object} ReviewSummary - * @property {number} reviews Number of done reviews - * @property {number} average_estimated_quality - * @property {number} issues_unsolved - * @property {number} issues_resolved - * @property {string[]} assignees - * @property {string[]} reviewers - */ - /** - * Method returns brief summary of within all reviews - * @method reviewsSummary - * @type {ReviewSummary} - * @memberof module:API.cvat.classes.Job - * @readonly - * @instance - * @async - * @throws {module:API.cvat.exceptions.ServerError} - * @throws {module:API.cvat.exceptions.PluginError} - */ - async reviewsSummary() { - const result = await PluginRegistry.apiWrapper.call(this, Job.prototype.reviewsSummary); - return result; - } } /** @@ -1072,7 +991,6 @@ url: job.url, id: job.id, assignee: job.assignee, - reviewer: job.reviewer, status: job.status, start_frame: segment.start_frame, stop_frame: segment.stop_frame, @@ -1743,9 +1661,6 @@ case 'assignee': jobData.assignee_id = this.assignee ? this.assignee.id : null; break; - case 'reviewer': - jobData.reviewer_id = this.reviewer ? this.reviewer.id : null; - break; default: break; } @@ -1762,42 +1677,6 @@ throw new ArgumentError('Can not save job without and id'); }; - Job.prototype.issues.implementation = async function () { - const result = await serverProxy.jobs.issues(this.id); - return result.map((issue) => new Issue(issue)); - }; - - Job.prototype.reviews.implementation = async function () { - const result = await serverProxy.jobs.reviews.get(this.id); - const reviews = result.map((review) => new Review(review)); - - // try to get not finished review from the local storage - const data = store.get(`job-${this.id}-review`); - if (data) { - reviews.push(new Review(JSON.parse(data))); - } - - return reviews; - }; - - Job.prototype.reviewsSummary.implementation = async function () { - const reviews = await serverProxy.jobs.reviews.get(this.id); - const issues = await serverProxy.jobs.issues(this.id); - - const qualities = reviews.map((review) => review.estimated_quality); - const reviewers = reviews.filter((review) => review.reviewer).map((review) => review.reviewer.username); - const assignees = reviews.filter((review) => review.assignee).map((review) => review.assignee.username); - - return { - reviews: reviews.length, - average_estimated_quality: qualities.reduce((acc, quality) => acc + quality, 0) / (qualities.length || 1), - issues_unsolved: issues.filter((issue) => !issue.resolved_date).length, - issues_resolved: issues.filter((issue) => issue.resolved_date).length, - assignees: Array.from(new Set(assignees.filter((assignee) => assignee !== null))), - reviewers: Array.from(new Set(reviewers.filter((reviewer) => reviewer !== null))), - }; - }; - Job.prototype.frames.get.implementation = async function (frame, isPlaying, step) { if (!Number.isInteger(frame) || frame < 0) { throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 9a3c866ef36c..1ea4d38036f9 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -123,7 +123,6 @@ export enum AnnotationActionTypes { CONFIRM_CANVAS_READY = 'CONFIRM_CANVAS_READY', DRAG_CANVAS = 'DRAG_CANVAS', ZOOM_CANVAS = 'ZOOM_CANVAS', - SELECT_ISSUE_POSITION = 'SELECT_ISSUE_POSITION', MERGE_OBJECTS = 'MERGE_OBJECTS', GROUP_OBJECTS = 'GROUP_OBJECTS', SPLIT_TRACK = 'SPLIT_TRACK', @@ -186,8 +185,6 @@ export enum AnnotationActionTypes { INTERACT_WITH_CANVAS = 'INTERACT_WITH_CANVAS', SET_AI_TOOLS_REF = 'SET_AI_TOOLS_REF', GET_DATA_FAILED = 'GET_DATA_FAILED', - SWITCH_REQUEST_REVIEW_DIALOG = 'SWITCH_REQUEST_REVIEW_DIALOG', - SWITCH_SUBMIT_REVIEW_DIALOG = 'SWITCH_SUBMIT_REVIEW_DIALOG', SET_FORCE_EXIT_ANNOTATION_PAGE_FLAG = 'SET_FORCE_EXIT_ANNOTATION_PAGE_FLAG', UPDATE_PREDICTOR_STATE = 'UPDATE_PREDICTOR_STATE', GET_PREDICTIONS = 'GET_PREDICTIONS', @@ -1016,8 +1013,6 @@ export function getJobAsync(tid: number, jid: number, initialFrame: number, init }); } const states = await job.annotations.get(frameNumber, showAllInterpolationTracks, filters); - const issues = await job.issues(); - const reviews = await job.reviews(); const [minZ, maxZ] = computeZRange(states); const colors = [...cvat.enums.colors]; @@ -1029,8 +1024,6 @@ export function getJobAsync(tid: number, jid: number, initialFrame: number, init payload: { openTime, job, - issues, - reviews, states, frameNumber, frameFilename: frameData.filename, @@ -1158,15 +1151,6 @@ export function shapeDrawn(): AnyAction { }; } -export function selectIssuePosition(enabled: boolean): AnyAction { - return { - type: AnnotationActionTypes.SELECT_ISSUE_POSITION, - payload: { - enabled, - }, - }; -} - export function mergeObjects(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.MERGE_OBJECTS, @@ -1597,24 +1581,6 @@ export function redrawShapeAsync(): ThunkAction { }; } -export function switchRequestReviewDialog(visible: boolean): AnyAction { - return { - type: AnnotationActionTypes.SWITCH_REQUEST_REVIEW_DIALOG, - payload: { - visible, - }, - }; -} - -export function switchSubmitReviewDialog(visible: boolean): AnyAction { - return { - type: AnnotationActionTypes.SWITCH_SUBMIT_REVIEW_DIALOG, - payload: { - visible, - }, - }; -} - export function setForceExitAnnotationFlag(forceExit: boolean): AnyAction { return { type: AnnotationActionTypes.SET_FORCE_EXIT_ANNOTATION_PAGE_FLAG, diff --git a/cvat-ui/src/actions/review-actions.ts b/cvat-ui/src/actions/review-actions.ts deleted file mode 100644 index b433d96872fa..000000000000 --- a/cvat-ui/src/actions/review-actions.ts +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; -import { updateTaskSuccess } from './tasks-actions'; - -const cvat = getCore(); - -export enum ReviewActionTypes { - INITIALIZE_REVIEW_SUCCESS = 'INITIALIZE_REVIEW_SUCCESS', - INITIALIZE_REVIEW_FAILED = 'INITIALIZE_REVIEW_FAILED', - CREATE_ISSUE = 'CREATE_ISSUE', - START_ISSUE = 'START_ISSUE', - FINISH_ISSUE_SUCCESS = 'FINISH_ISSUE_SUCCESS', - FINISH_ISSUE_FAILED = 'FINISH_ISSUE_FAILED', - CANCEL_ISSUE = 'CANCEL_ISSUE', - RESOLVE_ISSUE = 'RESOLVE_ISSUE', - RESOLVE_ISSUE_SUCCESS = 'RESOLVE_ISSUE_SUCCESS', - RESOLVE_ISSUE_FAILED = 'RESOLVE_ISSUE_FAILED', - REOPEN_ISSUE = 'REOPEN_ISSUE', - REOPEN_ISSUE_SUCCESS = 'REOPEN_ISSUE_SUCCESS', - REOPEN_ISSUE_FAILED = 'REOPEN_ISSUE_FAILED', - COMMENT_ISSUE = 'COMMENT_ISSUE', - COMMENT_ISSUE_SUCCESS = 'COMMENT_ISSUE_SUCCESS', - COMMENT_ISSUE_FAILED = 'COMMENT_ISSUE_FAILED', - SUBMIT_REVIEW = 'SUBMIT_REVIEW', - SUBMIT_REVIEW_SUCCESS = 'SUBMIT_REVIEW_SUCCESS', - SUBMIT_REVIEW_FAILED = 'SUBMIT_REVIEW_FAILED', - SWITCH_ISSUES_HIDDEN_FLAG = 'SWITCH_ISSUES_HIDDEN_FLAG', -} - -export const reviewActions = { - initializeReviewSuccess: (reviewInstance: any, frame: number) => - createAction(ReviewActionTypes.INITIALIZE_REVIEW_SUCCESS, { reviewInstance, frame }), - initializeReviewFailed: (error: any) => createAction(ReviewActionTypes.INITIALIZE_REVIEW_FAILED, { error }), - createIssue: () => createAction(ReviewActionTypes.CREATE_ISSUE, {}), - startIssue: (position: number[]) => - createAction(ReviewActionTypes.START_ISSUE, { position: cvat.classes.Issue.hull(position) }), - finishIssueSuccess: (frame: number, issue: any) => - createAction(ReviewActionTypes.FINISH_ISSUE_SUCCESS, { frame, issue }), - finishIssueFailed: (error: any) => createAction(ReviewActionTypes.FINISH_ISSUE_FAILED, { error }), - cancelIssue: () => createAction(ReviewActionTypes.CANCEL_ISSUE), - commentIssue: (issueId: number) => createAction(ReviewActionTypes.COMMENT_ISSUE, { issueId }), - commentIssueSuccess: () => createAction(ReviewActionTypes.COMMENT_ISSUE_SUCCESS), - commentIssueFailed: (error: any) => createAction(ReviewActionTypes.COMMENT_ISSUE_FAILED, { error }), - resolveIssue: (issueId: number) => createAction(ReviewActionTypes.RESOLVE_ISSUE, { issueId }), - resolveIssueSuccess: () => createAction(ReviewActionTypes.RESOLVE_ISSUE_SUCCESS), - resolveIssueFailed: (error: any) => createAction(ReviewActionTypes.RESOLVE_ISSUE_FAILED, { error }), - reopenIssue: (issueId: number) => createAction(ReviewActionTypes.REOPEN_ISSUE, { issueId }), - reopenIssueSuccess: () => createAction(ReviewActionTypes.REOPEN_ISSUE_SUCCESS), - reopenIssueFailed: (error: any) => createAction(ReviewActionTypes.REOPEN_ISSUE_FAILED, { error }), - submitReview: (reviewId: number) => createAction(ReviewActionTypes.SUBMIT_REVIEW, { reviewId }), - 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 }), -}; - -export type ReviewActions = ActionUnion; - -export const initializeReviewAsync = (): ThunkAction => async (dispatch, getState) => { - try { - const state = getState(); - const { - annotation: { - job: { instance: jobInstance }, - player: { - frame: { number: frame }, - }, - }, - } = state; - - const reviews = await jobInstance.reviews(); - const count = reviews.length; - let reviewInstance = null; - if (count && reviews[count - 1].id < 0) { - reviewInstance = reviews[count - 1]; - } else { - reviewInstance = new cvat.classes.Review({ job: jobInstance.id }); - } - - dispatch(reviewActions.initializeReviewSuccess(reviewInstance, frame)); - } catch (error) { - dispatch(reviewActions.initializeReviewFailed(error)); - } -}; - -export const finishIssueAsync = (message: string): ThunkAction => async (dispatch, getState) => { - const state = getState(); - const { - auth: { user }, - annotation: { - player: { - frame: { number: frameNumber }, - }, - }, - review: { activeReview, newIssuePosition }, - } = state; - - try { - const issue = await activeReview.openIssue({ - frame: frameNumber, - position: newIssuePosition, - owner: user, - comment_set: [ - { - message, - author: user, - }, - ], - }); - await activeReview.toLocalStorage(); - dispatch(reviewActions.finishIssueSuccess(frameNumber, issue)); - } catch (error) { - dispatch(reviewActions.finishIssueFailed(error)); - } -}; - -export const commentIssueAsync = (id: number, message: string): ThunkAction => async (dispatch, getState) => { - const state = getState(); - const { - auth: { user }, - review: { frameIssues, activeReview }, - } = state; - - try { - dispatch(reviewActions.commentIssue(id)); - const [issue] = frameIssues.filter((_issue: any): boolean => _issue.id === id); - await issue.comment({ - message, - author: user, - }); - if (activeReview && activeReview.issues.includes(issue)) { - await activeReview.toLocalStorage(); - } - dispatch(reviewActions.commentIssueSuccess()); - } catch (error) { - dispatch(reviewActions.commentIssueFailed(error)); - } -}; - -export const resolveIssueAsync = (id: number): ThunkAction => async (dispatch, getState) => { - const state = getState(); - const { - auth: { user }, - review: { frameIssues, activeReview }, - } = state; - - try { - dispatch(reviewActions.resolveIssue(id)); - const [issue] = frameIssues.filter((_issue: any): boolean => _issue.id === id); - await issue.resolve(user); - if (activeReview && activeReview.issues.includes(issue)) { - await activeReview.toLocalStorage(); - } - - dispatch(reviewActions.resolveIssueSuccess()); - } catch (error) { - dispatch(reviewActions.resolveIssueFailed(error)); - } -}; - -export const reopenIssueAsync = (id: number): ThunkAction => async (dispatch, getState) => { - const state = getState(); - const { - auth: { user }, - review: { frameIssues, activeReview }, - } = state; - - try { - dispatch(reviewActions.reopenIssue(id)); - const [issue] = frameIssues.filter((_issue: any): boolean => _issue.id === id); - await issue.reopen(user); - if (activeReview && activeReview.issues.includes(issue)) { - await activeReview.toLocalStorage(); - } - - dispatch(reviewActions.reopenIssueSuccess()); - } catch (error) { - dispatch(reviewActions.reopenIssueFailed(error)); - } -}; - -export const submitReviewAsync = (review: any): ThunkAction => async (dispatch, getState) => { - const state = getState(); - const { - annotation: { - job: { instance: jobInstance }, - }, - } = state; - - try { - dispatch(reviewActions.submitReview(review.id)); - await review.submit(jobInstance.id); - - const [task] = await cvat.tasks.get({ id: jobInstance.task.id }); - dispatch(updateTaskSuccess(task, jobInstance.task.id)); - dispatch(reviewActions.submitReviewSuccess()); - } catch (error) { - dispatch(reviewActions.submitReviewFailed(error)); - } -}; diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index 5bedffaa0bdb..23a62b5c4a84 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -10,9 +10,6 @@ import Spin from 'antd/lib/spin'; import notification from 'antd/lib/notification'; import AttributeAnnotationWorkspace from 'components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace'; -import SubmitAnnotationsModal from 'components/annotation-page/request-review-modal'; -import ReviewAnnotationsWorkspace from 'components/annotation-page/review-workspace/review-workspace'; -import SubmitReviewModal from 'components/annotation-page/review/submit-review-modal'; import StandardWorkspaceComponent from 'components/annotation-page/standard-workspace/standard-workspace'; import StandardWorkspace3DComponent from 'components/annotation-page/standard3D-workspace/standard3D-workspace'; import TagAnnotationWorkspace from 'components/annotation-page/tag-annotation-workspace/tag-annotation-workspace'; @@ -126,15 +123,8 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { )} - {workspace === Workspace.REVIEW_WORKSPACE && ( - - - - )} - - ); } diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx index c85cffd1e578..094025a4c26d 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-context-menu.tsx @@ -1,16 +1,12 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT import React from 'react'; import ReactDOM from 'react-dom'; -import Menu from 'antd/lib/menu'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { MenuInfo } from 'rc-menu/lib/interface'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; import { Workspace } from 'reducers/interfaces'; -import consts from 'consts'; interface Props { readonly: boolean; @@ -20,56 +16,6 @@ interface Props { visible: boolean; left: number; top: number; - onStartIssue(position: number[]): void; - openIssue(position: number[], message: string): void; - latestComments: string[]; -} - -interface ReviewContextMenuProps { - top: number; - left: number; - latestComments: string[]; - onClick: (param: MenuInfo) => void; -} - -enum ReviewContextMenuKeys { - OPEN_ISSUE = 'open_issue', - QUICK_ISSUE_POSITION = 'quick_issue_position', - QUICK_ISSUE_ATTRIBUTE = 'quick_issue_attribute', - QUICK_ISSUE_FROM_LATEST = 'quick_issue_from_latest', -} - -function ReviewContextMenu({ - top, left, latestComments, onClick, -}: ReviewContextMenuProps): JSX.Element { - return ( - - - Open an issue ... - - - Quick issue: incorrect position - - - Quick issue: incorrect attribute - - {latestComments.length ? ( - - {latestComments.map( - (comment: string, id: number): JSX.Element => ( - - {comment} - - ), - )} - - ) : null} - - ); } export default function CanvasContextMenu(props: Props): JSX.Element | null { @@ -80,53 +26,12 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null { left, top, readonly, - workspace, - latestComments, - onStartIssue, - openIssue, } = props; if (!visible || contextMenuClientID === null) { return null; } - if (workspace === Workspace.REVIEW_WORKSPACE) { - return ReactDOM.createPortal( - { - const [state] = objectStates.filter( - (_state: any): boolean => _state.clientID === contextMenuClientID, - ); - if (param.key === ReviewContextMenuKeys.OPEN_ISSUE) { - if (state) { - onStartIssue(state.points); - } - } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_POSITION) { - if (state) { - openIssue(state.points, consts.QUICK_ISSUE_INCORRECT_POSITION_TEXT); - } - } else if (param.key === ReviewContextMenuKeys.QUICK_ISSUE_ATTRIBUTE) { - if (state) { - openIssue(state.points, consts.QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT); - } - } else if ( - param.keyPath.length === 2 && - param.keyPath[1] === ReviewContextMenuKeys.QUICK_ISSUE_FROM_LATEST - ) { - if (state) { - openIssue(state.points, latestComments[+param.keyPath[0]]); - } - } - }} - />, - window.document.body, - ); - } - return ReactDOM.createPortal(
{ @@ -118,13 +116,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { autoborders: automaticBordering, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, - forceDisableEditing: workspace === Workspace.REVIEW_WORKSPACE, + forceDisableEditing: false, intelligentPolygonCrop, showProjections, }); this.initialSetup(); - this.updateIssueRegions(); this.updateCanvas(); } @@ -136,7 +133,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { outlined, outlineColor, showBitmap, - frameIssues, frameData, frameAngle, annotations, @@ -232,10 +228,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (prevProps.frameIssues !== frameIssues) { - this.updateIssueRegions(); - } - if ( prevProps.annotations !== annotations || prevProps.frameData !== frameData || @@ -272,18 +264,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.rotate(frameAngle); } - if (prevProps.workspace !== workspace) { - if (workspace === Workspace.REVIEW_WORKSPACE) { - canvasInstance.configure({ - forceDisableEditing: true, - }); - } else if (prevProps.workspace === Workspace.REVIEW_WORKSPACE) { - canvasInstance.configure({ - forceDisableEditing: false, - }); - } - } - const loadingAnimation = window.document.getElementById('cvat_canvas_loading_animation'); if (loadingAnimation && frameFetching !== prevProps.frameFetching) { if (frameFetching) { @@ -399,9 +379,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasPositionSelected = (event: any): void => { - const { onResetCanvas, onStartIssue } = this.props; + const { onResetCanvas } = this.props; const { points } = event.detail; - onStartIssue(points); onResetCanvas(); }; @@ -495,7 +474,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { jobInstance, activatedStateID, workspace, onActivateObject, } = this.props; - if (![Workspace.STANDARD, Workspace.REVIEW_WORKSPACE].includes(workspace)) { + if (![Workspace.STANDARD].includes(workspace)) { return; } @@ -654,23 +633,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - private updateIssueRegions(): void { - const { frameIssues } = this.props; - const { canvasInstance } = this.props as { canvasInstance: Canvas }; - if (frameIssues === null) { - canvasInstance.setupIssueRegions({}); - } else { - const regions = frameIssues.reduce((acc: Record, issue: any): Record< - number, - number[] - > => { - acc[issue.id] = issue.position; - return acc; - }, {}); - canvasInstance.setupIssueRegions(regions); - } - } - private updateCanvas(): void { const { curZLayer, annotations, frameData, canvasInstance, diff --git a/cvat-ui/src/components/annotation-page/request-review-modal.tsx b/cvat-ui/src/components/annotation-page/request-review-modal.tsx deleted file mode 100644 index 884f14471af4..000000000000 --- a/cvat-ui/src/components/annotation-page/request-review-modal.tsx +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useState } from 'react'; -import { AnyAction } from 'redux'; -import { useSelector, useDispatch } from 'react-redux'; -import { useHistory } from 'react-router'; -import Text from 'antd/lib/typography/Text'; -import Title from 'antd/lib/typography/Title'; -import Modal from 'antd/lib/modal'; -import { Row, Col } from 'antd/lib/grid'; - -import UserSelector, { User } from 'components/task-page/user-selector'; -import { CombinedState, TaskStatus } from 'reducers/interfaces'; -import { switchRequestReviewDialog } from 'actions/annotation-actions'; -import { updateJobAsync } from 'actions/tasks-actions'; - -export default function RequestReviewModal(): JSX.Element | null { - const dispatch = useDispatch(); - const history = useHistory(); - const isVisible = useSelector((state: CombinedState): boolean => state.annotation.requestReviewDialogVisible); - const job = useSelector((state: CombinedState): any => state.annotation.job.instance); - const [reviewer, setReviewer] = useState(job.reviewer ? job.reviewer : null); - const close = (): AnyAction => dispatch(switchRequestReviewDialog(false)); - const submitAnnotations = (): void => { - job.reviewer = reviewer; - job.status = TaskStatus.REVIEW; - dispatch(updateJobAsync(job)); - history.push(`/tasks/${job.task.id}`); - }; - - if (!isVisible) { - return null; - } - - return ( - - - - Assign a user who is responsible for review - - - - - Reviewer: - - - - - - - You might not be able to change the job after this action. Continue? - - - ); -} diff --git a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx deleted file mode 100644 index e89cfd3a9095..000000000000 --- a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Layout from 'antd/lib/layout'; - -import GlobalHotKeys, { KeyMap } from 'utils/mousetrap-react'; -import { ActiveControl, Rotation } from 'reducers/interfaces'; -import { Canvas } from 'cvat-canvas-wrapper'; - -import RotateControl from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control'; -import CursorControl from 'components/annotation-page/standard-workspace/controls-side-bar/cursor-control'; -import MoveControl from 'components/annotation-page/standard-workspace/controls-side-bar/move-control'; -import FitControl from 'components/annotation-page/standard-workspace/controls-side-bar/fit-control'; -import ResizeControl from 'components/annotation-page/standard-workspace/controls-side-bar/resize-control'; -import IssueControl from './issue-control'; - -interface Props { - canvasInstance: Canvas; - activeControl: ActiveControl; - keyMap: KeyMap; - normalizedKeyMap: Record; - - rotateFrame(rotation: Rotation): void; - selectIssuePosition(enabled: boolean): void; -} - -export default function ControlsSideBarComponent(props: Props): JSX.Element { - const { - canvasInstance, activeControl, normalizedKeyMap, keyMap, rotateFrame, selectIssuePosition, - } = props; - - const preventDefault = (event: KeyboardEvent | undefined): void => { - if (event) { - event.preventDefault(); - } - }; - - const subKeyMap = { - CANCEL: keyMap.CANCEL, - OPEN_REVIEW_ISSUE: keyMap.OPEN_REVIEW_ISSUE, - }; - - const handlers = { - CANCEL: (event: KeyboardEvent | undefined) => { - preventDefault(event); - if (activeControl !== ActiveControl.CURSOR) { - canvasInstance.cancel(); - } - }, - OPEN_REVIEW_ISSUE: (event: KeyboardEvent | undefined) => { - preventDefault(event); - if (activeControl === ActiveControl.OPEN_ISSUE) { - canvasInstance.selectRegion(false); - selectIssuePosition(false); - } else { - canvasInstance.cancel(); - canvasInstance.selectRegion(true); - selectIssuePosition(true); - } - }, - }; - - return ( - - - - - - -
- - - - -
- -
- ); -} diff --git a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx b/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx deleted file mode 100644 index 4947f7661518..000000000000 --- a/cvat-ui/src/components/annotation-page/review-workspace/controls-side-bar/issue-control.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import Icon from '@ant-design/icons'; - -import { ActiveControl } from 'reducers/interfaces'; -import { Canvas } from 'cvat-canvas-wrapper'; -import { RectangleIcon } from 'icons'; -import CVATTooltip from 'components/common/cvat-tooltip'; - -interface Props { - canvasInstance: Canvas; - activeControl: ActiveControl; - selectIssuePosition(enabled: boolean): void; -} - -function ResizeControl(props: Props): JSX.Element { - const { activeControl, canvasInstance, selectIssuePosition } = props; - - return ( - - { - if (activeControl === ActiveControl.OPEN_ISSUE) { - canvasInstance.selectRegion(false); - selectIssuePosition(false); - } else { - canvasInstance.cancel(); - canvasInstance.selectRegion(true); - selectIssuePosition(true); - } - }} - /> - - ); -} - -export default React.memo(ResizeControl); diff --git a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx b/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx deleted file mode 100644 index 095a69c73fd1..000000000000 --- a/cvat-ui/src/components/annotation-page/review-workspace/review-workspace.tsx +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; -import React, { useEffect } from 'react'; -import Layout from 'antd/lib/layout'; -import { useDispatch, useSelector } from 'react-redux'; - -import { CombinedState } from 'reducers/interfaces'; -import { initializeReviewAsync } from 'actions/review-actions'; - -import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; -import ControlsSideBarContainer from 'containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar'; -import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; -import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; -import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; -import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator'; - -export default function ReviewWorkspaceComponent(): JSX.Element { - const dispatch = useDispatch(); - const frame = useSelector((state: CombinedState): number => state.annotation.player.frame.number); - const states = useSelector((state: CombinedState): any[] => state.annotation.annotations.states); - const review = useSelector((state: CombinedState): any => state.review.activeReview); - - useEffect(() => { - if (review) { - review.reviewFrame(frame); - review.reviewStates( - states - .map((state: any): number | undefined => state.serverID) - .filter((serverID: number | undefined): boolean => typeof serverID !== 'undefined') - .map((serverID: number | undefined): string => `${frame}_${serverID}`), - ); - } - }, [frame, states, review]); - useEffect(() => { - dispatch(initializeReviewAsync()); - }, []); - - return ( - - - - } /> - - - - ); -} diff --git a/cvat-ui/src/components/annotation-page/review-workspace/styles.scss b/cvat-ui/src/components/annotation-page/review-workspace/styles.scss deleted file mode 100644 index 1c9d33483d74..000000000000 --- a/cvat-ui/src/components/annotation-page/review-workspace/styles.scss +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -@import 'base.scss'; - -.cvat-review-workspace.ant-layout { - height: 100%; -} - -.cvat-issue-control { - font-size: 40px; - - &::after { - content: '\FE56'; - font-size: 32px; - position: absolute; - bottom: $grid-unit-size; - right: -$grid-unit-size; - } -} diff --git a/cvat-ui/src/components/annotation-page/review/create-issue-dialog.tsx b/cvat-ui/src/components/annotation-page/review/create-issue-dialog.tsx deleted file mode 100644 index fcd415dafe5f..000000000000 --- a/cvat-ui/src/components/annotation-page/review/create-issue-dialog.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { ReactPortal } from 'react'; -import ReactDOM from 'react-dom'; -import { useDispatch } from 'react-redux'; -import Form from 'antd/lib/form'; -import Input from 'antd/lib/input'; -import Button from 'antd/lib/button'; -import { Row, Col } from 'antd/lib/grid'; - -import { reviewActions, finishIssueAsync } from 'actions/review-actions'; -import { Store } from 'antd/lib/form/interface'; - -interface FormProps { - top: number; - left: number; - submit(message: string): void; - cancel(): void; -} - -function MessageForm(props: FormProps): JSX.Element { - const { - top, left, submit, cancel, - } = props; - - function handleSubmit(values: Store): void { - submit(values.issue_description); - } - - return ( -
- - - - - - - - - - - -
- ); -} - -interface Props { - top: number; - left: number; -} - -export default function CreateIssueDialog(props: Props): ReactPortal { - const dispatch = useDispatch(); - const { top, left } = props; - - return ReactDOM.createPortal( - { - dispatch(finishIssueAsync(message)); - }} - cancel={() => { - dispatch(reviewActions.cancelIssue()); - }} - />, - window.document.getElementById('cvat_canvas_attachment_board') as HTMLElement, - ); -} diff --git a/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx b/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx deleted file mode 100644 index 1be37564d951..000000000000 --- a/cvat-ui/src/components/annotation-page/review/hidden-issue-label.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { ReactPortal, useEffect, useRef } from 'react'; -import ReactDOM from 'react-dom'; -import Tag from 'antd/lib/tag'; -import { CheckOutlined, CloseCircleOutlined } from '@ant-design/icons'; - -import CVATTooltip from 'components/common/cvat-tooltip'; - -interface Props { - id: number; - message: string; - top: number; - left: number; - resolved: boolean; - onClick: () => void; - highlight: () => void; - blur: () => void; -} - -export default function HiddenIssueLabel(props: Props): ReactPortal { - const { - id, message, top, left, resolved, onClick, highlight, blur, - } = props; - - const ref = useRef(null); - - useEffect(() => { - if (!resolved) { - setTimeout(highlight); - } else { - setTimeout(blur); - } - }, [resolved]); - - const elementID = `cvat-hidden-issue-label-${id}`; - return ReactDOM.createPortal( - - { - if (ref.current !== null) { - const selfElement = ref.current; - if (event.deltaX > 0) { - selfElement.parentElement?.appendChild(selfElement); - } else { - selfElement.parentElement?.prepend(selfElement); - } - } - }} - style={{ top, left }} - className='cvat-hidden-issue-label' - > - {resolved ? ( - - ) : ( - - )} - {message} - - , - window.document.getElementById('cvat_canvas_attachment_board') as HTMLElement, - ); -} diff --git a/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx b/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx deleted file mode 100644 index 27b89772ced7..000000000000 --- a/cvat-ui/src/components/annotation-page/review/issue-dialog.tsx +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useState, useEffect, useRef } from 'react'; -import ReactDOM from 'react-dom'; -import { Row, Col } from 'antd/lib/grid'; -import { CloseOutlined } from '@ant-design/icons'; -import Comment from 'antd/lib/comment'; -import Text from 'antd/lib/typography/Text'; -import Title from 'antd/lib/typography/Title'; -import Button from 'antd/lib/button'; -import Input from 'antd/lib/input'; -import moment from 'moment'; -import CVATTooltip from 'components/common/cvat-tooltip'; - -interface Props { - id: number; - comments: any[]; - left: number; - top: number; - resolved: boolean; - isFetching: boolean; - collapse: () => void; - resolve: () => void; - reopen: () => void; - comment: (message: string) => void; - highlight: () => void; - blur: () => void; -} - -export default function IssueDialog(props: Props): JSX.Element { - const ref = useRef(null); - const [currentText, setCurrentText] = useState(''); - const { - comments, - id, - left, - top, - resolved, - isFetching, - collapse, - resolve, - reopen, - comment, - highlight, - blur, - } = props; - - useEffect(() => { - if (!resolved) { - setTimeout(highlight); - } else { - setTimeout(blur); - } - }, [resolved]); - - const lines = comments.map( - (_comment: any): JSX.Element => { - const created = _comment.createdDate ? moment(_comment.createdDate) : moment(moment.now()); - const diff = created.fromNow(); - - return ( - {_comment.author ? _comment.author.username : 'Unknown'}} - content={

{_comment.message}

} - datetime={( - - {diff} - - )} - /> - ); - }, - ); - - const resolveButton = resolved ? ( - - ) : ( - - ); - - return ReactDOM.createPortal( -
- - - {id >= 0 ? `Issue #${id}` : 'Issue'} - - - - - - - - - {lines} - - - - ) => { - setCurrentText(event.target.value); - }} - onPressEnter={() => { - if (currentText) { - comment(currentText); - setCurrentText(''); - } - }} - /> - - - - - {currentText.length ? ( - - ) : ( - resolveButton - )} - - -
, - window.document.getElementById('cvat_canvas_attachment_board') as HTMLElement, - ); -} diff --git a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx b/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx deleted file mode 100644 index 484e39e27db4..000000000000 --- a/cvat-ui/src/components/annotation-page/review/issues-aggregator.tsx +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; -import React, { useState, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; - -import { CombinedState } from 'reducers/interfaces'; -import { Canvas } from 'cvat-canvas/src/typescript/canvas'; - -import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions'; - -import CreateIssueDialog from './create-issue-dialog'; -import HiddenIssueLabel from './hidden-issue-label'; -import IssueDialog from './issue-dialog'; - -const scaleHandler = (canvasInstance: Canvas): void => { - const { geometry } = canvasInstance; - const createDialogs = window.document.getElementsByClassName('cvat-create-issue-dialog'); - const hiddenIssues = window.document.getElementsByClassName('cvat-hidden-issue-label'); - const issues = window.document.getElementsByClassName('cvat-issue-dialog'); - for (const element of [...Array.from(createDialogs), ...Array.from(hiddenIssues), ...Array.from(issues)]) { - (element as HTMLSpanElement).style.transform = `scale(${1 / geometry.scale}) rotate(${-geometry.angle}deg)`; - } -}; - -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 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 issueFetching = useSelector((state: CombinedState): number | null => state.review.fetching.issueId); - const issueLabels: JSX.Element[] = []; - const issueDialogs: JSX.Element[] = []; - - useEffect(() => { - scaleHandler(canvasInstance); - }); - - useEffect(() => { - const regions = frameIssues.reduce((acc: Record, issue: any): Record => { - acc[issue.id] = issue.position; - return acc; - }, {}); - - if (newIssuePosition) { - regions[0] = newIssuePosition; - } - - canvasInstance.setupIssueRegions(regions); - - if (newIssuePosition) { - setExpandedIssue(null); - const element = window.document.getElementById('cvat_canvas_issue_region_0'); - if (element) { - element.style.display = 'block'; - } - } - }, [newIssuePosition]); - - useEffect(() => { - const listener = (): void => scaleHandler(canvasInstance); - - canvasInstance.html().addEventListener('canvas.zoom', listener); - canvasInstance.html().addEventListener('canvas.fit', listener); - - return () => { - canvasInstance.html().removeEventListener('canvas.zoom', listener); - canvasInstance.html().removeEventListener('canvas.fit', listener); - }; - }, []); - - if (!canvasIsReady) { - return null; - } - - const { geometry } = canvasInstance; - for (const issue of frameIssues) { - if (issuesHidden) break; - const issueResolved = !!issue.resolver; - const offset = 15; - const translated = issue.position.map((coord: number): number => coord + geometry.offset); - const minX = Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 === 0)) + offset; - const minY = Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 !== 0)) + offset; - const { id } = issue; - const highlight = (): void => { - const element = window.document.getElementById(`cvat_canvas_issue_region_${id}`); - if (element) { - element.style.display = 'block'; - } - }; - - const blur = (): void => { - if (issueResolved) { - const element = window.document.getElementById(`cvat_canvas_issue_region_${id}`); - if (element) { - element.style.display = ''; - } - } - }; - - if (expandedIssue === id) { - issueDialogs.push( - { - setExpandedIssue(null); - }} - resolve={() => { - dispatch(resolveIssueAsync(issue.id)); - setExpandedIssue(null); - }} - reopen={() => { - dispatch(reopenIssueAsync(issue.id)); - }} - comment={(message: string) => { - dispatch(commentIssueAsync(issue.id, message)); - }} - />, - ); - } else if (issue.comments.length) { - issueLabels.push( - { - setExpandedIssue(id); - }} - />, - ); - } - } - - const translated = newIssuePosition ? newIssuePosition.map((coord: number): number => coord + geometry.offset) : []; - const createLeft = translated.length ? - Math.max(...translated.filter((_: number, idx: number): boolean => idx % 2 === 0)) : - null; - const createTop = translated.length ? - Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 !== 0)) : - null; - - return ( - <> - {createLeft !== null && createTop !== null && } - {issueDialogs} - {issueLabels} - - ); -} diff --git a/cvat-ui/src/components/annotation-page/review/styles.scss b/cvat-ui/src/components/annotation-page/review/styles.scss deleted file mode 100644 index be2381c9c7c2..000000000000 --- a/cvat-ui/src/components/annotation-page/review/styles.scss +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -@import 'base.scss'; - -.cvat-create-issue-dialog { - position: absolute; - pointer-events: auto; - width: $grid-unit-size * 30; - padding: $grid-unit-size; - background: $background-color-2; - z-index: 100; - transform-origin: top left; - box-shadow: $box-shadow-base; - - button { - width: $grid-unit-size * 12; - } -} - -.cvat-hidden-issue-label { - position: absolute; - min-width: 8 * $grid-unit-size; - opacity: 0.8; - z-index: 100; - transition: none; - pointer-events: auto; - max-width: 16 * $grid-unit-size; - overflow: hidden; - text-overflow: ellipsis; - border-radius: 0; - transform-origin: top left; - - &:hover { - opacity: 1; - } -} - -.cvat-issue-dialog { - width: $grid-unit-size * 35; - position: absolute; - z-index: 100; - transition: none; - pointer-events: auto; - background: $background-color-2; - padding: $grid-unit-size; - transform-origin: top left; - box-shadow: $box-shadow-base; - border-radius: 0.5 * $grid-unit-size; - opacity: 0.95; - - .cvat-issue-dialog-chat { - > div { - width: 100%; - } - - .ant-comment { - user-select: auto; - padding: $grid-unit-size; - padding-bottom: 0; - - .ant-comment-content { - line-height: 14px; - } - - .ant-comment-avatar { - margin: 0; - } - } - - border-radius: 0.5 * $grid-unit-size; - background: $background-color-1; - padding: $grid-unit-size; - max-height: $grid-unit-size * 45; - overflow-y: auto; - width: 100%; - } - - .cvat-issue-dialog-input { - background: $background-color-1; - margin-top: $grid-unit-size; - } - - .cvat-issue-dialog-footer { - margin-top: $grid-unit-size; - } - - .ant-comment > .ant-comment-inner { - padding: 0; - } - - &:hover { - opacity: 1; - } -} - -.cvat-hidden-issue-indicator { - margin-right: $grid-unit-size; -} - -.cvat-hidden-issue-resolved-indicator { - @extend .cvat-hidden-issue-indicator; - - color: $ok-icon-color; -} - -.cvat-hidden-issue-unsolved-indicator { - @extend .cvat-hidden-issue-indicator; - - color: $danger-icon-color; -} diff --git a/cvat-ui/src/components/annotation-page/review/submit-review-modal.tsx b/cvat-ui/src/components/annotation-page/review/submit-review-modal.tsx deleted file mode 100644 index 7bbfeecf990c..000000000000 --- a/cvat-ui/src/components/annotation-page/review/submit-review-modal.tsx +++ /dev/null @@ -1,149 +0,0 @@ -// 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(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(job.reviewer ? job.reviewer : null); - const [reviewStatus, setReviewStatus] = useState(ReviewStatus.ACCEPTED); - const [estimatedQuality, setEstimatedQuality] = useState(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 ( - - - - Submitting your review - - - - - - {estimatedQuality} - - {numberOfIssues} - {!!numberOfNewIssues && {` (+${numberOfNewIssues})`}} - - {reviewedFrames} - {reviewedStates} - - - - - - { - if (typeof event.target.value !== 'undefined') { - setReviewStatus(event.target.value); - } - }} - > - Accept - Review next - Reject - - {reviewStatus === ReviewStatus.REVIEW_FURTHER && ( - - - Reviewer: - - - - - - )} - - - { - if (typeof value !== 'undefined') { - setEstimatedQuality(value); - } - }} - /> - - - - - - - - ); -} 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 deleted file mode 100644 index 1504cd630298..000000000000 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/issues-list.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { - LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined, -} from '@ant-design/icons'; -import Alert from 'antd/lib/alert'; -import { Row, Col } from 'antd/lib/grid'; - -import { changeFrameAsync } from 'actions/annotation-actions'; -import { reviewActions } from 'actions/review-actions'; -import CVATTooltip from 'components/common/cvat-tooltip'; -import { CombinedState } from 'reducers/interfaces'; - -export default function LabelsListComponent(): JSX.Element { - const dispatch = useDispatch(); - const frame = useSelector((state: CombinedState): number => state.annotation.player.frame.number); - const frameIssues = useSelector((state: CombinedState): any[] => state.review.frameIssues); - 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 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]; - const dinamicLeftProps: any = Number.isInteger(nearestLeft) ? - { - onClick: () => dispatch(changeFrameAsync(nearestLeft)), - } : - { - style: { - pointerEvents: 'none', - opacity: 0.5, - }, - }; - - const nearestRight = frames.filter((_frame: number): boolean => _frame > frame)[0]; - const dinamicRightProps: any = Number.isInteger(nearestRight) ? - { - onClick: () => dispatch(changeFrameAsync(nearestRight)), - } : - { - style: { - pointerEvents: 'none', - opacity: 0.5, - }, - }; - - return ( - <> -
- - - - - - - - - - - - - - {issuesHidden ? ( - dispatch(reviewActions.switchIssuesHiddenFlag(false))} - /> - ) : ( - dispatch(reviewActions.switchIssuesHiddenFlag(true))} - /> - )} - - - -
-
- {frameIssues.map( - (frameIssue: any): JSX.Element => ( -
{ - const element = window.document.getElementById( - `cvat_canvas_issue_region_${frameIssue.id}`, - ); - if (element) { - element.setAttribute('fill', 'url(#cvat_issue_region_pattern_2)'); - } - }} - onMouseLeave={() => { - const element = window.document.getElementById( - `cvat_canvas_issue_region_${frameIssue.id}`, - ); - if (element) { - element.setAttribute('fill', 'url(#cvat_issue_region_pattern_1)'); - } - }} - > - {frameIssue.resolver ? ( - {`By ${frameIssue.resolver.username}`}} - message='Resolved' - type='success' - showIcon - /> - ) : ( - {`By ${frameIssue.owner.username}`}} - message='Opened' - type='warning' - showIcon - /> - )} -
- ), - )} -
- - ); -} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index d28d294d09ad..0b2cd1842d80 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -18,7 +18,6 @@ import LabelsList from 'components/annotation-page/standard-workspace/objects-si import { adjustContextImagePosition } from 'components/annotation-page/standard-workspace/context-image/context-image'; import { collapseSidebar as collapseSidebarAction } from 'actions/annotation-actions'; import AppearanceBlock from 'components/annotation-page/appearance-block'; -import IssuesListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/issues-list'; interface OwnProps { objectsList: JSX.Element; @@ -106,9 +105,6 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E Labels} key='labels'> - Issues} key='issues'> - - {!sidebarCollapsed && } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index 667278fd02ad..91d849b45562 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -76,55 +76,6 @@ } } -.cvat-objects-sidebar-issues-list-header { - background: $objects-bar-tabs-color; - padding: $grid-unit-size; - height: $grid-unit-size * 4; - box-sizing: border-box; - - > div > div { - > span[role='img'] { - font-size: 16px; - color: $objects-bar-icons-color; - - &:hover { - transform: scale(1.1); - opacity: 0.8; - } - - &:active { - transform: scale(1); - opacity: 0.7; - } - } - } -} - -.cvat-objects-sidebar-issues-list { - background-color: $background-color-2; - height: calc(100% - 32px); - overflow-y: auto; - overflow-x: hidden; -} - -.cvat-objects-sidebar-issue-item { - width: 100%; - margin: 1px; - padding: 2px; - - &:hover { - padding: 0; - - > .ant-alert { - border-width: 3px; - } - } - - > .ant-alert.ant-alert-with-description { - padding: $grid-unit-size $grid-unit-size $grid-unit-size $grid-unit-size * 8; - } -} - .cvat-objects-sidebar-states-header { background: $objects-bar-tabs-color; padding: 5px; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 75adab1514d7..d3ca5f41f307 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -13,7 +13,6 @@ import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; -import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -24,7 +23,6 @@ export default function StandardWorkspaceComponent(): JSX.Element { - ); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ccc6962f836b..f26f7f331742 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -119,7 +119,6 @@ .cvat-merge-control, .cvat-group-control, .cvat-split-track-control, -.cvat-issue-control, .cvat-tools-control, .cvat-extra-controls-control, .cvat-opencv-control { diff --git a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx index 258cf2b23354..7bf9a883dea4 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx @@ -20,7 +20,6 @@ interface Props { loadActivity: string | null; dumpActivities: string[] | null; exportActivities: string[] | null; - isReviewer: boolean; jobInstance: any; onClickMenu(params: MenuInfo, file?: File): void; setForceExitAnnotationFlag(forceExit: boolean): void; @@ -33,8 +32,6 @@ export enum Actions { EXPORT_TASK_DATASET = 'export_task_dataset', REMOVE_ANNO = 'remove_anno', OPEN_TASK = 'open_task', - REQUEST_REVIEW = 'request_review', - SUBMIT_REVIEW = 'submit_review', FINISH_JOB = 'finish_job', RENEW_JOB = 'renew_job', } @@ -47,7 +44,6 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { loadActivity, dumpActivities, exportActivities, - isReviewer, jobInstance, onClickMenu, setForceExitAnnotationFlag, @@ -130,8 +126,6 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { }, okText: 'Delete', }); - } else if ([Actions.REQUEST_REVIEW].includes(copyParams.key as Actions)) { - checkUnsavedChanges(copyParams); } else if (copyParams.key === Actions.FINISH_JOB) { Modal.confirm({ title: 'The job status is going to be switched', @@ -192,11 +186,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { Open the task - {jobStatus === 'annotation' && is2d && Request a review} {jobStatus === 'annotation' && Finish the job} - {jobStatus === 'validation' && isReviewer && ( - Submit the review - )} {jobStatus === 'completed' && Renew the job} ); diff --git a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx index 10d7563b740f..45ab0e1e8eb6 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/statistics-modal.tsx @@ -18,7 +18,6 @@ interface Props { data: any; visible: boolean; assignee: string; - reviewer: string; startFrame: number; stopFrame: number; bugTracker: string; @@ -34,7 +33,6 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { data, visible, assignee, - reviewer, startFrame, stopFrame, bugTracker, @@ -184,12 +182,6 @@ export default function StatisticsModalComponent(props: Props): JSX.Element { {assignee} - - - Reviewer - - {reviewer} - Start frame diff --git a/cvat-ui/src/components/search-tooltip/search-tooltip.tsx b/cvat-ui/src/components/search-tooltip/search-tooltip.tsx index 4e8a8bb0e5c4..a31ed4a8d3ba 100644 --- a/cvat-ui/src/components/search-tooltip/search-tooltip.tsx +++ b/cvat-ui/src/components/search-tooltip/search-tooltip.tsx @@ -64,7 +64,7 @@ export default function SearchTooltip(props: Props): JSX.Element { ) : null} status: annotation - annotation, validation, or completed + annotation or completed id: 5 diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 7ce15b0625e3..9418418ed771 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: MIT -import React, { useEffect, useState } from 'react'; +import React 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 { 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'; @@ -28,72 +28,6 @@ interface Props { 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, @@ -178,8 +112,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { 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'; } @@ -187,16 +119,12 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { 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, @@ -234,27 +162,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { 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; @@ -274,7 +181,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { started: `${created.format('MMMM Do YYYY HH:MM')}`, duration: `${moment.duration(now.diff(created)).humanize()}`, assignee: job, - reviewer: job, }); return acc; @@ -306,10 +212,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { serialized += `\t assigned to "${job.assignee.username}"`; } - if (job.reviewer) { - serialized += `\t reviewed by "${job.reviewer.username}"`; - } - serialized += '\n'; } copy(serialized); diff --git a/cvat-ui/src/consts.ts b/cvat-ui/src/consts.ts index e5df214cc701..2d0768f5ba10 100644 --- a/cvat-ui/src/consts.ts +++ b/cvat-ui/src/consts.ts @@ -18,9 +18,6 @@ const NUCLIO_GUIDE = 'https://openvinotoolkit.github.io/cvat//docs/administration/advanced/installation_automatic_annotation/'; const CANVAS_BACKGROUND_COLORS = ['#ffffff', '#f1f1f1', '#e5e5e5', '#d8d8d8', '#CCCCCC', '#B3B3B3', '#999999']; const NEW_LABEL_COLOR = '#b3b3b3'; -const LATEST_COMMENTS_SHOWN_QUICK_ISSUE = 3; -const QUICK_ISSUE_INCORRECT_POSITION_TEXT = 'Wrong position'; -const QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT = 'Wrong attribute'; const DEFAULT_PROJECT_SUBSETS = ['Train', 'Test', 'Validation']; export default { @@ -37,8 +34,5 @@ export default { CANVAS_BACKGROUND_COLORS, NEW_LABEL_COLOR, NUCLIO_GUIDE, - LATEST_COMMENTS_SHOWN_QUICK_ISSUE, - QUICK_ISSUE_INCORRECT_POSITION_TEXT, - QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT, DEFAULT_PROJECT_SUBSETS, }; diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx index b46020e161b7..4a550667b3ea 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-context-menu.tsx @@ -8,9 +8,6 @@ import { connect } from 'react-redux'; import { CombinedState, ContextMenuType, Workspace } from 'reducers/interfaces'; import CanvasContextMenuComponent from 'components/annotation-page/canvas/canvas-context-menu'; -import { updateCanvasContextMenu } from 'actions/annotation-actions'; -import { reviewActions, finishIssueAsync } from 'actions/review-actions'; -import { ThunkDispatch } from 'utils/redux'; interface OwnProps { readonly: boolean; @@ -25,12 +22,6 @@ interface StateToProps { type: ContextMenuType; collapsed: boolean | undefined; workspace: Workspace; - latestComments: string[]; -} - -interface DispatchToProps { - onStartIssue(position: number[]): void; - openIssue(position: number[], message: string): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -45,7 +36,6 @@ function mapStateToProps(state: CombinedState): StateToProps { }, workspace, }, - review: { latestComments }, } = state; return { @@ -61,25 +51,10 @@ function mapStateToProps(state: CombinedState): StateToProps { top, type, workspace, - latestComments, }; } -function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps { - return { - onStartIssue(position: number[]): void { - dispatch(reviewActions.startIssue(position)); - dispatch(updateCanvasContextMenu(false, 0, 0)); - }, - openIssue(position: number[], message: string): void { - dispatch(reviewActions.startIssue(position)); - dispatch(finishIssueAsync(message)); - dispatch(updateCanvasContextMenu(false, 0, 0)); - }, - }; -} - -type Props = StateToProps & DispatchToProps & OwnProps; +type Props = StateToProps & OwnProps; interface State { latestLeft: number; @@ -206,15 +181,7 @@ class CanvasContextMenuContainer extends React.PureComponent { public render(): JSX.Element { const { left, top } = this.state; const { - visible, - contextMenuClientID, - objectStates, - type, - readonly, - workspace, - latestComments, - onStartIssue, - openIssue, + visible, contextMenuClientID, objectStates, type, readonly, workspace, } = this.props; return ( @@ -228,9 +195,6 @@ class CanvasContextMenuContainer extends React.PureComponent { visible={visible} objectStates={objectStates} workspace={workspace} - latestComments={latestComments} - onStartIssue={onStartIssue} - openIssue={openIssue} /> )} @@ -238,4 +202,4 @@ class CanvasContextMenuContainer extends React.PureComponent { } } -export default connect(mapStateToProps, mapDispatchToProps)(CanvasContextMenuContainer); +export default connect(mapStateToProps)(CanvasContextMenuContainer); 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 cc92a23131ee..356d18e4b28d 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -38,7 +38,6 @@ import { changeSaturationLevel, switchAutomaticBordering, } from 'actions/settings-actions'; -import { reviewActions } from 'actions/review-actions'; import { ColorBy, GridColor, @@ -60,7 +59,6 @@ interface StateToProps { activatedAttributeID: number | null; selectedStatesID: number[]; annotations: any[]; - frameIssues: any[] | null; frameData: any; frameAngle: number; frameFetching: boolean; @@ -124,7 +122,6 @@ interface DispatchToProps { onSwitchAutomaticBordering(enabled: boolean): void; onFetchAnnotation(): void; onGetDataFailed(error: any): void; - onStartIssue(position: number[]): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -170,7 +167,6 @@ function mapStateToProps(state: CombinedState): StateToProps { opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, }, }, - review: { frameIssues, issuesHidden }, shortcuts: { keyMap }, } = state; @@ -178,8 +174,6 @@ function mapStateToProps(state: CombinedState): StateToProps { sidebarCollapsed, canvasInstance, jobInstance, - frameIssues: - issuesHidden || ![Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) ? null : frameIssues, frameData, frameAngle: frameAngles[frame - jobInstance.startFrame], frameFetching, @@ -319,9 +313,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onGetDataFailed(error: any): void { dispatch(getDataFailed(error)); }, - onStartIssue(position: number[]): void { - dispatch(reviewActions.startIssue(position)); - }, }; } diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx index 8912a4606c70..7d00fc86ecbd 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper3D.tsx @@ -43,7 +43,6 @@ interface StateToProps { activatedStateID: number | null; activatedAttributeID: number | null; selectedStatesID: number[]; - frameIssues: any[] | null; frameAngle: number; frameFetching: boolean; frame: number; @@ -134,7 +133,6 @@ function mapStateToProps(state: CombinedState): StateToProps { opacity, colorBy, selectedOpacity, outlined, outlineColor, showBitmap, showProjections, }, }, - review: { frameIssues, issuesHidden }, shortcuts: { keyMap }, } = state; @@ -146,8 +144,6 @@ function mapStateToProps(state: CombinedState): StateToProps { contextMenuVisibility, annotations, sidebarCollapsed, - frameIssues: - issuesHidden || ![Workspace.REVIEW_WORKSPACE, Workspace.STANDARD].includes(workspace) ? null : frameIssues, frameAngle: frameAngles[frame - jobInstance.startFrame], frameFetching, frame, diff --git a/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx deleted file mode 100644 index 8f3c67e340a7..000000000000 --- a/cvat-ui/src/containers/annotation-page/review-workspace/controls-side-bar/controls-side-bar.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { connect } from 'react-redux'; - -import { Canvas } from 'cvat-canvas-wrapper'; -import { selectIssuePosition as selectIssuePositionAction, rotateCurrentFrame } from 'actions/annotation-actions'; -import ControlsSideBarComponent from 'components/annotation-page/review-workspace/controls-side-bar/controls-side-bar'; -import { ActiveControl, CombinedState, Rotation } from 'reducers/interfaces'; -import { KeyMap } from 'utils/mousetrap-react'; - -interface StateToProps { - canvasInstance: Canvas; - rotateAll: boolean; - activeControl: ActiveControl; - keyMap: KeyMap; - normalizedKeyMap: Record; -} - -interface DispatchToProps { - rotateFrame(angle: Rotation): void; - selectIssuePosition(enabled: boolean): void; -} - -function mapStateToProps(state: CombinedState): StateToProps { - const { - annotation: { - canvas: { instance: canvasInstance, activeControl }, - }, - settings: { - player: { rotateAll }, - }, - shortcuts: { keyMap, normalizedKeyMap }, - } = state; - - return { - rotateAll, - canvasInstance: canvasInstance as Canvas, - activeControl, - normalizedKeyMap, - keyMap, - }; -} - -function dispatchToProps(dispatch: any): DispatchToProps { - return { - selectIssuePosition(enabled: boolean): void { - dispatch(selectIssuePositionAction(enabled)); - }, - rotateFrame(rotation: Rotation): void { - dispatch(rotateCurrentFrame(rotation)); - }, - }; -} - -export default connect(mapStateToProps, dispatchToProps)(ControlsSideBarComponent); diff --git a/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx index a52d3e11a0f2..70c25596f08c 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -15,8 +15,6 @@ import { uploadJobAnnotationsAsync, removeAnnotationsAsync, saveAnnotationsAsync, - switchRequestReviewDialog as switchRequestReviewDialogAction, - switchSubmitReviewDialog as switchSubmitReviewDialogAction, setForceExitAnnotationFlag as setForceExitAnnotationFlagAction, } from 'actions/annotation-actions'; @@ -34,8 +32,6 @@ interface DispatchToProps { dumpAnnotations(task: any, dumper: any): void; exportDataset(task: any, exporter: any): void; removeAnnotations(sessionInstance: any): void; - switchRequestReviewDialog(visible: boolean): void; - switchSubmitReviewDialog(visible: boolean): void; setForceExitAnnotationFlag(forceExit: boolean): void; saveAnnotations(jobInstance: any, afterSave?: () => void): void; updateJob(jobInstance: any): void; @@ -81,12 +77,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { removeAnnotations(sessionInstance: any): void { dispatch(removeAnnotationsAsync(sessionInstance)); }, - switchRequestReviewDialog(visible: boolean): void { - dispatch(switchRequestReviewDialogAction(visible)); - }, - switchSubmitReviewDialog(visible: boolean): void { - dispatch(switchSubmitReviewDialogAction(visible)); - }, setForceExitAnnotationFlag(forceExit: boolean): void { dispatch(setForceExitAnnotationFlagAction(forceExit)); }, @@ -114,8 +104,6 @@ function AnnotationMenuContainer(props: Props): JSX.Element { dumpAnnotations, exportDataset, removeAnnotations, - switchRequestReviewDialog, - switchSubmitReviewDialog, setForceExitAnnotationFlag, saveAnnotations, updateJob, @@ -147,10 +135,6 @@ function AnnotationMenuContainer(props: Props): JSX.Element { const [action] = params.keyPath; if (action === Actions.REMOVE_ANNO) { removeAnnotations(jobInstance); - } else if (action === Actions.REQUEST_REVIEW) { - switchRequestReviewDialog(true); - } else if (action === Actions.SUBMIT_REVIEW) { - switchSubmitReviewDialog(true); } else if (action === Actions.RENEW_JOB) { jobInstance.status = TaskStatus.ANNOTATION; updateJob(jobInstance); @@ -165,8 +149,6 @@ function AnnotationMenuContainer(props: Props): JSX.Element { } }; - const isReviewer = jobInstance.reviewer?.id === user.id || user.isSuperuser; - return ( ); } diff --git a/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx b/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx index 1dff816e4189..c2c3db98d944 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/statistics-modal.tsx @@ -70,7 +70,6 @@ class StatisticsModalContainer extends React.PureComponent { startFrame={jobInstance.startFrame} stopFrame={jobInstance.stopFrame} assignee={jobInstance.assignee ? jobInstance.assignee.username : 'Nobody'} - reviewer={jobInstance.reviewer ? jobInstance.reviewer.username : 'Nobody'} savingJobStatus={savingJobStatus} closeStatistics={closeStatistics} /> diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index eb71b8fa16f0..7c707942d52a 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -113,8 +113,6 @@ const defaultState: AnnotationState = { sidebarCollapsed: false, appearanceCollapsed: false, filtersPanelVisible: false, - requestReviewDialogVisible: false, - submitReviewDialogVisible: false, predictor: { enabled: false, error: null, @@ -159,7 +157,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { maxZ, } = action.payload; - const isReview = job.status === TaskStatus.REVIEW; let workspaceSelected = Workspace.STANDARD; let activeShapeType = ShapeType.RECTANGLE; @@ -216,7 +213,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { instance: job.task.dimension === DimensionType.DIM_2D ? new Canvas() : new Canvas3d(), }, colors, - workspace: isReview ? Workspace.REVIEW_WORKSPACE : workspaceSelected, + workspace: workspaceSelected, }; } case AnnotationActionTypes.GET_JOB_FAILED: { @@ -517,22 +514,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.SELECT_ISSUE_POSITION: { - const { enabled } = action.payload; - const activeControl = enabled ? ActiveControl.OPEN_ISSUE : ActiveControl.CURSOR; - - return { - ...state, - annotations: { - ...state.annotations, - activatedStateID: null, - }, - canvas: { - ...state.canvas, - activeControl, - }, - }; - } case AnnotationActionTypes.MERGE_OBJECTS: { const { enabled } = action.payload; const activeControl = enabled ? ActiveControl.MERGE : ActiveControl.CURSOR; @@ -1079,20 +1060,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.SWITCH_REQUEST_REVIEW_DIALOG: { - const { visible } = action.payload; - return { - ...state, - requestReviewDialogVisible: visible, - }; - } - case AnnotationActionTypes.SWITCH_SUBMIT_REVIEW_DIALOG: { - const { visible } = action.payload; - return { - ...state, - submitReviewDialogVisible: visible, - }; - } case AnnotationActionTypes.SET_FORCE_EXIT_ANNOTATION_PAGE_FLAG: { const { forceExit } = action.payload; return { diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index e5a722903251..443383548d60 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -189,7 +189,6 @@ export interface Model { export type OpenCVTool = IntelligentScissors; export enum TaskStatus { ANNOTATION = 'annotation', - REVIEW = 'validation', COMPLETED = 'completed', } @@ -306,14 +305,6 @@ export interface NotificationsState { userAgreements: { fetching: null | ErrorState; }; - review: { - initialization: null | ErrorState; - finishingIssue: null | ErrorState; - resolvingIssue: null | ErrorState; - reopeningIssue: null | ErrorState; - commentingIssue: null | ErrorState; - submittingReview: null | ErrorState; - }; predictor: { prediction: null | ErrorState; }; @@ -347,7 +338,6 @@ export enum ActiveControl { GROUP = 'group', SPLIT = 'split', EDIT = 'edit', - OPEN_ISSUE = 'open_issue', AI_TOOLS = 'ai_tools', PHOTO_CONTEXT = 'PHOTO_CONTEXT', OPENCV_TOOLS = 'opencv_tools', @@ -488,8 +478,6 @@ export interface AnnotationState { }; colors: any[]; filtersPanelVisible: boolean; - requestReviewDialogVisible: boolean; - submitReviewDialogVisible: boolean; sidebarCollapsed: boolean; appearanceCollapsed: boolean; workspace: Workspace; @@ -502,7 +490,6 @@ export enum Workspace { STANDARD = 'Standard', ATTRIBUTE_ANNOTATION = 'Attribute annotation', TAG_ANNOTATION = 'Tag annotation', - REVIEW_WORKSPACE = 'Review', } export enum GridColor { @@ -576,26 +563,6 @@ export interface ShortcutsState { normalizedKeyMap: Record; } -export enum ReviewStatus { - ACCEPTED = 'accepted', - REJECTED = 'rejected', - REVIEW_FURTHER = 'review_further', -} - -export interface ReviewState { - reviews: any[]; - issues: any[]; - frameIssues: any[]; - latestComments: string[]; - activeReview: any | null; - newIssuePosition: number[] | null; - issuesHidden: boolean; - fetching: { - reviewId: number | null; - issueId: number | null; - }; -} - export interface CombinedState { auth: AuthState; projects: ProjectsState; @@ -610,7 +577,6 @@ export interface CombinedState { annotation: AnnotationState; settings: SettingsState; shortcuts: ShortcutsState; - review: ReviewState; } export enum DimensionType { diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 6c6299bbfb98..91322c0b34e9 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -15,7 +15,6 @@ import { AnnotationActionTypes } from 'actions/annotation-actions'; import { NotificationsActionType } from 'actions/notification-actions'; import { BoundariesActionTypes } from 'actions/boundaries-actions'; import { UserAgreementsActionTypes } from 'actions/useragreements-actions'; -import { ReviewActionTypes } from 'actions/review-actions'; import { NotificationsState } from './interfaces'; @@ -96,14 +95,6 @@ const defaultState: NotificationsState = { userAgreements: { fetching: null, }, - review: { - commentingIssue: null, - finishingIssue: null, - initialization: null, - reopeningIssue: null, - resolvingIssue: null, - submittingReview: null, - }, predictor: { prediction: null, }, @@ -992,96 +983,6 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } - case ReviewActionTypes.INITIALIZE_REVIEW_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - initialization: { - message: 'Could not initialize review session', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } - case ReviewActionTypes.FINISH_ISSUE_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - finishingIssue: { - message: 'Could not open a new issue', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } - case ReviewActionTypes.RESOLVE_ISSUE_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - resolvingIssue: { - message: 'Could not resolve the issue', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } - case ReviewActionTypes.REOPEN_ISSUE_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - reopeningIssue: { - message: 'Could not reopen the issue', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } - case ReviewActionTypes.COMMENT_ISSUE_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - commentingIssue: { - message: 'Could not comment the issue', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } - case ReviewActionTypes.SUBMIT_REVIEW_FAILED: { - return { - ...state, - errors: { - ...state.errors, - review: { - ...state.errors.review, - submittingReview: { - message: 'Could not submit review session to the server', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } case NotificationsActionType.RESET_ERRORS: { return { ...state, diff --git a/cvat-ui/src/reducers/review-reducer.ts b/cvat-ui/src/reducers/review-reducer.ts deleted file mode 100644 index 919ea7bad54c..000000000000 --- a/cvat-ui/src/reducers/review-reducer.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import consts from 'consts'; -import { AnnotationActionTypes } from 'actions/annotation-actions'; -import { ReviewActionTypes } from 'actions/review-actions'; -import { AuthActionTypes } from 'actions/auth-actions'; -import { ReviewState } from './interfaces'; - -const defaultState: ReviewState = { - reviews: [], // saved on the server - issues: [], // saved on the server - latestComments: [], - frameIssues: [], // saved on the server and not saved on the server - activeReview: null, // not saved on the server - newIssuePosition: null, - issuesHidden: false, - fetching: { - reviewId: null, - issueId: null, - }, -}; - -function computeFrameIssues(issues: any[], activeReview: any, frame: number): any[] { - const combinedIssues = activeReview ? issues.concat(activeReview.issues) : issues; - return combinedIssues.filter((issue: any): boolean => issue.frame === frame); -} - -export default function (state: ReviewState = defaultState, action: any): ReviewState { - switch (action.type) { - case AnnotationActionTypes.GET_JOB_SUCCESS: { - const { - reviews, - issues, - frameData: { number: frame }, - } = action.payload; - const frameIssues = computeFrameIssues(issues, state.activeReview, frame); - - return { - ...state, - reviews, - issues, - frameIssues, - }; - } - case AnnotationActionTypes.CHANGE_FRAME: { - return { - ...state, - newIssuePosition: null, - }; - } - case ReviewActionTypes.SUBMIT_REVIEW: { - const { reviewId } = action.payload; - return { - ...state, - fetching: { - ...state.fetching, - reviewId, - }, - }; - } - case ReviewActionTypes.SUBMIT_REVIEW_SUCCESS: { - return { - ...state, - fetching: { - ...state.fetching, - reviewId: null, - }, - }; - } - case ReviewActionTypes.SUBMIT_REVIEW_FAILED: { - return { - ...state, - fetching: { - ...state.fetching, - reviewId: null, - }, - }; - } - case AnnotationActionTypes.CHANGE_FRAME_SUCCESS: { - const { number: frame } = action.payload; - return { - ...state, - frameIssues: computeFrameIssues(state.issues, state.activeReview, frame), - }; - } - case ReviewActionTypes.INITIALIZE_REVIEW_SUCCESS: { - const { reviewInstance, frame } = action.payload; - const frameIssues = computeFrameIssues(state.issues, reviewInstance, frame); - - return { - ...state, - activeReview: reviewInstance, - frameIssues, - }; - } - case ReviewActionTypes.START_ISSUE: { - const { position } = action.payload; - return { - ...state, - newIssuePosition: position, - }; - } - case ReviewActionTypes.FINISH_ISSUE_SUCCESS: { - const { frame, issue } = action.payload; - const frameIssues = computeFrameIssues(state.issues, state.activeReview, frame); - - return { - ...state, - latestComments: state.latestComments.includes(issue.comments[0].message) ? - state.latestComments : - Array.from( - new Set( - [...state.latestComments, issue.comments[0].message].filter( - (message: string): boolean => - ![ - consts.QUICK_ISSUE_INCORRECT_POSITION_TEXT, - consts.QUICK_ISSUE_INCORRECT_ATTRIBUTE_TEXT, - ].includes(message), - ), - ), - ).slice(-consts.LATEST_COMMENTS_SHOWN_QUICK_ISSUE), - frameIssues, - newIssuePosition: null, - }; - } - case ReviewActionTypes.CANCEL_ISSUE: { - return { - ...state, - newIssuePosition: null, - }; - } - case ReviewActionTypes.COMMENT_ISSUE: - case ReviewActionTypes.RESOLVE_ISSUE: - case ReviewActionTypes.REOPEN_ISSUE: { - const { issueId } = action.payload; - return { - ...state, - fetching: { - ...state.fetching, - issueId, - }, - }; - } - case ReviewActionTypes.COMMENT_ISSUE_FAILED: - case ReviewActionTypes.RESOLVE_ISSUE_FAILED: - case ReviewActionTypes.REOPEN_ISSUE_FAILED: { - return { - ...state, - fetching: { - ...state.fetching, - issueId: null, - }, - }; - } - case ReviewActionTypes.RESOLVE_ISSUE_SUCCESS: - case ReviewActionTypes.REOPEN_ISSUE_SUCCESS: - case ReviewActionTypes.COMMENT_ISSUE_SUCCESS: { - const { issues, frameIssues } = state; - - return { - ...state, - issues: [...issues], - frameIssues: [...frameIssues], - fetching: { - ...state.fetching, - issueId: null, - }, - }; - } - case ReviewActionTypes.SWITCH_ISSUES_HIDDEN_FLAG: { - const { hidden } = action.payload; - return { - ...state, - issuesHidden: hidden, - }; - } - case AnnotationActionTypes.CLOSE_JOB: - case AuthActionTypes.LOGOUT_SUCCESS: { - return { ...defaultState }; - } - default: - return state; - } - - return state; -} diff --git a/cvat-ui/src/reducers/root-reducer.ts b/cvat-ui/src/reducers/root-reducer.ts index 04358b44e636..2db8916a09d6 100644 --- a/cvat-ui/src/reducers/root-reducer.ts +++ b/cvat-ui/src/reducers/root-reducer.ts @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Intel Corporation +// Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT @@ -16,7 +16,6 @@ import annotationReducer from './annotation-reducer'; import settingsReducer from './settings-reducer'; import shortcutsReducer from './shortcuts-reducer'; import userAgreementsReducer from './useragreements-reducer'; -import reviewReducer from './review-reducer'; export default function createRootReducer(): Reducer { return combineReducers({ @@ -33,6 +32,5 @@ export default function createRootReducer(): Reducer { settings: settingsReducer, shortcuts: shortcutsReducer, userAgreements: userAgreementsReducer, - review: reviewReducer, }); } diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index 5460080bf09a..378093883c42 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -158,12 +158,6 @@ const defaultKeyMap = ({ sequences: ['shift+n', 'n'], action: 'keydown', }, - OPEN_REVIEW_ISSUE: { - name: 'Open an issue', - description: 'Create a new issues in the review workspace', - sequences: ['n'], - action: 'keydown', - }, SWITCH_MERGE_MODE: { name: 'Merge mode', description: 'Activate or deactivate mode to merging shapes', diff --git a/cvat-ui/src/utils/web3wallets.ts b/cvat-ui/src/utils/web3wallets.ts index cc0bfabe8987..ea3fa58b4302 100644 --- a/cvat-ui/src/utils/web3wallets.ts +++ b/cvat-ui/src/utils/web3wallets.ts @@ -1,7 +1,6 @@ // Copyright (C) 2021 Intel Corporation // // SPDX-License-Identifier: MIT -import Authereum from 'authereum'; import MewConnect from '@myetherwallet/mewconnect-web-client'; import WalletConnectProvider from '@walletconnect/web3-provider'; @@ -16,9 +15,6 @@ const providerOptions = { rpc: `wss://${process.env.WEB3_NETWORK}.infura.io/ws/v3/${process.env.INFURA_ID}`, }, }, - authereum: { - package: Authereum, - }, walletconnect: { package: WalletConnectProvider, options: { diff --git a/cvat/apps/authentication/auth.py b/cvat/apps/authentication/auth.py index dcdf456092a3..e5a222898492 100644 --- a/cvat/apps/authentication/auth.py +++ b/cvat/apps/authentication/auth.py @@ -92,11 +92,6 @@ def is_project_annotator(db_user, db_project): db_tasks = list(db_project.tasks.prefetch_related('segment_set').all()) return any([is_task_annotator(db_user, db_task) for db_task in db_tasks]) -@rules.predicate -def is_project_reviewer(db_user, db_project): - db_tasks = list(db_project.tasks.prefetch_related('segment_set').all()) - return any([is_task_reviewer(db_user, db_task) for db_task in db_tasks]) - @rules.predicate def is_task_owner(db_user, db_task): # If owner is None (null) the task can be accessed/changed/deleted @@ -107,12 +102,6 @@ def is_task_owner(db_user, db_task): def is_task_assignee(db_user, db_task): return db_task.assignee == db_user or is_project_assignee(db_user, db_task.project) -@rules.predicate -def is_task_reviewer(db_user, db_task): - db_segments = list(db_task.segment_set.prefetch_related('job_set__assignee').all()) - return any([is_job_reviewer(db_user, db_job) - for db_segment in db_segments for db_job in db_segment.job_set.all()]) - @rules.predicate def is_task_annotator(db_user, db_task): db_segments = list(db_task.segment_set.prefetch_related('job_set__assignee').all()) @@ -140,26 +129,9 @@ def has_change_permissions(db_user, db_job): has_rights = (db_task.assignee is None and not settings.RESTRICTIONS['reduce_task_visibility']) or is_task_assignee(db_user, db_task) if db_job.assignee is not None: has_rights |= (db_user == db_job.assignee) and (db_job.status == 'annotation') - if db_job.reviewer is not None: - has_rights |= (db_user == db_job.reviewer) and (db_job.status == 'validation') return has_rights -@rules.predicate -def is_job_reviewer(db_user, db_job): - has_rights = db_job.reviewer == db_user - return has_rights - -@rules.predicate -def is_issue_owner(db_user, db_issue): - has_rights = db_issue.owner == db_user - return has_rights - -@rules.predicate -def is_comment_author(db_user, db_comment): - has_rights = (db_comment.author == db_user) - return has_rights - @rules.predicate def is_cloud_storage_owner(db_user, db_storage): return db_storage.owner == db_user @@ -179,21 +151,14 @@ def is_cloud_storage_owner(db_user, db_storage): rules.add_perm('engine.task.create', has_admin_role | has_user_role) rules.add_perm('engine.task.access', has_admin_role | has_observer_role | - is_task_owner | is_task_annotator | is_task_reviewer) + is_task_owner | is_task_annotator) rules.add_perm('engine.task.change', has_admin_role | is_task_owner | is_task_assignee) rules.add_perm('engine.task.delete', has_admin_role | is_task_owner) rules.add_perm('engine.job.access', has_admin_role | has_observer_role | - is_job_owner | is_job_annotator | is_job_reviewer) + is_job_owner | is_job_annotator) rules.add_perm('engine.job.change', has_admin_role | is_job_owner | has_change_permissions) -rules.add_perm('engine.job.review', has_admin_role | (is_job_reviewer & has_change_permissions)) - -rules.add_perm('engine.issue.change', has_admin_role | is_issue_owner) -rules.add_perm('engine.issue.destroy', has_admin_role | is_issue_owner) - -rules.add_perm('engine.comment.change', has_admin_role | is_comment_author) - rules.add_perm('engine.cloudstorage.create', has_admin_role | has_user_role) rules.add_perm('engine.cloudstorage.change', has_admin_role | is_cloud_storage_owner) @@ -259,8 +224,7 @@ def get_queryset(self): else: return queryset.filter(Q(owner=user) | Q(assignee=user) | Q(task__owner=user) | Q(task__assignee=user) | - Q(task__segment__job__assignee=user) | - Q(task__segment__job__reviewer=user)).distinct() + Q(task__segment__job__assignee=user)).distinct() def filter_task_queryset(queryset, user): # Don't filter queryset for admin, observer @@ -268,7 +232,7 @@ def filter_task_queryset(queryset, user): return queryset query_filter = Q(owner=user) | Q(assignee=user) | \ - Q(segment__job__assignee=user) | Q(segment__job__reviewer=user) + Q(segment__job__assignee=user) if not settings.RESTRICTIONS['reduce_task_visibility']: query_filter |= Q(assignee=None) @@ -304,39 +268,6 @@ class JobChangePermission(BasePermission): def has_object_permission(self, request, view, obj): return request.user.has_perm('engine.job.change', obj) -class JobReviewPermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): - return request.user.has_perm('engine.job.review', obj) - -class IssueAccessPermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): - db_job = obj.job - return request.user.has_perm('engine.job.access', db_job) - -class IssueDestroyPermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): - return request.user.has_perm('engine.issue.destroy', obj) - -class IssueChangePermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): - db_job = obj.job - return (request.user.has_perm('engine.job.change', db_job) - or request.user.has_perm('engine.issue.change', obj)) - -class CommentCreatePermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): # obj is db_job - return request.user.has_perm('engine.job.access', obj) - -class CommentChangePermission(BasePermission): - # pylint: disable=no-self-use - def has_object_permission(self, request, view, obj): - return request.user.has_perm('engine.comment.change', obj) - class CloudStorageAccessPermission(BasePermission): # pylint: disable=no-self-use def has_object_permission(self, request, view, obj): diff --git a/cvat/apps/engine/backup.py b/cvat/apps/engine/backup.py index da42cab6b305..9b17c2c60ff0 100644 --- a/cvat/apps/engine/backup.py +++ b/cvat/apps/engine/backup.py @@ -17,8 +17,7 @@ from cvat.apps.engine import models from cvat.apps.engine.log import slogger from cvat.apps.engine.serializers import (AttributeSerializer, DataSerializer, - LabeledDataSerializer, SegmentSerializer, SimpleJobSerializer, TaskSerializer, - ReviewSerializer, IssueSerializer, CommentSerializer) + LabeledDataSerializer, SegmentSerializer, SimpleJobSerializer, TaskSerializer) from cvat.apps.engine.utils import av_scan_paths from cvat.apps.engine.models import StorageChoice, StorageMethodChoice, DataChoice from cvat.apps.engine.task import _create_thread @@ -154,32 +153,6 @@ def _update_label(shape): return annotations - def _prepare_review_meta(self, review): - allowed_fields = { - 'estimated_quality', - 'status', - 'issues', - } - return self._prepare_meta(allowed_fields, review) - - def _prepare_issue_meta(self, issue): - allowed_fields = { - 'frame', - 'position', - 'created_date', - 'resolved_date', - 'comments', - } - return self._prepare_meta(allowed_fields, issue) - - def _prepare_comment_meta(self, comment): - allowed_fields = { - 'message', - 'created_date', - 'updated_date', - } - return self._prepare_meta(allowed_fields, comment) - def _get_db_jobs(self): if self._db_task: db_segments = list(self._db_task.segment_set.all().prefetch_related('job_set')) @@ -286,38 +259,11 @@ def serialize_task(): return task - def serialize_comment(db_comment): - comment_serializer = CommentSerializer(db_comment) - comment_serializer.fields.pop('author') - - return self._prepare_comment_meta(comment_serializer.data) - - def serialize_issue(db_issue): - issue_serializer = IssueSerializer(db_issue) - issue_serializer.fields.pop('owner') - issue_serializer.fields.pop('resolver') - - issue = issue_serializer.data - issue['comments'] = (serialize_comment(c) for c in db_issue.comment_set.order_by('id')) - - return self._prepare_issue_meta(issue) - - def serialize_review(db_review): - review_serializer = ReviewSerializer(db_review) - review_serializer.fields.pop('reviewer') - review_serializer.fields.pop('assignee') - - review = review_serializer.data - review['issues'] = (serialize_issue(i) for i in db_review.issue_set.order_by('id')) - - return self._prepare_review_meta(review) - def serialize_segment(db_segment): db_job = db_segment.job_set.first() job_serializer = SimpleJobSerializer(db_job) job_serializer.fields.pop('url') job_serializer.fields.pop('assignee') - job_serializer.fields.pop('reviewer') job_data = self._prepare_job_meta(job_serializer.data) segment_serailizer = SegmentSerializer(db_segment) @@ -325,9 +271,6 @@ def serialize_segment(db_segment): segment = segment_serailizer.data segment.update(job_data) - db_reviews = db_job.review_set.order_by('id') - segment['reviews'] = (serialize_review(r) for r in db_reviews) - return segment def serialize_jobs(): @@ -440,40 +383,6 @@ def _calculate_segment_size(jobs): def _import_task(self): - def _create_comment(comment, db_issue): - comment['issue'] = db_issue.id - comment_serializer = CommentSerializer(data=comment) - comment_serializer.is_valid(raise_exception=True) - db_comment = comment_serializer.save() - return db_comment - - def _create_issue(issue, db_review, db_job): - issue['review'] = db_review.id - issue['job'] = db_job.id - comments = issue.pop('comments') - - issue_serializer = IssueSerializer(data=issue) - issue_serializer.is_valid( raise_exception=True) - db_issue = issue_serializer.save() - - for comment in comments: - _create_comment(comment, db_issue) - - return db_issue - - def _create_review(review, db_job): - review['job'] = db_job.id - issues = review.pop('issues') - - review_serializer = ReviewSerializer(data=review) - review_serializer.is_valid(raise_exception=True) - db_review = review_serializer.save() - - for issue in issues: - _create_issue(issue, db_review, db_job) - - return db_review - data = self._manifest.pop('data') labels = self._manifest.pop('labels') jobs = self._manifest.pop('jobs') @@ -529,9 +438,6 @@ def _create_review(review, db_job): db_job.status = job['status'] db_job.save() - for review in job['reviews']: - _create_review(review, db_job) - def _import_annotations(self): db_jobs = self._get_db_jobs() for db_job, annotations in zip(db_jobs, self._annotations): diff --git a/cvat/apps/engine/migrations/0042_delete_review_issue_comment.py b/cvat/apps/engine/migrations/0042_delete_review_issue_comment.py new file mode 100644 index 000000000000..2a9117609719 --- /dev/null +++ b/cvat/apps/engine/migrations/0042_delete_review_issue_comment.py @@ -0,0 +1,54 @@ +# Generated by Django 3.1.13 on 2021-09-17 10:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0041_task_is_exchange_notified'), + ] + + operations = [ + migrations.RemoveField( + model_name='issue', + name='job', + ), + migrations.RemoveField( + model_name='issue', + name='owner', + ), + migrations.RemoveField( + model_name='issue', + name='resolver', + ), + migrations.RemoveField( + model_name='issue', + name='review', + ), + migrations.RemoveField( + model_name='review', + name='assignee', + ), + migrations.RemoveField( + model_name='review', + name='job', + ), + migrations.RemoveField( + model_name='review', + name='reviewer', + ), + migrations.RemoveField( + model_name='job', + name='reviewer', + ), + migrations.DeleteModel( + name='Comment', + ), + migrations.DeleteModel( + name='Issue', + ), + migrations.DeleteModel( + name='Review', + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index b0f3ce486664..445823ea8630 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -328,7 +328,6 @@ class Meta: class Job(models.Model): segment = models.ForeignKey(Segment, on_delete=models.CASCADE) assignee = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) - reviewer = models.ForeignKey(User, null=True, blank=True, related_name='review_job_set', on_delete=models.SET_NULL) status = models.CharField(max_length=32, choices=StatusChoice.choices(), default=StatusChoice.ANNOTATION) @@ -419,18 +418,6 @@ def choices(self): def __str__(self): return self.value -class ReviewStatus(str, Enum): - ACCEPTED = 'accepted' - REJECTED = 'rejected' - REVIEW_FURTHER = 'review_further' - - @classmethod - def choices(self): - return tuple((x.value, x.name) for x in self) - - def __str__(self): - return self.value - class Annotation(models.Model): id = models.BigAutoField(primary_key=True) job = models.ForeignKey(Job, on_delete=models.CASCADE) @@ -516,30 +503,6 @@ class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) rating = models.FloatField(default=0.0) -class Review(models.Model): - job = models.ForeignKey(Job, on_delete=models.CASCADE) - reviewer = models.ForeignKey(User, null=True, blank=True, related_name='reviews', on_delete=models.SET_NULL) - assignee = models.ForeignKey(User, null=True, blank=True, related_name='reviewed', on_delete=models.SET_NULL) - estimated_quality = models.FloatField() - status = models.CharField(max_length=16, choices=ReviewStatus.choices()) - -class Issue(models.Model): - frame = models.PositiveIntegerField() - position = FloatArrayField() - job = models.ForeignKey(Job, on_delete=models.CASCADE) - review = models.ForeignKey(Review, null=True, blank=True, on_delete=models.SET_NULL) - owner = models.ForeignKey(User, null=True, blank=True, related_name='issues', on_delete=models.SET_NULL) - resolver = models.ForeignKey(User, null=True, blank=True, related_name='resolved_issues', on_delete=models.SET_NULL) - created_date = models.DateTimeField(auto_now_add=True) - resolved_date = models.DateTimeField(null=True, blank=True) - -class Comment(models.Model): - issue = models.ForeignKey(Issue, on_delete=models.CASCADE) - author = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) - message = models.TextField(default='') - created_date = models.DateTimeField(auto_now_add=True) - updated_date = models.DateTimeField(auto_now=True) - class CloudProviderChoice(str, Enum): AWS_S3 = 'AWS_S3_BUCKET' AZURE_CONTAINER = 'AZURE_CONTAINER' diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 34c51efc12e8..873d69a1ca7c 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -144,25 +144,21 @@ class JobSerializer(serializers.ModelSerializer): stop_frame = serializers.ReadOnlyField(source="segment.stop_frame") assignee = BasicUserSerializer(allow_null=True, required=False) assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - reviewer = BasicUserSerializer(allow_null=True, required=False) - reviewer_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) class Meta: model = models.Job - fields = ('url', 'id', 'assignee', 'assignee_id', 'reviewer', - 'reviewer_id', 'status', 'start_frame', 'stop_frame', 'task_id') - read_only_fields = ('assignee', 'reviewer') + fields = ('url', 'id', 'assignee', 'assignee_id', + 'status', 'start_frame', 'stop_frame', 'task_id') + read_only_fields = ('assignee', ) class SimpleJobSerializer(serializers.ModelSerializer): assignee = BasicUserSerializer(allow_null=True) assignee_id = serializers.IntegerField(write_only=True, allow_null=True) - reviewer = BasicUserSerializer(allow_null=True, required=False) - reviewer_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) class Meta: model = models.Job - fields = ('url', 'id', 'assignee', 'assignee_id', 'reviewer', 'reviewer_id', 'status') - read_only_fields = ('assignee', 'reviewer') + fields = ('url', 'id', 'assignee', 'assignee_id', 'status') + read_only_fields = ('assignee', ) class SegmentSerializer(serializers.ModelSerializer): jobs = SimpleJobSerializer(many=True, source='job_set') @@ -711,67 +707,6 @@ class AnnotationFileSerializer(serializers.Serializer): class TaskFileSerializer(serializers.Serializer): task_file = serializers.FileField() -class ReviewSerializer(serializers.ModelSerializer): - assignee = BasicUserSerializer(allow_null=True, required=False) - assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - reviewer = BasicUserSerializer(allow_null=True, required=False) - reviewer_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - - class Meta: - model = models.Review - fields = '__all__' - read_only_fields = ('id', 'assignee', 'reviewer', ) - write_once_fields = ('job', 'reviewer_id', 'assignee_id', 'estimated_quality', 'status', ) - ordering = ['-id'] - -class IssueSerializer(serializers.ModelSerializer): - owner = BasicUserSerializer(allow_null=True, required=False) - owner_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - resolver = BasicUserSerializer(allow_null=True, required=False) - resolver_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - - position = serializers.ListField( - child=serializers.FloatField(), - allow_empty=False, - ) - - class Meta: - model = models.Issue - fields = '__all__' - read_only_fields = ('created_date', 'id', 'owner', 'resolver', ) - write_once_fields = ('frame', 'position', 'job', 'owner_id', 'review', ) - ordering = ['-id'] - -class CommentSerializer(serializers.ModelSerializer): - author = BasicUserSerializer(allow_null=True, required=False) - author_id = serializers.IntegerField(write_only=True, allow_null=True, required=False) - - class Meta: - model = models.Comment - fields = '__all__' - read_only_fields = ('created_date', 'updated_date', 'id', 'author', ) - write_once_fields = ('issue', 'author_id', ) - -class CombinedIssueSerializer(IssueSerializer): - comment_set = CommentSerializer(many=True) - -class CombinedReviewSerializer(ReviewSerializer): - issue_set = CombinedIssueSerializer(many=True) - - def create(self, validated_data): - issues_validated_data = validated_data.pop('issue_set') - db_review = models.Review.objects.create(**validated_data) - for issue in issues_validated_data: - issue['review'] = db_review - - comments_validated_data = issue.pop('comment_set') - db_issue = models.Issue.objects.create(**issue) - for comment in comments_validated_data: - comment['issue'] = db_issue - models.Comment.objects.create(**comment) - - return db_review - class BaseCloudStorageSerializer(serializers.ModelSerializer): owner = BasicUserSerializer(required=False) class Meta: diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index d87a2a1b7ab8..b3d1196ffc3f 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -395,227 +395,6 @@ def test_api_v1_jobs_id_admin_partial(self): response = self._run_api_v1_jobs_id(self.job.id, self.owner, data) self._check_request(response, data) -class JobReview(APITestCase): - def setUp(self): - self.client = APIClient() - - @classmethod - def setUpTestData(cls): - create_db_users(cls) - cls.task = create_dummy_db_tasks(cls)[0] - cls.job = Job.objects.filter(segment__task_id=cls.task.id).first() - cls.reviewer = cls.annotator - cls.job.reviewer = cls.reviewer - cls.job.assignee = cls.assignee - cls.job.save() - cls.reject_review_data = { - "job": cls.job.id, - "issue_set": [ - { - "position": [ - 50, 50, 100, 100 - ], - "comment_set": [ - { - "message": "This is wrong!" - }, { - "message": "This is wrong 2!" - } - ], - "frame": 0 - } - ], - "estimated_quality": 3, - "status": "rejected" - } - - cls.accept_review_data = { - "job": cls.job.id, - "issue_set": [], - "estimated_quality": 5, - "status": "accepted" - } - - cls.review_further_data = { - "job": cls.job.id, - "issue_set": [], - "estimated_quality": 4, - "status": "review_further", - "reviewer_id": cls.reviewer.id - } - - cls.create_comment_data = [{ - "message": "This is testing message" - }, { - "message": "This is testing message 2" - }, { - "message": "This is testing message 3" - }] - - def _post_request(self, path, user, data): - with ForceLogin(user, self.client): - response = self.client.post(path, data=data, format='json') - - return response - - def _patch_request(self, path, user, data): - with ForceLogin(user, self.client): - response = self.client.patch(path, data=data, format='json') - - return response - - def _get_request(self, path, user): - with ForceLogin(user, self.client): - response = self.client.get(path) - - return response - - def _delete_request(self, path, user): - with ForceLogin(user, self.client): - response = self.client.delete(path) - - return response - - def _fetch_job_from_db(self): - self.job = Job.objects.prefetch_related( - 'review_set', - 'review_set__issue_set', - 'review_set__issue_set__comment_set').filter(segment__task_id=self.task.id).first() - - def _set_annotation_status(self): - self._patch_request('/api/v1/jobs/{}'.format(self.job.id), self.admin, {'status': 'annotation'}) - - def _set_validation_status(self): - self._patch_request('/api/v1/jobs/{}'.format(self.job.id), self.admin, {'status': 'validation'}) - - def test_api_v1_job_annotation_review(self): - self._set_annotation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.accept_review_data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - response = self._post_request('/api/v1/reviews', self.assignee, self.accept_review_data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_api_v1_job_validation_review_create(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.accept_review_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._fetch_job_from_db() - self.assertEqual(self.job.status, 'completed') - response = self._post_request('/api/v1/reviews', self.assignee, self.accept_review_data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.job.review_set.first().delete() - - def test_api_v1_job_reject_review(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.reject_review_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._fetch_job_from_db() - self.assertEqual(self.job.status, 'annotation') - self.job.review_set.first().delete() - - def test_api_v1_job_review_further(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.review_further_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self._fetch_job_from_db() - self.assertEqual(self.job.status, 'validation') - self.job.review_set.first().delete() - - def test_api_v1_create_review_comment(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.reject_review_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - issue_id = response.data['issue_set'][0]['id'] - comments = self.create_comment_data[:] - for comment in comments: - comment.update({ - 'issue': issue_id - }) - response = self._post_request('/api/v1/comments', self.assignee, comment) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self._get_request('/api/v1/issues/{}/comments'.format(issue_id), self.reviewer) - self.assertIsInstance(response.data, cls = list) - self.assertEqual(len(response.data), 5) - self.job.review_set.all().delete() - self.job.issue_set.all().delete() - - def test_api_v1_edit_review_comment(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.reject_review_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - issue_id = response.data['issue_set'][0]['id'] - comments = self.create_comment_data[:] - for comment in comments: - comment.update({ - 'issue': issue_id - }) - response = self._post_request('/api/v1/comments', self.assignee, comment) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self._get_request('/api/v1/issues/{}/comments'.format(issue_id), self.reviewer) - last_comment = max(response.data, key=lambda comment: comment['id']) - last_comment.update({ - 'message': 'fixed message 3' - }) - last_comment.update({ - 'author_id': last_comment['author']['id'], - 'author': None - }) - response = self._patch_request('/api/v1/comments/{}'.format(last_comment['id']), self.reviewer, last_comment) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - response = self._patch_request('/api/v1/comments/{}'.format(last_comment['id']), self.assignee, last_comment) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['message'], last_comment['message']) - response = self._get_request('/api/v1/issues/{}/comments'.format(issue_id), self.reviewer) - updated_last_comment = max(response.data, key=lambda comment: comment['id']) - self.assertEqual(updated_last_comment['message'], last_comment['message']) - self.job.review_set.all().delete() - self.job.issue_set.all().delete() - - def test_api_v1_remove_comment(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.reject_review_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - issue_id = response.data['issue_set'][0]['id'] - comments = self.create_comment_data[:] - for comment in comments: - comment.update({ - 'issue': issue_id - }) - response = self._post_request('/api/v1/comments', self.assignee, comment) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self._get_request('/api/v1/issues/{}/comments'.format(issue_id), self.reviewer) - last_comment = max(response.data, key=lambda comment: comment['id']) - response = self._delete_request('/api/v1/comments/{}'.format(last_comment['id']), self.reviewer) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - response = self._delete_request('/api/v1/comments/{}'.format(last_comment['id']), self.assignee) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self._fetch_job_from_db() - ids = list(map(lambda comment: comment.id, self.job.issue_set.first().comment_set.all())) - self.assertNotIn(last_comment['id'], ids) - self.job.review_set.all().delete() - self.job.issue_set.all().delete() - - def test_api_v1_resolve_reopen_issue(self): - self._set_validation_status() - response = self._post_request('/api/v1/reviews', self.reviewer, self.reject_review_data) - response = self._get_request('/api/v1/jobs/{}/issues'.format(self.job.id), self.assignee) - issue_id = response.data[0]['id'] - - response = self._patch_request('/api/v1/issues/{}'.format(issue_id), self.assignee, {'resolver_id': self.assignee.id}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self._get_request('/api/v1/jobs/{}/issues'.format(self.job.id), self.assignee) - self.assertEqual(response.data[0]['resolver']['id'], self.assignee.id) - - response = self._patch_request('/api/v1/issues/{}'.format(issue_id), self.reviewer, {'resolver_id': None}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self._get_request('/api/v1/jobs/{}/issues'.format(self.job.id), self.assignee) - self.assertEqual(response.data[0]['resolver'], None) - - response = self._patch_request('/api/v1/issues/{}'.format(issue_id), self.reviewer, {'resolver_id': self.reviewer.id}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self._get_request('/api/v1/jobs/{}/issues'.format(self.job.id), self.reviewer) - self.assertEqual(response.data[0]['resolver']['id'], self.reviewer.id) - class ServerAboutAPITestCase(APITestCase): def setUp(self): self.client = APIClient() diff --git a/cvat/apps/engine/urls.py b/cvat/apps/engine/urls.py index e46228efae6f..8a91c23affc2 100644 --- a/cvat/apps/engine/urls.py +++ b/cvat/apps/engine/urls.py @@ -50,9 +50,6 @@ def _map_format_to_schema(request, scheme=None): router.register('jobs', views.JobViewSet) router.register('users', views.UserViewSet) router.register('server', views.ServerViewSet, basename='server') -router.register('reviews', views.ReviewViewSet) -router.register('issues', views.IssueViewSet) -router.register('comments', views.CommentViewSet) router.register('restrictions', RestrictionsViewSet, basename='restrictions') router.register('predict', PredictView, basename='predict') router.register('cloudstorages', views.CloudStorageViewSet) diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 3d6d154a23e3..7ba68a8cfd9d 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -45,8 +45,8 @@ from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer from cvat.apps.engine.frame_provider import FrameProvider from cvat.apps.engine.models import ( - Job, StatusChoice, Task, Project, Review, Issue, - Comment, StorageMethodChoice, ReviewStatus, StorageChoice, Image, + Job, StatusChoice, Task, Project, + StorageMethodChoice, StorageChoice, Image, CredentialsTypeChoice, CloudProviderChoice ) from cvat.apps.engine.models import CloudStorage as CloudStorageModel @@ -55,8 +55,7 @@ DataMetaSerializer, DataSerializer, ExceptionSerializer, FileInfoSerializer, JobSerializer, LabeledDataSerializer, LogEventSerializer, ProjectSerializer, ProjectSearchSerializer, ProjectWithoutTaskSerializer, - RqStatusSerializer, TaskSerializer, UserSerializer, PluginsSerializer, ReviewSerializer, - CombinedReviewSerializer, IssueSerializer, CombinedIssueSerializer, CommentSerializer, + RqStatusSerializer, TaskSerializer, UserSerializer, PluginsSerializer, CloudStorageSerializer, BaseCloudStorageSerializer, TaskFileSerializer,) from utils.dataset_manifest import ImageManifestManager from cvat.apps.engine.utils import av_scan_paths @@ -885,169 +884,6 @@ def annotations(self, request, pk): return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST) return Response(data) - @swagger_auto_schema(method='get', operation_summary='Method returns list of reviews for the job', - responses={'200': ReviewSerializer(many=True)} - ) - @action(detail=True, methods=['GET'], serializer_class=ReviewSerializer) - def reviews(self, request, pk): - db_job = self.get_object() - queryset = db_job.review_set - serializer = ReviewSerializer(queryset, context={'request': request}, many=True) - return Response(serializer.data) - - @swagger_auto_schema(method='get', operation_summary='Method returns list of issues for the job', - responses={'200': CombinedIssueSerializer(many=True)} - ) - @action(detail=True, methods=['GET'], serializer_class=CombinedIssueSerializer) - def issues(self, request, pk): - db_job = self.get_object() - queryset = db_job.issue_set - serializer = CombinedIssueSerializer(queryset, context={'request': request}, many=True) - return Response(serializer.data) - -@method_decorator(name='create', decorator=swagger_auto_schema(operation_summary='Submit a review for a job')) -@method_decorator(name='destroy', decorator=swagger_auto_schema(operation_summary='Method removes a review from a job')) -class ReviewViewSet(viewsets.GenericViewSet, mixins.DestroyModelMixin, mixins.CreateModelMixin): - queryset = Review.objects.all().order_by('id') - - def get_serializer_class(self): - if self.request.method == 'POST': - return CombinedReviewSerializer - else: - return ReviewSerializer - - def get_permissions(self): - permissions = [IsAuthenticated] - if self.request.method == 'POST': - permissions.append(auth.JobReviewPermission) - else: - permissions.append(auth.AdminRolePermission) - - return [perm() for perm in permissions] - - def create(self, request, *args, **kwargs): - job_id = request.data['job'] - db_job = get_object_or_404(Job, pk=job_id) - self.check_object_permissions(self.request, db_job) - - if request.data['status'] == ReviewStatus.REVIEW_FURTHER: - if 'reviewer_id' not in request.data: - return Response('Must provide a new reviewer', status=status.HTTP_400_BAD_REQUEST) - reviewer_id = request.data['reviewer_id'] - reviewer = get_object_or_404(User, pk=reviewer_id) - - request.data.update({ - 'reviewer_id': request.user.id, - }) - if db_job.assignee: - request.data.update({ - 'assignee_id': db_job.assignee.id, - }) - - issue_set = request.data['issue_set'] - for issue in issue_set: - issue['job'] = db_job.id - issue['owner_id'] = request.user.id - comment_set = issue['comment_set'] - for comment in comment_set: - comment['author_id'] = request.user.id - - serializer = self.get_serializer(data=request.data, partial=True) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - headers = self.get_success_headers(serializer.data) - - if serializer.data['status'] == ReviewStatus.ACCEPTED: - db_job.status = StatusChoice.COMPLETED - db_job.save() - elif serializer.data['status'] == ReviewStatus.REJECTED: - db_job.status = StatusChoice.ANNOTATION - db_job.save() - else: - db_job.reviewer = reviewer - db_job.save() - - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) - -@method_decorator(name='destroy', decorator=swagger_auto_schema(operation_summary='Method removes an issue from a job')) -@method_decorator(name='partial_update', decorator=swagger_auto_schema(operation_summary='Method updates an issue. It is used to resolve/reopen an issue')) -class IssueViewSet(viewsets.GenericViewSet, mixins.DestroyModelMixin, mixins.UpdateModelMixin): - queryset = Issue.objects.all().order_by('id') - http_method_names = ['get', 'patch', 'delete', 'options'] - - def get_serializer_class(self): - return IssueSerializer - - def partial_update(self, request, *args, **kwargs): - db_issue = self.get_object() - if 'resolver_id' in request.data and request.data['resolver_id'] and db_issue.resolver is None: - # resolve - db_issue.resolver = request.user - db_issue.resolved_date = datetime.now() - db_issue.save(update_fields=['resolver', 'resolved_date']) - elif 'resolver_id' in request.data and not request.data['resolver_id'] and db_issue.resolver is not None: - # reopen - db_issue.resolver = None - db_issue.resolved_date = None - db_issue.save(update_fields=['resolver', 'resolved_date']) - serializer = self.get_serializer(db_issue) - return Response(serializer.data) - - def get_permissions(self): - http_method = self.request.method - permissions = [IsAuthenticated] - - if http_method in SAFE_METHODS: - permissions.append(auth.IssueAccessPermission) - elif http_method in ['DELETE']: - permissions.append(auth.IssueDestroyPermission) - elif http_method in ['PATCH']: - permissions.append(auth.IssueChangePermission) - else: - permissions.append(auth.AdminRolePermission) - - return [perm() for perm in permissions] - - @swagger_auto_schema(method='get', operation_summary='The action returns all comments of a specific issue', - responses={'200': CommentSerializer(many=True)} - ) - @action(detail=True, methods=['GET'], serializer_class=CommentSerializer) - def comments(self, request, pk): - db_issue = self.get_object() - queryset = db_issue.comment_set - serializer = CommentSerializer(queryset, context={'request': request}, many=True) - return Response(serializer.data) - -@method_decorator(name='partial_update', decorator=swagger_auto_schema(operation_summary='Method updates comment in an issue')) -@method_decorator(name='destroy', decorator=swagger_auto_schema(operation_summary='Method removes a comment from an issue')) -class CommentViewSet(viewsets.GenericViewSet, - mixins.DestroyModelMixin, mixins.UpdateModelMixin, mixins.CreateModelMixin): - queryset = Comment.objects.all().order_by('id') - serializer_class = CommentSerializer - http_method_names = ['get', 'post', 'patch', 'delete', 'options'] - - def create(self, request, *args, **kwargs): - request.data.update({ - 'author_id': request.user.id, - }) - issue_id = request.data['issue'] - db_issue = get_object_or_404(Issue, pk=issue_id) - self.check_object_permissions(self.request, db_issue.job) - return super().create(request, args, kwargs) - - def get_permissions(self): - http_method = self.request.method - permissions = [IsAuthenticated] - - if http_method in ['PATCH', 'DELETE']: - permissions.append(auth.CommentChangePermission) - elif http_method in ['POST']: - permissions.append(auth.CommentCreatePermission) - else: - permissions.append(auth.AdminRolePermission) - - return [perm() for perm in permissions] - class UserFilter(filters.FilterSet): class Meta: model = User diff --git a/site/content/en/docs/administration/basics/REST_API_guide.md b/site/content/en/docs/administration/basics/REST_API_guide.md index 5ba6c65720b7..424039faefec 100644 --- a/site/content/en/docs/administration/basics/REST_API_guide.md +++ b/site/content/en/docs/administration/basics/REST_API_guide.md @@ -18,8 +18,6 @@ The HTTP protocol is used to transport a data. Requests are divided into groups: - `auth` - user authorization queries -- `comments` - requests to post/delete comments to issues -- `issues` - update, delete and view problem comments - `jobs` -requests to manage the job - `lambda` - requests to work with lambda function - `projects` - project management queries @@ -27,7 +25,7 @@ Requests are divided into groups: - `reviews` -adding and removing the review of the job - `server` - server information requests - `tasks` - requests to manage tasks -- `users` - user management queries +- `users` - user management queries Besides it contains `Models`. Models - the data type is described using a  diff --git a/site/content/en/docs/manual/advanced/review.md b/site/content/en/docs/manual/advanced/review.md deleted file mode 100644 index ce55733c4869..000000000000 --- a/site/content/en/docs/manual/advanced/review.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: 'Review' -linkTitle: 'Review' -weight: 22 ---- - -A special mode to check the annotation allows you to point to an object or area in the frame containing an error. -`Review mode` is not available in 3D tasks. -To go into review mode, you need to select `Request a review` in the menu and assign the user to run a check. - -![](/images/image194.jpg) - -After that, the job status will be changed to `validation` -and the reviewer will be able to open the task in review mode. -Review mode is a UI mode, there is a special "issue" tool which you can use to identify objects -or areas in the frame and describe the problem. - -- To do this, first click `open an issue` icon on the controls sidebar: - - ![](/images/image195.jpg) - -- Then click on an object in the frame to highlight the object or highlight the area by holding the left mouse button - and describe the problem. The object or area will be shaded in red. -- The created issue will appear in the workspace and in the `issues` tab on the objects sidebar. -- After you save the annotation, other users will be able to see the problem, comment on each issue - and change the status of the problem to `resolved`. -- You can use the arrows on the issues tab to navigate the frames that contain problems. - - ![](/images/image196_detrac.jpg) - -- Once all the problems are marked, save the annotation, open the menu and select "submit the review". - After that you'll see a form containing the verification statistics, - here you can give an assessment of the job and choose further actions: - - - Accept - changes the status of the job to `completed`. - - Review next – passes the job to another user for re-review. - - Reject - changes the status of the job to `annotation`. - - ![](/images/image197.jpg) diff --git a/tests/cypress/integration/actions_projects_models/case_98_models_page.js b/tests/cypress/integration/actions_projects_models/case_98_models_page.js index f6a122485eed..e2fb8400314a 100644 --- a/tests/cypress/integration/actions_projects_models/case_98_models_page.js +++ b/tests/cypress/integration/actions_projects_models/case_98_models_page.js @@ -8,7 +8,7 @@ context('Models page.', () => { const caseId = '51'; before(() => { - cy.visit('/'); + cy.visit('/admin'); cy.login(); }); diff --git a/tests/cypress/integration/actions_tasks/registration_involved/case_69_filters_sorting_jobs.js b/tests/cypress/integration/actions_tasks/registration_involved/case_69_filters_sorting_jobs.js index 8b9404b73b3e..f94f9670ba1c 100644 --- a/tests/cypress/integration/actions_tasks/registration_involved/case_69_filters_sorting_jobs.js +++ b/tests/cypress/integration/actions_tasks/registration_involved/case_69_filters_sorting_jobs.js @@ -36,13 +36,12 @@ context('Filters, sorting jobs.', () => { } } - function checkContentsRow(index, status, assignee, reviewer) { + function checkContentsRow(index, status, assignee) { cy.get('.cvat-task-jobs-table-row').then(($jobsTableRows) => { cy.get($jobsTableRows[index]).within(() => { cy.get('.cvat-job-item-status').invoke('text').should('equal', status); [ ['.cvat-job-assignee-selector', assignee], - ['.cvat-job-reviewer-selector', reviewer], ].forEach(([el, val]) => { cy.get(el).find('[type="search"]').invoke('val').should('equal', val); }); @@ -94,19 +93,6 @@ context('Filters, sorting jobs.', () => { cy.openTask(taskName); cy.assignJobToUser(0, Cypress.env('regularUserEmail')); cy.assignJobToUser(1, Cypress.env('regularUserEmail')); - cy.reviewJobToUser(1, Cypress.env('user')); - - // The first job is transferred to the validation status - cy.openJob(); - cy.interactMenu('Request a review'); - cy.get('.cvat-request-review-dialog') - .should('exist') - .within(() => { - cy.get('.cvat-user-search-field') - .find('[type="search"]') - .type(`${Cypress.env('user')}{Enter}`); - cy.contains('[type="button"]', 'Submit').click(); - }); // The first job is transferred to the complete status cy.openJob(1); @@ -124,47 +110,30 @@ context('Filters, sorting jobs.', () => { describe(`Testing "${labelName}".`, () => { it('Filtering jobs by status.', () => { testSetJobFilter({ column: '.cvat-job-item-status', menuItem: 'annotation' }); - checkJobsTableRowCount(1); - checkContentsRow(0, 'annotation', '', ''); + checkJobsTableRowCount(2); + checkContentsRow(1, 'annotation', ''); }); it('Filtering jobs by status and by assignee.', () => { testSetJobFilter({ column: '.cvat-job-item-assignee', menuItem: Cypress.env('regularUserEmail') }); - checkJobsTableRowCount(0); - testSetJobFilter({ column: '.cvat-job-item-assignee', reset: true }); checkJobsTableRowCount(1); + testSetJobFilter({ column: '.cvat-job-item-assignee', reset: true }); + checkJobsTableRowCount(2); + testSetJobFilter({ column: '.cvat-job-item-status', reset: true }); }); - it('Filtering jobs by status. Annotation and validation', () => { - testSetJobFilter({ column: '.cvat-job-item-status', menuItem: 'validation' }); + it('Filtering jobs by status. Annotation', () => { + testSetJobFilter({ column: '.cvat-job-item-status', menuItem: 'annotation' }); checkJobsTableRowCount(2); - checkContentsRow(0, 'validation', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(1, 'annotation', '', ''); + checkContentsRow(1, 'annotation', ''); }); - it('Filtering jobs by status. Annotation, validation, completed', () => { + it('Filtering jobs by status. Annotation, completed', () => { testSetJobFilter({ column: '.cvat-job-item-status', menuItem: 'completed' }); checkJobsTableRowCount(3); - checkContentsRow(0, 'validation', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(1, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(2, 'annotation', '', ''); + checkContentsRow(1, 'completed', Cypress.env('regularUserEmail')); + checkContentsRow(2, 'annotation', ''); testSetJobFilter({ column: '.cvat-job-item-status', reset: true }); // Reset filter by status }); - - it('Filtering jobs by reviewer and sort by ascending status.', () => { - testSetJobFilter({ column: '.cvat-job-item-reviewer', menuItem: Cypress.env('user') }); - checkContentsRow(0, 'validation', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(1, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - cy.contains('.cvat-job-item-status', 'Status').click(); - checkContentsRow(0, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(1, 'validation', Cypress.env('regularUserEmail'), Cypress.env('user')); - }); - - it('Filtering jobs by reviewer and sort by ascending status, assignee.', () => { - cy.contains('.cvat-job-item-status', 'Status').click(); - cy.contains('.cvat-job-item-assignee', 'Assignee').click(); - checkContentsRow(0, 'validation', Cypress.env('regularUserEmail'), Cypress.env('user')); - checkContentsRow(1, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - }); }); }); diff --git a/tests/cypress/integration/actions_users/issue_2524_2633_issue_not_reset_after_change_task_issue_point_firefox.js b/tests/cypress/integration/actions_users/issue_2524_2633_issue_not_reset_after_change_task_issue_point_firefox.js deleted file mode 100644 index a647febf4c17..000000000000 --- a/tests/cypress/integration/actions_users/issue_2524_2633_issue_not_reset_after_change_task_issue_point_firefox.js +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -/// - -import '../../support/preserve_cookies'; - -context("Some parts of the Redux state (issues) isn't reset after changing a task.", () => { - const issueId = '2524_2633'; - const labelName = `Issue ${issueId}`; - const taskName = { - firstTaskName: `First task issue ${issueId}`, - secondTaskName: `Second task issue ${issueId}`, - }; - const attrName = `Attr for ${labelName}`; - const textDefaultValue = 'Some default value for type Text'; - const imagesCount = 1; - const imageFileName = `image_${labelName.replace(' ', '_').toLowerCase()}`; - const width = 800; - const height = 800; - const posX = 10; - const posY = 10; - const color = 'gray'; - const archiveName = `${imageFileName}.zip`; - const archivePath = `cypress/fixtures/${archiveName}`; - const imagesFolder = `cypress/fixtures/${imageFileName}`; - const directoryToArchive = imagesFolder; - - const createIssueRectangle = { - type: 'rectangle', - description: 'rectangle issue', - firstX: 550, - firstY: 100, - secondX: 650, - secondY: 200, - }; - const createIssuePoint = { - type: 'point', - description: 'point issue', - firstX: 500, - firstY: 300, - }; - - before(() => { - cy.clearLocalStorageSnapshot(); - cy.imageGenerator(imagesFolder, imageFileName, width, height, color, posX, posY, labelName, imagesCount); - cy.createZipArchive(directoryToArchive, archivePath); - cy.visit('/admin'); - cy.login(); - cy.createAnnotationTask(taskName.firstTaskName, labelName, attrName, textDefaultValue, archiveName); - cy.goToTaskList(); - cy.createAnnotationTask(taskName.secondTaskName, labelName, attrName, textDefaultValue, archiveName); - }); - - beforeEach(() => { - cy.restoreLocalStorage(); - }); - - afterEach(() => { - cy.saveLocalStorage(); - }); - - after(() => { - cy.goToTaskList(); - cy.deleteTask(taskName.firstTaskName); - cy.reload(); - cy.closeModalUnsupportedPlatform(); - cy.deleteTask(taskName.secondTaskName); - }); - - describe(`Testing "${labelName}"`, () => { - it('Open first task and request to review.', () => { - cy.openTaskJob(taskName.firstTaskName); - cy.interactMenu('Request a review'); - cy.get('.cvat-request-review-dialog') - .should('exist') - .within(() => { - cy.get('.cvat-user-search-field').click(); - }); - cy.get('.ant-select-dropdown').within(() => { - cy.contains(new RegExp(`^${Cypress.env('user')}`)).click(); - }); - cy.contains('.cvat-request-review-dialog', 'Reviewer:').within(() => { - cy.contains('[type="button"]', 'Submit').click(); - }); - cy.url().should('include', '/tasks'); - }); - - it('Open job again and create an issue. Check issue 2633.', () => { - cy.openJob(); - cy.createIssueFromControlButton(createIssueRectangle); - cy.createIssueFromControlButton(createIssuePoint); // Issue 2633 - }); - - it('Open the second task. Open job. Issue not exist.', () => { - cy.goToTaskList(); - cy.openTaskJob(taskName.secondTaskName); - cy.get('.cvat-hidden-issue-label').should('not.exist'); - }); - }); -}); diff --git a/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js b/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js deleted file mode 100644 index ac1ffb5713ea..000000000000 --- a/tests/cypress/integration/actions_users/registration_involved/case_28_review_pipeline_feature.js +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright (C) 2020-2021 Intel Corporation -// -// SPDX-License-Identifier: MIT - -/// - -context('Review pipeline feature', () => { - const caseId = '28'; - const labelName = `Case ${caseId}`; - const taskName = 'Review pipeline feature'; - const attrName = `Attr for ${labelName}`; - const textDefaultValue = 'Some default value for type Text'; - const imagesCount = 30; - const imageFileName = `image_${labelName.replace(' ', '_').toLowerCase()}`; - const width = 800; - const height = 800; - const posX = 10; - const posY = 10; - const color = 'gray'; - const archiveName = `${imageFileName}.zip`; - const archivePath = `cypress/fixtures/${archiveName}`; - const imagesFolder = `cypress/fixtures/${imageFileName}`; - const directoryToArchive = imagesFolder; - const advancedConfigurationParams = { - multiJobs: true, - segmentSize: 10, - }; - - const createRectangleShape2Points = { - points: 'By 2 Points', - type: 'Shape', - labelName: labelName, - firstX: 250, - firstY: 350, - secondX: 350, - secondY: 450, - }; - - const createRectangleShape2PointsSecond = { - points: 'By 2 Points', - type: 'Shape', - labelName: labelName, - firstX: 400, - firstY: 350, - secondX: 500, - secondY: 450, - }; - - const createPointsShape = { - type: 'Shape', - labelName: labelName, - pointsMap: [{ x: 650, y: 350 }], - complete: true, - numberOfPoints: null, - }; - - const createPointsShapeSecond = { - type: 'Shape', - labelName: labelName, - pointsMap: [{ x: 700, y: 350 }], - complete: true, - numberOfPoints: null, - }; - - const createPointsShapeThird = { - type: 'Shape', - labelName: labelName, - pointsMap: [{ x: 750, y: 350 }], - complete: true, - numberOfPoints: null, - }; - - const createPointsShapeFourth = { - type: 'Shape', - labelName: labelName, - pointsMap: [{ x: 700, y: 400 }], - complete: true, - numberOfPoints: null, - }; - - const customeIssueDescription = 'Custom issue'; - - const createIssueRectangle = { - type: 'rectangle', - description: 'rectangle issue', - firstX: 550, - firstY: 100, - secondX: 650, - secondY: 200, - }; - - const createIssuePoint = { - type: 'point', - description: 'point issue', - firstX: 700, - firstY: 100, - }; - - before(() => { - cy.clearLocalStorageSnapshot(); - cy.imageGenerator(imagesFolder, imageFileName, width, height, color, posX, posY, labelName, imagesCount); - cy.createZipArchive(directoryToArchive, archivePath); - cy.visit('/'); - }); - - beforeEach(() => { - cy.restoreLocalStorage(); - }); - - afterEach(() => { - cy.saveLocalStorage(); - }); - - after(() => { - cy.deletingRegisteredUsers([Cypress.env('regularUserEmail'), Cypress.env('regularUser2Email')]); - cy.visit('/admin'); - cy.login(); - cy.deleteTask(taskName); - cy.logout(); - }); - - describe(`Testing "${labelName}"`, () => { - it('Registration of required users.', () => { - cy.userRegistration( - Cypress.env('regularUserEmail'), - Cypress.env('regularUserEmail'), - Cypress.env('regularUserWalletAddress'), - Cypress.env('regularUserSignedEmail'), - ); - cy.logout(); - cy.userRegistration( - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2WalletAddress'), - Cypress.env('regularUser2SignedEmail'), - ); - cy.logout(); - }); - - it('First user login. Create a task. Open the task. Assign to himself.', () => { - cy.visit('/admin'); - cy.login(); - cy.createAnnotationTask( - taskName, - labelName, - attrName, - textDefaultValue, - archiveName, - null, - advancedConfigurationParams, - ); - cy.openTask(taskName); - cy.assignTaskToUser(Cypress.env('user')); - cy.logout(); - }); - - it('Login the second, the third user. The task is missing.', () => { - cy.regularUserLogin( - Cypress.env('regularUserEmail'), - Cypress.env('regularUserWalletAddress'), - Cypress.env('regularUserSignedEmail'), - ); - cy.contains('.cvat-item-task-name', taskName).should('not.exist'); - cy.logout(); - cy.regularUserLogin( - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2WalletAddress'), - Cypress.env('regularUser2SignedEmail'), - ); - cy.contains('.cvat-item-task-name', taskName).should('not.exist'); - cy.logout(); - }); - - it('First user login. Assign the first job to the second user.', () => { - cy.visit('/admin'); - cy.login(); - cy.openTask(taskName); - cy.assignJobToUser(0, Cypress.env('regularUserEmail')); - cy.logout(); - }); - - it('Second user login. Open the task, open the job and annotates it.', () => { - cy.regularUserLogin( - Cypress.env('regularUserEmail'), - Cypress.env('regularUserWalletAddress'), - Cypress.env('regularUserSignedEmail'), - ); - cy.openTaskJob(taskName, 0, false); - cy.createRectangle(createRectangleShape2PointsSecond); - for (let i = 1; i < 4; i++) { - cy.createRectangle(createRectangleShape2Points); - cy.goToNextFrame(i); - } - - cy.intercept('POST', '/api/v1/server/logs').as('sendLogs'); - cy.interactMenu('Request a review'); - cy.contains('.cvat-modal-content-save-job', 'The job has unsaved annotations') - .should('exist') - .within(() => { - cy.contains('[type="button"]', 'OK').click(); - }); - cy.wait('@sendLogs').its('response.statusCode').should('equal', 201); - cy.get('.cvat-request-review-dialog') - .should('exist') - .within(() => { - cy.get('.cvat-user-search-field').click(); - }); - cy.get('.ant-select-dropdown').within(() => { - cy.contains(new RegExp(`^${Cypress.env('regularUser2Email')}`, 'g')).click(); - }); - cy.contains('.cvat-request-review-dialog', 'Reviewer:').within(() => { - cy.contains('[type="button"]', 'Submit').click(); - }); - cy.url().should('include', '/tasks'); - cy.contains('.cvat-task-details', taskName).should('exist'); - cy.checkJobStatus(0, 'validation', Cypress.env('regularUserEmail'), Cypress.env('regularUser2Email')); // Check status, assignee, reviewer of the job - - cy.openJob(0, false); - cy.get('.cvat-workspace-selector').should('have.text', 'Review'); - cy.changeWorkspace('Standard', labelName); - cy.createPoint(createPointsShape); - cy.saveJob('PATCH', 403); - cy.get('.cvat-notification-notice-save-annotations-failed') - .should('exist') - .within(() => { - cy.get('[data-icon="close"]').click(); // Close the notice. - }); - cy.goToTaskList(); - cy.logout(); - }); - - it('The third user opens the job. Review mode is opened automatically.', () => { - cy.regularUserLogin( - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2WalletAddress'), - Cypress.env('regularUser2SignedEmail'), - ); - cy.openTaskJob(taskName, 0, false); - cy.get('.cvat-workspace-selector').should('have.text', 'Review'); - - // Use quick issues "Incorrect position". Issue will be created immediately - cy.createIssueFromObject('#cvat_canvas_shape_1', 'Quick issue: incorrect position'); - cy.checkIssueLabel('Wrong position'); - - // Item submenu: "Quick issue ..." does not appear. - cy.get('#cvat_canvas_shape_2').trigger('mousemove').rightclick(); - cy.get('.cvat-canvas-context-menu') - .contains('.cvat-context-menu-item', 'Quick issue ...') - .should('not.exist'); - cy.get('.cvat-canvas-container').click(); // Close the context menu - }); - - it('Use quick issues "Incorrect position". Issue will be created immediately.', () => { - cy.createIssueFromObject('#cvat_canvas_shape_1', 'Quick issue: incorrect position'); - cy.checkIssueLabel('Wrong position'); - }); - - it('Item submenu: "Quick issue ..." does not appear.', () => { - cy.get('#cvat_canvas_shape_2').trigger('mousemove').rightclick(); - cy.get('.cvat-canvas-context-menu') - .contains('.cvat-context-menu-item', 'Quick issue ...') - .should('not.exist'); - cy.get('.cvat-canvas-container').click(); // Close the context menu - }); - - it('Create different issues with a custom text.', () => { - cy.createIssueFromObject('#cvat_canvas_shape_2', 'Open an issue ...', customeIssueDescription); - cy.checkIssueLabel(customeIssueDescription); - }); - - it('Now item submenu: "Quick issue ..." appears and it contains several latest options.', () => { - cy.get('#cvat_canvas_shape_1').trigger('mousemove', { force: true }).rightclick({ force: true }); - cy.get('.cvat-canvas-context-menu') - .contains('.cvat-context-menu-item', 'Quick issue ...') - .should('exist') - .trigger('mousemove') - .trigger('mouseover'); - cy.get('[id="quick_issue_from_latest$Menu"]').within(() => { - cy.contains('.cvat-context-menu-item', new RegExp(`^${customeIssueDescription}$`, 'g')) - .should('exist') - .and('have.text', customeIssueDescription); - }); - }); - - it('Use one of items to create quick issue on another object on another frame. Issue has been created.', () => { - cy.goCheckFrameNumber(2); - cy.createIssueFromObject('#cvat_canvas_shape_4', 'Quick issue: incorrect attribute'); - cy.checkIssueLabel('Wrong attribute'); - cy.goCheckFrameNumber(0); // Back to first frame - - cy.get('.cvat-canvas-container').should('exist'); - cy.checkIssueLabel(customeIssueDescription); - cy.checkIssueLabel('Wrong position'); - }); - - it('Use button on the left panel to create a couple of issues (in the first case draw a rectangle, in the second draw a point).', () => { - cy.createIssueFromControlButton(createIssueRectangle); - cy.createIssueFromControlButton(createIssuePoint); - }); - - it('Go to "Standard mode". Create a couple of objects. Save the job. Saving was successful.', () => { - cy.changeWorkspace('Standard', labelName); - cy.createPoint(createPointsShape); - cy.saveJob(); - cy.get('.cvat-notification-notice-save-annotations-failed').should('not.exist'); - }); - - it('Reject review. The third user was redirected to a task page. The job has status "annotation"', () => { - cy.interactMenu('Submit the review'); - cy.submitReview('Reject'); - cy.url().should('include', '/tasks'); - cy.contains('.cvat-task-details', taskName).should('exist'); - cy.checkJobStatus(0, 'annotation', Cypress.env('regularUserEmail'), Cypress.env('regularUser2Email')); - }); - - it("Reopen the job. Change something there. Save work. That saving wasn't successful. The third user logout.", () => { - cy.openJob(0, false); - cy.createPoint(createPointsShapeSecond); - cy.saveJob('PATCH', 403); - cy.get('.cvat-notification-notice-save-annotations-failed') - .should('exist') - .within(() => { - cy.get('[data-icon="close"]').click({ multiple: true }); // Close the notice. - }); - cy.goToTaskList(); - cy.logout(); - }); - - it('The second user login. Opens the job again. All issues are visible.', () => { - cy.regularUserLogin( - Cypress.env('regularUserEmail'), - Cypress.env('regularUserWalletAddress'), - Cypress.env('regularUserSignedEmail'), - ); - cy.openTaskJob(taskName, 0, false); - cy.get('.cvat-workspace-selector').should('have.text', 'Standard'); - for (const j of [ - customeIssueDescription, - 'Wrong position', - createIssueRectangle.description, - createIssuePoint.description, - ]) { - cy.checkIssueLabel(j); - } - cy.goCheckFrameNumber(2); - cy.checkIssueLabel('Wrong attribute'); - cy.goCheckFrameNumber(0); - }); - - it('Go to "Issues" tab at right sidebar and select an issue.', () => { - cy.get('.cvat-objects-sidebar').within(() => { - cy.contains('Issues').click(); - }); - cy.get('.cvat-objects-sidebar-issue-item').then((sidebarIssueItems) => { - cy.get('.cvat-hidden-issue-label').then((issueLabels) => { - expect(sidebarIssueItems.length).to.be.equal(issueLabels.length); - }); - }); - }); - - it('Select an issue on sidebar. Issue indication has changed the color for highlighted issue', () => { - cy.collectIssueRegionId().then(($issueRegionList) => { - for (const issueRegionID of $issueRegionList) { - const objectsSidebarIssueItem = `#cvat-objects-sidebar-issue-item-${issueRegionID}`; - const canvasIssueRegion = `#cvat_canvas_issue_region_${issueRegionID}`; - cy.get(objectsSidebarIssueItem).trigger('mousemove').trigger('mouseover'); - cy.get(canvasIssueRegion).should('have.attr', 'fill', 'url(#cvat_issue_region_pattern_2)'); - cy.get(objectsSidebarIssueItem).trigger('mouseout'); - cy.get(canvasIssueRegion).should('have.attr', 'fill', 'url(#cvat_issue_region_pattern_1)'); - } - }); - }); - - it('Issue navigation. Navigation works and go only to frames with issues.', () => { - cy.get('.cvat-issues-sidebar-previous-frame').should('have.attr', 'style').and('contain', 'opacity: 0.5;'); // The element is not active - cy.get('.cvat-issues-sidebar-next-frame').click(); - cy.checkFrameNum(2); // Frame changed to 2 - cy.get('.cvat-issues-sidebar-next-frame').should('have.attr', 'style').and('contain', 'opacity: 0.5;'); // The element is not active - cy.get('.cvat-issues-sidebar-previous-frame').click(); - cy.checkFrameNum(0); // Frame changed to 0 - }); - - it('Hide all issues. All issues are hidden on all frames.', () => { - cy.get('.cvat-issues-sidebar-shown-issues').click(); - cy.get('.cvat-hidden-issue-label').should('not.exist'); - cy.get('.cvat-issues-sidebar-next-frame').click(); - cy.get('.cvat-hidden-issue-label').should('not.exist'); - cy.get('.cvat-issues-sidebar-previous-frame').click(); - }); - - it('Display all the issues again. Comment a couple of issues and resolve all them.', () => { - function resolveReopenIssue(reopen) { - cy.collectIssueLabel().then((issueLabelList) => { - for (let label = 0; label < issueLabelList.length; label++) { - reopen - ? cy.resolveReopenIssue(issueLabelList[label], 'Please fix', true) - : cy.resolveReopenIssue(issueLabelList[label], 'Done'); - } - }); - } - - cy.get('.cvat-issues-sidebar-hidden-issues').click(); - cy.get('.cvat-hidden-issue-label').should('exist').and('have.length', 4); - cy.get('.cvat-issues-sidebar-next-frame').click(); - cy.get('.cvat-hidden-issue-label').should('exist').and('have.length', 1); - cy.get('.cvat-issues-sidebar-previous-frame').click(); - - resolveReopenIssue(); - cy.checkIssueLabel('Done', 'resolved'); - cy.goCheckFrameNumber(2); - resolveReopenIssue(); - cy.checkIssueLabel('Done', 'resolved'); - cy.goCheckFrameNumber(0); - - // Reopen issues - resolveReopenIssue(true); - // Resolve again - resolveReopenIssue(); - }); - - it('Request a review again. Assign the third user again. The second user logout.', () => { - cy.interactMenu('Request a review'); - cy.contains('.cvat-request-review-dialog', 'Reviewer:').within(() => { - cy.get('.cvat-user-search-field').within(() => { - cy.get('input[type="search"]').should('have.value', Cypress.env('regularUser2Email')); - }); - cy.contains('[type="button"]', 'Submit').click(); - }); - cy.logout(); - }); - - it('The third user login, opens the job, goes to menu, "Submit review" => "Review next" => Assign the first user => Submit.', () => { - cy.regularUserLogin( - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2WalletAddress'), - Cypress.env('regularUser2SignedEmail'), - ); - cy.openTaskJob(taskName, 0, false); - cy.interactMenu('Submit the review'); - cy.submitReview('Review next', Cypress.env('user')); - cy.get('.cvat-not-found').should('exist'); - }); - it('The third user logout. The first user login and opens the job, goes to menu, "Submit review" => Accept => Submit', () => { - cy.logout(); - cy.visit('/admin'); - cy.login(); - cy.openTaskJob(taskName, 0, false); - cy.interactMenu('Submit the review'); - cy.submitReview('Accept'); - cy.url().should('include', '/tasks'); - cy.contains('.cvat-task-details', taskName).should('exist'); - cy.checkJobStatus(0, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - }); - - it("The first user can change annotations. The second users can't change annotations. For the third user the task is not visible.", () => { - cy.openJob(0, false); - cy.createPoint(createPointsShapeThird); - cy.saveJob(); - cy.get('.cvat-notification-notice-save-annotations-failed').should('not.exist'); - cy.logout(); - cy.regularUserLogin( - Cypress.env('regularUserEmail'), - Cypress.env('regularUserWalletAddress'), - Cypress.env('regularUserSignedEmail'), - ); - cy.openTaskJob(taskName, 0, false); - cy.createPoint(createPointsShapeFourth); - cy.saveJob(); - cy.get('.cvat-notification-notice-save-annotations-failed').should('exist'); - cy.goToTaskList(); - cy.logout(); - cy.regularUserLogin( - Cypress.env('regularUser2Email'), - Cypress.env('regularUser2WalletAddress'), - Cypress.env('regularUser2SignedEmail'), - ); - cy.contains('strong', taskName).should('not.exist'); - cy.goToTaskList(); - cy.logout(); - }); - - it('The first user opens the job and presses "Renew the job".', () => { - cy.login(); - cy.openTaskJob(taskName, 0, false); - cy.interactMenu('Renew the job'); - cy.get('.cvat-modal-content-renew-job').within(() => { - cy.contains('button', 'Continue').click(); - }); - cy.url().should('include', '/tasks'); - cy.contains('.cvat-task-details', taskName).should('exist'); - cy.checkJobStatus(0, 'annotation', Cypress.env('regularUserEmail'), Cypress.env('user')); - }); - - it('The first user opens the job and presses "Finish the job".', () => { - cy.openJob(0, false); - cy.interactMenu('Finish the job'); - cy.get('.cvat-modal-content-finish-job').within(() => { - cy.contains('button', 'Continue').click(); - }); - cy.url().should('include', '/tasks'); - cy.contains('.cvat-task-details', taskName).should('exist'); - cy.checkJobStatus(0, 'completed', Cypress.env('regularUserEmail'), Cypress.env('user')); - }); - - it('In column "status" the job has question circle. The first user hover it, short statistics about reviews shown.', () => { - cy.get('.cvat-job-completed-color').within(() => { - cy.get('[aria-label="question-circle"]').trigger('mouseover'); - }); - let summary = []; - cy.get('.cvat-review-summary-description').within(() => { - cy.get('td').then(($td) => { - for (let i = 0; i < $td.length; i++) { - summary.push($td[i].outerText); - } - expect(Number(summary[1])).to.be.equal(3); // Reviews 3 - expect(Number(summary[5])).to.be.equal(0); // Unsolved issues 0 - expect(Number(summary[7])).to.be.equal(5); // Resolved issues 5 - }); - }); - }); - }); -}); diff --git a/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js b/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js index 529f03d3c78e..f421624f4019 100644 --- a/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js +++ b/tests/cypress/integration/actions_users/registration_involved/case_4_assign_taks_job_users.js @@ -22,15 +22,14 @@ context('Multiple users. Assign task, job. Deactivating users.', () => { const imagesFolder = `cypress/fixtures/${imageFileName}`; const directoryToArchive = imagesFolder; - let authKey; const isStaff = false; const isSuperuser = false; const isActive = false; function changeCheckUserStatusOpenTask(userName) { - cy.changeUserActiveStatus(authKey, userName, isActive); - cy.checkUserStatuses(authKey, userName, isStaff, isSuperuser, isActive); - cy.intercept('GET', `/api/v1/users*${thirdUserName}*`).as('users'); + cy.changeUserActiveStatus(userName, isActive); + cy.checkUserStatuses(userName, isStaff, isSuperuser, isActive); + cy.intercept('GET', `/api/v1/users*${Cypress.env()}*`).as('users'); cy.openTask(taskName); cy.wait('@users'); cy.get('.cvat-global-boundary').should('not.exist'); @@ -161,22 +160,13 @@ context('Multiple users. Assign task, job. Deactivating users.', () => { cy.logout(); }); - it('First user login. Getting authKey.', () => { - cy.visit('/'); - cy.intercept('POST', '/api/v1/auth/login').as('login'); - cy.login(); - cy.wait('@login').then((response) => { - authKey = response['response']['body']['key']; - }); - }); - it('Deactivate the second user (task assigned). Trying to open the task. Should be succefull.', () => { - changeCheckUserStatusOpenTask(secondUserName); + changeCheckUserStatusOpenTask(Cypress.env('regularUserEmail')); cy.goToTaskList(); }); it('Deactivate the third user (job assigned). Trying to open the task. Should be succefull.', () => { - changeCheckUserStatusOpenTask(thirdUserName); + changeCheckUserStatusOpenTask(Cypress.env('regularUser2Email')); }); }); }); diff --git a/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js b/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js index 39c17853168c..15968580ff95 100644 --- a/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js +++ b/tests/cypress/integration/canvas3d_functionality/case_56_canvas3d_functionality_basic_actions.js @@ -131,7 +131,6 @@ context('Canvas 3D functionality. Basic actions.', () => { for (const dropdownItems of [ '[title="Attribute annotation"]', '[title="Tag annotation"]', - '[title="Review"]', ]) { cy.get('.cvat-workspace-selector-dropdown') .not('.ant-select-dropdown-hidden') diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 9be7de765480..cec28e4dc3d1 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -108,11 +108,12 @@ Cypress.Commands.add('deletingRegisteredUsers', (accountToDelete) => { }); }); -Cypress.Commands.add('changeUserActiveStatus', (authKey, accountsToChangeActiveStatus, isActive) => { +Cypress.Commands.add('changeUserActiveStatus', (accountsToChangeActiveStatus, isActive) => { cy.request({ url: '/api/v1/users?page_size=all', - headers: { - Authorization: `Token ${authKey}`, + auth: { + username: Cypress.env('user'), + password: Cypress.env('password'), }, }).then((response) => { const responceResult = response['body']['results']; @@ -123,23 +124,25 @@ Cypress.Commands.add('changeUserActiveStatus', (authKey, accountsToChangeActiveS cy.request({ method: 'PATCH', url: `/api/v1/users/${userId}`, - headers: { - Authorization: `Token ${authKey}`, + auth: { + username: Cypress.env('user'), + password: Cypress.env('password'), + }, + body: { + is_active: isActive, }, - body: { - is_active: isActive, - }, }); } }); }); }); -Cypress.Commands.add('checkUserStatuses', (authKey, userName, staffStatus, superuserStatus, activeStatus) => { +Cypress.Commands.add('checkUserStatuses', (userName, staffStatus, superuserStatus, activeStatus) => { cy.request({ url: '/api/v1/users?page_size=all', - headers: { - Authorization: `Token ${authKey}`, + auth: { + username: Cypress.env('user'), + password: Cypress.env('password'), }, }).then((response) => { const responceResult = response['body']['results']; diff --git a/tests/cypress/support/commands_review_pipeline.js b/tests/cypress/support/commands_review_pipeline.js index e1add3e8a5d9..d90b7d97f158 100644 --- a/tests/cypress/support/commands_review_pipeline.js +++ b/tests/cypress/support/commands_review_pipeline.js @@ -26,19 +26,7 @@ Cypress.Commands.add('assignJobToUser', (jobID, user) => { .click(); }); -Cypress.Commands.add('reviewJobToUser', (jobID, user) => { - cy.getJobNum(jobID).then(($job) => { - cy.get('.cvat-task-jobs-table') - .contains('a', `Job #${$job}`) - .parents('.cvat-task-jobs-table-row') - .find('.cvat-job-reviewer-selector') - .find('[type="search"]') - .clear() - .type(`${user}{Enter}`); - }); -}); - -Cypress.Commands.add('checkJobStatus', (jobID, status, assignee, reviewer) => { +Cypress.Commands.add('checkJobStatus', (jobID, status, assignee) => { cy.getJobNum(jobID).then(($job) => { cy.get('.cvat-task-jobs-table') .contains('a', `Job #${$job}`) @@ -48,120 +36,6 @@ Cypress.Commands.add('checkJobStatus', (jobID, status, assignee, reviewer) => { cy.get('.cvat-job-assignee-selector').within(() => { cy.get('input[type="search"]').should('have.value', assignee); }); - cy.get('.cvat-job-reviewer-selector').within(() => { - cy.get('input[type="search"]').should('have.value', reviewer); - }); - }); - }); -}); - -Cypress.Commands.add('collectIssueLabel', () => { - cy.document().then((doc) => { - return Array.from(doc.querySelectorAll('.cvat-hidden-issue-label')); - }); -}); - -Cypress.Commands.add('checkIssueLabel', (issueDescription, status = 'unsolved') => { - cy.collectIssueLabel().then((issueLabelList) => { - for (let i = 0; i < issueLabelList.length; i++) { - cy.get(issueLabelList[i]) - .invoke('text') - .then((issueText) => { - if (issueText === issueDescription) { - cy.get(issueLabelList[i]) - .should('exist') - .and('have.text', issueDescription) - .within(() => { - cy.get(`.cvat-hidden-issue-${status}-indicator`).should('exist'); // "unsolved" or "resolved" only. - }); - } - }); - } - }); -}); - -Cypress.Commands.add('collectIssueRegionId', () => { - let issueRegionIdList = []; - cy.document().then((doc) => { - const issueRegionList = Array.from(doc.querySelectorAll('.cvat_canvas_issue_region')); - for (let i = 0; i < issueRegionList.length; i++) { - issueRegionIdList.push(Number(issueRegionList[i].id.match(/-?\d+$/))); - } - return issueRegionIdList; - }); -}); - -Cypress.Commands.add('checkIssueRegion', () => { - const sccSelectorIssueRegionId = '#cvat_canvas_issue_region_'; - cy.collectIssueRegionId().then((issueRegionIdList) => { - const maxId = Math.max(...issueRegionIdList); - cy.get(`${sccSelectorIssueRegionId}${maxId}`).trigger('mousemove').should('exist').and('be.visible'); - }); -}); - -Cypress.Commands.add('createIssueFromObject', (object, issueType, customeIssueDescription) => { - cy.get(object).trigger('mousemove').rightclick(); - cy.get('.cvat-canvas-context-menu').within(() => { - cy.contains('.cvat-context-menu-item', new RegExp(`^${issueType}$`, 'g')).click(); - }); - if (issueType === 'Open an issue ...') { - cy.get('.cvat-create-issue-dialog').within(() => { - cy.get('#issue_description').type(customeIssueDescription); - cy.get('[type="submit"]').click(); - }); - } else if (issueType === 'Quick issue ...') { - cy.get('[id="quick_issue_from_latest$Menu"]') - .should('be.visible') - .contains('.cvat-context-menu-item', new RegExp(`^${customeIssueDescription}$`, 'g')) - .click(); - } - cy.checkIssueRegion(); -}); - -Cypress.Commands.add('createIssueFromControlButton', (createIssueParams) => { - cy.get('.cvat-issue-control').click(); - if (createIssueParams.type === 'rectangle') { - cy.get('.cvat-canvas-container') - .trigger('mousedown', createIssueParams.firstX, createIssueParams.firstY, { button: 0 }) - .trigger('mousemove', createIssueParams.secondX, createIssueParams.secondY) - .trigger('mouseup'); - } else if (createIssueParams.type === 'point') { - cy.get('.cvat-canvas-container') - .trigger('mousedown', createIssueParams.firstX, createIssueParams.firstY, { button: 0 }) - .trigger('mouseup'); - } - cy.get('.cvat-create-issue-dialog').within(() => { - cy.get('#issue_description').type(createIssueParams.description); - cy.get('[type="submit"]').click(); - }); - cy.checkIssueRegion(); -}); - -Cypress.Commands.add('resolveReopenIssue', (issueLabel, resolveText, reopen) => { - cy.get(issueLabel).click(); - cy.intercept('POST', '/api/v1/comments').as('postComment'); - cy.intercept('PATCH', '/api/v1/issues/**').as('resolveReopenIssue'); - cy.get('.cvat-issue-dialog-input').type(resolveText); - cy.get('.cvat-issue-dialog-footer').within(() => { - cy.contains('button', 'Comment').click(); - reopen ? cy.contains('button', 'Reopen').click() : cy.contains('button', 'Resolve').click(); - }); - if (reopen) cy.get('.cvat-issue-dialog-header').find('[aria-label="close"]').click(); - cy.wait('@postComment').its('response.statusCode').should('equal', 201); - cy.wait('@resolveReopenIssue').its('response.statusCode').should('equal', 200); -}); - -Cypress.Commands.add('submitReview', (decision, user) => { - cy.get('.cvat-submit-review-dialog').within(() => { - cy.contains(new RegExp(`^${decision}$`, 'g')).click(); - if (decision === 'Review next') { - cy.intercept('GET', `/api/v1/users?search=${user}&limit=10&is_active=true`).as('searchUsers'); - cy.get('.cvat-user-search-field').within(() => { - cy.get('input[type="search"]').clear().type(`${user}`); - cy.wait('@searchUsers').its('response.statusCode').should('equal', 200); - cy.get('input[type="search"]').type('{Enter}'); }); - } - cy.contains('button', 'Submit').click(); }); });