Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git flexibility after task creation #3886

Merged
merged 6 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for working with ellipses (<https://github.com/openvinotoolkit/cvat/pull/4062>)
- Add several flags to task creation CLI (<https://github.com/openvinotoolkit/cvat/pull/4119>)
- Add YOLOv5 serverless function for automatic annotation (<https://github.com/openvinotoolkit/cvat/pull/4178>)
- Add possibility to change git repository and git export format from already created task (<https://github.com/openvinotoolkit/cvat/pull/3886>)
- Basic page with jobs list, basic filtration to this list (<https://github.com/openvinotoolkit/cvat/pull/4258>)
- Added OpenCV.js TrackerMIL as tracking tool (<https://github.com/openvinotoolkit/cvat/pull/4200>)
- Ability to continue working from the latest frame where an annotator was before (<https://github.com/openvinotoolkit/cvat/pull/4297>)
Expand Down
208 changes: 137 additions & 71 deletions cvat-ui/src/components/task-page/details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import Title from 'antd/lib/typography/Title';
import moment from 'moment';

import Descriptions from 'antd/lib/descriptions';
import Paragraph from 'antd/lib/typography/Paragraph';
import Select from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import getCore from 'cvat-core-wrapper';
import { getReposData, syncRepos } from 'utils/git-utils';
import { getReposData, syncRepos, changeRepo } from 'utils/git-utils';
import { ActiveInference } from 'reducers/interfaces';
import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress';
import Descriptions from 'antd/lib/descriptions';
import Space from 'antd/lib/space';
import UserSelector, { User } from './user-selector';
import BugTrackerEditor from './bug-tracker-editor';
import LabelsEditorComponent from '../labels-editor/labels-editor';
import ProjectSubsetField from '../create-task-page/project-subset-field';

const { Option } = Select;

const core = getCore();

interface Props {
Expand All @@ -32,6 +37,8 @@ interface Props {
projectSubsets: string[];
cancelAutoAnnotation(): void;
onTaskUpdate: (taskInstance: any) => void;
dumpers: any[];
user: any;
}

interface State {
Expand All @@ -40,13 +47,13 @@ interface State {
repository: string;
repositoryStatus: string;
format: string;
lfs: boolean;
updatingRepository: boolean;
}

export default class DetailsComponent extends React.PureComponent<Props, State> {
private mounted: boolean;

private previewImageElement: HTMLImageElement;

private previewWrapperRef: React.RefObject<HTMLDivElement>;

constructor(props: Props) {
Expand All @@ -63,6 +70,8 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
repository: '',
format: '',
repositoryStatus: '',
lfs: false,
updatingRepository: false,
};
}

Expand Down Expand Up @@ -103,6 +112,7 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
this.setState({
repository: data.url,
format: data.format,
lfs: !!data.lfs,
});
}
})
Expand Down Expand Up @@ -130,6 +140,54 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
this.mounted = false;
}

private onChangeRepoValue = (value: string): void => {
const { taskInstance } = this.props;
const { repository } = this.state;
const old = repository;
this.setState({ repository: value, updatingRepository: true });
changeRepo(taskInstance.id, 'url', value)
.catch((error) => {
this.setState({ repository: old });
notification.error({
message: 'Could not update repository',
description: error,
});
})
.finally(() => this.setState({ updatingRepository: false }));
};

private onChangeLFSValue = (event: CheckboxChangeEvent): void => {
const { taskInstance } = this.props;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same comment like above for other change callbacks

const { lfs } = this.state;
const old = lfs;
this.setState({ lfs: event.target.checked, updatingRepository: true });
changeRepo(taskInstance.id, 'lfs', event.target.checked)
.catch((error) => {
this.setState({ lfs: old });
notification.error({
message: 'Could not update LFS',
description: error,
});
})
.finally(() => this.setState({ updatingRepository: false }));
};

private onChangeFormatValue = (value: string): void => {
const { taskInstance } = this.props;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same comment like above for other change callbacks

const { format } = this.state;
const old = format;
this.setState({ format: value, updatingRepository: true });
changeRepo(taskInstance.id, 'format', value)
.catch((error) => {
this.setState({ format: old });
notification.error({
message: 'Could not update format',
description: error,
});
})
.finally(() => this.setState({ updatingRepository: false }));
};

private renderTaskName(): JSX.Element {
const { name } = this.state;
const { taskInstance, onTaskUpdate } = this.props;
Expand Down Expand Up @@ -205,81 +263,89 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
}

private renderDatasetRepository(): JSX.Element | boolean {
const { taskInstance } = this.props;
const { repository, repositoryStatus, format } = this.state;

const { taskInstance, dumpers } = this.props;
const {
repository, repositoryStatus, format, lfs, updatingRepository,
} = this.state;
return (
!!repository && (
<Row>
<Col className='cvat-dataset-repository-url'>
<Text strong className='cvat-text-color'>
Dataset Repository
</Text>
<br />
<a href={repository} rel='noopener noreferrer' target='_blank'>
{repository}
</a>
<br />
<p>
Using format
{' '}
<Text strong>
{format}
<Paragraph>
<Text editable={{ onChange: this.onChangeRepoValue }} disabled={updatingRepository}>
{repository}
</Text>
</p>
{repositoryStatus === 'sync' && (
<Tag color='blue'>
<CheckCircleOutlined />
Synchronized
</Tag>
)}
{repositoryStatus === 'merged' && (
<Tag color='green'>
<CheckCircleOutlined />
Merged
</Tag>
)}
{repositoryStatus === 'syncing' && (
<Tag color='purple'>
<LoadingOutlined />
Syncing
</Tag>
)}
{repositoryStatus === '!sync' && (
<Tag
color='red'
onClick={(): void => {
this.setState({
repositoryStatus: 'syncing',
});

syncRepos(taskInstance.id)
.then((): void => {
if (this.mounted) {
this.setState({
repositoryStatus: 'sync',
});
}
})
.catch((error): void => {
if (this.mounted) {
Modal.error({
width: 800,
title: 'Could not synchronize the repository',
content: error.toString(),
});

this.setState({
repositoryStatus: '!sync',
});
}
{repositoryStatus === 'sync' && (
<Tag color='blue'>
<CheckCircleOutlined />
Synchronized
</Tag>
)}
{repositoryStatus === 'merged' && (
<Tag color='green'>
<CheckCircleOutlined />
Merged
</Tag>
)}
{repositoryStatus === 'syncing' && (
<Tag color='purple'>
<LoadingOutlined />
Syncing
</Tag>
)}
{repositoryStatus === '!sync' && (
<Tag
color='red'
onClick={(): void => {
this.setState({
repositoryStatus: 'syncing',
});
}}
>
<ExclamationCircleOutlined />
Synchronize
</Tag>
)}

syncRepos(taskInstance.id)
.then((): void => {
if (this.mounted) {
this.setState({
repositoryStatus: 'sync',
});
}
})
.catch((error): void => {
if (this.mounted) {
Modal.error({
width: 800,
title: 'Could not synchronize the repository',
content: error.toString(),
});

this.setState({
repositoryStatus: '!sync',
});
}
});
}}
>
<ExclamationCircleOutlined />
Synchronize
</Tag>
)}
</Paragraph>
<Text strong className='cvat-text-color'>Using format: </Text>
<Space>
<Select disabled={updatingRepository} onChange={this.onChangeFormatValue} className='cvat-repository-format-select' value={format}>
{
dumpers.map((dumper: any) => (
<Option key={dumper.name} value={dumper.name}>{dumper.name}</Option>
))
}
</Select>
<Checkbox disabled={updatingRepository} onChange={this.onChangeLFSValue} checked={lfs}>
Large file support
</Checkbox>
{updatingRepository && <LoadingOutlined style={{ fontSize: 14 }} spin />}
</Space>
</Col>
</Row>
)
Expand Down
32 changes: 21 additions & 11 deletions cvat-ui/src/components/task-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,35 @@

.cvat-dataset-repository-url {
> a {
margin-right: 10px;
margin-right: $grid-unit-size;
}

> .ant-tag-red {
user-select: none;
opacity: 0.4;
> .ant-typography {
> .ant-tag {
margin-left: $grid-unit-size;

&:hover {
opacity: 0.8;
> span[role='img'] {
margin-right: $grid-unit-size;
}
}

&:active {
opacity: 1;
> .ant-tag-red {
user-select: none;
opacity: 0.4;

&:hover {
opacity: 0.8;
}

&:active {
opacity: 1;
}
}
}
}

> .ant-tag > span[role='img'] {
margin-right: 5px;
}
.cvat-repository-format-select {
width: 100%;
}

.cvat-task-job-list {
Expand Down
10 changes: 8 additions & 2 deletions cvat-ui/src/containers/task-page/details.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

Expand All @@ -18,6 +18,8 @@ interface StateToProps {
activeInference: ActiveInference | null;
installedGit: boolean;
projectSubsets: string[];
dumpers: any[];
user: any;
}

interface DispatchToProps {
Expand All @@ -30,6 +32,8 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const [taskProject] = state.projects.current.filter((project) => project.id === own.task.instance.projectId);

return {
dumpers: state.formats.annotationFormats.dumpers,
user: state.auth.user,
installedGit: list.GIT_INTEGRATION,
activeInference: state.models.inferences[own.task.instance.id] || null,
projectSubsets: taskProject ?
Expand All @@ -53,11 +57,13 @@ function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {

function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JSX.Element {
const {
task, installedGit, activeInference, projectSubsets, cancelAutoAnnotation, onTaskUpdate,
task, installedGit, activeInference, projectSubsets, cancelAutoAnnotation, onTaskUpdate, dumpers, user,
} = props;

return (
<DetailsComponent
dumpers={dumpers}
user={user}
previewImage={task.preview}
taskInstance={task.instance}
installedGit={installedGit}
Expand Down
Loading