Batch of fixes (#2031)

main
Dmitry Kalinin 6 years ago committed by GitHub
parent 9c4e717ddb
commit 582e23bf88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,6 +24,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 (<https://github.com/opencv/cvat/pull/1996>)
- Basic functions of CVAT works without activated nuclio dashboard
- Fixed error with creating task with labels with the same name (<https://github.com/opencv/cvat/pull/2031>)
### Security
-

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.7.1",
"version": "1.7.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -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": {

@ -344,10 +344,12 @@ function createTask(): AnyAction {
return action;
}
function createTaskSuccess(): AnyAction {
function createTaskSuccess(taskId: number): AnyAction {
const action = {
type: TasksActionTypes.CREATE_TASK_SUCCESS,
payload: {},
payload: {
taskId,
},
};
return action;
@ -433,10 +435,10 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
dispatch(createTask());
try {
await taskInstance.save((status: string): void => {
const savedTask = await taskInstance.save((status: string): void => {
dispatch(createTaskUpdateStatus(status));
});
dispatch(createTaskSuccess());
dispatch(createTaskSuccess(savedTask.id));
} catch (error) {
dispatch(createTaskFailed(error));
}

@ -3,6 +3,8 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
import Alert from 'antd/lib/alert';
import Button from 'antd/lib/button';
@ -26,6 +28,7 @@ export interface CreateTaskData {
interface Props {
onCreate: (data: CreateTaskData) => void;
status: string;
taskId: number | null;
installedGit: boolean;
}
@ -48,22 +51,31 @@ const defaultState = {
},
};
export default class CreateTaskContent extends React.PureComponent<Props, State> {
class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps, State> {
private basicConfigurationComponent: any;
private advancedConfigurationComponent: any;
private fileManagerContainer: any;
public constructor(props: Props) {
public constructor(props: Props & RouteComponentProps) {
super(props);
this.state = { ...defaultState };
}
public componentDidUpdate(prevProps: Props): void {
const { status } = this.props;
const { status, history, taskId } = this.props;
if (status === 'CREATED' && prevProps.status !== 'CREATED') {
const btn = (
<Button
onClick={() => history.push(`/tasks/${taskId}`)}
>
Open task
</Button>
);
notification.info({
message: 'The task has been created',
btn,
});
this.basicConfigurationComponent.resetFields();
@ -252,3 +264,5 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
);
}
}
export default withRouter(CreateTaskContent);

@ -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 {
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>Create a new task</Text>
<CreateTaskContent
taskId={taskId}
status={status}
onCreate={onCreate}
installedGit={installedGit}

@ -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 {
<Button
className='cvat-header-button'
type='link'
href={`${serverHost}/analytics/app/kibana`}
onClick={
(): void => {
(event: React.MouseEvent): void => {
event.preventDefault();
// false positive
// eslint-disable-next-line
window.open(`${serverHost}/analytics/app/kibana`, '_blank');
@ -211,8 +221,10 @@ function HeaderContainer(props: Props): JSX.Element {
<Button
className='cvat-header-button'
type='link'
href={GITHUB_URL}
onClick={
(): void => {
(event: React.MouseEvent): void => {
event.preventDefault();
// false positive
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(GITHUB_URL, '_blank');
@ -225,8 +237,10 @@ function HeaderContainer(props: Props): JSX.Element {
<Button
className='cvat-header-button'
type='link'
href={`${serverHost}/documentation/user_guide.html`}
onClick={
(): void => {
(event: React.MouseEvent): void => {
event.preventDefault();
// false positive
// eslint-disable-next-line
window.open(`${serverHost}/documentation/user_guide.html`, '_blank')

@ -8,14 +8,30 @@ 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;
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 (
<div className='cvat-label-constructor-creator'>
<LabelForm label={null} onSubmit={onCreate} />
<LabelForm label={null} onSubmit={onCreate} labelNames={labelNames} />
</div>
);
}
export default React.memo(ConstructorCreator, compareProps);

@ -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<Props, {}> {
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<Props, {}> {
}, {
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');
}
},
}],
})(<Input disabled={locked} placeholder='Label name' />)}
</Form.Item>

@ -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
&& (
<ConstructorCreator
labelNames={labels.map((l) => l.name)}
onCreate={this.handleCreate}
/>
)

@ -28,6 +28,10 @@ class RawViewer extends React.PureComponent<Props> {
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 {

@ -10,6 +10,7 @@ import { CreateTaskData } from 'components/create-task-page/create-task-content'
import { createTaskAsync } from 'actions/tasks-actions';
interface StateToProps {
taskId: number | null;
status: string;
error: string;
installedGit: boolean;

@ -60,6 +60,7 @@ export interface TasksState {
[tid: number]: boolean; // deleted (deleting if in dictionary)
};
creates: {
taskId: number | null;
status: string;
error: string;
};

@ -31,6 +31,7 @@ const defaultState: TasksState = {
loads: {},
deletes: {},
creates: {
taskId: null,
status: '',
error: '',
},
@ -238,6 +239,7 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
activities: {
...state.activities,
creates: {
taskId: null,
status: '',
error: '',
},
@ -259,12 +261,14 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
};
}
case TasksActionTypes.CREATE_TASK_SUCCESS: {
const { taskId } = action.payload;
return {
...state,
activities: {
...state.activities,
creates: {
...state.activities.creates,
taskId,
status: 'CREATED',
},
},

@ -37,6 +37,7 @@ class AttributeSerializer(serializers.ModelSerializer):
class LabelSerializer(serializers.ModelSerializer):
attributes = AttributeSerializer(many=True, source='attributespec_set',
default=[])
class Meta:
model = models.Label
fields = ('id', 'name', 'attributes')
@ -305,6 +306,15 @@ class TaskSerializer(WriteOnceMixin, serializers.ModelSerializer):
instance.save()
return instance
def validate_labels(self, value):
if not value:
raise serializers.ValidationError('Label set must not be empty')
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):
class Meta:
model = models.Project

@ -44,7 +44,7 @@ Cypress.Commands.add('createAnnotationTask', (taksName='New annotation task',
cy.get('input[type="file"]').attachFile(image, { subjectType: 'drag-n-drop' });
cy.contains('button', 'Submit').click()
cy.contains('The task has been created', {timeout: '8000'})
cy.get('button[value="tasks"]').click()
cy.get('[value="tasks"]').click()
cy.url().should('include', '/tasks?page=')
})

Loading…
Cancel
Save