diff --git a/.eslintignore b/.eslintignore index 60bff4f5..54f42aa5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,3 +7,4 @@ datumaro/ keys/ logs/ static/ +templates/ diff --git a/.eslintrc.js b/.eslintrc.js index 27ecc3c8..b171226f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,14 +4,13 @@ module.exports = { env: { - node: false, + node: true, browser: true, es6: true, - jquery: true, - qunit: true, }, parserOptions: { - sourceType: 'script', + sourceType: 'module', + ecmaVersion: 2018, }, plugins: ['eslint-plugin-header'], extends: ['eslint:recommended', 'prettier'], diff --git a/.prettierignore b/.prettierignore index 60bff4f5..54f42aa5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ datumaro/ keys/ logs/ static/ +templates/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 506e3105..7efcd4e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,27 +1,22 @@ { "python.pythonPath": ".env/bin/python", "eslint.enable": true, - "eslint.validate": [ + "eslint.probe": [ "javascript", "typescript", - "typescriptreact", + "typescriptreact" ], + "eslint.onIgnoredFiles": "warn", "eslint.workingDirectories": [ { - "directory": "./cvat-core", - "changeProcessCWD": true + "directory": "${cwd}", }, { - "directory": "./cvat-canvas", - "changeProcessCWD": true + "pattern": "cvat-*" }, { - "directory": "./cvat-ui", - "changeProcessCWD": true - }, - { - "directory": ".", - "changeProcessCWD": true + "directory": "tests", + "!cwd": true } ], "python.linting.pylintEnabled": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a5cd4ec..eb9c68ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.0] - Unreleased +## [1.2.0-beta] - Unreleased + +### Added + +- + +### Changed + +- + +### Deprecated + +- + +### Removed + +- + +### Fixed + +- Django templates for email and user guide () + +### Security + +- + +## [1.2.0-alpha] - 2020-11-09 ### Added -- Removed Z-Order flag from task creation process - Ability to login into CVAT-UI with token from api/v1/auth/login () - Added layout grids toggling ('ctrl + alt + Enter') - Added password reset functionality () @@ -29,6 +54,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to upload prepared meta information along with a video when creating a task () - Optional chaining plugin for cvat-canvas and cvat-ui () - MOTS png mask format support () +- Ability to correct upload video with a rotation record in the metadata () +- User search field for assignee fields () ### Changed @@ -42,13 +69,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Track frames in skips between annotation is presented in MOT and MOTS formats are marked `outside` () - UI packages installation with `npm ci` instead of `npm install` () -### Deprecated - -- - ### Removed -- +- Removed Z-Order flag from task creation process ### Fixed @@ -72,10 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 'List of tasks' Kibana visualization () - An error on exporting not `jpg` or `png` images in TF Detection API format () -### Security - -- - ## [1.1.0] - 2020-08-31 ### Added diff --git a/README.md b/README.md index 0ca054f4..33901c51 100644 --- a/README.md +++ b/README.md @@ -122,3 +122,7 @@ Other ways to ask questions and get our support: - [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat) - [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation) - [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/) + +## Projects using CVAT + +- [Onepanel](https://github.com/onepanelio/core) - Onepanel is an open source vision AI platform that fully integrates CVAT with scalable data processing and parallelized training pipelines. diff --git a/cvat-core/.eslintrc.js b/cvat-core/.eslintrc.js index b67fbe97..5d883037 100644 --- a/cvat-core/.eslintrc.js +++ b/cvat-core/.eslintrc.js @@ -4,11 +4,9 @@ module.exports = { env: { - node: false, + node: true, browser: true, es6: true, - jquery: true, - qunit: true, 'jest/globals': true, }, parserOptions: { @@ -16,7 +14,7 @@ module.exports = { sourceType: 'module', ecmaVersion: 2018, }, - plugins: ['security', 'jest', 'no-unsanitized', 'no-unsafe-innerhtml'], + plugins: ['security', 'jest', 'no-unsafe-innerhtml'], extends: ['eslint:recommended', 'plugin:security/recommended', 'plugin:no-unsanitized/DOM', 'airbnb-base'], rules: { 'no-await-in-loop': [0], diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index 6128b1d4..52e1e138 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.8.1", + "version": "3.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2822,6 +2822,10 @@ }, "cvat-data": { "version": "file:../cvat-data", + "requires": { + "async-mutex": "^0.2.4", + "jszip": "3.5.0" + }, "dependencies": { "@babel/cli": { "version": "7.6.4", @@ -3932,9 +3936,19 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" }, "async-mutex": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.1.4.tgz", - "integrity": "sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.4.tgz", + "integrity": "sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } }, "atob": { "version": "2.1.2", @@ -5068,11 +5082,6 @@ "event-emitter": "~0.3.5" } }, - "es6-promise": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", - "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=" - }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -6781,22 +6790,14 @@ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" }, "jszip": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", - "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", + "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", "requires": { - "core-js": "~2.3.0", - "es6-promise": "~3.0.2", - "lie": "~3.1.0", + "lie": "~3.3.0", "pako": "~1.0.2", - "readable-stream": "~2.0.6" - }, - "dependencies": { - "core-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", - "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=" - } + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" } }, "kind-of": { @@ -6822,9 +6823,9 @@ } }, "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "~3.0.5" } @@ -7699,9 +7700,9 @@ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -7891,15 +7892,16 @@ } }, "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, @@ -8243,6 +8245,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -8620,9 +8627,12 @@ } }, "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } }, "strip-ansi": { "version": "3.0.1", diff --git a/cvat-core/package.json b/cvat-core/package.json index 0227549f..84140dde 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.8.1", + "version": "3.9.0", "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/api-implementation.js b/cvat-core/src/api-implementation.js index 0766fb79..f7f9a110 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -17,26 +17,6 @@ const { ArgumentError } = require('./exceptions'); const { Task } = require('./session'); - function attachUsers(task, users) { - if (task.assignee !== null) { - [task.assignee] = users.filter((user) => user.id === task.assignee); - } - - for (const segment of task.segments) { - for (const job of segment.jobs) { - if (job.assignee !== null) { - [job.assignee] = users.filter((user) => user.id === job.assignee); - } - } - } - - if (task.owner !== null) { - [task.owner] = users.filter((user) => user.id === task.owner); - } - - return task; - } - function implementAPI(cvat) { cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); @@ -122,7 +102,10 @@ cvat.users.get.implementation = async (filter) => { checkFilter(filter, { + id: isInteger, self: isBoolean, + search: isString, + limit: isInteger, }); let users = null; @@ -130,7 +113,13 @@ users = await serverProxy.users.getSelf(); users = [users]; } else { - users = await serverProxy.users.getUsers(); + const searchParams = {}; + for (const key in filter) { + if (filter[key] && key !== 'self') { + searchParams[key] = filter[key]; + } + } + users = await serverProxy.users.getUsers(new URLSearchParams(searchParams).toString()); } users = users.map((user) => new User(user)); @@ -163,8 +152,7 @@ // If task was found by its id, then create task instance and get Job instance from it if (tasks !== null && tasks.length) { - const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData)); - const task = new Task(attachUsers(tasks[0], users)); + const task = new Task(tasks[0]); return filter.jobID ? task.jobs.filter((job) => job.id === filter.jobID) : task.jobs; } @@ -203,9 +191,8 @@ } } - const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData)); const tasksData = await serverProxy.tasks.getTasks(searchParams.toString()); - const tasks = tasksData.map((task) => attachUsers(task, users)).map((task) => new Task(task)); + const tasks = tasksData.map((task) => new Task(task)); tasks.count = tasksData.count; diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index aaea4dce..524defe7 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -500,20 +500,14 @@ } } - async function getUsers(id = null) { + async function getUsers(filter = 'page_size=all') { const { backendAPI } = config; let response = null; try { - if (id === null) { - response = await Axios.get(`${backendAPI}/users?page_size=all`, { - proxy: config.proxy, - }); - } else { - response = await Axios.get(`${backendAPI}/users/${id}`, { - proxy: config.proxy, - }); - } + response = await Axios.get(`${backendAPI}/users?${filter}`, { + proxy: config.proxy, + }); } catch (errorData) { throw generateError(errorData); } diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 8c47fa43..26483cc1 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -686,6 +686,8 @@ } } + if (data.assignee) data.assignee = new User(data.assignee); + Object.defineProperties( this, Object.freeze({ @@ -883,6 +885,9 @@ } } + if (data.assignee) data.assignee = new User(data.assignee); + if (data.owner) data.owner = new User(data.owner); + data.labels = []; data.jobs = []; data.files = Object.freeze({ @@ -1440,7 +1445,7 @@ if (this.id) { const jobData = { status: this.status, - assignee: this.assignee ? this.assignee.id : null, + assignee_id: this.assignee ? this.assignee.id : null, }; await serverProxy.jobs.saveJob(this.id, jobData); @@ -1649,7 +1654,7 @@ if (typeof this.id !== 'undefined') { // If the task has been already created, we update it const taskData = { - assignee: this.assignee ? this.assignee.id : null, + assignee_id: this.assignee ? this.assignee.id : null, name: this.name, bug_tracker: this.bugTracker, labels: [...this.labels.map((el) => el.toJSON())], diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index 8b3b5d5a..efbc16e4 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -154,7 +154,11 @@ const tasksDummyData = { name: 'Test', size: 1, mode: 'annotation', - owner: 1, + owner: { + url: 'http://localhost:7000/api/v1/users/1', + id: 1, + username: 'admin', + }, assignee: null, bug_tracker: '', created_date: '2019-09-05T11:59:22.987942Z', @@ -194,7 +198,11 @@ const tasksDummyData = { name: 'Image Task', size: 9, mode: 'annotation', - owner: 1, + owner: { + url: 'http://localhost:7000/api/v1/users/1', + id: 1, + username: 'admin', + }, assignee: null, bug_tracker: '', created_date: '2019-06-18T13:05:08.941304+03:00', @@ -239,7 +247,11 @@ const tasksDummyData = { name: 'Video Task', size: 5002, mode: 'interpolation', - owner: 1, + owner: { + url: 'http://localhost:7000/api/v1/users/1', + id: 1, + username: 'admin', + }, assignee: null, bug_tracker: '', created_date: '2019-06-21T16:34:49.199691+03:00', @@ -558,7 +570,11 @@ const tasksDummyData = { name: 'Test Task', size: 5002, mode: 'interpolation', - owner: 2, + owner: { + url: 'http://localhost:7000/api/v1/users/2', + id: 2, + username: 'bsekache', + }, assignee: null, bug_tracker: '', created_date: '2019-05-16T13:08:00.621747+03:00', @@ -767,7 +783,11 @@ const tasksDummyData = { name: 'Video', size: 75, mode: 'interpolation', - owner: 1, + owner: { + url: 'http://localhost:7000/api/v1/users/1', + id: 1, + username: 'admin', + }, assignee: null, bug_tracker: '', created_date: '2019-05-15T11:40:19.487999+03:00', @@ -964,7 +984,11 @@ const tasksDummyData = { name: 'Labels Set', size: 9, mode: 'annotation', - owner: 1, + owner: { + url: 'http://localhost:7000/api/v1/users/1', + id: 1, + username: 'admin', + }, assignee: null, bug_tracker: 'http://bugtracker.com/issue12345', created_date: '2019-05-13T15:35:29.871003+03:00', diff --git a/cvat-core/tests/mocks/server-proxy.mock.js b/cvat-core/tests/mocks/server-proxy.mock.js index 2db07977..983562e4 100644 --- a/cvat-core/tests/mocks/server-proxy.mock.js +++ b/cvat-core/tests/mocks/server-proxy.mock.js @@ -94,8 +94,8 @@ class ServerProxy { const object = tasksDummyData.results.filter((task) => task.id === id)[0]; for (const prop in taskData) { if ( - Object.prototype.hasOwnProperty.call(taskData, prop) && - Object.prototype.hasOwnProperty.call(object, prop) + Object.prototype.hasOwnProperty.call(taskData, prop) + && Object.prototype.hasOwnProperty.call(object, prop) ) { object[prop] = taskData[prop]; } @@ -110,7 +110,10 @@ class ServerProxy { name: taskData.name, size: 5000, mode: 'interpolation', - owner: 2, + owner: { + id: 2, + username: 'bsekache', + }, assignee: null, bug_tracker: taskData.bug_tracker, created_date: '2019-05-16T13:08:00.621747+03:00', @@ -175,8 +178,8 @@ class ServerProxy { for (const prop in jobData) { if ( - Object.prototype.hasOwnProperty.call(jobData, prop) && - Object.prototype.hasOwnProperty.call(object, prop) + Object.prototype.hasOwnProperty.call(jobData, prop) + && Object.prototype.hasOwnProperty.call(object, prop) ) { object[prop] = jobData[prop]; } diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index a78e8173..c8a305a6 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.9.14", + "version": "1.10.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1179,6 +1179,11 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.165", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz", + "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1208,9 +1213,9 @@ "dev": true }, "@types/react": { - "version": "16.9.52", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.52.tgz", - "integrity": "sha512-EHRjmnxiNivwhGdMh9sz1Yw9AUxTSZFxKqdBWAAzyZx3sufWwx6ogqHYh/WB1m/I4ZpjkoZLExF5QTy2ekVi/Q==", + "version": "16.9.53", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz", + "integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -17047,7 +17052,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": false, + "resolved": "", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "optional": true, "requires": { @@ -17062,7 +17067,7 @@ }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { @@ -17078,7 +17083,7 @@ }, "glob": { "version": "7.1.6", - "resolved": false, + "resolved": "", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "optional": true, "requires": { @@ -17116,7 +17121,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { @@ -17168,7 +17173,7 @@ }, "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "optional": true, "requires": { @@ -17178,7 +17183,7 @@ }, "minizlib": { "version": "1.3.3", - "resolved": false, + "resolved": "", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "optional": true, "requires": { @@ -17213,7 +17218,7 @@ }, "node-pre-gyp": { "version": "0.14.0", - "resolved": false, + "resolved": "", "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "optional": true, "requires": { @@ -17267,7 +17272,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { @@ -17291,7 +17296,7 @@ }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { @@ -17361,7 +17366,7 @@ }, "rimraf": { "version": "2.7.1", - "resolved": false, + "resolved": "", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "optional": true, "requires": { @@ -17441,7 +17446,7 @@ }, "tar": { "version": "4.4.13", - "resolved": false, + "resolved": "", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "optional": true, "requires": { @@ -17471,13 +17476,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "optional": true } @@ -25949,9 +25954,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -28850,9 +28855,9 @@ } }, "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -28883,9 +28888,9 @@ } }, "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 581b927b..1afed2a4 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.9.14", + "version": "1.10.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { @@ -47,8 +47,9 @@ "worker-loader": "^2.0.0" }, "dependencies": { + "@types/lodash": "^4.14.165", "@types/platform": "^1.3.3", - "@types/react": "^16.9.52", + "@types/react": "^16.9.53", "@types/react-color": "^3.0.4", "@types/react-dom": "^16.9.0", "@types/react-redux": "^7.1.2", @@ -62,13 +63,14 @@ "cvat-core": "file:../cvat-core", "dotenv-webpack": "^1.8.0", "error-stack-parser": "^2.0.6", + "lodash": "^4.17.20", "moment": "^2.29.1", "platform": "^1.3.6", "prop-types": "^15.7.2", - "react": "^16.13.1", + "react": "^16.14.0", "react-color": "^2.18.1", "react-cookie": "^4.0.3", - "react-dom": "^16.13.1", + "react-dom": "^16.14.0", "react-hotkeys": "^2.0.0", "react-redux": "^7.1.1", "react-router": "^5.1.0", diff --git a/cvat-ui/src/actions/users-actions.ts b/cvat-ui/src/actions/users-actions.ts deleted file mode 100644 index 154b4fee..00000000 --- a/cvat-ui/src/actions/users-actions.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import getCore from 'cvat-core-wrapper'; - -const core = getCore(); - -export enum UsersActionTypes { - GET_USERS = 'GET_USERS', - GET_USERS_SUCCESS = 'GET_USERS_SUCCESS', - GET_USERS_FAILED = 'GET_USERS_FAILED', -} - -const usersActions = { - getUsers: () => createAction(UsersActionTypes.GET_USERS), - getUsersSuccess: (users: any[]) => createAction(UsersActionTypes.GET_USERS_SUCCESS, { users }), - getUsersFailed: (error: any) => createAction(UsersActionTypes.GET_USERS_FAILED, { error }), -}; - -export type UsersActions = ActionUnion; - -export function getUsersAsync(): ThunkAction { - return async (dispatch): Promise => { - dispatch(usersActions.getUsers()); - - try { - const users = await core.users.get(); - const wrappedUsers = users.map((userData: any): any => new core.classes.User(userData)); - dispatch(usersActions.getUsersSuccess(wrappedUsers)); - } catch (error) { - dispatch(usersActions.getUsersFailed(error)); - } - }; -} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 2ceea61a..8b600cd5 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -34,7 +34,6 @@ import '../styles.scss'; interface CVATAppProps { loadFormats: () => void; - loadUsers: () => void; loadAbout: () => void; verifyAuthorized: () => void; loadUserAgreements: () => void; @@ -54,8 +53,6 @@ interface CVATAppProps { modelsFetching: boolean; formatsInitialized: boolean; formatsFetching: boolean; - usersInitialized: boolean; - usersFetching: boolean; aboutInitialized: boolean; aboutFetching: boolean; userAgreementsFetching: boolean; @@ -92,7 +89,6 @@ class CVATApplication extends React.PureComponent - {`The browser you are using is ${info.name} ${info.version} based on ${info.engine} .` + + {`The browser you are using is ${name} ${version} based on ${engine}.` + ' CVAT was tested in the latest versions of Chrome and Firefox.' + ' We recommend to use Chrome (or another Chromium based browser)'} @@ -286,7 +278,7 @@ class CVATApplication extends React.PureComponent - {`The operating system is ${info.os}`} + {`The operating system is ${os}`} diff --git a/cvat-ui/src/components/task-page/details.tsx b/cvat-ui/src/components/task-page/details.tsx index cfda2acb..ebe7dbde 100644 --- a/cvat-ui/src/components/task-page/details.tsx +++ b/cvat-ui/src/components/task-page/details.tsx @@ -18,7 +18,7 @@ import patterns from 'utils/validation-patterns'; import { getReposData, syncRepos } from 'utils/git-utils'; import { ActiveInference } from 'reducers/interfaces'; import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress'; -import UserSelector from './user-selector'; +import UserSelector, { User } from './user-selector'; import LabelsEditorComponent from '../labels-editor/labels-editor'; const core = getCore(); @@ -27,7 +27,6 @@ interface Props { previewImage: string; taskInstance: any; installedGit: boolean; // change to git repos url - registeredUsers: any[]; activeInference: ActiveInference | null; cancelAutoAnnotation(): void; onTaskUpdate: (taskInstance: any) => void; @@ -196,35 +195,26 @@ export default class DetailsComponent extends React.PureComponent } private renderUsers(): JSX.Element { - const { taskInstance, registeredUsers, onTaskUpdate } = this.props; + const { taskInstance, onTaskUpdate } = this.props; const owner = taskInstance.owner ? taskInstance.owner.username : null; - const assignee = taskInstance.assignee ? taskInstance.assignee.username : null; + const assignee = taskInstance.assignee ? taskInstance.assignee : null; const created = moment(taskInstance.createdDate).format('MMMM Do YYYY'); const assigneeSelect = ( { - let [userInstance] = registeredUsers.filter((user: any) => user.username === value); - - if (userInstance === undefined) { - userInstance = null; - } - - taskInstance.assignee = userInstance; + onSelect={(value: User | null): void => { + taskInstance.assignee = value; onTaskUpdate(taskInstance); }} /> ); return ( - + {owner && {`Created by ${owner} on ${created}`}} - - Assigned to - {assigneeSelect} - + Assigned to + {assigneeSelect} ); diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index ff9b6ec6..2b10e225 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -15,7 +15,7 @@ import moment from 'moment'; import copy from 'copy-to-clipboard'; import getCore from 'cvat-core-wrapper'; -import UserSelector from './user-selector'; +import UserSelector, { User } from './user-selector'; const core = getCore(); @@ -23,14 +23,12 @@ const baseURL = core.config.backendAPI.slice(0, -7); interface Props { taskInstance: any; - registeredUsers: any[]; onJobUpdate(jobInstance: any): void; } function JobListComponent(props: Props & RouteComponentProps): JSX.Element { const { taskInstance, - registeredUsers, onJobUpdate, history: { push }, } = props; @@ -100,21 +98,14 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { dataIndex: 'assignee', key: 'assignee', render: (jobInstance: any): JSX.Element => { - const assignee = jobInstance.assignee ? jobInstance.assignee.username : null; + const assignee = jobInstance.assignee ? jobInstance.assignee : null; return ( { - let [userInstance] = [...registeredUsers].filter((user: any) => user.username === value); - - if (userInstance === undefined) { - userInstance = null; - } - + onSelect={(value: User | null): void => { // eslint-disable-next-line - jobInstance.assignee = userInstance; + jobInstance.assignee = value; onJobUpdate(jobInstance); }} /> diff --git a/cvat-ui/src/components/task-page/styles.scss b/cvat-ui/src/components/task-page/styles.scss index 2674edac..dfdf229a 100644 --- a/cvat-ui/src/components/task-page/styles.scss +++ b/cvat-ui/src/components/task-page/styles.scss @@ -17,6 +17,12 @@ padding: 20px; background: $background-color-1; + .cvat-task-details-user-block { + > div:nth-child(2) > span { + margin-right: 8px; + } + } + > div:nth-child(2) { > div:nth-child(2) { padding-left: 20px; @@ -87,11 +93,6 @@ background-color: $background-color-2; } -.cvat-user-selector { - margin-left: 10px; - width: 150px; -} - .cvat-open-bug-tracker-button { margin-left: 15px; } diff --git a/cvat-ui/src/components/task-page/user-selector.tsx b/cvat-ui/src/components/task-page/user-selector.tsx index d2778223..0cc42db9 100644 --- a/cvat-ui/src/components/task-page/user-selector.tsx +++ b/cvat-ui/src/components/task-page/user-selector.tsx @@ -2,30 +2,114 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; -import Select from 'antd/lib/select'; +import React, { useState, useEffect, useRef } from 'react'; +import Autocomplete from 'antd/lib/auto-complete'; +import Input from 'antd/lib/input'; + +import getCore from 'cvat-core-wrapper'; +import { SelectValue } from 'antd/lib/select'; + +import debounce from 'lodash/debounce'; + +const core = getCore(); + +export interface User { + id: number; + username: string; +} interface Props { - value: string | null; - users: any[]; - onChange: (user: string) => void; + value: User | null; + onSelect: (user: User | null) => void; } +const searchUsers = debounce( + (searchValue: string, setUsers: (users: User[]) => void): void => { + core.users + .get({ + search: searchValue, + limit: 10, + }) + .then((result: User[]) => { + if (result) { + setUsers(result); + } + }); + }, + 250, + { + maxWait: 750, + }, +); + export default function UserSelector(props: Props): JSX.Element { - const { value, users, onChange } = props; + const { value, onSelect } = props; + const [searchPhrase, setSearchPhrase] = useState(''); + + const [users, setUsers] = useState([]); + const autocompleteRef = useRef(null); + + const handleSearch = (searchValue: string): void => { + if (searchValue) { + searchUsers(searchValue, setUsers); + } else { + setUsers([]); + } + setSearchPhrase(searchValue); + }; + + const handleFocus = (open: boolean): void => { + if (!users.length && open) { + core.users.get({ limit: 10 }).then((result: User[]) => { + if (result) { + setUsers(result); + } + }); + } + if (!open && searchPhrase !== value?.username) { + setSearchPhrase(''); + if (value) { + onSelect(null); + } + } + }; + + const handleSelect = (_value: SelectValue): void => { + setSearchPhrase(users.filter((user) => user.id === +_value)[0].username); + onSelect(_value ? users.filter((user) => user.id === +_value)[0] : null); + }; + + useEffect(() => { + if (value && !users.filter((user) => user.id === value.id).length) { + core.users.get({ id: value.id }).then((result: User[]) => { + const [user] = result; + setUsers([ + ...users, + { + id: user.id, + username: user.username, + }, + ]); + setSearchPhrase(user.username); + }); + } + }, [value]); return ( - + ({ + value: user.id.toString(), + text: user.username, + }))} + > + autocompleteRef.current?.blur()} /> + ); } diff --git a/cvat-ui/src/containers/task-page/details.tsx b/cvat-ui/src/containers/task-page/details.tsx index c0e2ce86..c05c6f33 100644 --- a/cvat-ui/src/containers/task-page/details.tsx +++ b/cvat-ui/src/containers/task-page/details.tsx @@ -15,7 +15,6 @@ interface OwnProps { } interface StateToProps { - registeredUsers: any[]; activeInference: ActiveInference | null; installedGit: boolean; } @@ -29,7 +28,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { const { list } = state.plugins; return { - registeredUsers: state.users.users, installedGit: list.GIT_INTEGRATION, activeInference: state.models.inferences[own.task.instance.id] || null, }; @@ -47,14 +45,15 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps { } function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { - const { task, installedGit, activeInference, registeredUsers, cancelAutoAnnotation, onTaskUpdate } = props; + const { + task, installedGit, activeInference, cancelAutoAnnotation, onTaskUpdate, + } = props; return ( dispatch(updateJobAsync(jobInstance)), }; } -function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { - const { task, registeredUsers, onJobUpdate } = props; +function TaskPageContainer(props: DispatchToProps & OwnProps): JSX.Element { + const { task, onJobUpdate } = props; - return ( - - ); + return ; } -export default connect(mapStateToProps, mapDispatchToProps)(TaskPageContainer); +export default connect(null, mapDispatchToProps)(TaskPageContainer); diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index b11f9465..31188f77 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -10,7 +10,6 @@ import { getPluginsAsync } from 'actions/plugins-actions'; import { switchSettingsDialog } from 'actions/settings-actions'; import { shortcutsActions } from 'actions/shortcuts-actions'; import { getUserAgreementsAsync } from 'actions/useragreements-actions'; -import { getUsersAsync } from 'actions/users-actions'; import CVATApplication from 'components/cvat-app'; import LayoutGrid from 'components/layout-grid/layout-grid'; import logger, { LogType } from 'cvat-logger'; @@ -34,8 +33,6 @@ interface StateToProps { modelsFetching: boolean; userInitialized: boolean; userFetching: boolean; - usersInitialized: boolean; - usersFetching: boolean; aboutInitialized: boolean; aboutFetching: boolean; formatsInitialized: boolean; @@ -55,7 +52,6 @@ interface StateToProps { interface DispatchToProps { loadFormats: () => void; verifyAuthorized: () => void; - loadUsers: () => void; loadAbout: () => void; initModels: () => void; initPlugins: () => void; @@ -71,7 +67,6 @@ function mapStateToProps(state: CombinedState): StateToProps { const { plugins } = state; const { auth } = state; const { formats } = state; - const { users } = state; const { about } = state; const { shortcuts } = state; const { userAgreements } = state; @@ -84,8 +79,6 @@ function mapStateToProps(state: CombinedState): StateToProps { pluginsFetching: plugins.fetching, modelsInitialized: models.initialized, modelsFetching: models.fetching, - usersInitialized: users.initialized, - usersFetching: users.fetching, aboutInitialized: about.initialized, aboutFetching: about.fetching, formatsInitialized: formats.initialized, @@ -110,7 +103,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()), initPlugins: (): void => dispatch(getPluginsAsync()), initModels: (): void => dispatch(getModelsAsync()), - loadUsers: (): void => dispatch(getUsersAsync()), loadAbout: (): void => dispatch(getAboutAsync()), resetErrors: (): void => dispatch(resetErrors()), resetMessages: (): void => dispatch(resetMessages()), diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 93639108..8c283a7a 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -93,12 +93,6 @@ export interface PluginsState { list: PluginsList; } -export interface UsersState { - users: any[]; - fetching: boolean; - initialized: boolean; -} - export interface AboutState { server: any; packageVersion: { @@ -494,7 +488,6 @@ export interface MetaState { export interface CombinedState { auth: AuthState; tasks: TasksState; - users: UsersState; about: AboutState; share: ShareState; formats: FormatsState; diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 54e931db..862dd674 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -9,7 +9,6 @@ import { FormatsActionTypes } from 'actions/formats-actions'; import { ModelsActionTypes } from 'actions/models-actions'; import { ShareActionTypes } from 'actions/share-actions'; import { TasksActionTypes } from 'actions/tasks-actions'; -import { UsersActionTypes } from 'actions/users-actions'; import { AboutActionTypes } from 'actions/about-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions'; import { NotificationsActionType } from 'actions/notification-actions'; @@ -357,8 +356,7 @@ export default function (state = defaultState, action: AnyAction): Notifications tasks: { ...state.errors.tasks, updating: { - message: - 'Could not update ' + `task ${taskID}`, + message: `Could not update task ${taskID}`, reason: action.payload.error.toString(), }, }, @@ -431,21 +429,6 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } - case UsersActionTypes.GET_USERS_FAILED: { - return { - ...state, - errors: { - ...state.errors, - users: { - ...state.errors.users, - fetching: { - message: 'Could not get users from the server', - reason: action.payload.error.toString(), - }, - }, - }, - }; - } case AboutActionTypes.GET_ABOUT_FAILED: { return { ...state, diff --git a/cvat-ui/src/reducers/root-reducer.ts b/cvat-ui/src/reducers/root-reducer.ts index e726b94e..7d96841f 100644 --- a/cvat-ui/src/reducers/root-reducer.ts +++ b/cvat-ui/src/reducers/root-reducer.ts @@ -5,7 +5,6 @@ import { combineReducers, Reducer } from 'redux'; import authReducer from './auth-reducer'; import tasksReducer from './tasks-reducer'; -import usersReducer from './users-reducer'; import aboutReducer from './about-reducer'; import shareReducer from './share-reducer'; import formatsReducer from './formats-reducer'; @@ -21,7 +20,6 @@ export default function createRootReducer(): Reducer { return combineReducers({ auth: authReducer, tasks: tasksReducer, - users: usersReducer, about: aboutReducer, share: shareReducer, formats: formatsReducer, diff --git a/cvat-ui/src/reducers/users-reducer.ts b/cvat-ui/src/reducers/users-reducer.ts deleted file mode 100644 index 6fe50116..00000000 --- a/cvat-ui/src/reducers/users-reducer.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2020 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import { BoundariesActionTypes, BoundariesActions } from 'actions/boundaries-actions'; -import { AuthActionTypes, AuthActions } from 'actions/auth-actions'; -import { UsersActionTypes, UsersActions } from 'actions/users-actions'; -import { UsersState } from './interfaces'; - -const defaultState: UsersState = { - users: [], - fetching: false, - initialized: false, -}; - -export default function ( - state: UsersState = defaultState, - action: UsersActions | AuthActions | BoundariesActions, -): UsersState { - switch (action.type) { - case UsersActionTypes.GET_USERS: { - return { - ...state, - fetching: true, - initialized: false, - }; - } - case UsersActionTypes.GET_USERS_SUCCESS: - return { - ...state, - fetching: false, - initialized: true, - users: action.payload.users, - }; - case UsersActionTypes.GET_USERS_FAILED: - return { - ...state, - fetching: false, - initialized: true, - }; - case BoundariesActionTypes.RESET_AFTER_ERROR: - case AuthActionTypes.LOGOUT_SUCCESS: { - return { ...defaultState }; - } - default: - return state; - } -} diff --git a/cvat/__init__.py b/cvat/__init__.py index f96a1724..f4cd65b4 100644 --- a/cvat/__init__.py +++ b/cvat/__init__.py @@ -4,6 +4,6 @@ from cvat.utils.version import get_version -VERSION = (1, 2, 0, 'alpha', 0) +VERSION = (1, 2, 0, 'beta', 0) __version__ = get_version(VERSION) diff --git a/cvat/apps/authentication/templates/account/email/email_confirmation_signup_message.html b/cvat/apps/authentication/templates/account/email/email_confirmation_signup_message.html index c121b810..ec80776a 100644 --- a/cvat/apps/authentication/templates/account/email/email_confirmation_signup_message.html +++ b/cvat/apps/authentication/templates/account/email/email_confirmation_signup_message.html @@ -1,5 +1,6 @@ -{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with -site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}! +{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %} +{% blocktrans with site_name=current_site.name site_domain=current_site.domain %} +Hello from {{ site_name }}!

You're receiving this e-mail because user {{ user_display }} has given yours as an e-mail address @@ -7,6 +8,7 @@ site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site

To confirm this is correct, go to {{ activate_url }}

-{% endblocktrans %} {% blocktrans with site_name=current_site.name site_domain=current_site.domain %} +{% endblocktrans %} +{% blocktrans with site_name=current_site.name site_domain=current_site.domain %} {{ site_domain }} {% endblocktrans %} {% endautoescape %} diff --git a/cvat/apps/authentication/templates/authentication/password_reset_email.html b/cvat/apps/authentication/templates/authentication/password_reset_email.html index e2c68db1..fa3eef8b 100644 --- a/cvat/apps/authentication/templates/authentication/password_reset_email.html +++ b/cvat/apps/authentication/templates/authentication/password_reset_email.html @@ -1,5 +1,16 @@ -{% load i18n %}{% autoescape off %} {% blocktrans %}You're receiving this email because you requested a password reset -for your user account at {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page and choose a new -password:" %} {% block reset_link %} {{ protocol }}://{{ domain }}/auth/password/reset/confirm?uid={{ uid }}&token={{ -token }} {% endblock %} {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} {% trans "Thanks -for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endautoescape %} +{% load i18n %}{% autoescape off %} +{% blocktrans %} +You're receiving this email because you requested a password reset for your user account at {{ site_name }}. +{% endblocktrans %} + +{% trans "Please go to the following page and choose a new password:" %} +{% block reset_link %} +{{ protocol }}://{{ domain }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }} +{% endblock %} +{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} + +{% endautoescape %} diff --git a/cvat/apps/dataset_manager/formats/README.md b/cvat/apps/dataset_manager/formats/README.md index c3a9b6d0..b4728701 100644 --- a/cvat/apps/dataset_manager/formats/README.md +++ b/cvat/apps/dataset_manager/formats/README.md @@ -18,6 +18,7 @@ - [PASCAL VOC and mask](#voc) - [YOLO](#yolo) - [TF detection API](#tfrecord) + - [ImageNet](#imagenet) ## How to add a new annotation format support @@ -802,3 +803,35 @@ taskname.zip/ ``` - supported annotations: Rectangles, Polygons, Masks (as polygons) + +### [ImageNet](http://www.image-net.org) + +#### ImageNet Dumper + +Downloaded file: a zip archive of the following structure: + +```bash +# if we save images: +taskname.zip/ +└── label1/ + ├── label1_image1.jpg + └── label1_image2.jpg +└── label2/ + ├── label2_image1.jpg + ├── label2_image3.jpg + └── label2_image4.jpg + +# if we keep only annotation: +taskname.zip/ +└── .txt +└── synsets.txt + +``` + +- supported annotations: Labels + +#### ImageNet Loader + +Uploaded file: a zip archive of the structure above + +- supported annotations: Labels diff --git a/cvat/apps/dataset_manager/formats/imagenet.py b/cvat/apps/dataset_manager/formats/imagenet.py new file mode 100644 index 00000000..d9847549 --- /dev/null +++ b/cvat/apps/dataset_manager/formats/imagenet.py @@ -0,0 +1,41 @@ +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import os.path as osp +from glob import glob + +import zipfile +from tempfile import TemporaryDirectory + +from datumaro.components.project import Dataset +from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, \ + import_dm_annotations +from cvat.apps.dataset_manager.util import make_zip_archive + +from .registry import dm_env, exporter, importer + + +@exporter(name='ImageNet', ext='ZIP', version='1.0') +def _export(dst_file, task_data, save_images=False): + extractor = CvatTaskDataExtractor(task_data, include_images=save_images) + extractor = Dataset.from_extractors(extractor) # apply lazy transform + with TemporaryDirectory() as temp_dir: + if save_images: + dm_env.converters.get('imagenet').convert(extractor, + save_dir=temp_dir, save_images=save_images) + else: + dm_env.converters.get('imagenet_txt').convert(extractor, + save_dir=temp_dir, save_images=save_images) + + make_zip_archive(temp_dir, dst_file) + +@importer(name='ImageNet', ext='ZIP', version='1.0') +def _import(src_file, task_data): + with TemporaryDirectory() as tmp_dir: + zipfile.ZipFile(src_file).extractall(tmp_dir) + if glob(osp.join(tmp_dir, '*.txt')): + dataset = dm_env.make_importer('imagenet_txt')(tmp_dir).make_dataset() + else: + dataset = dm_env.make_importer('imagenet')(tmp_dir).make_dataset() + import_dm_annotations(dataset, task_data) \ No newline at end of file diff --git a/cvat/apps/dataset_manager/formats/registry.py b/cvat/apps/dataset_manager/formats/registry.py index c84d60fc..c175a42b 100644 --- a/cvat/apps/dataset_manager/formats/registry.py +++ b/cvat/apps/dataset_manager/formats/registry.py @@ -90,4 +90,5 @@ import cvat.apps.dataset_manager.formats.mot import cvat.apps.dataset_manager.formats.mots import cvat.apps.dataset_manager.formats.pascal_voc import cvat.apps.dataset_manager.formats.tfrecord -import cvat.apps.dataset_manager.formats.yolo \ No newline at end of file +import cvat.apps.dataset_manager.formats.yolo +import cvat.apps.dataset_manager.formats.imagenet \ No newline at end of file diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index 1eb3e2b5..d41e253c 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -218,8 +218,6 @@ class TaskExportTest(_DbTestBase): def _generate_task(self, images): task = { "name": "my task #1", - "owner": '', - "assignee": '', "overlap": 0, "segment_size": 100, "labels": [ @@ -271,6 +269,7 @@ class TaskExportTest(_DbTestBase): 'Segmentation mask 1.1', 'TFRecord 1.0', 'YOLO 1.1', + 'ImageNet 1.0', }) def test_import_formats_query(self): @@ -287,6 +286,7 @@ class TaskExportTest(_DbTestBase): 'Segmentation mask 1.1', 'TFRecord 1.0', 'YOLO 1.1', + 'ImageNet 1.0', }) def test_exports(self): @@ -322,6 +322,7 @@ class TaskExportTest(_DbTestBase): ('Segmentation mask 1.1', 'voc'), ('TFRecord 1.0', 'tf_detection_api'), ('YOLO 1.1', 'yolo'), + ('ImageNet 1.0', 'imagenet_txt'), ]: with self.subTest(format=format_name): if not dm.formats.registry.EXPORT_FORMATS[format_name].ENABLED: @@ -435,8 +436,6 @@ class FrameMatchingTest(_DbTestBase): def _generate_task(self, images): task = { "name": "my task #1", - "owner": '', - "assignee": '', "overlap": 0, "segment_size": 100, "labels": [ diff --git a/cvat/apps/documentation/templates/documentation/base_page.html b/cvat/apps/documentation/templates/documentation/base_page.html index be127111..f49fe5f1 100644 --- a/cvat/apps/documentation/templates/documentation/base_page.html +++ b/cvat/apps/documentation/templates/documentation/base_page.html @@ -1,5 +1,5 @@ @@ -15,8 +15,7 @@ -