mirror of
https://github.com/clearml/clearml-server
synced 2025-04-08 23:14:44 +00:00
Add projects.validate_delete
This commit is contained in:
parent
56aea1ffb8
commit
d4edeaaf1b
@ -554,7 +554,7 @@ class ProjectBLL:
|
||||
user_ids: Optional[Sequence[str]] = None,
|
||||
) -> Set[str]:
|
||||
"""
|
||||
Get the set of user ids that created tasks/models/dataviews in the given projects
|
||||
Get the set of user ids that created tasks/models in the given projects
|
||||
If project_ids is empty then all projects are examined
|
||||
If user_ids are passed then only subset of these users is returned
|
||||
"""
|
||||
@ -676,8 +676,8 @@ class ProjectBLL:
|
||||
@classmethod
|
||||
def calc_own_contents(cls, company: str, project_ids: Sequence[str]) -> Dict[str, dict]:
|
||||
"""
|
||||
Returns the amount of task/dataviews/models per requested project
|
||||
Use separate aggregation calls on Task/Dataview/Model instead of lookup
|
||||
Returns the amount of task/models per requested project
|
||||
Use separate aggregation calls on Task/Model instead of lookup
|
||||
aggregation on projects in order not to hit memory limits on large tasks
|
||||
"""
|
||||
if not project_ids:
|
||||
|
@ -30,6 +30,28 @@ class DeleteProjectResult:
|
||||
urls: TaskUrls = None
|
||||
|
||||
|
||||
def validate_project_delete(company: str, project_id: str):
|
||||
project = Project.get_for_writing(
|
||||
company=company, id=project_id, _only=("id", "path")
|
||||
)
|
||||
if not project:
|
||||
raise errors.bad_request.InvalidProjectId(id=project_id)
|
||||
|
||||
project_ids = _ids_with_children([project_id])
|
||||
ret = {}
|
||||
for cls in (Task, Model):
|
||||
ret[f"{cls.__name__.lower()}s"] = cls.objects(
|
||||
project__in=project_ids,
|
||||
).count()
|
||||
for cls in (Task, Model):
|
||||
ret[f"non_archived_{cls.__name__.lower()}s"] = cls.objects(
|
||||
project__in=project_ids,
|
||||
system_tags__nin=[EntityVisibility.archived.value],
|
||||
).count()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def delete_project(
|
||||
company: str, project_id: str, force: bool, delete_contents: bool
|
||||
) -> Tuple[DeleteProjectResult, Set[str]]:
|
||||
|
@ -379,7 +379,7 @@ get_all {
|
||||
items { type: string }
|
||||
}
|
||||
page {
|
||||
description: "Page number, returns a specific page out of the resulting list of dataviews"
|
||||
description: "Page number, returns a specific page out of the resulting list of projects"
|
||||
type: integer
|
||||
minimum: 0
|
||||
}
|
||||
@ -469,7 +469,7 @@ get_all_ex {
|
||||
default: false
|
||||
}
|
||||
check_own_contents {
|
||||
description: "If set to 'true' and project ids are passed to the query then for these projects their own tasks, models and dataviews are counted"
|
||||
description: "If set to 'true' and project ids are passed to the query then for these projects their own tasks and models are counted"
|
||||
type: boolean
|
||||
default: false
|
||||
}
|
||||
@ -594,7 +594,7 @@ merge {
|
||||
type: object
|
||||
properties {
|
||||
moved_entities {
|
||||
description: "The number of tasks, models and dataviews moved from the merged project into the destination"
|
||||
description: "The number of tasks and models moved from the merged project into the destination"
|
||||
type: integer
|
||||
}
|
||||
moved_projects {
|
||||
@ -605,6 +605,42 @@ merge {
|
||||
}
|
||||
}
|
||||
}
|
||||
validate_delete {
|
||||
"2.14" {
|
||||
description: "Validates that the project existis and can be deleted"
|
||||
request {
|
||||
type: object
|
||||
required: [ project ]
|
||||
properties {
|
||||
project {
|
||||
description: "Project ID"
|
||||
type: string
|
||||
}
|
||||
}
|
||||
}
|
||||
response {
|
||||
type: object
|
||||
properties {
|
||||
tasks {
|
||||
description: "The total number of tasks under the project and all its children"
|
||||
type: integer
|
||||
}
|
||||
non_archived_tasks {
|
||||
description: "The total number of non-archived tasks under the project and all its children"
|
||||
type: integer
|
||||
}
|
||||
models {
|
||||
description: "The total number of models under the project and all its children"
|
||||
type: integer
|
||||
}
|
||||
non_archived_models {
|
||||
description: "The total number of non-archived models under the project and all its children"
|
||||
type: integer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete {
|
||||
"2.1" {
|
||||
description: "Deletes a project"
|
||||
@ -613,7 +649,7 @@ delete {
|
||||
required: [ project ]
|
||||
properties {
|
||||
project {
|
||||
description: "Project id"
|
||||
description: "Project ID"
|
||||
type: string
|
||||
}
|
||||
force {
|
||||
|
@ -16,10 +16,14 @@ from apiserver.apimodels.projects import (
|
||||
MoveRequest,
|
||||
MergeRequest,
|
||||
ProjectOrNoneRequest,
|
||||
ProjectRequest,
|
||||
)
|
||||
from apiserver.bll.organization import OrgBLL, Tags
|
||||
from apiserver.bll.project import ProjectBLL
|
||||
from apiserver.bll.project.project_cleanup import delete_project
|
||||
from apiserver.bll.project.project_cleanup import (
|
||||
delete_project,
|
||||
validate_project_delete,
|
||||
)
|
||||
from apiserver.bll.task import TaskBLL
|
||||
from apiserver.database.errors import translate_errors_context
|
||||
from apiserver.database.model.project import Project
|
||||
@ -230,6 +234,13 @@ def merge(call: APICall, company: str, request: MergeRequest):
|
||||
}
|
||||
|
||||
|
||||
@endpoint("projects.validate_delete")
|
||||
def validate_delete(call: APICall, company_id: str, request: ProjectRequest):
|
||||
call.result.data = validate_project_delete(
|
||||
company=company_id, project_id=request.project
|
||||
)
|
||||
|
||||
|
||||
@endpoint("projects.delete", request_data_model=DeleteRequest)
|
||||
def delete(call: APICall, company_id: str, request: DeleteRequest):
|
||||
res, affected_projects = delete_project(
|
||||
|
54
apiserver/tests/automated/test_project_delete.py
Normal file
54
apiserver/tests/automated/test_project_delete.py
Normal file
@ -0,0 +1,54 @@
|
||||
from apiserver.apierrors import errors
|
||||
from apiserver.database.model import EntityVisibility
|
||||
from apiserver.tests.automated import TestService
|
||||
from apiserver.database.utils import id as db_id
|
||||
|
||||
|
||||
class TestProjectsDelete(TestService):
|
||||
def setUp(self, version="2.14"):
|
||||
super().setUp(version=version)
|
||||
|
||||
def new_task(self, **kwargs):
|
||||
return self.create_temp(
|
||||
"tasks", type="testing", name=db_id(), input=dict(view=dict()), **kwargs
|
||||
)
|
||||
|
||||
def new_model(self, **kwargs):
|
||||
return self.create_temp("models", uri="file:///a/b", name=db_id(), labels={}, **kwargs)
|
||||
|
||||
def new_project(self, **kwargs):
|
||||
return self.create_temp("projects", name=db_id(), description="", **kwargs)
|
||||
|
||||
def test_delete_fails_with_active_task(self):
|
||||
project = self.new_project()
|
||||
self.new_task(project=project)
|
||||
res = self.api.projects.validate_delete(project=project)
|
||||
self.assertEqual(res.tasks, 1)
|
||||
self.assertEqual(res.non_archived_tasks, 1)
|
||||
with self.api.raises(errors.bad_request.ProjectHasTasks):
|
||||
self.api.projects.delete(project=project)
|
||||
|
||||
def test_delete_with_archived_task(self):
|
||||
project = self.new_project()
|
||||
self.new_task(project=project, system_tags=[EntityVisibility.archived.value])
|
||||
res = self.api.projects.validate_delete(project=project)
|
||||
self.assertEqual(res.tasks, 1)
|
||||
self.assertEqual(res.non_archived_tasks, 0)
|
||||
self.api.projects.delete(project=project)
|
||||
|
||||
def test_delete_fails_with_active_model(self):
|
||||
project = self.new_project()
|
||||
self.new_model(project=project)
|
||||
res = self.api.projects.validate_delete(project=project)
|
||||
self.assertEqual(res.models, 1)
|
||||
self.assertEqual(res.non_archived_models, 1)
|
||||
with self.api.raises(errors.bad_request.ProjectHasModels):
|
||||
self.api.projects.delete(project=project)
|
||||
|
||||
def test_delete_with_archived_model(self):
|
||||
project = self.new_project()
|
||||
self.new_model(project=project, system_tags=[EntityVisibility.archived.value])
|
||||
res = self.api.projects.validate_delete(project=project)
|
||||
self.assertEqual(res.models, 1)
|
||||
self.assertEqual(res.non_archived_models, 0)
|
||||
self.api.projects.delete(project=project)
|
Loading…
Reference in New Issue
Block a user