未验证 提交 582e23bf 编写于 作者: D Dmitry Kalinin 提交者: GitHub

Batch of fixes (#2031)

上级 9c4e717d
......@@ -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
-
......
{
"name": "cvat-ui",
"version": "1.7.1",
"version": "1.7.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
{
"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=')
})
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册