Merge remote-tracking branch 'origin/develop' into az/fix_templates

main
Andrey Zhavoronkov 5 years ago
commit 6833ab05fc

@ -8,26 +8,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.2.0-beta] - Unreleased ## [1.2.0-beta] - Unreleased
### Added ### Added
- -
### Changed ### Changed
- -
### Deprecated ### Deprecated
- -
### Removed ### Removed
- -
### Fixed ### Fixed
- -
### Security ### Security
- -
## [1.2.0-alpha] - 2020-11-09 ## [1.2.0-alpha] - 2020-11-09
### Added ### Added
- Ability to login into CVAT-UI with token from api/v1/auth/login (<https://github.com/openvinotoolkit/cvat/pull/2234>) - Ability to login into CVAT-UI with token from api/v1/auth/login (<https://github.com/openvinotoolkit/cvat/pull/2234>)
- Added layout grids toggling ('ctrl + alt + Enter') - Added layout grids toggling ('ctrl + alt + Enter')
- Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>) - Added password reset functionality (<https://github.com/opencv/cvat/pull/2058>)
@ -48,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Optional chaining plugin for cvat-canvas and cvat-ui (<https://github.com/openvinotoolkit/cvat/pull/2249>) - Optional chaining plugin for cvat-canvas and cvat-ui (<https://github.com/openvinotoolkit/cvat/pull/2249>)
- MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>) - MOTS png mask format support (<https://github.com/openvinotoolkit/cvat/pull/2198>)
- Ability to correct upload video with a rotation record in the metadata (<https://github.com/openvinotoolkit/cvat/pull/2218>) - Ability to correct upload video with a rotation record in the metadata (<https://github.com/openvinotoolkit/cvat/pull/2218>)
- User search field for assignee fields (<https://github.com/openvinotoolkit/cvat/pull/2370>)
### Changed ### Changed
@ -65,7 +73,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed Z-Order flag from task creation process - Removed Z-Order flag from task creation process
### Fixed ### Fixed
- Fixed multiple errors which arises when polygon is of length 5 or less (<https://github.com/opencv/cvat/pull/2100>) - Fixed multiple errors which arises when polygon is of length 5 or less (<https://github.com/opencv/cvat/pull/2100>)

@ -124,4 +124,5 @@ Other ways to ask questions and get our support:
- [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/) - [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 ## 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. - [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.

@ -4,6 +4,7 @@
module.exports = { module.exports = {
env: { env: {
amd: true,
node: false, node: false,
browser: true, browser: true,
es6: true, es6: true,

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.8.1", "version": "3.9.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "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", "description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js", "main": "babel.config.js",
"scripts": { "scripts": {

@ -17,26 +17,6 @@
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
const { Task } = require('./session'); 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) { function implementAPI(cvat) {
cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat);
@ -122,7 +102,10 @@
cvat.users.get.implementation = async (filter) => { cvat.users.get.implementation = async (filter) => {
checkFilter(filter, { checkFilter(filter, {
id: isInteger,
self: isBoolean, self: isBoolean,
search: isString,
limit: isInteger,
}); });
let users = null; let users = null;
@ -130,7 +113,13 @@
users = await serverProxy.users.getSelf(); users = await serverProxy.users.getSelf();
users = [users]; users = [users];
} else { } 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)); 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 task was found by its id, then create task instance and get Job instance from it
if (tasks !== null && tasks.length) { if (tasks !== null && tasks.length) {
const users = (await serverProxy.users.getUsers()).map((userData) => new User(userData)); const task = new Task(tasks[0]);
const task = new Task(attachUsers(tasks[0], users));
return filter.jobID ? task.jobs.filter((job) => job.id === filter.jobID) : task.jobs; 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 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; tasks.count = tasksData.count;

@ -500,20 +500,14 @@
} }
} }
async function getUsers(id = null) { async function getUsers(filter = 'page_size=all') {
const { backendAPI } = config; const { backendAPI } = config;
let response = null; let response = null;
try { try {
if (id === null) { response = await Axios.get(`${backendAPI}/users?${filter}`, {
response = await Axios.get(`${backendAPI}/users?page_size=all`, { proxy: config.proxy,
proxy: config.proxy, });
});
} else {
response = await Axios.get(`${backendAPI}/users/${id}`, {
proxy: config.proxy,
});
}
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }

@ -686,6 +686,8 @@
} }
} }
if (data.assignee) data.assignee = new User(data.assignee);
Object.defineProperties( Object.defineProperties(
this, this,
Object.freeze({ 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.labels = [];
data.jobs = []; data.jobs = [];
data.files = Object.freeze({ data.files = Object.freeze({
@ -1440,7 +1445,7 @@
if (this.id) { if (this.id) {
const jobData = { const jobData = {
status: this.status, 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); await serverProxy.jobs.saveJob(this.id, jobData);
@ -1649,7 +1654,7 @@
if (typeof this.id !== 'undefined') { if (typeof this.id !== 'undefined') {
// If the task has been already created, we update it // If the task has been already created, we update it
const taskData = { const taskData = {
assignee: this.assignee ? this.assignee.id : null, assignee_id: this.assignee ? this.assignee.id : null,
name: this.name, name: this.name,
bug_tracker: this.bugTracker, bug_tracker: this.bugTracker,
labels: [...this.labels.map((el) => el.toJSON())], labels: [...this.labels.map((el) => el.toJSON())],

@ -154,7 +154,11 @@ const tasksDummyData = {
name: 'Test', name: 'Test',
size: 1, size: 1,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-09-05T11:59:22.987942Z', created_date: '2019-09-05T11:59:22.987942Z',
@ -194,7 +198,11 @@ const tasksDummyData = {
name: 'Image Task', name: 'Image Task',
size: 9, size: 9,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-06-18T13:05:08.941304+03:00', created_date: '2019-06-18T13:05:08.941304+03:00',
@ -239,7 +247,11 @@ const tasksDummyData = {
name: 'Video Task', name: 'Video Task',
size: 5002, size: 5002,
mode: 'interpolation', mode: 'interpolation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-06-21T16:34:49.199691+03:00', created_date: '2019-06-21T16:34:49.199691+03:00',
@ -558,7 +570,11 @@ const tasksDummyData = {
name: 'Test Task', name: 'Test Task',
size: 5002, size: 5002,
mode: 'interpolation', mode: 'interpolation',
owner: 2, owner: {
url: 'http://localhost:7000/api/v1/users/2',
id: 2,
username: 'bsekache',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-05-16T13:08:00.621747+03:00', created_date: '2019-05-16T13:08:00.621747+03:00',
@ -767,7 +783,11 @@ const tasksDummyData = {
name: 'Video', name: 'Video',
size: 75, size: 75,
mode: 'interpolation', mode: 'interpolation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: '', bug_tracker: '',
created_date: '2019-05-15T11:40:19.487999+03:00', created_date: '2019-05-15T11:40:19.487999+03:00',
@ -964,7 +984,11 @@ const tasksDummyData = {
name: 'Labels Set', name: 'Labels Set',
size: 9, size: 9,
mode: 'annotation', mode: 'annotation',
owner: 1, owner: {
url: 'http://localhost:7000/api/v1/users/1',
id: 1,
username: 'admin',
},
assignee: null, assignee: null,
bug_tracker: 'http://bugtracker.com/issue12345', bug_tracker: 'http://bugtracker.com/issue12345',
created_date: '2019-05-13T15:35:29.871003+03:00', created_date: '2019-05-13T15:35:29.871003+03:00',

@ -94,8 +94,8 @@ class ServerProxy {
const object = tasksDummyData.results.filter((task) => task.id === id)[0]; const object = tasksDummyData.results.filter((task) => task.id === id)[0];
for (const prop in taskData) { for (const prop in taskData) {
if ( if (
Object.prototype.hasOwnProperty.call(taskData, prop) && Object.prototype.hasOwnProperty.call(taskData, prop)
Object.prototype.hasOwnProperty.call(object, prop) && Object.prototype.hasOwnProperty.call(object, prop)
) { ) {
object[prop] = taskData[prop]; object[prop] = taskData[prop];
} }
@ -110,7 +110,10 @@ class ServerProxy {
name: taskData.name, name: taskData.name,
size: 5000, size: 5000,
mode: 'interpolation', mode: 'interpolation',
owner: 2, owner: {
id: 2,
username: 'bsekache',
},
assignee: null, assignee: null,
bug_tracker: taskData.bug_tracker, bug_tracker: taskData.bug_tracker,
created_date: '2019-05-16T13:08:00.621747+03:00', created_date: '2019-05-16T13:08:00.621747+03:00',
@ -175,8 +178,8 @@ class ServerProxy {
for (const prop in jobData) { for (const prop in jobData) {
if ( if (
Object.prototype.hasOwnProperty.call(jobData, prop) && Object.prototype.hasOwnProperty.call(jobData, prop)
Object.prototype.hasOwnProperty.call(object, prop) && Object.prototype.hasOwnProperty.call(object, prop)
) { ) {
object[prop] = jobData[prop]; object[prop] = jobData[prop];
} }

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.9.14", "version": "1.10.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1179,6 +1179,11 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true "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": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -17047,7 +17052,7 @@
}, },
"fs-minipass": { "fs-minipass": {
"version": "1.2.7", "version": "1.2.7",
"resolved": false, "resolved": "",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17062,7 +17067,7 @@
}, },
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": false, "resolved": "",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17078,7 +17083,7 @@
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": false, "resolved": "",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17116,7 +17121,7 @@
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": false, "resolved": "",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17168,7 +17173,7 @@
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"resolved": false, "resolved": "",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17178,7 +17183,7 @@
}, },
"minizlib": { "minizlib": {
"version": "1.3.3", "version": "1.3.3",
"resolved": false, "resolved": "",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17213,7 +17218,7 @@
}, },
"node-pre-gyp": { "node-pre-gyp": {
"version": "0.14.0", "version": "0.14.0",
"resolved": false, "resolved": "",
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17267,7 +17272,7 @@
}, },
"npmlog": { "npmlog": {
"version": "4.1.2", "version": "4.1.2",
"resolved": false, "resolved": "",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17291,7 +17296,7 @@
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": false, "resolved": "",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17361,7 +17366,7 @@
}, },
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": false, "resolved": "",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17441,7 +17446,7 @@
}, },
"tar": { "tar": {
"version": "4.4.13", "version": "4.4.13",
"resolved": false, "resolved": "",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17471,13 +17476,13 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": false, "resolved": "",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true "optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": false, "resolved": "",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true "optional": true
} }
@ -25949,9 +25954,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.19", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}, },
"lodash._reinterpolate": { "lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.9.14", "version": "1.10.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
@ -47,6 +47,7 @@
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"@types/lodash": "^4.14.165",
"@types/platform": "^1.3.3", "@types/platform": "^1.3.3",
"@types/react": "^16.9.53", "@types/react": "^16.9.53",
"@types/react-color": "^3.0.4", "@types/react-color": "^3.0.4",
@ -62,6 +63,7 @@
"cvat-core": "file:../cvat-core", "cvat-core": "file:../cvat-core",
"dotenv-webpack": "^1.8.0", "dotenv-webpack": "^1.8.0",
"error-stack-parser": "^2.0.6", "error-stack-parser": "^2.0.6",
"lodash": "^4.17.20",
"moment": "^2.29.1", "moment": "^2.29.1",
"platform": "^1.3.6", "platform": "^1.3.6",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",

@ -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<typeof usersActions>;
export function getUsersAsync(): ThunkAction {
return async (dispatch): Promise<void> => {
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));
}
};
}

@ -34,7 +34,6 @@ import '../styles.scss';
interface CVATAppProps { interface CVATAppProps {
loadFormats: () => void; loadFormats: () => void;
loadUsers: () => void;
loadAbout: () => void; loadAbout: () => void;
verifyAuthorized: () => void; verifyAuthorized: () => void;
loadUserAgreements: () => void; loadUserAgreements: () => void;
@ -54,8 +53,6 @@ interface CVATAppProps {
modelsFetching: boolean; modelsFetching: boolean;
formatsInitialized: boolean; formatsInitialized: boolean;
formatsFetching: boolean; formatsFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean; aboutInitialized: boolean;
aboutFetching: boolean; aboutFetching: boolean;
userAgreementsFetching: boolean; userAgreementsFetching: boolean;
@ -92,7 +89,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
const { const {
verifyAuthorized, verifyAuthorized,
loadFormats, loadFormats,
loadUsers,
loadAbout, loadAbout,
loadUserAgreements, loadUserAgreements,
initPlugins, initPlugins,
@ -102,8 +98,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
userFetching, userFetching,
formatsInitialized, formatsInitialized,
formatsFetching, formatsFetching,
usersInitialized,
usersFetching,
aboutInitialized, aboutInitialized,
aboutFetching, aboutFetching,
pluginsInitialized, pluginsInitialized,
@ -143,10 +137,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadFormats(); loadFormats();
} }
if (!usersInitialized && !usersFetching) {
loadUsers();
}
if (!aboutInitialized && !aboutFetching) { if (!aboutInitialized && !aboutFetching) {
loadAbout(); loadAbout();
} }
@ -235,7 +225,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
public render(): JSX.Element { public render(): JSX.Element {
const { const {
userInitialized, userInitialized,
usersInitialized,
aboutInitialized, aboutInitialized,
pluginsInitialized, pluginsInitialized,
formatsInitialized, formatsInitialized,
@ -248,7 +237,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
const readyForRender = const readyForRender =
(userInitialized && (user == null || !user.isVerified)) || (userInitialized && (user == null || !user.isVerified)) ||
(userInitialized && formatsInitialized && pluginsInitialized && usersInitialized && aboutInitialized); (userInitialized && formatsInitialized && pluginsInitialized && aboutInitialized);
const subKeyMap = { const subKeyMap = {
SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS, SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
@ -270,7 +259,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
if (showPlatformNotification()) { if (showPlatformNotification()) {
stopNotifications(false); stopNotifications(false);
const info = platformInfo(); const {
name, version, engine, os,
} = platformInfo();
Modal.warning({ Modal.warning({
title: 'Unsupported platform detected', title: 'Unsupported platform detected',
content: ( content: (
@ -278,7 +270,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Row> <Row>
<Col> <Col>
<Text> <Text>
{`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.' + ' CVAT was tested in the latest versions of Chrome and Firefox.' +
' We recommend to use Chrome (or another Chromium based browser)'} ' We recommend to use Chrome (or another Chromium based browser)'}
</Text> </Text>
@ -286,7 +278,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
</Row> </Row>
<Row> <Row>
<Col> <Col>
<Text type='secondary'>{`The operating system is ${info.os}`}</Text> <Text type='secondary'>{`The operating system is ${os}`}</Text>
</Col> </Col>
</Row> </Row>
</> </>

@ -18,7 +18,7 @@ import patterns from 'utils/validation-patterns';
import { getReposData, syncRepos } from 'utils/git-utils'; import { getReposData, syncRepos } from 'utils/git-utils';
import { ActiveInference } from 'reducers/interfaces'; import { ActiveInference } from 'reducers/interfaces';
import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress'; 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'; import LabelsEditorComponent from '../labels-editor/labels-editor';
const core = getCore(); const core = getCore();
@ -27,7 +27,6 @@ interface Props {
previewImage: string; previewImage: string;
taskInstance: any; taskInstance: any;
installedGit: boolean; // change to git repos url installedGit: boolean; // change to git repos url
registeredUsers: any[];
activeInference: ActiveInference | null; activeInference: ActiveInference | null;
cancelAutoAnnotation(): void; cancelAutoAnnotation(): void;
onTaskUpdate: (taskInstance: any) => void; onTaskUpdate: (taskInstance: any) => void;
@ -196,35 +195,26 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
} }
private renderUsers(): JSX.Element { private renderUsers(): JSX.Element {
const { taskInstance, registeredUsers, onTaskUpdate } = this.props; const { taskInstance, onTaskUpdate } = this.props;
const owner = taskInstance.owner ? taskInstance.owner.username : null; 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 created = moment(taskInstance.createdDate).format('MMMM Do YYYY');
const assigneeSelect = ( const assigneeSelect = (
<UserSelector <UserSelector
users={registeredUsers}
value={assignee} value={assignee}
onChange={(value: string): void => { onSelect={(value: User | null): void => {
let [userInstance] = registeredUsers.filter((user: any) => user.username === value); taskInstance.assignee = value;
if (userInstance === undefined) {
userInstance = null;
}
taskInstance.assignee = userInstance;
onTaskUpdate(taskInstance); onTaskUpdate(taskInstance);
}} }}
/> />
); );
return ( return (
<Row type='flex' justify='space-between' align='middle'> <Row className='cvat-task-details-user-block' type='flex' justify='space-between' align='middle'>
<Col span={12}>{owner && <Text type='secondary'>{`Created by ${owner} on ${created}`}</Text>}</Col> <Col span={12}>{owner && <Text type='secondary'>{`Created by ${owner} on ${created}`}</Text>}</Col>
<Col span={10}> <Col span={10}>
<Text type='secondary'> <Text type='secondary'>Assigned to</Text>
Assigned to {assigneeSelect}
{assigneeSelect}
</Text>
</Col> </Col>
</Row> </Row>
); );

@ -15,7 +15,7 @@ import moment from 'moment';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
import UserSelector from './user-selector'; import UserSelector, { User } from './user-selector';
const core = getCore(); const core = getCore();
@ -23,14 +23,12 @@ const baseURL = core.config.backendAPI.slice(0, -7);
interface Props { interface Props {
taskInstance: any; taskInstance: any;
registeredUsers: any[];
onJobUpdate(jobInstance: any): void; onJobUpdate(jobInstance: any): void;
} }
function JobListComponent(props: Props & RouteComponentProps): JSX.Element { function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
const { const {
taskInstance, taskInstance,
registeredUsers,
onJobUpdate, onJobUpdate,
history: { push }, history: { push },
} = props; } = props;
@ -100,21 +98,14 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
dataIndex: 'assignee', dataIndex: 'assignee',
key: 'assignee', key: 'assignee',
render: (jobInstance: any): JSX.Element => { render: (jobInstance: any): JSX.Element => {
const assignee = jobInstance.assignee ? jobInstance.assignee.username : null; const assignee = jobInstance.assignee ? jobInstance.assignee : null;
return ( return (
<UserSelector <UserSelector
users={registeredUsers}
value={assignee} value={assignee}
onChange={(value: string): void => { onSelect={(value: User | null): void => {
let [userInstance] = [...registeredUsers].filter((user: any) => user.username === value);
if (userInstance === undefined) {
userInstance = null;
}
// eslint-disable-next-line // eslint-disable-next-line
jobInstance.assignee = userInstance; jobInstance.assignee = value;
onJobUpdate(jobInstance); onJobUpdate(jobInstance);
}} }}
/> />

@ -17,6 +17,12 @@
padding: 20px; padding: 20px;
background: $background-color-1; 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) {
> div:nth-child(2) { > div:nth-child(2) {
padding-left: 20px; padding-left: 20px;
@ -87,11 +93,6 @@
background-color: $background-color-2; background-color: $background-color-2;
} }
.cvat-user-selector {
margin-left: 10px;
width: 150px;
}
.cvat-open-bug-tracker-button { .cvat-open-bug-tracker-button {
margin-left: 15px; margin-left: 15px;
} }

@ -2,30 +2,114 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { useState, useEffect, useRef } from 'react';
import Select from 'antd/lib/select'; 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 { interface Props {
value: string | null; value: User | null;
users: any[]; onSelect: (user: User | null) => void;
onChange: (user: string) => 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 { 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<User[]>([]);
const autocompleteRef = useRef<Autocomplete | null>(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 ( return (
<Select defaultValue={value || '—'} size='small' showSearch className='cvat-user-selector' onChange={onChange}> <Autocomplete
<Select.Option key='-1' value='—'> ref={autocompleteRef}
value={searchPhrase}
</Select.Option> placeholder='Select a user'
{users.map( onSearch={handleSearch}
(user): JSX.Element => ( onSelect={handleSelect}
<Select.Option key={user.id} value={user.username}> className='cvat-user-search-field'
{user.username} onDropdownVisibleChange={handleFocus}
</Select.Option> dataSource={users.map((user) => ({
), value: user.id.toString(),
)} text: user.username,
</Select> }))}
>
<Input onPressEnter={() => autocompleteRef.current?.blur()} />
</Autocomplete>
); );
} }

@ -15,7 +15,6 @@ interface OwnProps {
} }
interface StateToProps { interface StateToProps {
registeredUsers: any[];
activeInference: ActiveInference | null; activeInference: ActiveInference | null;
installedGit: boolean; installedGit: boolean;
} }
@ -29,7 +28,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { list } = state.plugins; const { list } = state.plugins;
return { return {
registeredUsers: state.users.users,
installedGit: list.GIT_INTEGRATION, installedGit: list.GIT_INTEGRATION,
activeInference: state.models.inferences[own.task.instance.id] || null, 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 { function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element {
const { task, installedGit, activeInference, registeredUsers, cancelAutoAnnotation, onTaskUpdate } = props; const {
task, installedGit, activeInference, cancelAutoAnnotation, onTaskUpdate,
} = props;
return ( return (
<DetailsComponent <DetailsComponent
previewImage={task.preview} previewImage={task.preview}
taskInstance={task.instance} taskInstance={task.instance}
installedGit={installedGit} installedGit={installedGit}
registeredUsers={registeredUsers}
activeInference={activeInference} activeInference={activeInference}
onTaskUpdate={onTaskUpdate} onTaskUpdate={onTaskUpdate}
cancelAutoAnnotation={cancelAutoAnnotation} cancelAutoAnnotation={cancelAutoAnnotation}

@ -7,38 +7,26 @@ import { connect } from 'react-redux';
import JobListComponent from 'components/task-page/job-list'; import JobListComponent from 'components/task-page/job-list';
import { updateJobAsync } from 'actions/tasks-actions'; import { updateJobAsync } from 'actions/tasks-actions';
import { Task, CombinedState } from 'reducers/interfaces'; import { Task } from 'reducers/interfaces';
interface OwnProps { interface OwnProps {
task: Task; task: Task;
} }
interface StateToProps {
registeredUsers: any[];
}
interface DispatchToProps { interface DispatchToProps {
onJobUpdate(jobInstance: any): void; onJobUpdate(jobInstance: any): void;
} }
function mapStateToProps(state: CombinedState): StateToProps {
return {
registeredUsers: state.users.users,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps {
return { return {
onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)), onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)),
}; };
} }
function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element { function TaskPageContainer(props: DispatchToProps & OwnProps): JSX.Element {
const { task, registeredUsers, onJobUpdate } = props; const { task, onJobUpdate } = props;
return ( return <JobListComponent taskInstance={task.instance} onJobUpdate={onJobUpdate} />;
<JobListComponent taskInstance={task.instance} registeredUsers={registeredUsers} onJobUpdate={onJobUpdate} />
);
} }
export default connect(mapStateToProps, mapDispatchToProps)(TaskPageContainer); export default connect(null, mapDispatchToProps)(TaskPageContainer);

@ -10,7 +10,6 @@ import { getPluginsAsync } from 'actions/plugins-actions';
import { switchSettingsDialog } from 'actions/settings-actions'; import { switchSettingsDialog } from 'actions/settings-actions';
import { shortcutsActions } from 'actions/shortcuts-actions'; import { shortcutsActions } from 'actions/shortcuts-actions';
import { getUserAgreementsAsync } from 'actions/useragreements-actions'; import { getUserAgreementsAsync } from 'actions/useragreements-actions';
import { getUsersAsync } from 'actions/users-actions';
import CVATApplication from 'components/cvat-app'; import CVATApplication from 'components/cvat-app';
import LayoutGrid from 'components/layout-grid/layout-grid'; import LayoutGrid from 'components/layout-grid/layout-grid';
import logger, { LogType } from 'cvat-logger'; import logger, { LogType } from 'cvat-logger';
@ -34,8 +33,6 @@ interface StateToProps {
modelsFetching: boolean; modelsFetching: boolean;
userInitialized: boolean; userInitialized: boolean;
userFetching: boolean; userFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean; aboutInitialized: boolean;
aboutFetching: boolean; aboutFetching: boolean;
formatsInitialized: boolean; formatsInitialized: boolean;
@ -55,7 +52,6 @@ interface StateToProps {
interface DispatchToProps { interface DispatchToProps {
loadFormats: () => void; loadFormats: () => void;
verifyAuthorized: () => void; verifyAuthorized: () => void;
loadUsers: () => void;
loadAbout: () => void; loadAbout: () => void;
initModels: () => void; initModels: () => void;
initPlugins: () => void; initPlugins: () => void;
@ -71,7 +67,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { plugins } = state; const { plugins } = state;
const { auth } = state; const { auth } = state;
const { formats } = state; const { formats } = state;
const { users } = state;
const { about } = state; const { about } = state;
const { shortcuts } = state; const { shortcuts } = state;
const { userAgreements } = state; const { userAgreements } = state;
@ -84,8 +79,6 @@ function mapStateToProps(state: CombinedState): StateToProps {
pluginsFetching: plugins.fetching, pluginsFetching: plugins.fetching,
modelsInitialized: models.initialized, modelsInitialized: models.initialized,
modelsFetching: models.fetching, modelsFetching: models.fetching,
usersInitialized: users.initialized,
usersFetching: users.fetching,
aboutInitialized: about.initialized, aboutInitialized: about.initialized,
aboutFetching: about.fetching, aboutFetching: about.fetching,
formatsInitialized: formats.initialized, formatsInitialized: formats.initialized,
@ -110,7 +103,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()), loadUserAgreements: (): void => dispatch(getUserAgreementsAsync()),
initPlugins: (): void => dispatch(getPluginsAsync()), initPlugins: (): void => dispatch(getPluginsAsync()),
initModels: (): void => dispatch(getModelsAsync()), initModels: (): void => dispatch(getModelsAsync()),
loadUsers: (): void => dispatch(getUsersAsync()),
loadAbout: (): void => dispatch(getAboutAsync()), loadAbout: (): void => dispatch(getAboutAsync()),
resetErrors: (): void => dispatch(resetErrors()), resetErrors: (): void => dispatch(resetErrors()),
resetMessages: (): void => dispatch(resetMessages()), resetMessages: (): void => dispatch(resetMessages()),

@ -93,12 +93,6 @@ export interface PluginsState {
list: PluginsList; list: PluginsList;
} }
export interface UsersState {
users: any[];
fetching: boolean;
initialized: boolean;
}
export interface AboutState { export interface AboutState {
server: any; server: any;
packageVersion: { packageVersion: {
@ -494,7 +488,6 @@ export interface MetaState {
export interface CombinedState { export interface CombinedState {
auth: AuthState; auth: AuthState;
tasks: TasksState; tasks: TasksState;
users: UsersState;
about: AboutState; about: AboutState;
share: ShareState; share: ShareState;
formats: FormatsState; formats: FormatsState;

@ -9,7 +9,6 @@ import { FormatsActionTypes } from 'actions/formats-actions';
import { ModelsActionTypes } from 'actions/models-actions'; import { ModelsActionTypes } from 'actions/models-actions';
import { ShareActionTypes } from 'actions/share-actions'; import { ShareActionTypes } from 'actions/share-actions';
import { TasksActionTypes } from 'actions/tasks-actions'; import { TasksActionTypes } from 'actions/tasks-actions';
import { UsersActionTypes } from 'actions/users-actions';
import { AboutActionTypes } from 'actions/about-actions'; import { AboutActionTypes } from 'actions/about-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions'; import { AnnotationActionTypes } from 'actions/annotation-actions';
import { NotificationsActionType } from 'actions/notification-actions'; import { NotificationsActionType } from 'actions/notification-actions';
@ -357,8 +356,7 @@ export default function (state = defaultState, action: AnyAction): Notifications
tasks: { tasks: {
...state.errors.tasks, ...state.errors.tasks,
updating: { updating: {
message: message: `Could not update <a href="/tasks/${taskID}" target="_blank">task ${taskID}</a>`,
'Could not update ' + `<a href="/tasks/${taskID}" target="_blank">task ${taskID}</a>`,
reason: action.payload.error.toString(), 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: { case AboutActionTypes.GET_ABOUT_FAILED: {
return { return {
...state, ...state,

@ -5,7 +5,6 @@
import { combineReducers, Reducer } from 'redux'; import { combineReducers, Reducer } from 'redux';
import authReducer from './auth-reducer'; import authReducer from './auth-reducer';
import tasksReducer from './tasks-reducer'; import tasksReducer from './tasks-reducer';
import usersReducer from './users-reducer';
import aboutReducer from './about-reducer'; import aboutReducer from './about-reducer';
import shareReducer from './share-reducer'; import shareReducer from './share-reducer';
import formatsReducer from './formats-reducer'; import formatsReducer from './formats-reducer';
@ -21,7 +20,6 @@ export default function createRootReducer(): Reducer {
return combineReducers({ return combineReducers({
auth: authReducer, auth: authReducer,
tasks: tasksReducer, tasks: tasksReducer,
users: usersReducer,
about: aboutReducer, about: aboutReducer,
share: shareReducer, share: shareReducer,
formats: formatsReducer, formats: formatsReducer,

@ -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;
}
}

@ -218,8 +218,6 @@ class TaskExportTest(_DbTestBase):
def _generate_task(self, images): def _generate_task(self, images):
task = { task = {
"name": "my task #1", "name": "my task #1",
"owner": '',
"assignee": '',
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -438,8 +436,6 @@ class FrameMatchingTest(_DbTestBase):
def _generate_task(self, images): def _generate_task(self, images):
task = { task = {
"name": "my task #1", "name": "my task #1",
"owner": '',
"assignee": '',
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [

@ -13,6 +13,36 @@ from cvat.apps.engine import models
from cvat.apps.engine.log import slogger from cvat.apps.engine.log import slogger
from cvat.apps.dataset_manager.formats.utils import get_label_color from cvat.apps.dataset_manager.formats.utils import get_label_color
class BasicUserSerializer(serializers.ModelSerializer):
def validate(self, data):
if hasattr(self, 'initial_data'):
unknown_keys = set(self.initial_data.keys()) - set(self.fields.keys())
if unknown_keys:
if set(['is_staff', 'is_superuser', 'groups']) & unknown_keys:
message = 'You do not have permissions to access some of' + \
' these fields: {}'.format(unknown_keys)
else:
message = 'Got unknown fields: {}'.format(unknown_keys)
raise serializers.ValidationError(message)
return data
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name')
ordering = ['-id']
class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(many=True,
slug_field='name', queryset=Group.objects.all())
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email',
'groups', 'is_staff', 'is_superuser', 'is_active', 'last_login',
'date_joined')
read_only_fields = ('last_login', 'date_joined')
write_only_fields = ('password', )
ordering = ['-id']
class AttributeSerializer(serializers.ModelSerializer): class AttributeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -53,16 +83,21 @@ class JobSerializer(serializers.ModelSerializer):
task_id = serializers.ReadOnlyField(source="segment.task.id") task_id = serializers.ReadOnlyField(source="segment.task.id")
start_frame = serializers.ReadOnlyField(source="segment.start_frame") start_frame = serializers.ReadOnlyField(source="segment.start_frame")
stop_frame = serializers.ReadOnlyField(source="segment.stop_frame") 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)
class Meta: class Meta:
model = models.Job model = models.Job
fields = ('url', 'id', 'assignee', 'status', 'start_frame', fields = ('url', 'id', 'assignee', 'assignee_id', 'status', 'start_frame',
'stop_frame', 'task_id') 'stop_frame', 'task_id')
class SimpleJobSerializer(serializers.ModelSerializer): class SimpleJobSerializer(serializers.ModelSerializer):
assignee = BasicUserSerializer(allow_null=True)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True)
class Meta: class Meta:
model = models.Job model = models.Job
fields = ('url', 'id', 'assignee', 'status') fields = ('url', 'id', 'assignee', 'assignee_id', 'status')
class SegmentSerializer(serializers.ModelSerializer): class SegmentSerializer(serializers.ModelSerializer):
jobs = SimpleJobSerializer(many=True, source='job_set') jobs = SimpleJobSerializer(many=True, source='job_set')
@ -239,14 +274,18 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
size = serializers.ReadOnlyField(source='data.size') size = serializers.ReadOnlyField(source='data.size')
image_quality = serializers.ReadOnlyField(source='data.image_quality') image_quality = serializers.ReadOnlyField(source='data.image_quality')
data = serializers.ReadOnlyField(source='data.id') data = serializers.ReadOnlyField(source='data.id')
owner = BasicUserSerializer(required=False)
owner_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
assignee = BasicUserSerializer(allow_null=True, required=False)
assignee_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
class Meta: class Meta:
model = models.Task model = models.Task
fields = ('url', 'id', 'name', 'mode', 'owner', 'assignee', fields = ('url', 'id', 'name', 'mode', 'owner', 'assignee', 'owner_id', 'assignee_id',
'bug_tracker', 'created_date', 'updated_date', 'overlap', 'bug_tracker', 'created_date', 'updated_date', 'overlap',
'segment_size', 'status', 'labels', 'segments', 'segment_size', 'status', 'labels', 'segments',
'project', 'data_chunk_size', 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data') 'project', 'data_chunk_size', 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data')
read_only_fields = ('mode', 'created_date', 'updated_date', 'status', 'data_chunk_size', read_only_fields = ('mode', 'created_date', 'updated_date', 'status', 'data_chunk_size', 'owner', 'asignee',
'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data') 'data_compressed_chunk_type', 'data_original_chunk_type', 'size', 'image_quality', 'data')
write_once_fields = ('overlap', 'segment_size') write_once_fields = ('overlap', 'segment_size')
ordering = ['-id'] ordering = ['-id']
@ -278,8 +317,8 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
# pylint: disable=no-self-use # pylint: disable=no-self-use
def update(self, instance, validated_data): def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name) instance.name = validated_data.get('name', instance.name)
instance.owner = validated_data.get('owner', instance.owner) instance.owner_id = validated_data.get('owner_id', instance.owner_id)
instance.assignee = validated_data.get('assignee', instance.assignee) instance.assignee_id = validated_data.get('assignee_id', instance.assignee_id)
instance.bug_tracker = validated_data.get('bug_tracker', instance.bug_tracker = validated_data.get('bug_tracker',
instance.bug_tracker) instance.bug_tracker)
instance.project = validated_data.get('project', instance.project) instance.project = validated_data.get('project', instance.project)
@ -339,37 +378,6 @@ class ProjectSerializer(serializers.ModelSerializer):
read_only_fields = ('created_date', 'updated_date', 'status') read_only_fields = ('created_date', 'updated_date', 'status')
ordering = ['-id'] ordering = ['-id']
class BasicUserSerializer(serializers.ModelSerializer):
def validate(self, data):
if hasattr(self, 'initial_data'):
unknown_keys = set(self.initial_data.keys()) - set(self.fields.keys())
if unknown_keys:
if set(['is_staff', 'is_superuser', 'groups']) & unknown_keys:
message = 'You do not have permissions to access some of' + \
' these fields: {}'.format(unknown_keys)
else:
message = 'Got unknown fields: {}'.format(unknown_keys)
raise serializers.ValidationError(message)
return data
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name')
ordering = ['-id']
class UserSerializer(serializers.ModelSerializer):
groups = serializers.SlugRelatedField(many=True,
slug_field='name', queryset=Group.objects.all())
class Meta:
model = User
fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email',
'groups', 'is_staff', 'is_superuser', 'is_active', 'last_login',
'date_joined')
read_only_fields = ('last_login', 'date_joined')
write_only_fields = ('password', )
ordering = ['-id']
class ExceptionSerializer(serializers.Serializer): class ExceptionSerializer(serializers.Serializer):
system = serializers.CharField(max_length=255) system = serializers.CharField(max_length=255)
client = serializers.CharField(max_length=255) client = serializers.CharField(max_length=255)

@ -297,47 +297,47 @@ class JobUpdateAPITestCase(APITestCase):
self.assertEqual(response.data["id"], self.job.id) self.assertEqual(response.data["id"], self.job.id)
self.assertEqual(response.data["status"], data.get('status', self.job.status)) self.assertEqual(response.data["status"], data.get('status', self.job.status))
assignee = self.job.assignee.id if self.job.assignee else None assignee = self.job.assignee.id if self.job.assignee else None
self.assertEqual(response.data["assignee"], data.get('assignee', assignee)) self.assertEqual(response.data["assignee"]["id"], data.get('assignee_id', assignee))
self.assertEqual(response.data["start_frame"], self.job.segment.start_frame) self.assertEqual(response.data["start_frame"], self.job.segment.start_frame)
self.assertEqual(response.data["stop_frame"], self.job.segment.stop_frame) self.assertEqual(response.data["stop_frame"], self.job.segment.stop_frame)
def test_api_v1_jobs_id_admin(self): def test_api_v1_jobs_id_admin(self):
data = {"status": StatusChoice.COMPLETED, "assignee": self.owner.id} data = {"status": StatusChoice.COMPLETED, "assignee_id": self.owner.id}
response = self._run_api_v1_jobs_id(self.job.id, self.admin, data) response = self._run_api_v1_jobs_id(self.job.id, self.admin, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.admin, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.admin, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_owner(self): def test_api_v1_jobs_id_owner(self):
data = {"status": StatusChoice.VALIDATION, "assignee": self.annotator.id} data = {"status": StatusChoice.VALIDATION, "assignee_id": self.annotator.id}
response = self._run_api_v1_jobs_id(self.job.id, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id, self.owner, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.owner, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_annotator(self): def test_api_v1_jobs_id_annotator(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.annotator, data) response = self._run_api_v1_jobs_id(self.job.id, self.annotator, data)
self._check_request(response, data) self._check_request(response, data)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.annotator, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.annotator, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_observer(self): def test_api_v1_jobs_id_observer(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.admin.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.admin.id}
response = self._run_api_v1_jobs_id(self.job.id, self.observer, data) response = self._run_api_v1_jobs_id(self.job.id, self.observer, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.observer, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.observer, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_user(self): def test_api_v1_jobs_id_user(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.user, data) response = self._run_api_v1_jobs_id(self.job.id, self.user, data)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
response = self._run_api_v1_jobs_id(self.job.id + 10, self.user, data) response = self._run_api_v1_jobs_id(self.job.id + 10, self.user, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_api_v1_jobs_id_no_auth(self): def test_api_v1_jobs_id_no_auth(self):
data = {"status": StatusChoice.ANNOTATION, "assignee": self.user.id} data = {"status": StatusChoice.ANNOTATION, "assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, None, data) response = self._run_api_v1_jobs_id(self.job.id, None, data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
response = self._run_api_v1_jobs_id(self.job.id + 10, None, data) response = self._run_api_v1_jobs_id(self.job.id + 10, None, data)
@ -356,7 +356,7 @@ class JobPartialUpdateAPITestCase(JobUpdateAPITestCase):
self._check_request(response, data) self._check_request(response, data)
def test_api_v1_jobs_id_admin_partial(self): def test_api_v1_jobs_id_admin_partial(self):
data = {"assignee": self.user.id} data = {"assignee_id": self.user.id}
response = self._run_api_v1_jobs_id(self.job.id, self.owner, data) response = self._run_api_v1_jobs_id(self.job.id, self.owner, data)
self._check_request(response, data) self._check_request(response, data)
@ -1073,9 +1073,11 @@ class TaskGetAPITestCase(APITestCase):
self.assertEqual(response.data["size"], db_task.data.size) self.assertEqual(response.data["size"], db_task.data.size)
self.assertEqual(response.data["mode"], db_task.mode) self.assertEqual(response.data["mode"], db_task.mode)
owner = db_task.owner.id if db_task.owner else None owner = db_task.owner.id if db_task.owner else None
self.assertEqual(response.data["owner"], owner) response_owner = response.data["owner"]["id"] if response.data["owner"] else None
self.assertEqual(response_owner, owner)
assignee = db_task.assignee.id if db_task.assignee else None assignee = db_task.assignee.id if db_task.assignee else None
self.assertEqual(response.data["assignee"], assignee) response_assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(response_assignee, assignee)
self.assertEqual(response.data["overlap"], db_task.overlap) self.assertEqual(response.data["overlap"], db_task.overlap)
self.assertEqual(response.data["segment_size"], db_task.segment_size) self.assertEqual(response.data["segment_size"], db_task.segment_size)
self.assertEqual(response.data["image_quality"], db_task.data.image_quality) self.assertEqual(response.data["image_quality"], db_task.data.image_quality)
@ -1179,11 +1181,13 @@ class TaskUpdateAPITestCase(APITestCase):
mode = data.get("mode", db_task.mode) mode = data.get("mode", db_task.mode)
self.assertEqual(response.data["mode"], mode) self.assertEqual(response.data["mode"], mode)
owner = db_task.owner.id if db_task.owner else None owner = db_task.owner.id if db_task.owner else None
owner = data.get("owner", owner) owner = data.get("owner_id", owner)
self.assertEqual(response.data["owner"], owner) response_owner = response.data["owner"]["id"] if response.data["owner"] else None
self.assertEqual(response_owner, owner)
assignee = db_task.assignee.id if db_task.assignee else None assignee = db_task.assignee.id if db_task.assignee else None
assignee = data.get("assignee", assignee) assignee = data.get("assignee_id", assignee)
self.assertEqual(response.data["assignee"], assignee) response_assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(response_assignee, assignee)
self.assertEqual(response.data["overlap"], db_task.overlap) self.assertEqual(response.data["overlap"], db_task.overlap)
self.assertEqual(response.data["segment_size"], db_task.segment_size) self.assertEqual(response.data["segment_size"], db_task.segment_size)
image_quality = data.get("image_quality", db_task.data.image_quality) image_quality = data.get("image_quality", db_task.data.image_quality)
@ -1213,7 +1217,7 @@ class TaskUpdateAPITestCase(APITestCase):
def test_api_v1_tasks_id_admin(self): def test_api_v1_tasks_id_admin(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.owner.id, "owner_id": self.owner.id,
"labels": [{ "labels": [{
"name": "non-vehicle", "name": "non-vehicle",
"attributes": [{ "attributes": [{
@ -1229,7 +1233,7 @@ class TaskUpdateAPITestCase(APITestCase):
def test_api_v1_tasks_id_user(self): def test_api_v1_tasks_id_user(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.assignee.id, "owner_id": self.assignee.id,
"labels": [{ "labels": [{
"name": "car", "name": "car",
"attributes": [{ "attributes": [{
@ -1277,7 +1281,7 @@ class TaskPartialUpdateAPITestCase(TaskUpdateAPITestCase):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.owner.id "owner_id": self.owner.id
} }
self._check_api_v1_tasks_id(self.admin, data) self._check_api_v1_tasks_id(self.admin, data)
# Now owner is updated, but self.db_tasks are obsolete # Now owner is updated, but self.db_tasks are obsolete
@ -1300,8 +1304,8 @@ class TaskPartialUpdateAPITestCase(TaskUpdateAPITestCase):
self._check_api_v1_tasks_id(self.user, data) self._check_api_v1_tasks_id(self.user, data)
data = { data = {
"owner": self.observer.id, "owner_id": self.observer.id,
"assignee": self.annotator.id "assignee_id": self.annotator.id
} }
self._check_api_v1_tasks_id(self.user, data) self._check_api_v1_tasks_id(self.user, data)
@ -1339,8 +1343,9 @@ class TaskCreateAPITestCase(APITestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data["name"], data["name"]) self.assertEqual(response.data["name"], data["name"])
self.assertEqual(response.data["mode"], "") self.assertEqual(response.data["mode"], "")
self.assertEqual(response.data["owner"], data.get("owner", user.id)) self.assertEqual(response.data["owner"]["id"], data.get("owner_id", user.id))
self.assertEqual(response.data["assignee"], data.get("assignee")) assignee = response.data["assignee"]["id"] if response.data["assignee"] else None
self.assertEqual(assignee, data.get("assignee_id", None))
self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", "")) self.assertEqual(response.data["bug_tracker"], data.get("bug_tracker", ""))
self.assertEqual(response.data["overlap"], data.get("overlap", None)) self.assertEqual(response.data["overlap"], data.get("overlap", None))
self.assertEqual(response.data["segment_size"], data.get("segment_size", 0)) self.assertEqual(response.data["segment_size"], data.get("segment_size", 0))
@ -1377,7 +1382,7 @@ class TaskCreateAPITestCase(APITestCase):
def test_api_v1_tasks_user(self): def test_api_v1_tasks_user(self):
data = { data = {
"name": "new name for the task", "name": "new name for the task",
"owner": self.assignee.id, "owner_id": self.assignee.id,
"labels": [{ "labels": [{
"name": "car", "name": "car",
"attributes": [{ "attributes": [{
@ -1663,8 +1668,8 @@ class TaskDataAPITestCase(APITestCase):
response = self._get_task(user, task_id) response = self._get_task(user, task_id)
expected_status_code = status.HTTP_200_OK expected_status_code = status.HTTP_200_OK
if user == self.user and "owner" in spec and spec["owner"] != user.id and \ if user == self.user and "owner_id" in spec and spec["owner_id"] != user.id and \
"assignee" in spec and spec["assignee"] != user.id: "assignee_id" in spec and spec["assignee_id"] != user.id:
expected_status_code = status.HTTP_403_FORBIDDEN expected_status_code = status.HTTP_403_FORBIDDEN
self.assertEqual(response.status_code, expected_status_code) self.assertEqual(response.status_code, expected_status_code)
@ -1746,8 +1751,8 @@ class TaskDataAPITestCase(APITestCase):
def _test_api_v1_tasks_id_data(self, user): def _test_api_v1_tasks_id_data(self, user):
task_spec = { task_spec = {
"name": "my task #1", "name": "my task #1",
"owner": self.owner.id, "owner_id": self.owner.id,
"assignee": self.assignee.id, "assignee_id": self.assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -2085,8 +2090,8 @@ class TaskDataAPITestCase(APITestCase):
def test_api_v1_tasks_id_data_no_auth(self): def test_api_v1_tasks_id_data_no_auth(self):
data = { data = {
"name": "my task #3", "name": "my task #3",
"owner": self.owner.id, "owner_id": self.owner.id,
"assignee": self.assignee.id, "assignee_id": self.assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [
@ -2131,8 +2136,8 @@ class JobAnnotationAPITestCase(APITestCase):
def _create_task(self, owner, assignee): def _create_task(self, owner, assignee):
data = { data = {
"name": "my task #1", "name": "my task #1",
"owner": owner.id, "owner_id": owner.id,
"assignee": assignee.id, "assignee_id": assignee.id,
"overlap": 0, "overlap": 0,
"segment_size": 100, "segment_size": 100,
"labels": [ "labels": [

@ -704,7 +704,16 @@ class JobViewSet(viewsets.GenericViewSet,
return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST) return Response(data=str(e), status=status.HTTP_400_BAD_REQUEST)
return Response(data) return Response(data)
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ("id",)
@method_decorator(name='list', decorator=swagger_auto_schema( @method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[
openapi.Parameter('id',openapi.IN_QUERY,description="A unique number value identifying this user",type=openapi.TYPE_NUMBER),
],
operation_summary='Method provides a paginated list of users registered on the server')) operation_summary='Method provides a paginated list of users registered on the server'))
@method_decorator(name='retrieve', decorator=swagger_auto_schema( @method_decorator(name='retrieve', decorator=swagger_auto_schema(
operation_summary='Method provides information of a specific user')) operation_summary='Method provides information of a specific user'))
@ -716,6 +725,8 @@ class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin): mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin):
queryset = User.objects.prefetch_related('groups').all().order_by('id') queryset = User.objects.prefetch_related('groups').all().order_by('id')
http_method_names = ['get', 'post', 'head', 'patch', 'delete'] http_method_names = ['get', 'post', 'head', 'patch', 'delete']
search_fields = ('username', 'first_name', 'last_name')
filterset_class = UserFilter
def get_serializer_class(self): def get_serializer_class(self):
user = self.request.user user = self.request.user

@ -45,16 +45,20 @@ context('Merge/split features', () => {
it('Create rectangle shape on first frame', () => { it('Create rectangle shape on first frame', () => {
goCheckFrameNumber(frameNum); goCheckFrameNumber(frameNum);
cy.createRectangle(createRectangleShape2Points); cy.createRectangle(createRectangleShape2Points);
cy.get('#cvat_canvas_shape_1').should('have.attr', 'x').then(xCoords => { cy.get('#cvat_canvas_shape_1')
xCoordinatesObjectFirstFrame = Math.floor(xCoords); .should('have.attr', 'x')
}); .then((xCoords) => {
xCoordinatesObjectFirstFrame = Math.floor(xCoords);
});
}); });
it('Create rectangle shape on third frame with another position', () => { it('Create rectangle shape on third frame with another position', () => {
goCheckFrameNumber(frameNum + 2); goCheckFrameNumber(frameNum + 2);
cy.createRectangle(createRectangleShape2PointsSecond); cy.createRectangle(createRectangleShape2PointsSecond);
cy.get('#cvat_canvas_shape_2').should('have.attr', 'x').then(xCoords => { cy.get('#cvat_canvas_shape_2')
xCoordinatesObjectThirdFrame = Math.floor(xCoords); .should('have.attr', 'x')
}); .then((xCoords) => {
xCoordinatesObjectThirdFrame = Math.floor(xCoords);
});
}); });
it('Merge the objects with "Merge button"', () => { it('Merge the objects with "Merge button"', () => {
cy.get('.cvat-merge-control').click(); cy.get('.cvat-merge-control').click();
@ -65,14 +69,20 @@ context('Merge/split features', () => {
}); });
it('Get a track with keyframes on first and third frame', () => { it('Get a track with keyframes on first and third frame', () => {
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .should('contain', '3')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
goCheckFrameNumber(frameNum + 2); goCheckFrameNumber(frameNum + 2);
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .should('contain', '3')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
}); });
it('On the second frame and on the fourth frame the track is invisible', () => { it('On the second frame and on the fourth frame the track is invisible', () => {
goCheckFrameNumber(frameNum + 1); goCheckFrameNumber(frameNum + 1);
@ -82,29 +92,43 @@ context('Merge/split features', () => {
}); });
it('Go to the second frame and remove "outside" flag from the track. The track now visible.', () => { it('Go to the second frame and remove "outside" flag from the track. The track now visible.', () => {
goCheckFrameNumber(frameNum + 1); goCheckFrameNumber(frameNum + 1);
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-outside').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('.cvat-object-item-button-outside').click();
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
});
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
}); });
it('Remove "keyframe" flag from the track. Track now interpolated between position on the first and the third frames.', () => { it('Remove "keyframe" flag from the track. Track now interpolated between position on the first and the third frames.', () => {
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('#cvat_canvas_shape_3').should('have.attr', 'x').then(xCoords => { cy.get('.cvat-object-item-button-keyframe').click();
// expected 9785 to be within 9642..9928 cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist');
expect(Math.floor(xCoords)).to.be.within(xCoordinatesObjectFirstFrame, xCoordinatesObjectThirdFrame); });
}); cy.get('#cvat_canvas_shape_3')
.should('have.attr', 'x')
.then((xCoords) => {
// expected 9785 to be within 9642..9928
expect(Math.floor(xCoords)).to.be.within(
xCoordinatesObjectFirstFrame,
xCoordinatesObjectThirdFrame,
);
});
}); });
it('On the fourth frame remove "keyframe" flag from the track. The track now visible and "outside" flag is disabled.', () => { it('On the fourth frame remove "keyframe" flag from the track. The track now visible and "outside" flag is disabled.', () => {
goCheckFrameNumber(frameNum + 3); goCheckFrameNumber(frameNum + 3);
cy.get('#cvat-objects-sidebar-state-item-3').should('contain', '3').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-3')
cy.get('.cvat-object-item-button-keyframe').click(); .should('contain', '3')
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist'); .and('contain', 'RECTANGLE TRACK')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .within(() => {
}); cy.get('.cvat-object-item-button-keyframe').click();
cy.get('.cvat-object-item-button-keyframe-enabled').should('not.exist');
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
});
cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_3').should('exist').and('be.visible');
}); });
it('Split a track with "split" button. Previous track became invisible (has "outside" flag). One more track and it is visible.', () => { it('Split a track with "split" button. Previous track became invisible (has "outside" flag). One more track and it is visible.', () => {
@ -112,14 +136,20 @@ context('Merge/split features', () => {
// A single click does not reproduce the split a track scenario in cypress test. // A single click does not reproduce the split a track scenario in cypress test.
cy.get('#cvat_canvas_shape_3').click().click(); cy.get('#cvat_canvas_shape_3').click().click();
cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden'); cy.get('#cvat_canvas_shape_4').should('exist').and('be.hidden');
cy.get('#cvat-objects-sidebar-state-item-4').should('contain', '4').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-4')
cy.get('.cvat-object-item-button-outside-enabled').should('exist'); .should('contain', '4')
}); .and('contain', 'RECTANGLE TRACK')
.within(() => {
cy.get('.cvat-object-item-button-outside-enabled').should('exist');
});
cy.get('#cvat_canvas_shape_5').should('exist').and('be.visible'); cy.get('#cvat_canvas_shape_5').should('exist').and('be.visible');
cy.get('#cvat-objects-sidebar-state-item-5').should('contain', '5').and('contain', 'RECTANGLE TRACK').within(() => { cy.get('#cvat-objects-sidebar-state-item-5')
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist'); .should('contain', '5')
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist'); .and('contain', 'RECTANGLE TRACK')
}); .within(() => {
cy.get('.cvat-object-item-button-outside-enabled').should('not.exist');
cy.get('.cvat-object-item-button-keyframe-enabled').should('exist');
});
}); });
}); });
}); });

@ -0,0 +1,126 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/// <reference types="cypress" />
import { taskName } from '../../support/const';
context('Group features', () => {
const caseId = '15';
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
switchLabel: false,
firstX: 250,
firstY: 350,
secondX: 350,
secondY: 450,
};
const createRectangleShape2PointsSecond = {
points: 'By 2 Points',
type: 'Shape',
switchLabel: false,
firstX: createRectangleShape2Points.firstX + 300,
firstY: createRectangleShape2Points.firstY,
secondX: createRectangleShape2Points.secondX + 300,
secondY: createRectangleShape2Points.secondY,
};
const createRectangleTrack2Points = {
points: 'By 2 Points',
type: 'Track',
switchLabel: false,
firstX: 250,
firstY: 600,
secondX: 350,
secondY: 700,
};
const createRectangleTrack2PointsSecond = {
points: 'By 2 Points',
type: 'Track',
switchLabel: false,
firstX: createRectangleTrack2Points.firstX + 300,
firstY: createRectangleTrack2Points.firstY,
secondX: createRectangleTrack2Points.secondX + 300,
secondY: createRectangleTrack2Points.secondY,
};
let defaultGroupColor = '';
let shapesGroupColor = '';
let tracksGroupColor = '';
before(() => {
cy.openTaskJob(taskName);
});
describe(`Testing case "${caseId}"`, () => {
it('Create two shapes and two tracks.', () => {
cy.createRectangle(createRectangleShape2Points);
cy.createRectangle(createRectangleShape2PointsSecond);
cy.createRectangle(createRectangleTrack2Points);
cy.createRectangle(createRectangleTrack2PointsSecond);
});
it('Set option "Color by" to "Group".', () => {
cy.changeAppearance('Group');
cy.get('.cvat_canvas_shape').then($listCanvasShapes => {
for (let i=0; i<$listCanvasShapes.length; i++) {
cy.get($listCanvasShapes[i]).should('have.css', 'fill').then($fill => {
defaultGroupColor = $fill;
});
}
});
cy.get('.cvat-objects-sidebar-state-item').then($listObjectsSidebarStateItem => {
for (let i=0; i<$listObjectsSidebarStateItem.length; i++) {
cy.get($listObjectsSidebarStateItem[i]).should('have.css', 'background-color').then($bColorObjectsSidebarStateItem => {
// expected rgba(224, 224, 224, 0.533) to include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorObjectsSidebarStateItem).contain(defaultGroupColor.match(/\d+, \d+, \d+/));
});
}
});
});
it('With group button unite two shapes. They have corresponding colors.', () => {
cy.get('.cvat-group-control').click();
for (const shapeToGroup of ['#cvat_canvas_shape_1', '#cvat_canvas_shape_2']) {
cy.get(shapeToGroup).click();
}
cy.get('.cvat-group-control').click();
for (const groupedShape of ['#cvat_canvas_shape_1', '#cvat_canvas_shape_2']) {
cy.get(groupedShape).should('have.css', 'fill').then($shapesGroupColor => {
// expected rgb(250, 50, 83) to not equal rgb(224, 224, 224)
expect($shapesGroupColor).to.not.equal(defaultGroupColor);
shapesGroupColor = $shapesGroupColor;
});
}
for (const objectSideBarShape of ['#cvat-objects-sidebar-state-item-1', '#cvat-objects-sidebar-state-item-2']) {
cy.get(objectSideBarShape).should('have.css', 'background-color').then($bColorobjectSideBarShape => {
// expected rgba(250, 50, 83, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorobjectSideBarShape).to.not.contain(defaultGroupColor.match(/\d+, \d+, \d+/));
// expected rgba(250, 50, 83, 0.533) to include [ 250, 50, 83, index: 4, input: 'rgb(250, 50, 83)', groups: undefined ]
expect($bColorobjectSideBarShape).to.be.contain(shapesGroupColor.match(/\d+, \d+, \d+/));
});
}
});
it('With group button unite two track. They have corresponding colors.', () => {
cy.get('.cvat-group-control').click();
for (const trackToGroup of ['#cvat_canvas_shape_3', '#cvat_canvas_shape_4']) {
cy.get(trackToGroup).click();
}
cy.get('.cvat-group-control').click();
for (const groupedTrack of ['#cvat_canvas_shape_3', '#cvat_canvas_shape_4']) {
cy.get(groupedTrack).should('have.css', 'fill').then($tracksGroupColor => {
// expected rgb(250, 50, 83) to not equal rgb(224, 224, 224)
expect($tracksGroupColor).to.not.equal(defaultGroupColor);
tracksGroupColor = $tracksGroupColor;
});
}
for (const objectSideBarTrack of ['#cvat-objects-sidebar-state-item-3', '#cvat-objects-sidebar-state-item-4']) {
cy.get(objectSideBarTrack).should('have.css', 'background-color').then($bColorobjectSideBarTrack => {
// expected rgba(52, 209, 183, 0.533) to not include [ 224, 224, 224, index: 4, input: 'rgb(224, 224, 224)', groups: undefined ]
expect($bColorobjectSideBarTrack).to.not.contain(defaultGroupColor.match(/\d+, \d+, \d+/));
// expected rgba(52, 209, 183, 0.533) to include [ 52, 209, 183, index: 4, input: 'rgb(52, 209, 183)', groups: undefined ]
expect($bColorobjectSideBarTrack).to.be.contain(tracksGroupColor.match(/\d+, \d+, \d+/));
});
}
});
});
});

@ -86,7 +86,7 @@ context('Multiple users. Assign task, job.', () => {
it('Assign the task to the second user and logout', () => { it('Assign the task to the second user and logout', () => {
cy.openTask(taskName); cy.openTask(taskName);
cy.get('.cvat-task-details').within(() => { cy.get('.cvat-task-details').within(() => {
cy.get('.cvat-user-selector').click({ force: true }); cy.get('.cvat-user-search-field').click({ force: true });
}); });
cy.contains(secondUserName).click(); cy.contains(secondUserName).click();
cy.logout(); cy.logout();
@ -112,7 +112,7 @@ context('Multiple users. Assign task, job.', () => {
cy.get('[value="tasks"]').click(); cy.get('[value="tasks"]').click();
cy.openTask(taskName); cy.openTask(taskName);
cy.get('.cvat-task-job-list').within(() => { cy.get('.cvat-task-job-list').within(() => {
cy.get('.cvat-user-selector').click({ force: true }); cy.get('.cvat-user-search-field').click({ force: true });
}); });
cy.contains(thirdUserName).click(); cy.contains(thirdUserName).click();
cy.logout(); cy.logout();

@ -21,7 +21,7 @@ module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => { on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) { if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--disable-gpu'); launchOptions.args.push('--disable-gpu');
return launchOptions return launchOptions;
} }
}); });
return config; return config;

@ -100,13 +100,14 @@ Cypress.Commands.add('createRectangle', (createRectangleParams) => {
cy.switchLabel(createRectangleParams.labelName); cy.switchLabel(createRectangleParams.labelName);
} }
cy.contains('Draw new rectangle') cy.contains('Draw new rectangle')
.parents('.cvat-draw-shape-popover-content').within(() => { .parents('.cvat-draw-shape-popover-content')
cy.get('.ant-select-selection-selected-value').then(($labelValue) => { .within(() => {
selectedValueGlobal = $labelValue.text(); cy.get('.ant-select-selection-selected-value').then(($labelValue) => {
selectedValueGlobal = $labelValue.text();
});
cy.get('.ant-radio-wrapper').contains(createRectangleParams.points).click();
cy.get('button').contains(createRectangleParams.type).click({ force: true });
}); });
cy.get('.ant-radio-wrapper').contains(createRectangleParams.points).click();
cy.get('button').contains(createRectangleParams.type).click({ force: true });
})
cy.get('.cvat-canvas-container').click(createRectangleParams.firstX, createRectangleParams.firstY); cy.get('.cvat-canvas-container').click(createRectangleParams.firstX, createRectangleParams.firstY);
cy.get('.cvat-canvas-container').click(createRectangleParams.secondX, createRectangleParams.secondY); cy.get('.cvat-canvas-container').click(createRectangleParams.secondX, createRectangleParams.secondY);
if (createRectangleParams.points === 'By 4 Points') { if (createRectangleParams.points === 'By 4 Points') {
@ -131,9 +132,11 @@ Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) =>
const maxId = Math.max(...listCanvasShapeId); const maxId = Math.max(...listCanvasShapeId);
cy.get(`#cvat_canvas_shape_${maxId}`).should('exist').and('be.visible'); cy.get(`#cvat_canvas_shape_${maxId}`).should('exist').and('be.visible');
cy.get(`#cvat-objects-sidebar-state-item-${maxId}`) cy.get(`#cvat-objects-sidebar-state-item-${maxId}`)
.should('contain', maxId).and('contain', `${objectType} ${objectParameters.type.toUpperCase()}`).within(() => { .should('contain', maxId)
cy.get('.ant-select-selection-selected-value').should('have.text', selectedValueGlobal); .and('contain', `${objectType} ${objectParameters.type.toUpperCase()}`)
}); .within(() => {
cy.get('.ant-select-selection-selected-value').should('have.text', selectedValueGlobal);
});
}); });
}); });

Loading…
Cancel
Save