From fac04399c46cb50f9452f0ff8a909ed7f8d4ab52 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 14 Aug 2020 01:39:52 +0300 Subject: [PATCH 1/8] Added label name unique validator to server and ui --- .../src/components/labels-editor/constructor-creator.tsx | 5 +++-- cvat-ui/src/components/labels-editor/label-form.tsx | 9 +++++++++ cvat-ui/src/components/labels-editor/labels-editor.tsx | 2 ++ cvat-ui/src/components/labels-editor/raw-viewer.tsx | 4 ++++ cvat/apps/engine/serializers.py | 7 +++++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx index b5c8664dcc87..83cb8039f826 100644 --- a/cvat-ui/src/components/labels-editor/constructor-creator.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -8,14 +8,15 @@ import LabelForm from './label-form'; import { Label } from './common'; interface Props { + labelNames: string[]; onCreate: (label: Label | null) => void; } export default function ConstructorCreator(props: Props): JSX.Element { - const { onCreate } = props; + const { onCreate, labelNames } = props; return (
- +
); } diff --git a/cvat-ui/src/components/labels-editor/label-form.tsx b/cvat-ui/src/components/labels-editor/label-form.tsx index 469826a4f6c7..12c0a48973ab 100644 --- a/cvat-ui/src/components/labels-editor/label-form.tsx +++ b/cvat-ui/src/components/labels-editor/label-form.tsx @@ -32,6 +32,7 @@ export enum AttributeType { type Props = FormComponentProps & { label: Label | null; + labelNames?: string[]; onSubmit: (label: Label | null) => void; }; @@ -384,6 +385,7 @@ class LabelForm extends React.PureComponent { const { label, form, + labelNames, } = this.props; const value = label ? label.name : ''; const locked = label ? label.id >= 0 : false; @@ -399,6 +401,13 @@ class LabelForm extends React.PureComponent { }, { pattern: patterns.validateAttributeName.pattern, message: patterns.validateAttributeName.message, + }, { + validator: + async (_rule: any, labelName: string, callback: Function) => { + if (labelNames && labelNames.includes(labelName)) { + callback('Label name must be unique for the task'); + } + }, }], })()} diff --git a/cvat-ui/src/components/labels-editor/labels-editor.tsx b/cvat-ui/src/components/labels-editor/labels-editor.tsx index 4ade51006e3d..89245b96b986 100644 --- a/cvat-ui/src/components/labels-editor/labels-editor.tsx +++ b/cvat-ui/src/components/labels-editor/labels-editor.tsx @@ -221,6 +221,7 @@ export default class LabelsEditor } public render(): JSX.Element { + const { labels } = this.props; const { savedLabels, unsavedLabels, @@ -319,6 +320,7 @@ export default class LabelsEditor constructorMode === ConstructorMode.CREATE && ( l.name)} onCreate={this.handleCreate} /> ) diff --git a/cvat-ui/src/components/labels-editor/raw-viewer.tsx b/cvat-ui/src/components/labels-editor/raw-viewer.tsx index eb798cd2e7ee..f7b95a08e0a6 100644 --- a/cvat-ui/src/components/labels-editor/raw-viewer.tsx +++ b/cvat-ui/src/components/labels-editor/raw-viewer.tsx @@ -28,6 +28,10 @@ class RawViewer extends React.PureComponent { if (!Array.isArray(parsed)) { callback('Field is expected to be a JSON array'); } + const labelNames = parsed.map((label: Label) => label.name); + if (new Set(labelNames).size !== labelNames.length) { + callback('Label names must be unique for the task'); + } for (const label of parsed) { try { diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 9edac559d784..edf39f318205 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -37,6 +37,7 @@ def to_representation(self, instance): class LabelSerializer(serializers.ModelSerializer): attributes = AttributeSerializer(many=True, source='attributespec_set', default=[]) + class Meta: model = models.Label fields = ('id', 'name', 'attributes') @@ -305,6 +306,12 @@ def update(self, instance, validated_data): instance.save() return instance + def validate_labels(self, data): + label_names = [label['name'] for label in data] + if len(label_names) != len(set(label_names)): + raise serializers.ValidationError('All label names must be unique for the task') + + class ProjectSerializer(serializers.ModelSerializer): class Meta: model = models.Project From c7a45008498b09c53e99cf554cd19de0505358f7 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 14 Aug 2020 01:47:19 +0300 Subject: [PATCH 2/8] Added CHANGELOG and npm package version --- CHANGELOG.md | 1 + cvat-ui/package-lock.json | 2 +- cvat-ui/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e3bafcdd66..2c160fe3b2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Issue loading openvino models for semi-automatic and automatic annotation () - Basic functions of CVAT works without activated nuclio dashboard +- Fixed error with creating task with labels with the same name () ### Security - diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 8def8aaa4709..1f47f08107da 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.7.1", + "version": "1.7.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-ui/package.json b/cvat-ui/package.json index f20e3eb6da67..f1a563ee4e8e 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.7.1", + "version": "1.7.2", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { From db624eaec021697d94d8219e7456dd4d6a209a3e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Sat, 15 Aug 2020 00:57:21 +0300 Subject: [PATCH 3/8] Added empty label set validator --- cvat/apps/engine/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index edf39f318205..39ee223d2d67 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -307,6 +307,8 @@ def update(self, instance, validated_data): return instance def validate_labels(self, data): + if not data: + raise serializers.ValidationError('Label set must not be empty') label_names = [label['name'] for label in data] if len(label_names) != len(set(label_names)): raise serializers.ValidationError('All label names must be unique for the task') From a19a2b16575e88facbc74ce35678b79ff38c51d4 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 17 Aug 2020 15:19:25 +0300 Subject: [PATCH 4/8] Added memoization to constructor-creator --- .../labels-editor/constructor-creator.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cvat-ui/src/components/labels-editor/constructor-creator.tsx b/cvat-ui/src/components/labels-editor/constructor-creator.tsx index 83cb8039f826..ff101b8af5fa 100644 --- a/cvat-ui/src/components/labels-editor/constructor-creator.tsx +++ b/cvat-ui/src/components/labels-editor/constructor-creator.tsx @@ -12,7 +12,20 @@ interface Props { onCreate: (label: Label | null) => void; } -export default function ConstructorCreator(props: Props): JSX.Element { +function compareProps(prevProps: Props, nextProps: Props): boolean { + if (prevProps.onCreate !== nextProps.onCreate) { + return false; + } + if (!(prevProps.labelNames.length === nextProps.labelNames.length + && prevProps.labelNames.map((value, index) => value === nextProps.labelNames[index]) + .reduce((prevValue, curValue) => prevValue && curValue, true) + )) { + return false; + } + return true; +} + +function ConstructorCreator(props: Props): JSX.Element { const { onCreate, labelNames } = props; return (
@@ -20,3 +33,5 @@ export default function ConstructorCreator(props: Props): JSX.Element {
); } + +export default React.memo(ConstructorCreator, compareProps); From 0fff0c2a03c75722f4caae3d33527a7850982d2b Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 17 Aug 2020 15:22:01 +0300 Subject: [PATCH 5/8] Fixed label validator --- cvat/apps/engine/serializers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index 39ee223d2d67..a878e9e67663 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -306,12 +306,13 @@ def update(self, instance, validated_data): instance.save() return instance - def validate_labels(self, data): - if not data: + def validate_labels(self, value): + if not value: raise serializers.ValidationError('Label set must not be empty') - label_names = [label['name'] for label in data] + label_names = [label['name'] for label in value] if len(label_names) != len(set(label_names)): raise serializers.ValidationError('All label names must be unique for the task') + return value class ProjectSerializer(serializers.ModelSerializer): From fbe6a027d400c256caccf106b2350157e7e41fea Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 17 Aug 2020 16:11:17 +0300 Subject: [PATCH 6/8] Added links in header --- cvat-ui/src/components/header/header.tsx | 26 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cvat-ui/src/components/header/header.tsx b/cvat-ui/src/components/header/header.tsx index 5391fe8df24a..87db4c51296c 100644 --- a/cvat-ui/src/components/header/header.tsx +++ b/cvat-ui/src/components/header/header.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT import './styles.scss'; -import React from 'react'; +import React, { MouseEvent } from 'react'; import { RouteComponentProps } from 'react-router'; import { withRouter } from 'react-router-dom'; import { Row, Col } from 'antd/lib/grid'; @@ -174,8 +174,12 @@ function HeaderContainer(props: Props): JSX.Element { className='cvat-header-button' type='link' value='tasks' + href='/tasks?page=1' onClick={ - (): void => props.history.push('/tasks?page=1') + (event: React.MouseEvent): void => { + event.preventDefault(); + props.history.push('/tasks?page=1'); + } } > Tasks @@ -184,8 +188,12 @@ function HeaderContainer(props: Props): JSX.Element { className='cvat-header-button' type='link' value='models' + href='/models' onClick={ - (): void => props.history.push('/models') + (event: React.MouseEvent): void => { + event.preventDefault(); + props.history.push('/models'); + } } > Models @@ -195,8 +203,10 @@ function HeaderContainer(props: Props): JSX.Element { + ); + notification.info({ message: 'The task has been created', + btn, }); this.basicConfigurationComponent.resetFields(); @@ -252,3 +264,5 @@ export default class CreateTaskContent extends React.PureComponent ); } } + +export default withRouter(CreateTaskContent); diff --git a/cvat-ui/src/components/create-task-page/create-task-page.tsx b/cvat-ui/src/components/create-task-page/create-task-page.tsx index 15c5f18c47a6..39549ea68dd8 100644 --- a/cvat-ui/src/components/create-task-page/create-task-page.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-page.tsx @@ -16,6 +16,7 @@ interface Props { onCreate: (data: CreateTaskData) => void; status: string; error: string; + taskId: number | null; installedGit: boolean; } @@ -23,6 +24,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { const { error, status, + taskId, onCreate, installedGit, } = props; @@ -66,6 +68,7 @@ export default function CreateTaskPage(props: Props): JSX.Element { Create a new task