Add projects.validate_delete

This commit is contained in:
allegroai 2021-07-25 14:17:29 +03:00
parent 56aea1ffb8
commit d4edeaaf1b
5 changed files with 131 additions and 8 deletions

View File

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

View File

@ -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]]:

View File

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

View File

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

View 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)