Merge remote-tracking branch 'upstream/develop' into dkru/cypress-test-check-email-verification

main
Kruchinin 5 years ago
commit eb3feebbcd

@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- - PATCH requests from cvat-core submit only changed fields (<https://github.com/openvinotoolkit/cvat/pull/2445>)
### Deprecated ### Deprecated
@ -26,7 +26,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Django templates for email and user guide (<https://github.com/openvinotoolkit/cvat/pull/2412>) - Django templates for email and user guide (<https://github.com/openvinotoolkit/cvat/pull/2412>)
- Saving relative paths in dummy chunks instead of absolute(<https://github.com/openvinotoolkit/cvat/pull/2424>) - Saving relative paths in dummy chunks instead of absolute (<https://github.com/openvinotoolkit/cvat/pull/2424>)
- Objects with a specific label cannot be displayed if at least one tag with the label exist (<https://github.com/openvinotoolkit/cvat/pull/2435>)
- Wrong attribute can be removed in labels editor (<https://github.com/openvinotoolkit/cvat/pull/2436>)
- UI fails with the error "Cannot read property 'label' of undefined" (<https://github.com/openvinotoolkit/cvat/pull/2442>)
- Exception: "Value must be a user instance" (<https://github.com/openvinotoolkit/cvat/pull/2441>)
- Reset zoom option doesn't work in tag annotation mode (<https://github.com/openvinotoolkit/cvat/pull/2443>)
- Canvas is busy error (<https://github.com/openvinotoolkit/cvat/pull/2437>)
### Security ### Security

@ -21,8 +21,6 @@ COPY cvat-canvas/package*.json /tmp/cvat-canvas/
COPY cvat-ui/package*.json /tmp/cvat-ui/ COPY cvat-ui/package*.json /tmp/cvat-ui/
COPY cvat-data/package*.json /tmp/cvat-data/ COPY cvat-data/package*.json /tmp/cvat-data/
RUN npm config set loglevel info
# Install cvat-data dependencies # Install cvat-data dependencies
WORKDIR /tmp/cvat-data/ WORKDIR /tmp/cvat-data/
RUN npm ci RUN npm ci

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.9.0", "version": "3.9.1",
"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": {
@ -39,7 +39,7 @@
"detect-browser": "^5.2.0", "detect-browser": "^5.2.0",
"error-stack-parser": "^2.0.2", "error-stack-parser": "^2.0.2",
"form-data": "^2.5.0", "form-data": "^2.5.0",
"jest-config": "^24.8.0", "jest-config": "^26.0.0",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"jsonpath": "^1.0.2", "jsonpath": "^1.0.2",
"platform": "^1.3.5", "platform": "^1.3.5",

@ -674,6 +674,11 @@
task: undefined, task: undefined,
}; };
let updatedFields = {
assignee: false,
status: false,
};
for (const property in data) { for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property)) { if (Object.prototype.hasOwnProperty.call(data, property)) {
if (property in initialData) { if (property in initialData) {
@ -715,6 +720,7 @@
if (assignee !== null && !(assignee instanceof User)) { if (assignee !== null && !(assignee instanceof User)) {
throw new ArgumentError('Value must be a user instance'); throw new ArgumentError('Value must be a user instance');
} }
updatedFields.assignee = true;
data.assignee = assignee; data.assignee = assignee;
}, },
}, },
@ -743,6 +749,7 @@
); );
} }
updatedFields.status = true;
data.status = status; data.status = status;
}, },
}, },
@ -776,6 +783,12 @@
task: { task: {
get: () => data.task, get: () => data.task,
}, },
__updatedFields: {
get: () => updatedFields,
set: (fields) => {
updatedFields = fields;
},
},
}), }),
); );
@ -879,6 +892,13 @@
use_cache: undefined, use_cache: undefined,
}; };
let updatedFields = {
name: false,
assignee: false,
bug_tracker: false,
labels: false,
};
for (const property in data) { for (const property in data) {
if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) { if (Object.prototype.hasOwnProperty.call(data, property) && property in initialData) {
data[property] = initialData[property]; data[property] = initialData[property];
@ -948,6 +968,7 @@
if (!value.trim().length) { if (!value.trim().length) {
throw new ArgumentError('Value must not be empty'); throw new ArgumentError('Value must not be empty');
} }
updatedFields.name = true;
data.name = value; data.name = value;
}, },
}, },
@ -1006,6 +1027,7 @@
if (assignee !== null && !(assignee instanceof User)) { if (assignee !== null && !(assignee instanceof User)) {
throw new ArgumentError('Value must be a user instance'); throw new ArgumentError('Value must be a user instance');
} }
updatedFields.assignee = true;
data.assignee = assignee; data.assignee = assignee;
}, },
}, },
@ -1039,6 +1061,7 @@
bugTracker: { bugTracker: {
get: () => data.bug_tracker, get: () => data.bug_tracker,
set: (tracker) => { set: (tracker) => {
updatedFields.bug_tracker = true;
data.bug_tracker = tracker; data.bug_tracker = tracker;
}, },
}, },
@ -1145,6 +1168,7 @@
} }
} }
updatedFields.labels = true;
data.labels = [...labels]; data.labels = [...labels];
}, },
}, },
@ -1311,6 +1335,12 @@
dataChunkType: { dataChunkType: {
get: () => data.data_compressed_chunk_type, get: () => data.data_compressed_chunk_type,
}, },
__updatedFields: {
get: () => updatedFields,
set: (fields) => {
updatedFields = fields;
},
},
}), }),
); );
@ -1443,12 +1473,30 @@
Job.prototype.save.implementation = async function () { Job.prototype.save.implementation = async function () {
// TODO: Add ability to change an assignee // TODO: Add ability to change an assignee
if (this.id) { if (this.id) {
const jobData = { const jobData = {};
status: this.status,
assignee_id: this.assignee ? this.assignee.id : null, for (const [field, isUpdated] of Object.entries(this.__updatedFields)) {
}; if (isUpdated) {
switch (field) {
case 'status':
jobData.status = this.status;
break;
case 'assignee':
jobData.assignee_id = this.assignee ? this.assignee.id : null;
break;
default:
break;
}
}
}
await serverProxy.jobs.saveJob(this.id, jobData); await serverProxy.jobs.saveJob(this.id, jobData);
this.__updatedFields = {
status: false,
assignee: false,
};
return this; return this;
} }
@ -1653,14 +1701,38 @@
// TODO: Add ability to change an owner and an assignee // TODO: Add ability to change an owner and an assignee
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_id: this.assignee ? this.assignee.id : null,
name: this.name, for (const [field, isUpdated] of Object.entries(this.__updatedFields)) {
bug_tracker: this.bugTracker, if (isUpdated) {
labels: [...this.labels.map((el) => el.toJSON())], switch (field) {
}; case 'assignee':
taskData.assignee_id = this.assignee ? this.assignee.id : null;
break;
case 'name':
taskData.name = this.name;
break;
case 'bug_tracker':
taskData.bug_tracker = this.bugTracker;
break;
case 'labels':
taskData.labels = [...this.labels.map((el) => el.toJSON())];
break;
default:
break;
}
}
}
await serverProxy.tasks.saveTask(this.id, taskData); await serverProxy.tasks.saveTask(this.id, taskData);
this.updatedFields = {
assignee: false,
name: false,
bugTracker: false,
labels: false,
};
return this; return this;
} }

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.10.0", "version": "1.10.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -1213,9 +1213,9 @@
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "16.9.53", "version": "16.9.55",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.53.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz",
"integrity": "sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==", "integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@ -1231,9 +1231,9 @@
} }
}, },
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.8", "version": "16.9.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz",
"integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==",
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
@ -1854,9 +1854,9 @@
} }
}, },
"antd": { "antd": {
"version": "3.26.18", "version": "3.26.20",
"resolved": "https://registry.npmjs.org/antd/-/antd-3.26.18.tgz", "resolved": "https://registry.npmjs.org/antd/-/antd-3.26.20.tgz",
"integrity": "sha512-TPuacNJJNPji+LnapU46uWGqi+6JlyH75paMNs95IH0F7gGYtp4oSkua88gGsoAaUbDxTIF+cWI9mdIsr7ywlw==", "integrity": "sha512-VIous4ofZfxFtd9K1h9MpRX2sDDpj3QcOFi3YgIc9B/uyDli/GlLb8SWKfQfJaMkaxwatIv503dag2Tog+hiEg==",
"requires": { "requires": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"@ant-design/icons": "~2.1.1", "@ant-design/icons": "~2.1.1",
@ -3779,11 +3779,10 @@
} }
}, },
"create-react-class": { "create-react-class": {
"version": "15.6.3", "version": "15.7.0",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
"requires": { "requires": {
"fbjs": "^0.8.9",
"loose-envify": "^1.3.1", "loose-envify": "^1.3.1",
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
@ -3957,9 +3956,9 @@
} }
}, },
"csstype": { "csstype": {
"version": "3.0.4", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz",
"integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==" "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ=="
}, },
"currently-unhandled": { "currently-unhandled": {
"version": "0.4.1", "version": "0.4.1",
@ -17052,7 +17051,7 @@
}, },
"fs-minipass": { "fs-minipass": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "", "resolved": false,
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17067,7 +17066,7 @@
}, },
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": "", "resolved": false,
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17083,7 +17082,7 @@
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": "", "resolved": false,
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17121,7 +17120,7 @@
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "", "resolved": false,
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17173,7 +17172,7 @@
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "", "resolved": false,
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17183,7 +17182,7 @@
}, },
"minizlib": { "minizlib": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "", "resolved": false,
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17218,7 +17217,7 @@
}, },
"node-pre-gyp": { "node-pre-gyp": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "", "resolved": false,
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17272,7 +17271,7 @@
}, },
"npmlog": { "npmlog": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "", "resolved": false,
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17296,7 +17295,7 @@
}, },
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "", "resolved": false,
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17366,7 +17365,7 @@
}, },
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "", "resolved": false,
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17446,7 +17445,7 @@
}, },
"tar": { "tar": {
"version": "4.4.13", "version": "4.4.13",
"resolved": "", "resolved": false,
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"optional": true, "optional": true,
"requires": { "requires": {
@ -17476,13 +17475,13 @@
}, },
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "", "resolved": false,
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true "optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "", "resolved": false,
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true "optional": true
} }
@ -28684,9 +28683,9 @@
} }
}, },
"rc-switch": { "rc-switch": {
"version": "1.9.0", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.0.tgz", "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.2.tgz",
"integrity": "sha512-Isas+egaK6qSk64jaEw4GgPStY4umYDbT7ZY93bZF1Af+b/JEsKsJdNOU2qG3WI0Z6tXo2DDq0kJCv8Yhu0zww==", "integrity": "sha512-qaK7mY4FLDKy99Hq3A1tf8CcqfzKtHp9LPX8WTnZ0MzdHCTneSARb1XD7Eqeu8BactasYGsi2bF9p18Q+/5JEw==",
"requires": { "requires": {
"classnames": "^2.2.1", "classnames": "^2.2.1",
"prop-types": "^15.5.6", "prop-types": "^15.5.6",
@ -28928,15 +28927,43 @@
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-redux": { "react-redux": {
"version": "7.2.1", "version": "7.2.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz",
"integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==", "integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==",
"requires": { "requires": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.12.1",
"hoist-non-react-statics": "^3.3.0", "hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-is": "^16.9.0" "react-is": "^16.13.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
}
} }
}, },
"react-router": { "react-router": {
@ -32254,9 +32281,9 @@
"dev": true "dev": true
}, },
"whatwg-fetch": { "whatwg-fetch": {
"version": "3.4.1", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
"integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A=="
}, },
"which": { "which": {
"version": "1.3.1", "version": "1.3.1",

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.10.0", "version": "1.10.6",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
@ -49,15 +49,15 @@
"dependencies": { "dependencies": {
"@types/lodash": "^4.14.165", "@types/lodash": "^4.14.165",
"@types/platform": "^1.3.3", "@types/platform": "^1.3.3",
"@types/react": "^16.9.53", "@types/react": "^16.9.55",
"@types/react-color": "^3.0.4", "@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.9",
"@types/react-redux": "^7.1.2", "@types/react-redux": "^7.1.2",
"@types/react-router": "^5.0.5", "@types/react-router": "^5.0.5",
"@types/react-router-dom": "^5.1.6", "@types/react-router-dom": "^5.1.6",
"@types/react-share": "^3.0.3", "@types/react-share": "^3.0.3",
"@types/redux-logger": "^3.0.8", "@types/redux-logger": "^3.0.8",
"antd": "^3.26.18", "antd": "^3.26.20",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"cvat-canvas": "file:../cvat-canvas", "cvat-canvas": "file:../cvat-canvas",
"cvat-core": "file:../cvat-core", "cvat-core": "file:../cvat-core",
@ -72,7 +72,7 @@
"react-cookie": "^4.0.3", "react-cookie": "^4.0.3",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"react-hotkeys": "^2.0.0", "react-hotkeys": "^2.0.0",
"react-redux": "^7.1.1", "react-redux": "^7.2.2",
"react-router": "^5.1.0", "react-router": "^5.1.0",
"react-router-dom": "^5.1.0", "react-router-dom": "^5.1.0",
"react-share": "^3.0.1", "react-share": "^3.0.1",

@ -10,7 +10,9 @@ import Icon from 'antd/lib/icon';
import Layout from 'antd/lib/layout/layout'; import Layout from 'antd/lib/layout/layout';
import Slider, { SliderValue } from 'antd/lib/slider'; import Slider, { SliderValue } from 'antd/lib/slider';
import { ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType } from 'reducers/interfaces'; import {
ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType,
} from 'reducers/interfaces';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper'; import { Canvas } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
@ -217,10 +219,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
this.updateCanvas(); this.updateCanvas();
} }
if ( if (prevProps.frame !== frameData.number && resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION) {
prevProps.frame !== frameData.number &&
((resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION) || workspace === Workspace.TAG_ANNOTATION)
) {
canvasInstance.html().addEventListener( canvasInstance.html().addEventListener(
'canvas.setup', 'canvas.setup',
() => { () => {
@ -304,7 +303,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
private onCanvasShapeDrawn = (event: any): void => { private onCanvasShapeDrawn = (event: any): void => {
const { jobInstance, activeLabelID, activeObjectType, frame, onShapeDrawn, onCreateAnnotations } = this.props; const {
jobInstance, activeLabelID, activeObjectType, frame, onShapeDrawn, onCreateAnnotations,
} = this.props;
if (!event.detail.continue) { if (!event.detail.continue) {
onShapeDrawn(); onShapeDrawn();
@ -327,7 +328,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}; };
private onCanvasObjectsMerged = (event: any): void => { private onCanvasObjectsMerged = (event: any): void => {
const { jobInstance, frame, onMergeAnnotations, onMergeObjects } = this.props; const {
jobInstance, frame, onMergeAnnotations, onMergeObjects,
} = this.props;
onMergeObjects(false); onMergeObjects(false);
@ -340,7 +343,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}; };
private onCanvasObjectsGroupped = (event: any): void => { private onCanvasObjectsGroupped = (event: any): void => {
const { jobInstance, frame, onGroupAnnotations, onGroupObjects } = this.props; const {
jobInstance, frame, onGroupAnnotations, onGroupObjects,
} = this.props;
onGroupObjects(false); onGroupObjects(false);
@ -349,7 +354,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}; };
private onCanvasTrackSplitted = (event: any): void => { private onCanvasTrackSplitted = (event: any): void => {
const { jobInstance, frame, onSplitAnnotations, onSplitTrack } = this.props; const {
jobInstance, frame, onSplitAnnotations, onSplitTrack,
} = this.props;
onSplitTrack(false); onSplitTrack(false);
@ -429,7 +436,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}; };
private onCanvasCursorMoved = async (event: any): Promise<void> => { private onCanvasCursorMoved = async (event: any): Promise<void> => {
const { jobInstance, activatedStateID, workspace, onActivateObject } = this.props; const {
jobInstance, activatedStateID, workspace, onActivateObject,
} = this.props;
if (workspace !== Workspace.STANDARD) { if (workspace !== Workspace.STANDARD) {
return; return;
@ -560,7 +569,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
private updateShapesView(): void { private updateShapesView(): void {
const { annotations, opacity, colorBy, outlined, outlineColor } = this.props; const {
annotations, opacity, colorBy, outlined, outlineColor,
} = this.props;
for (const state of annotations) { for (const state of annotations) {
let shapeColor = ''; let shapeColor = '';
@ -588,7 +599,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
private updateCanvas(): void { private updateCanvas(): void {
const { curZLayer, annotations, frameData, canvasInstance } = this.props; const {
curZLayer, annotations, frameData, canvasInstance,
} = this.props;
if (frameData !== null) { if (frameData !== null) {
canvasInstance.setup( canvasInstance.setup(

@ -109,7 +109,7 @@ function PlayerButtons(props: Props): JSX.Element {
<Popover <Popover
trigger='contextMenu' trigger='contextMenu'
placement='bottom' placement='bottom'
content={ content={(
<> <>
<Tooltip title={`${prevRegularText}`} mouseLeaveDelay={0}> <Tooltip title={`${prevRegularText}`} mouseLeaveDelay={0}>
<Icon <Icon
@ -139,7 +139,7 @@ function PlayerButtons(props: Props): JSX.Element {
/> />
</Tooltip> </Tooltip>
</> </>
} )}
> >
<Tooltip <Tooltip
placement='top' placement='top'
@ -163,7 +163,7 @@ function PlayerButtons(props: Props): JSX.Element {
<Popover <Popover
trigger='contextMenu' trigger='contextMenu'
placement='bottom' placement='bottom'
content={ content={(
<> <>
<Tooltip title={`${nextRegularText}`} mouseLeaveDelay={0}> <Tooltip title={`${nextRegularText}`} mouseLeaveDelay={0}>
<Icon <Icon
@ -193,7 +193,7 @@ function PlayerButtons(props: Props): JSX.Element {
/> />
</Tooltip> </Tooltip>
</> </>
} )}
> >
<Tooltip placement='top' mouseLeaveDelay={0} title={`${nextButtonTooltipMessage} ${nextFrameShortcut}`}> <Tooltip placement='top' mouseLeaveDelay={0} title={`${nextButtonTooltipMessage} ${nextFrameShortcut}`}>
{nextButton} {nextButton}

@ -18,7 +18,9 @@ import ColorPicker from 'components/annotation-page/standard-workspace/objects-s
import { ColorizeIcon } from 'icons'; import { ColorizeIcon } from 'icons';
import patterns from 'utils/validation-patterns'; import patterns from 'utils/validation-patterns';
import consts from 'consts'; import consts from 'consts';
import { equalArrayHead, idGenerator, Label, Attribute } from './common'; import {
equalArrayHead, idGenerator, Label, Attribute,
} from './common';
export enum AttributeType { export enum AttributeType {
SELECT = 'SELECT', SELECT = 'SELECT',
@ -318,9 +320,9 @@ class LabelForm extends React.PureComponent<Props, {}> {
); );
} }
private renderAttribute = (key: number, index: number): JSX.Element => { private renderAttribute = (key: number): JSX.Element => {
const { label, form } = this.props; const { label, form } = this.props;
const attr = label && index < label.attributes.length ? label.attributes[index] : null; const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === key)[0] : null;
return ( return (
<Form.Item key={key}> <Form.Item key={key}>

@ -83,13 +83,7 @@ export default function UserSelector(props: Props): JSX.Element {
if (value && !users.filter((user) => user.id === value.id).length) { if (value && !users.filter((user) => user.id === value.id).length) {
core.users.get({ id: value.id }).then((result: User[]) => { core.users.get({ id: value.id }).then((result: User[]) => {
const [user] = result; const [user] = result;
setUsers([ setUsers([...users, user]);
...users,
{
id: user.id,
username: user.username,
},
]);
setSearchPhrase(user.username); setSearchPhrase(user.username);
}); });
} }

@ -24,7 +24,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
annotation: { annotation: {
annotations: { activatedStateID, collapsed, states: objectStates }, annotations: { activatedStateID, collapsed, states: objectStates },
canvas: { canvas: {
contextMenu: { visible, top, left, type }, contextMenu: {
visible, top, left, type,
},
ready,
}, },
}, },
} = state; } = state;
@ -33,7 +36,11 @@ function mapStateToProps(state: CombinedState): StateToProps {
activatedStateID, activatedStateID,
collapsed: activatedStateID !== null ? collapsed[activatedStateID] : undefined, collapsed: activatedStateID !== null ? collapsed[activatedStateID] : undefined,
objectStates, objectStates,
visible, visible:
activatedStateID !== null &&
visible &&
ready &&
objectStates.map((_state: any): number => _state.clientID).includes(activatedStateID),
left, left,
top, top,
type, type,
@ -166,7 +173,9 @@ class CanvasContextMenuContainer extends React.PureComponent<Props, State> {
public render(): JSX.Element { public render(): JSX.Element {
const { left, top } = this.state; const { left, top } = this.state;
const { visible, activatedStateID, objectStates, type } = this.props; const {
visible, activatedStateID, objectStates, type,
} = this.props;
return ( return (
<> <>

@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { updateAnnotationsAsync } from 'actions/annotation-actions'; import { updateAnnotationsAsync } from 'actions/annotation-actions';
import LabelItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/label-item'; import LabelItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/label-item';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState, ObjectType } from 'reducers/interfaces';
interface OwnProps { interface OwnProps {
labelID: number; labelID: number;
@ -92,8 +92,8 @@ class LabelItemContainer extends React.PureComponent<Props, State> {
let statesLocked = true; let statesLocked = true;
ownObjectStates.forEach((objectState: any) => { ownObjectStates.forEach((objectState: any) => {
const { lock } = objectState; const { lock, objectType } = objectState;
if (!lock) { if (!lock && objectType !== ObjectType.TAG) {
statesHidden = statesHidden && objectState.hidden; statesHidden = statesHidden && objectState.hidden;
statesLocked = statesLocked && objectState.lock; statesLocked = statesLocked && objectState.lock;
} }

@ -7,7 +7,9 @@ import copy from 'copy-to-clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { ActiveControl, CombinedState, ColorBy, ShapeType } from 'reducers/interfaces'; import {
ActiveControl, CombinedState, ColorBy, ShapeType,
} from 'reducers/interfaces';
import { import {
collapseObjectItems, collapseObjectItems,
updateAnnotationsAsync, updateAnnotationsAsync,
@ -83,40 +85,25 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const stateIDs = states.map((_state: any): number => _state.clientID); const stateIDs = states.map((_state: any): number => _state.clientID);
const index = stateIDs.indexOf(clientID); const index = stateIDs.indexOf(clientID);
try { const collapsedState =
const collapsedState = typeof statesCollapsed[clientID] === 'undefined' ? initialCollapsed : statesCollapsed[clientID];
typeof statesCollapsed[clientID] === 'undefined' ? initialCollapsed : statesCollapsed[clientID];
return {
return { objectState: states[index],
objectState: states[index], collapsed: collapsedState,
collapsed: collapsedState, attributes: jobAttributes[states[index].label.id],
attributes: jobAttributes[states[index].label.id], labels,
labels, ready,
ready, activeControl,
activeControl, colorBy,
colorBy, jobInstance,
jobInstance, frameNumber,
frameNumber, activated: activatedStateID === clientID,
activated: activatedStateID === clientID, minZLayer,
minZLayer, maxZLayer,
maxZLayer, normalizedKeyMap,
normalizedKeyMap, aiToolsRef,
aiToolsRef, };
};
} catch (exception) {
// we have an exception here sometimes
// but I cannot understand when it happens and what is the root reason
// maybe this temporary hack helps us
const dataObject = {
index,
frameNumber,
clientID: own.clientID,
stateIDs,
};
throw new Error(
`${exception.toString()} in mapStateToProps of ObjectItemContainer. Data are ${JSON.stringify(dataObject)}`,
);
}
} }
function mapDispatchToProps(dispatch: any): DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps {
@ -219,7 +206,9 @@ class ObjectItemContainer extends React.PureComponent<Props> {
}; };
private activate = (): void => { private activate = (): void => {
const { activateObject, objectState, ready, activeControl } = this.props; const {
activateObject, objectState, ready, activeControl,
} = this.props;
if (ready && activeControl === ActiveControl.CURSOR) { if (ready && activeControl === ActiveControl.CURSOR) {
activateObject(objectState.clientID); activateObject(objectState.clientID);
@ -324,7 +313,9 @@ class ObjectItemContainer extends React.PureComponent<Props> {
} }
public render(): JSX.Element { public render(): JSX.Element {
const { objectState, collapsed, labels, attributes, activated, colorBy, normalizedKeyMap } = this.props; const {
objectState, collapsed, labels, attributes, activated, colorBy, normalizedKeyMap,
} = this.props;
let stateColor = ''; let stateColor = '';
if (colorBy === ColorBy.INSTANCE) { if (colorBy === ColorBy.INSTANCE) {

@ -231,7 +231,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
} }
private undo = (): void => { private undo = (): void => {
const { undo, jobInstance, frameNumber, canvasInstance } = this.props; const {
undo, jobInstance, frameNumber, canvasInstance,
} = this.props;
if (canvasInstance.isAbleToChangeFrame()) { if (canvasInstance.isAbleToChangeFrame()) {
undo(jobInstance, frameNumber); undo(jobInstance, frameNumber);
@ -239,7 +241,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}; };
private redo = (): void => { private redo = (): void => {
const { redo, jobInstance, frameNumber, canvasInstance } = this.props; const {
redo, jobInstance, frameNumber, canvasInstance,
} = this.props;
if (canvasInstance.isAbleToChangeFrame()) { if (canvasInstance.isAbleToChangeFrame()) {
redo(jobInstance, frameNumber); redo(jobInstance, frameNumber);
@ -253,7 +257,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}; };
private onSwitchPlay = (): void => { private onSwitchPlay = (): void => {
const { frameNumber, jobInstance, onSwitchPlay, playing } = this.props; const {
frameNumber, jobInstance, onSwitchPlay, playing,
} = this.props;
if (playing) { if (playing) {
onSwitchPlay(false); onSwitchPlay(false);
@ -263,7 +269,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}; };
private onFirstFrame = (): void => { private onFirstFrame = (): void => {
const { frameNumber, jobInstance, playing, onSwitchPlay } = this.props; const {
frameNumber, jobInstance, playing, onSwitchPlay,
} = this.props;
const newFrame = jobInstance.startFrame; const newFrame = jobInstance.startFrame;
if (newFrame !== frameNumber) { if (newFrame !== frameNumber) {
@ -275,7 +283,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}; };
private onBackward = (): void => { private onBackward = (): void => {
const { frameNumber, frameStep, jobInstance, playing, onSwitchPlay } = this.props; const {
frameNumber, frameStep, jobInstance, playing, onSwitchPlay,
} = this.props;
const newFrame = Math.max(jobInstance.startFrame, frameNumber - frameStep); const newFrame = Math.max(jobInstance.startFrame, frameNumber - frameStep);
if (newFrame !== frameNumber) { if (newFrame !== frameNumber) {
@ -288,7 +298,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
private onPrevFrame = (): void => { private onPrevFrame = (): void => {
const { prevButtonType } = this.state; const { prevButtonType } = this.state;
const { frameNumber, jobInstance, playing, onSwitchPlay, searchAnnotations, searchEmptyFrame } = this.props; const {
frameNumber, jobInstance, playing, onSwitchPlay,
} = this.props;
const { startFrame } = jobInstance; const { startFrame } = jobInstance;
const newFrame = Math.max(jobInstance.startFrame, frameNumber - 1); const newFrame = Math.max(jobInstance.startFrame, frameNumber - 1);
@ -296,19 +308,22 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
if (playing) { if (playing) {
onSwitchPlay(false); onSwitchPlay(false);
} }
if (prevButtonType === 'regular') { if (prevButtonType === 'regular') {
this.changeFrame(newFrame); this.changeFrame(newFrame);
} else if (prevButtonType === 'filtered') { } else if (prevButtonType === 'filtered') {
searchAnnotations(jobInstance, frameNumber - 1, startFrame); this.searchAnnotations(frameNumber - 1, startFrame);
} else { } else {
searchEmptyFrame(jobInstance, frameNumber - 1, startFrame); this.searchEmptyFrame(frameNumber - 1, startFrame);
} }
} }
}; };
private onNextFrame = (): void => { private onNextFrame = (): void => {
const { nextButtonType } = this.state; const { nextButtonType } = this.state;
const { frameNumber, jobInstance, playing, onSwitchPlay, searchAnnotations, searchEmptyFrame } = this.props; const {
frameNumber, jobInstance, playing, onSwitchPlay,
} = this.props;
const { stopFrame } = jobInstance; const { stopFrame } = jobInstance;
const newFrame = Math.min(jobInstance.stopFrame, frameNumber + 1); const newFrame = Math.min(jobInstance.stopFrame, frameNumber + 1);
@ -316,18 +331,21 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
if (playing) { if (playing) {
onSwitchPlay(false); onSwitchPlay(false);
} }
if (nextButtonType === 'regular') { if (nextButtonType === 'regular') {
this.changeFrame(newFrame); this.changeFrame(newFrame);
} else if (nextButtonType === 'filtered') { } else if (nextButtonType === 'filtered') {
searchAnnotations(jobInstance, frameNumber + 1, stopFrame); this.searchAnnotations(frameNumber + 1, stopFrame);
} else { } else {
searchEmptyFrame(jobInstance, frameNumber + 1, stopFrame); this.searchEmptyFrame(frameNumber + 1, stopFrame);
} }
} }
}; };
private onForward = (): void => { private onForward = (): void => {
const { frameNumber, frameStep, jobInstance, playing, onSwitchPlay } = this.props; const {
frameNumber, frameStep, jobInstance, playing, onSwitchPlay,
} = this.props;
const newFrame = Math.min(jobInstance.stopFrame, frameNumber + frameStep); const newFrame = Math.min(jobInstance.stopFrame, frameNumber + frameStep);
if (newFrame !== frameNumber) { if (newFrame !== frameNumber) {
@ -339,7 +357,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
}; };
private onLastFrame = (): void => { private onLastFrame = (): void => {
const { frameNumber, jobInstance, playing, onSwitchPlay } = this.props; const {
frameNumber, jobInstance, playing, onSwitchPlay,
} = this.props;
const newFrame = jobInstance.stopFrame; const newFrame = jobInstance.stopFrame;
if (newFrame !== frameNumber) { if (newFrame !== frameNumber) {
@ -418,6 +438,20 @@ class AnnotationTopBarContainer extends React.PureComponent<Props, State> {
} }
} }
private searchAnnotations(start: number, stop: number): void {
const { canvasInstance, jobInstance, searchAnnotations } = this.props;
if (canvasInstance.isAbleToChangeFrame()) {
searchAnnotations(jobInstance, start, stop);
}
}
private searchEmptyFrame(start: number, stop: number): void {
const { canvasInstance, jobInstance, searchAnnotations } = this.props;
if (canvasInstance.isAbleToChangeFrame()) {
searchAnnotations(jobInstance, start, stop);
}
}
public render(): JSX.Element { public render(): JSX.Element {
const { nextButtonType, prevButtonType } = this.state; const { nextButtonType, prevButtonType } = this.state;
const { const {

@ -0,0 +1,37 @@
# REST API design principles
## REST API scheme
Common scheme for our REST API is `<VERB> [namespace] <objects> <id> <action>`.
- `VERB` can be `POST`, `GET`, `PATCH`, `PUT`, `DELETE`.
- `namespace` should scope some specific functionality like `auth`, `lambda`.
It is optional in the scheme.
- Typical `objects` are `tasks`, `projects`, `jobs`.
- When you want to extract a specific object from a collection, just specify its `id`.
- An `action` can be used to simplify REST API or provide an endpoint for entities
without `objects` endpoint like `annotations`, `data`, `data/meta`. Note: action
should not duplicate other endpoints without a reason.
## Design principles
- Use nouns instead of verbs in endpoint paths. For example,
`POST /api/v1/tasks` instead of `POST /api/v1/tasks/create`.
- Accept and respond with JSON whenever it is possible
- Name collections with plural nouns (e.g. `/tasks`, `/projects`)
- Try to keep the API structure flat. Prefer two separate endpoints
for `/projects` and `/tasks` instead of `/projects/:id1/tasks/:id2`. Use
filters to extract necessary information like `/tasks/:id2?project=:id1`.
In some cases it is useful to get all `tasks`. If the structure is
hierarchical, it cannot be done easily. Also you have to know both `:id1`
and `:id2` to get information about the task.
_Note: for now we accept `GET /tasks/:id2/jobs` but it should be replaced
by `/jobs?task=:id2` in the future_.
- Handle errors gracefully and return standard error codes (e.g. `201`, `400`)
- Allow filtering, sorting, and pagination
- Maintain good security practices
- Cache data to improve performance
- Versioning our APIs (e.g. `/api/v1`, `/api/v2`). It should be done when you
delete an endpoint or modify its behaviors.
## Links
- [Best practices for REST API design](https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/)
- [Flat vs. nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on polygon', () => { context('Actions on polygon', () => {
const caseId = '10'; const caseId = '10';
@ -12,7 +12,7 @@ context('Actions on polygon', () => {
const createPolygonShape = { const createPolygonShape = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 200, y: 200 }, { x: 200, y: 200 },
{ x: 250, y: 200 }, { x: 250, y: 200 },
@ -24,7 +24,7 @@ context('Actions on polygon', () => {
const createPolygonTrack = { const createPolygonTrack = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 300, y: 200 }, { x: 300, y: 200 },
{ x: 350, y: 200 }, { x: 350, y: 200 },
@ -36,7 +36,7 @@ context('Actions on polygon', () => {
const createPolygonShapePoints = { const createPolygonShapePoints = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 400, y: 200 }, { x: 400, y: 200 },
{ x: 450, y: 200 }, { x: 450, y: 200 },
@ -49,7 +49,7 @@ context('Actions on polygon', () => {
const createPolygonTrackPoints = { const createPolygonTrackPoints = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 500, y: 200 }, { x: 500, y: 200 },
{ x: 550, y: 200 }, { x: 550, y: 200 },
@ -62,7 +62,6 @@ context('Actions on polygon', () => {
const createPolygonShapeSwitchLabel = { const createPolygonShapeSwitchLabel = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 600, y: 200 }, { x: 600, y: 200 },
@ -75,7 +74,6 @@ context('Actions on polygon', () => {
const createPolygonTrackSwitchLabel = { const createPolygonTrackSwitchLabel = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 700, y: 200 }, { x: 700, y: 200 },

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on polylines', () => { context('Actions on polylines', () => {
const caseId = '11'; const caseId = '11';
const newLabelName = `New label for case ${caseId}`; const newLabelName = `New label for case ${caseId}`;
const createPolylinesShape = { const createPolylinesShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 200, y: 200 }, { x: 200, y: 200 },
{ x: 250, y: 200 }, { x: 250, y: 200 },
@ -22,7 +22,7 @@ context('Actions on polylines', () => {
}; };
const createPolylinesTrack = { const createPolylinesTrack = {
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 300, y: 200 }, { x: 300, y: 200 },
{ x: 350, y: 200 }, { x: 350, y: 200 },
@ -33,7 +33,7 @@ context('Actions on polylines', () => {
}; };
const createPolylinesShapePoints = { const createPolylinesShapePoints = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 400, y: 200 }, { x: 400, y: 200 },
{ x: 450, y: 200 }, { x: 450, y: 200 },
@ -45,7 +45,7 @@ context('Actions on polylines', () => {
}; };
const createPolylinesTrackPoints = { const createPolylinesTrackPoints = {
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 500, y: 200 }, { x: 500, y: 200 },
{ x: 550, y: 200 }, { x: 550, y: 200 },
@ -57,7 +57,6 @@ context('Actions on polylines', () => {
}; };
const createPolylinesShapeSwitchLabel = { const createPolylinesShapeSwitchLabel = {
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 600, y: 200 }, { x: 600, y: 200 },
@ -69,7 +68,6 @@ context('Actions on polylines', () => {
}; };
const createPolylinesTrackSwitchLabel = { const createPolylinesTrackSwitchLabel = {
type: 'Track', type: 'Track',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 700, y: 200 }, { x: 700, y: 200 },

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on points', () => { context('Actions on points', () => {
const caseId = '12'; const caseId = '12';
const newLabelName = `New label for case ${caseId}`; const newLabelName = `New label for case ${caseId}`;
const createPointsShape = { const createPointsShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 200, y: 200 }, { x: 200, y: 200 },
{ x: 250, y: 200 }, { x: 250, y: 200 },
@ -22,7 +22,7 @@ context('Actions on points', () => {
}; };
const createPointsTrack = { const createPointsTrack = {
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 300, y: 200 }, { x: 300, y: 200 },
{ x: 350, y: 200 }, { x: 350, y: 200 },
@ -33,7 +33,7 @@ context('Actions on points', () => {
}; };
const createPointsShapePoints = { const createPointsShapePoints = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 400, y: 200 }, { x: 400, y: 200 },
{ x: 450, y: 200 }, { x: 450, y: 200 },
@ -45,7 +45,7 @@ context('Actions on points', () => {
}; };
const createPointsTrackPoints = { const createPointsTrackPoints = {
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 500, y: 200 }, { x: 500, y: 200 },
{ x: 550, y: 200 }, { x: 550, y: 200 },
@ -57,7 +57,6 @@ context('Actions on points', () => {
}; };
const createPointsShapeSwitchLabel = { const createPointsShapeSwitchLabel = {
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 600, y: 200 }, { x: 600, y: 200 },
@ -69,7 +68,6 @@ context('Actions on points', () => {
}; };
const createPointsTrackSwitchLabel = { const createPointsTrackSwitchLabel = {
type: 'Track', type: 'Track',
switchLabel: true,
labelName: newLabelName, labelName: newLabelName,
pointsMap: [ pointsMap: [
{ x: 700, y: 200 }, { x: 700, y: 200 },

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Merge/split features', () => { context('Merge/split features', () => {
const caseId = '13'; const caseId = '13';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('Merge/split features', () => {
const createRectangleShape2PointsSecond = { const createRectangleShape2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX + 300, firstX: createRectangleShape2Points.firstX + 300,
firstY: createRectangleShape2Points.firstY, firstY: createRectangleShape2Points.firstY,
secondX: createRectangleShape2Points.secondX + 300, secondX: createRectangleShape2Points.secondX + 300,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Appearance features', () => { context('Appearance features', () => {
const caseId = '14'; const caseId = '14';
@ -15,7 +15,7 @@ context('Appearance features', () => {
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 100, firstX: 100,
firstY: 350, firstY: 350,
secondX: 200, secondX: 200,
@ -24,7 +24,7 @@ context('Appearance features', () => {
const createPolygonShape = { const createPolygonShape = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 250, y: 350 }, { x: 250, y: 350 },
{ x: 300, y: 300 }, { x: 300, y: 300 },
@ -35,7 +35,7 @@ context('Appearance features', () => {
}; };
const createPolylinesShape = { const createPolylinesShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 350, y: 350 }, { x: 350, y: 350 },
{ x: 400, y: 300 }, { x: 400, y: 300 },
@ -48,7 +48,7 @@ context('Appearance features', () => {
const createCuboidShape2Points = { const createCuboidShape2Points = {
points: 'From rectangle', points: 'From rectangle',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 450, firstX: 450,
firstY: 350, firstY: 350,
secondX: 550, secondX: 550,
@ -56,7 +56,7 @@ context('Appearance features', () => {
}; };
const createPointsShape = { const createPointsShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [{ x: 650, y: 350 }], pointsMap: [{ x: 650, y: 350 }],
complete: true, complete: true,
numberOfPoints: null, numberOfPoints: null,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Group features', () => { context('Group features', () => {
const caseId = '15'; const caseId = '15';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('Group features', () => {
const createRectangleShape2PointsSecond = { const createRectangleShape2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX + 300, firstX: createRectangleShape2Points.firstX + 300,
firstY: createRectangleShape2Points.firstY, firstY: createRectangleShape2Points.firstY,
secondX: createRectangleShape2Points.secondX + 300, secondX: createRectangleShape2Points.secondX + 300,
@ -29,7 +29,7 @@ context('Group features', () => {
const createRectangleTrack2Points = { const createRectangleTrack2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 600, firstY: 600,
secondX: 350, secondX: 350,
@ -38,7 +38,7 @@ context('Group features', () => {
const createRectangleTrack2PointsSecond = { const createRectangleTrack2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: createRectangleTrack2Points.firstX + 300, firstX: createRectangleTrack2Points.firstX + 300,
firstY: createRectangleTrack2Points.firstY, firstY: createRectangleTrack2Points.firstY,
secondX: createRectangleTrack2Points.secondX + 300, secondX: createRectangleTrack2Points.secondX + 300,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on polygon', () => { context('Actions on polygon', () => {
const caseId = '16'; const caseId = '16';
@ -12,7 +12,7 @@ context('Actions on polygon', () => {
const createPolygonShapeFirst = { const createPolygonShapeFirst = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 340, y: 200 }, { x: 340, y: 200 },
{ x: 590, y: 200 }, { x: 590, y: 200 },
@ -24,7 +24,7 @@ context('Actions on polygon', () => {
const createPolygonShapeSecond = { const createPolygonShapeSecond = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 190, y: 210 }, { x: 190, y: 210 },
{ x: 440, y: 210 }, { x: 440, y: 210 },

@ -16,7 +16,7 @@ context('Lock/hide features.', () => {
const createPolygonShape = { const createPolygonShape = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 200, y: 200 }, { x: 200, y: 200 },
{ x: 250, y: 200 }, { x: 250, y: 200 },
@ -28,7 +28,7 @@ context('Lock/hide features.', () => {
const createRectangleTrack2Points = { const createRectangleTrack2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: 260, firstX: 260,
firstY: 200, firstY: 200,
secondX: 360, secondX: 360,
@ -37,7 +37,7 @@ context('Lock/hide features.', () => {
const createCuboidShape4Points = { const createCuboidShape4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 400, firstX: 400,
firstY: 350, firstY: 350,
secondX: 500, secondX: 500,
@ -49,7 +49,6 @@ context('Lock/hide features.', () => {
}; };
const createPolylinesShapeSwitchLabel = { const createPolylinesShapeSwitchLabel = {
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName1, labelName: newLabelName1,
pointsMap: [ pointsMap: [
{ x: 600, y: 200 }, { x: 600, y: 200 },
@ -61,7 +60,6 @@ context('Lock/hide features.', () => {
}; };
const createPointsShapeSwitchLabel = { const createPointsShapeSwitchLabel = {
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName2, labelName: newLabelName2,
pointsMap: [ pointsMap: [
{ x: 700, y: 200 } { x: 700, y: 200 }
@ -72,7 +70,6 @@ context('Lock/hide features.', () => {
const createRectangleShape4Points = { const createRectangleShape4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: true,
labelName: newLabelName3, labelName: newLabelName3,
firstX: 550, firstX: 550,
firstY: 350, firstY: 350,
@ -86,7 +83,6 @@ context('Lock/hide features.', () => {
const createPolygonTrack = { const createPolygonTrack = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: true,
labelName: newLabelName4, labelName: newLabelName4,
pointsMap: [ pointsMap: [
{ x: 700, y: 350 }, { x: 700, y: 350 },

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on rectangle', () => { context('Actions on rectangle', () => {
const caseId = '8'; const caseId = '8';
@ -12,7 +12,7 @@ context('Actions on rectangle', () => {
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -21,7 +21,7 @@ context('Actions on rectangle', () => {
const createRectangleShape4Points = { const createRectangleShape4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 400, firstX: 400,
firstY: 350, firstY: 350,
secondX: 500, secondX: 500,
@ -34,7 +34,7 @@ context('Actions on rectangle', () => {
const createRectangleTrack2Points = { const createRectangleTrack2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX, firstX: createRectangleShape2Points.firstX,
firstY: createRectangleShape2Points.firstY - 150, firstY: createRectangleShape2Points.firstY - 150,
secondX: createRectangleShape2Points.secondX, secondX: createRectangleShape2Points.secondX,
@ -43,7 +43,7 @@ context('Actions on rectangle', () => {
const createRectangleTrack4Points = { const createRectangleTrack4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape4Points.firstX, firstX: createRectangleShape4Points.firstX,
firstY: createRectangleShape4Points.firstY - 150, firstY: createRectangleShape4Points.firstY - 150,
secondX: createRectangleShape4Points.secondX - 100, secondX: createRectangleShape4Points.secondX - 100,
@ -57,7 +57,6 @@ context('Actions on rectangle', () => {
labelName: newLabelName, labelName: newLabelName,
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: true,
firstX: createRectangleShape2Points.firstX, firstX: createRectangleShape2Points.firstX,
firstY: createRectangleShape2Points.firstY + 150, firstY: createRectangleShape2Points.firstY + 150,
secondX: createRectangleShape2Points.secondX, secondX: createRectangleShape2Points.secondX,
@ -67,7 +66,6 @@ context('Actions on rectangle', () => {
labelName: newLabelName, labelName: newLabelName,
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: true,
firstX: createRectangleShape4Points.firstX, firstX: createRectangleShape4Points.firstX,
firstY: createRectangleShape4Points.firstY + 150, firstY: createRectangleShape4Points.firstY + 150,
secondX: createRectangleShape4Points.secondX, secondX: createRectangleShape4Points.secondX,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Actions on Cuboid', () => { context('Actions on Cuboid', () => {
const caseId = '9'; const caseId = '9';
@ -12,7 +12,7 @@ context('Actions on Cuboid', () => {
const createCuboidShape2Points = { const createCuboidShape2Points = {
points: 'From rectangle', points: 'From rectangle',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -21,7 +21,7 @@ context('Actions on Cuboid', () => {
const createCuboidShape4Points = { const createCuboidShape4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 400, firstX: 400,
firstY: 350, firstY: 350,
secondX: 500, secondX: 500,
@ -34,7 +34,7 @@ context('Actions on Cuboid', () => {
const createCuboidTrack2Points = { const createCuboidTrack2Points = {
points: 'From rectangle', points: 'From rectangle',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: createCuboidShape2Points.firstX, firstX: createCuboidShape2Points.firstX,
firstY: createCuboidShape2Points.firstY - 150, firstY: createCuboidShape2Points.firstY - 150,
secondX: createCuboidShape2Points.secondX, secondX: createCuboidShape2Points.secondX,
@ -43,7 +43,7 @@ context('Actions on Cuboid', () => {
const createCuboidTrack4Points = { const createCuboidTrack4Points = {
points: 'By 4 Points', points: 'By 4 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: createCuboidShape4Points.firstX, firstX: createCuboidShape4Points.firstX,
firstY: createCuboidShape4Points.firstY - 150, firstY: createCuboidShape4Points.firstY - 150,
secondX: createCuboidShape4Points.secondX - 100, secondX: createCuboidShape4Points.secondX - 100,
@ -57,7 +57,6 @@ context('Actions on Cuboid', () => {
labelName: newLabelName, labelName: newLabelName,
points: 'From rectangle', points: 'From rectangle',
type: 'Shape', type: 'Shape',
switchLabel: true,
firstX: createCuboidShape2Points.firstX, firstX: createCuboidShape2Points.firstX,
firstY: createCuboidShape2Points.firstY + 150, firstY: createCuboidShape2Points.firstY + 150,
secondX: createCuboidShape2Points.secondX, secondX: createCuboidShape2Points.secondX,
@ -67,7 +66,6 @@ context('Actions on Cuboid', () => {
labelName: newLabelName, labelName: newLabelName,
points: 'By 4 Points', points: 'By 4 Points',
type: 'Shape', type: 'Shape',
switchLabel: true,
firstX: createCuboidShape4Points.firstX, firstX: createCuboidShape4Points.firstX,
firstY: createCuboidShape4Points.firstY + 150, firstY: createCuboidShape4Points.firstY + 150,
secondX: createCuboidShape4Points.secondX, secondX: createCuboidShape4Points.secondX,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Check if UI not fails with shape dragging over sidebar', () => { context('Check if UI not fails with shape dragging over sidebar', () => {
const issueId = '1216'; const issueId = '1216';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('Check if UI not fails with shape dragging over sidebar', () => {
const createRectangleShape2PointsSecond = { const createRectangleShape2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX, firstX: createRectangleShape2Points.firstX,
firstY: createRectangleShape2Points.firstY - 150, firstY: createRectangleShape2Points.firstY - 150,
secondX: createRectangleShape2Points.secondX, secondX: createRectangleShape2Points.secondX,

@ -4,13 +4,13 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Points track it is still invisible on next frames', () => { context('Points track it is still invisible on next frames', () => {
const issueId = '1368'; const issueId = '1368';
const createPointsTrack = { const createPointsTrack = {
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [{ x: 300, y: 410 }], pointsMap: [{ x: 300, y: 410 }],
complete: true, complete: true,
numberOfPoints: null, numberOfPoints: null,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('When delete a point, the required point is deleted.', () => { context('When delete a point, the required point is deleted.', () => {
const issueId = '1391'; const issueId = '1391';
@ -12,7 +12,7 @@ context('When delete a point, the required point is deleted.', () => {
let pointsСoordinatesAfterDeletePoint = []; let pointsСoordinatesAfterDeletePoint = [];
const createPolylinesShape = { const createPolylinesShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 309, y: 250 }, { x: 309, y: 250 },
{ x: 309, y: 350 }, { x: 309, y: 350 },

@ -12,7 +12,7 @@ context('The highlighted attribute in AAM should correspond to the chosen attrib
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,16 +4,15 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Check if the new label reflects in the options', () => { context('Check if the new label reflects in the options', () => {
const issueId = '1429'; const issueId = '1429';
const labelName = `Issue ${issueId}`;
const newLabelName = `New ${labelName}`; const newLabelName = `New ${labelName}`;
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Check hide functionality (H)', () => { context('Check hide functionality (H)', () => {
const issueId = '1433'; const issueId = '1433';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Cancel "multiple paste". UI is not locked.', () => { context('Cancel "multiple paste". UI is not locked.', () => {
const issueId = '1438'; const issueId = '1438';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -11,7 +11,7 @@ context('Information about a blocked object disappears if hover the cursor over
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('Information about a blocked object disappears if hover the cursor over
const createRectangleShape2PointsSecond = { const createRectangleShape2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX, firstX: createRectangleShape2Points.firstX,
firstY: createRectangleShape2Points.firstY - 150, firstY: createRectangleShape2Points.firstY - 150,
secondX: createRectangleShape2Points.secondX, secondX: createRectangleShape2Points.secondX,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Filter property "shape" work correctly', () => { context('Filter property "shape" work correctly', () => {
const issueId = '1444'; const issueId = '1444';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('Filter property "shape" work correctly', () => {
const createPolygonShape = { const createPolygonShape = {
reDraw: false, reDraw: false,
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 300, y: 100 }, { x: 300, y: 100 },
{ x: 400, y: 400 }, { x: 400, y: 400 },

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Dump annotation if cuboid created', () => { context('Dump annotation if cuboid created', () => {
const issueId = '1568'; const issueId = '1568';
const createCuboidShape2Points = { const createCuboidShape2Points = {
points: 'From rectangle', points: 'From rectangle',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -11,7 +11,7 @@ context('An error occurs in AAM when switching to 2 frames, if the frames have o
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -20,7 +20,7 @@ context('An error occurs in AAM when switching to 2 frames, if the frames have o
const createRectangleShape2PointsSecond = { const createRectangleShape2PointsSecond = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: createRectangleShape2Points.firstX, firstX: createRectangleShape2Points.firstX,
firstY: createRectangleShape2Points.firstY - 150, firstY: createRectangleShape2Points.firstY - 150,
secondX: createRectangleShape2Points.secondX, secondX: createRectangleShape2Points.secondX,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, advancedConfigurationParams } from '../../support/const'; import { taskName, advancedConfigurationParams, labelName } from '../../support/const';
context('Check propagation work from the latest frame', () => { context('Check propagation work from the latest frame', () => {
const issueId = '1785'; const issueId = '1785';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, advancedConfigurationParams } from '../../support/const'; import { taskName, advancedConfigurationParams, labelName } from '../../support/const';
context('First part of a splitted track is visible', () => { context('First part of a splitted track is visible', () => {
const issueId = '1819'; const issueId = '1819';
const createRectangleTrack2Points = { const createRectangleTrack2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context( context(
"Hidden objects mustn't consider when we want to group visible objects only and use an grouping area for it.", "Hidden objects mustn't consider when we want to group visible objects only and use an grouping area for it.",
@ -13,21 +13,21 @@ context(
let bgcolor = ''; let bgcolor = '';
const createFirstPointsShape = { const createFirstPointsShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [{ x: 300, y: 410 }], pointsMap: [{ x: 300, y: 410 }],
complete: true, complete: true,
numberOfPoints: null, numberOfPoints: null,
}; };
const createSecondPointsShape = { const createSecondPointsShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [{ x: 350, y: 410 }], pointsMap: [{ x: 350, y: 410 }],
complete: true, complete: true,
numberOfPoints: null, numberOfPoints: null,
}; };
const createThridPointsShape = { const createThridPointsShape = {
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
pointsMap: [{ x: 400, y: 410 }], pointsMap: [{ x: 400, y: 410 }],
complete: true, complete: true,
numberOfPoints: null, numberOfPoints: null,

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, textDefaultValue, attrName } from '../../support/const'; import { taskName, textDefaultValue, attrName, labelName } from '../../support/const';
context( context(
"Checks that the cursor doesn't automatically jump to the end of a word when the attribute value changes", "Checks that the cursor doesn't automatically jump to the end of a word when the attribute value changes",
@ -13,7 +13,7 @@ context(
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context("The points of the previous polygon mustn't appear while polygon's interpolation.", () => { context("The points of the previous polygon mustn't appear while polygon's interpolation.", () => {
const issueId = '1882'; const issueId = '1882';
const createPolygonTrack = { const createPolygonTrack = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 309, y: 431 }, { x: 309, y: 431 },
{ x: 360, y: 500 }, { x: 360, y: 500 },
@ -23,7 +23,7 @@ context("The points of the previous polygon mustn't appear while polygon's inter
const reDrawPolygonTrack = { const reDrawPolygonTrack = {
reDraw: true, reDraw: true,
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 359, y: 431 }, { x: 359, y: 431 },
{ x: 410, y: 500 }, { x: 410, y: 500 },

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, advancedConfigurationParams } from '../../support/const'; import { taskName, advancedConfigurationParams, labelName } from '../../support/const';
context("Point coordinates are not duplicated while polygon's interpolation.", () => { context("Point coordinates are not duplicated while polygon's interpolation.", () => {
const issueId = '1886'; const issueId = '1886';
@ -12,7 +12,7 @@ context("Point coordinates are not duplicated while polygon's interpolation.", (
const createPolygonTrack = { const createPolygonTrack = {
reDraw: false, reDraw: false,
type: 'Track', type: 'Track',
switchLabel: false, labelName: labelName,
pointsMap: [ pointsMap: [
{ x: 300, y: 450 }, { x: 300, y: 450 },
{ x: 400, y: 450 }, { x: 400, y: 450 },

@ -4,7 +4,7 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName, textDefaultValue, attrName } from '../../support/const'; import { taskName, textDefaultValue, attrName, labelName } from '../../support/const';
context('Check label attribute changes', () => { context('Check label attribute changes', () => {
const issueId = '1919'; const issueId = '1919';
@ -12,7 +12,7 @@ context('Check label attribute changes', () => {
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -0,0 +1,31 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/// <reference types="cypress" />
import { taskName, labelName } from '../../support/const';
context('Draw a point shape, specify one point', () => {
const issueId = '2306';
const createPointsShape = {
type: 'Shape',
labelName: labelName,
pointsMap: [{ x: 500, y: 200 }],
numberOfPoints: 1,
};
before(() => {
cy.openTaskJob(taskName);
});
describe(`Testing case "${issueId}"`, () => {
it('Draw a point shape, specify one point. Drag cursor.', () => {
cy.createPoint(createPointsShape);
cy.get('.cvat-canvas-container').trigger('mousemove');
// Test fail before fix with error:
// The following error originated from your application code, not from Cypress.
// > Cannot read property 'each' of undefined.
});
});
});

@ -0,0 +1,110 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
/// <reference types="cypress" />
context('Wrong attribute is removed in label constructor.', () => {
const issueId = '2411';
const taskRaw = [
{
name: "person",
color: "#ff6037",
attributes: [
{
name: "lower_body",
input_type: "select",
mutable: true,
values: [
"__undefined__",
"long",
"short",
"n/a"
]
},
{
name: "hair_color",
input_type: "select",
mutable: true,
values: [
"__undefined__",
"black",
"brown",
"blond",
"grey",
"other",
"n/a"
]
},
{
name: "cellphone",
input_type: "select",
mutable: true,
values: [
"__undefined__",
"yes",
"no",
"n/a"
]
}
]
}
];
before(() => {
cy.visit('auth/login');
cy.login();
});
describe(`Testing issue "${issueId}"`, () => {
it('Open the create task page.', () => {
cy.get('#cvat-create-task-button').click({ force: true });
});
it('Go to Raw labels editor. Insert values.', () => {
cy.get('[role="tab"]').contains('Raw').click();
cy.get('#labels').clear().type(JSON.stringify(taskRaw), { parseSpecialCharSequences: false });
cy.contains('Done').click();
});
it('Go to constructor tab. The label "person" appeared there.', () => {
cy.get('[role="tab"]').contains('Constructor').click();
cy.get('.cvat-constructor-viewer-item').should('have.text', 'person').within(() => {
cy.get('i[aria-label="icon: edit"]').click();
});
});
it('Remove the average attribute "hair_color". It has been deleted.', () => {
cy.get('.cvat-label-constructor-updater').within(() => {
cy.get('.ant-row-flex-space-between').eq(1).within(() => {
cy.get('[placeholder="Name"]').invoke('val').then(placeholderNameValue => {
expect(placeholderNameValue).to.be.equal('hair_color');
});
cy.get('.cvat-delete-attribute-button').click();
});
cy.get('.ant-row-flex-space-between').eq(0).within(() => {
cy.get('[placeholder="Name"]').invoke('val').then(placeholderNameValue => {
expect(placeholderNameValue).to.be.equal('cellphone');
});
});
cy.get('.ant-row-flex-space-between').eq(1).within(() => {
cy.get('[placeholder="Name"]').invoke('val').then(placeholderNameValue => {
expect(placeholderNameValue).to.be.equal('lower_body');
});
});
});
});
it('Remove the latest attribute "lower_body". It has been deleted.', () => {
cy.get('.cvat-label-constructor-updater').within(() => {
cy.get('.ant-row-flex-space-between').eq(1).within(() => {
cy.get('[placeholder="Name"]').invoke('val').then(placeholderNameValue => {
expect(placeholderNameValue).to.be.equal('lower_body');
});
cy.get('.cvat-delete-attribute-button').click();
});
cy.get('.ant-row-flex-space-between').eq(0).within(() => {
cy.get('[placeholder="Name"]').invoke('val').then(placeholderNameValue => {
expect(placeholderNameValue).to.be.equal('cellphone');
});
});
});
});
});
});

@ -4,14 +4,14 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { taskName } from '../../support/const'; import { taskName, labelName } from '../../support/const';
context('Check if the UI fails by moving to the next frame while dragging the object', () => { context('Check if the UI fails by moving to the next frame while dragging the object', () => {
const prId = '1370'; const prId = '1370';
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
switchLabel: false, labelName: labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,

@ -96,9 +96,7 @@ Cypress.Commands.add('openTaskJob', (taskName, jobNumber = 0) => {
Cypress.Commands.add('createRectangle', (createRectangleParams) => { Cypress.Commands.add('createRectangle', (createRectangleParams) => {
cy.get('.cvat-draw-rectangle-control').click(); cy.get('.cvat-draw-rectangle-control').click();
if (createRectangleParams.switchLabel) { cy.switchLabel(createRectangleParams.labelName, 'rectangle');
cy.switchLabel(createRectangleParams.labelName, 'rectangle');
}
cy.contains('Draw new rectangle') cy.contains('Draw new rectangle')
.parents('.cvat-draw-shape-popover-content') .parents('.cvat-draw-shape-popover-content')
.within(() => { .within(() => {
@ -123,7 +121,7 @@ Cypress.Commands.add('switchLabel', (labelName, objectType) => {
cy.contains(regex).parents('.cvat-draw-shape-popover-content').within(() => { cy.contains(regex).parents('.cvat-draw-shape-popover-content').within(() => {
cy.get('.ant-select-selection-selected-value').click(); cy.get('.ant-select-selection-selected-value').click();
}); });
cy.get('.ant-select-dropdown-menu').last().contains(labelName).click(); cy.get('.ant-select-dropdown').not('.ant-select-dropdown-hidden').contains(new RegExp(`^${labelName}$`, 'g')).click();
}); });
Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) => { Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) => {
@ -146,9 +144,7 @@ Cypress.Commands.add('checkObjectParameters', (objectParameters, objectType) =>
Cypress.Commands.add('createPoint', (createPointParams) => { Cypress.Commands.add('createPoint', (createPointParams) => {
cy.get('.cvat-draw-points-control').click(); cy.get('.cvat-draw-points-control').click();
if (createPointParams.switchLabel) { cy.switchLabel(createPointParams.labelName, 'points');
cy.switchLabel(createPointParams.labelName, 'points');
}
cy.contains('Draw new points') cy.contains('Draw new points')
.parents('.cvat-draw-shape-popover-content') .parents('.cvat-draw-shape-popover-content')
.within(() => { .within(() => {
@ -190,9 +186,7 @@ Cypress.Commands.add('shapeGrouping', (firstX, firstY, lastX, lastY) => {
Cypress.Commands.add('createPolygon', (createPolygonParams) => { Cypress.Commands.add('createPolygon', (createPolygonParams) => {
if (!createPolygonParams.reDraw) { if (!createPolygonParams.reDraw) {
cy.get('.cvat-draw-polygon-control').click(); cy.get('.cvat-draw-polygon-control').click();
if (createPolygonParams.switchLabel) { cy.switchLabel(createPolygonParams.labelName, 'polygon');
cy.switchLabel(createPolygonParams.labelName, 'polygon');
}
cy.contains('Draw new polygon') cy.contains('Draw new polygon')
.parents('.cvat-draw-shape-popover-content') .parents('.cvat-draw-shape-popover-content')
.within(() => { .within(() => {
@ -242,16 +236,14 @@ Cypress.Commands.add('changeLabelAAM', (labelName) => {
cy.get('.attribute-annotation-sidebar-basics-editor').within(() => { cy.get('.attribute-annotation-sidebar-basics-editor').within(() => {
cy.get('.ant-select-selection').click(); cy.get('.ant-select-selection').click();
}); });
cy.get('.ant-select-dropdown-menu-item').contains(labelName).click(); cy.get('.ant-select-dropdown').not('.ant-select-dropdown-hidden').contains(new RegExp(`^${labelName}$`, 'g')).click();
} }
}); });
}); });
Cypress.Commands.add('createCuboid', (createCuboidParams) => { Cypress.Commands.add('createCuboid', (createCuboidParams) => {
cy.get('.cvat-draw-cuboid-control').click(); cy.get('.cvat-draw-cuboid-control').click();
if (createCuboidParams.switchLabel) { cy.switchLabel(createCuboidParams.labelName, 'cuboid');
cy.switchLabel(createCuboidParams.labelName, 'cuboid');
}
cy.contains('Draw new cuboid') cy.contains('Draw new cuboid')
.parents('.cvat-draw-shape-popover-content') .parents('.cvat-draw-shape-popover-content')
.within(() => { .within(() => {
@ -280,9 +272,7 @@ Cypress.Commands.add('updateAttributes', (multiAttrParams) => {
Cypress.Commands.add('createPolyline', (createPolylineParams) => { Cypress.Commands.add('createPolyline', (createPolylineParams) => {
cy.get('.cvat-draw-polyline-control').click(); cy.get('.cvat-draw-polyline-control').click();
if (createPolylineParams.switchLabel) { cy.switchLabel(createPolylineParams.labelName, 'polyline');
cy.switchLabel(createPolylineParams.labelName, 'polyline');
}
cy.contains('Draw new polyline') cy.contains('Draw new polyline')
.parents('.cvat-draw-shape-popover-content') .parents('.cvat-draw-shape-popover-content')
.within(() => { .within(() => {

Loading…
Cancel
Save