Git flexibility after task creation (#3886)

main
PMazarovich 4 years ago committed by GitHub
parent eac5b60d9b
commit cca258656c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,6 +31,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>)

@ -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 {
@ -32,6 +37,8 @@ interface Props {
projectSubsets: string[];
cancelAutoAnnotation(): void;
onTaskUpdate: (taskInstance: any) => void;
dumpers: any[];
user: any;
}
interface State {
@ -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) {
@ -63,6 +70,8 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
repository: '',
format: '',
repositoryStatus: '',
lfs: false,
updatingRepository: false,
};
}
@ -103,6 +112,7 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
this.setState({
repository: data.url,
format: data.format,
lfs: !!data.lfs,
});
}
})
@ -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;
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;
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;
@ -205,9 +263,10 @@ 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>
@ -215,71 +274,78 @@ export default class DetailsComponent extends React.PureComponent<Props, State>
<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>
)

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

@ -1,4 +1,4 @@
// Copyright (C) 2019-2021 Intel Corporation
// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -18,6 +18,8 @@ interface StateToProps {
activeInference: ActiveInference | null;
installedGit: boolean;
projectSubsets: string[];
dumpers: any[];
user: any;
}
interface DispatchToProps {
@ -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 ?
@ -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}

@ -1,4 +1,4 @@
// Copyright (C) 2021 Intel Corporation
// Copyright (C) 2019-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT
@ -39,6 +39,7 @@ interface ReposData {
error: string | null;
};
format: string
lfs: boolean
}
function waitForClone(cloneResponse: any): Promise<void> {
@ -157,6 +158,7 @@ export async function getReposData(tid: number): Promise<ReposData | null> {
error: response.status.error,
},
format: response.format,
lfs: response.lfs,
};
}
@ -193,3 +195,20 @@ export function syncRepos(tid: number): Promise<void> {
});
});
}
export async function changeRepo(taskId: number, type: string, value: any): Promise<void> {
return new Promise((resolve, reject): void => {
core.server
.request(`${baseURL}/git/repository/${taskId}`, {
method: 'PATCH',
data: JSON.stringify({
type,
value,
}),
})
.then(resolve)
.catch((error: any): void => {
reject(error);
});
});
}

@ -223,10 +223,12 @@ class Git:
if self._ssh_url() != self._rep.git.remote('get-url', '--all', 'origin'):
slogger.task[self._tid].info("Local repository URL is obsolete.")
# We need reinitialize repository if it's false
raise git.exc.GitError("Actual and saved repository URLs aren't match")
slogger.task[self._tid].info("Local repository initialization..")
shutil.rmtree(self._cwd, True)
self._clone()
except git.exc.GitError:
if wo_remote:
raise Exception('Local repository is failed')
slogger.task[self._tid].info("Local repository is failed")
slogger.task[self._tid].info("Local repository initialization..")
shutil.rmtree(self._cwd, True)
self._clone()
@ -418,6 +420,7 @@ def get(tid, user):
response["url"] = {"value": None}
response["status"] = {"value": None, "error": None}
response["format"] = {"format": None}
response["lfs"] = {"lfs": None}
db_task = Task.objects.get(pk = tid)
if GitData.objects.filter(pk = db_task).exists():
db_git = GitData.objects.select_for_update().get(pk = db_task)
@ -430,6 +433,7 @@ def get(tid, user):
db_git.status = GitStatusChoice.SYNCING
response['status']['value'] = str(db_git.status)
response['format'] = str(db_git.format)
response["lfs"] = db_git.lfs
else:
try:
_git = Git(db_git, db_task, user)
@ -437,6 +441,7 @@ def get(tid, user):
db_git.status = _git.remote_status(db_task.updated_date)
response['status']['value'] = str(db_git.status)
response['format'] = str(db_git.format)
response["lfs"] = db_git.lfs
except git.exc.GitCommandError as ex:
_have_no_access_exception(ex)
db_git.save()

@ -13,4 +13,5 @@ urlpatterns = [
path('push/<int:tid>', views.push_repository),
path('check/<str:rq_id>', views.check_process),
path('meta/get', views.get_meta_info),
path('<int:tid>', views.update_git_repo)
]

@ -1,8 +1,9 @@
# Copyright (C) 2018-2021 Intel Corporation
#
# SPDX-License-Identifier: MIT
import http.client
from django.http import HttpResponseBadRequest, JsonResponse
from django.http import HttpResponseBadRequest, JsonResponse, HttpResponse
from rules.contrib.views import permission_required, objectgetter
from cvat.apps.iam.decorators import login_required
@ -85,6 +86,35 @@ def get_repository(request, tid):
return HttpResponseBadRequest(str(ex))
@login_required
@permission_required(perm=['engine.task.access'],
fn=objectgetter(models.Task, 'tid'), raise_exception=True)
def update_git_repo(request, tid):
try:
body = json.loads(request.body.decode('utf-8'))
req_type = body["type"]
value = body["value"]
git_data_obj = GitData.objects.filter(task_id=tid)[0]
if req_type == "url":
git_data_obj.url = value
git_data_obj.save(update_fields=["url"])
elif req_type == "lfs":
git_data_obj.lfs = bool(value)
git_data_obj.save(update_fields=["lfs"])
elif req_type == "format":
git_data_obj.format = value
git_data_obj.save(update_fields=["format"])
slogger.task[tid].info("get repository request")
return HttpResponse(
status=http.HTTPStatus.OK,
)
except Exception as ex:
try:
slogger.task[tid].error("error occurred during changing repository request", exc_info=True)
except Exception:
pass
return HttpResponseBadRequest(str(ex))
@login_required
def get_meta_info(request):

Loading…
Cancel
Save