diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c9ec5f2c2e..328e058bef53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hotkeys to switch a label of existing object or to change default label (for objects created with N) () - A script to convert some kinds of DICOM files to regular images () - Helm chart prototype () +- Initial implementation of moving tasks between projects () ### Changed diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index 2bdc11180320..776e8b2b84e6 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.13.0", + "version": "3.12.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-core/package.json b/cvat-core/package.json index e5e482fde5cb..9ed6e5e7cf03 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.13.0", + "version": "3.12.3", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "babel.config.js", "scripts": { diff --git a/cvat-core/src/common.js b/cvat-core/src/common.js index 80134d06f0be..ca30481d7909 100644 --- a/cvat-core/src/common.js +++ b/cvat-core/src/common.js @@ -104,6 +104,37 @@ } negativeIDGenerator.start = -1; + class FieldUpdateTrigger { + constructor(initialFields) { + const data = { ...initialFields }; + + Object.defineProperties( + this, + Object.freeze({ + ...Object.assign( + {}, + ...Array.from(Object.keys(data), (key) => ({ + [key]: { + get: () => data[key], + set: (value) => { + data[key] = value; + }, + enumerable: true, + }, + })), + ), + reset: { + value: () => { + Object.keys(data).forEach((key) => { + data[key] = false; + }); + }, + }, + }), + ); + } + } + module.exports = { isBoolean, isInteger, @@ -114,5 +145,6 @@ negativeIDGenerator, checkExclusiveFields, camelToSnake, + FieldUpdateTrigger, }; })(); diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index cf31969a302d..d2b63a9b11b1 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -1082,7 +1082,9 @@ const closureId = Date.now(); predictAnnotations.latestRequest.id = closureId; - const predicate = () => !predictAnnotations.latestRequest.fetching || predictAnnotations.latestRequest.id !== closureId; + const predicate = () => ( + !predictAnnotations.latestRequest.fetching || predictAnnotations.latestRequest.id !== closureId + ); if (predictAnnotations.latestRequest.fetching) { waitFor(5, predicate).then(() => { if (predictAnnotations.latestRequest.id !== closureId) { diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index daaee04a21ab..49dcb565f6fe 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -16,6 +16,7 @@ const User = require('./user'); const Issue = require('./issue'); const Review = require('./review'); + const { FieldUpdateTrigger } = require('./common'); function buildDublicatedAPI(prototype) { Object.defineProperties(prototype, { @@ -734,11 +735,11 @@ task: undefined, }; - let updatedFields = { + const updatedFields = new FieldUpdateTrigger({ assignee: false, reviewer: false, status: false, - }; + }); for (const property in data) { if (Object.prototype.hasOwnProperty.call(data, property)) { @@ -865,9 +866,6 @@ }, __updatedFields: { get: () => updatedFields, - set: (fields) => { - updatedFields = fields; - }, }, }), ); @@ -1040,13 +1038,14 @@ dimension: undefined, }; - let updatedFields = { + const updatedFields = new FieldUpdateTrigger({ name: false, assignee: false, bug_tracker: false, subset: false, labels: false, - }; + project_id: false, + }); for (const property in data) { if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { @@ -1126,11 +1125,18 @@ * @name projectId * @type {integer|null} * @memberof module:API.cvat.classes.Task - * @readonly * @instance */ projectId: { get: () => data.project_id, + set: (projectId) => { + if (!Number.isInteger(projectId) || projectId <= 0) { + throw new ArgumentError('Value must be a positive integer'); + } + + updatedFields.project_id = true; + data.project_id = projectId; + }, }, /** * @name status @@ -1558,9 +1564,6 @@ }, __updatedFields: { get: () => updatedFields, - set: (fields) => { - updatedFields = fields; - }, }, }), ); @@ -1721,11 +1724,7 @@ await serverProxy.jobs.save(this.id, jobData); - this.__updatedFields = { - status: false, - assignee: false, - reviewer: false, - }; + this.__updatedFields.reset(); return this; } @@ -2000,6 +1999,9 @@ case 'subset': taskData.subset = this.subset; break; + case 'project_id': + taskData.project_id = this.projectId; + break; case 'labels': taskData.labels = [...this._internalData.labels.map((el) => el.toJSON())]; break; @@ -2011,13 +2013,7 @@ await serverProxy.tasks.saveTask(this.id, taskData); - this.updatedFields = { - assignee: false, - name: false, - bugTracker: false, - subset: false, - labels: false, - }; + this.__updatedFields.reset(); return this; } diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index 3dcf75bcfbc8..cdb993cd1cb0 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -35,6 +35,7 @@ export enum TasksActionTypes { UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS', UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED', HIDE_EMPTY_TASKS = 'HIDE_EMPTY_TASKS', + SWITCH_MOVE_TASK_MODAL_VISIBLE = 'SWITCH_MOVE_TASK_MODAL_VISIBLE', } function getTasks(): AnyAction { @@ -519,3 +520,46 @@ export function hideEmptyTasks(hideEmpty: boolean): AnyAction { return action; } + +export function switchMoveTaskModalVisible(visible: boolean, taskId: number | null = null): AnyAction { + const action = { + type: TasksActionTypes.SWITCH_MOVE_TASK_MODAL_VISIBLE, + payload: { + taskId, + visible, + }, + }; + + return action; +} + +interface LabelMap { + label_id: number; + new_label_name: string | null; + clear_attributes: boolean; +} + +export function moveTaskToProjectAsync( + taskInstance: any, + projectId: any, + labelMap: LabelMap[], +): ThunkAction, {}, {}, AnyAction> { + return async (dispatch: ActionCreator): Promise => { + dispatch(updateTask()); + try { + // eslint-disable-next-line no-param-reassign + taskInstance.labels = labelMap.map((mapper) => { + const [label] = taskInstance.labels.filter((_label: any) => mapper.label_id === _label.id); + label.name = mapper.new_label_name; + return label; + }); + // eslint-disable-next-line no-param-reassign + taskInstance.projectId = projectId; + await taskInstance.save(); + const [task] = await cvat.tasks.get({ id: taskInstance.id }); + dispatch(updateTaskSuccess(task, task.id)); + } catch (error) { + dispatch(updateTaskFailed(error, taskInstance)); + } + }; +} diff --git a/cvat-ui/src/base.scss b/cvat-ui/src/base.scss index 9ef0e0ac60c5..4097dbe04692 100644 --- a/cvat-ui/src/base.scss +++ b/cvat-ui/src/base.scss @@ -13,6 +13,7 @@ $layout-lg-grid-color: rgba(0, 0, 0, 0.15); $header-color: #d8d8d8; $text-color: #303030; +$text-color-secondary: rgba(0, 0, 0, 0.45); $hover-menu-color: rgba(24, 144, 255, 0.05); $completed-progress-color: #61c200; $inprogress-progress-color: #1890ff; diff --git a/cvat-ui/src/components/actions-menu/actions-menu.tsx b/cvat-ui/src/components/actions-menu/actions-menu.tsx index 52e0f05bcb13..eed1db757567 100644 --- a/cvat-ui/src/components/actions-menu/actions-menu.tsx +++ b/cvat-ui/src/components/actions-menu/actions-menu.tsx @@ -33,6 +33,7 @@ export enum Actions { EXPORT_TASK_DATASET = 'export_task_dataset', DELETE_TASK = 'delete_task', RUN_AUTO_ANNOTATION = 'run_auto_annotation', + MOVE_TASK_TO_PROJECT = 'move_task_to_project', OPEN_BUG_TRACKER = 'open_bug_tracker', } @@ -128,6 +129,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element { Automatic annotation
+ Move to project Delete ); diff --git a/cvat-ui/src/components/actions-menu/load-submenu.tsx b/cvat-ui/src/components/actions-menu/load-submenu.tsx index 347ddbfaa2e2..6f72a7c14cd0 100644 --- a/cvat-ui/src/components/actions-menu/load-submenu.tsx +++ b/cvat-ui/src/components/actions-menu/load-submenu.tsx @@ -47,7 +47,12 @@ export default function LoadSubmenu(props: Props): JSX.Element { return false; }} > -