Skip to content

Commit 54d77f1

Browse files
Narek MkhitaryanNarek Mkhitaryan
authored andcommitted
added pause/resume user_activity
1 parent de4afbd commit 54d77f1

File tree

5 files changed

+162
-0
lines changed

5 files changed

+162
-0
lines changed

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
from lib.infrastructure.utils import extract_project_folder
7272
from lib.infrastructure.validators import wrap_error
7373
from lib.app.serializers import WMProjectSerializer
74+
from lib.core.entities.work_managament import WMUserTypeEnum
7475

7576
logger = logging.getLogger("sa")
7677

@@ -453,6 +454,32 @@ def list_users(self, *, include: List[Literal["custom_fields"]] = None, **filter
453454
self.controller.work_management.list_users(include=include, **filters)
454455
)
455456

457+
def pause_user_activity(
458+
self, pk: Union[int, str], projects: Union[List[int], List[str], Literal["*"]]
459+
):
460+
user = self.controller.work_management.get_user_metadata(pk=pk)
461+
if user.role is not WMUserTypeEnum.Contributor:
462+
raise AppException("User must have a contributor role to pause activity.")
463+
self.controller.work_management.update_user_activity(
464+
user_email=user.email, provided_projects=projects, action="pause"
465+
)
466+
logger.info(
467+
f"User with email {user.email} has been successfully paused from the specified projects: {projects}."
468+
)
469+
470+
def resume_user_activity(
471+
self, pk: Union[int, str], projects: Union[List[int], List[str], Literal["*"]]
472+
):
473+
user = self.controller.work_management.get_user_metadata(pk=pk)
474+
if user.role is not WMUserTypeEnum.Contributor:
475+
raise AppException("User must have a contributor role to resume activity.")
476+
self.controller.work_management.update_user_activity(
477+
user_email=user.email, provided_projects=projects, action="resume"
478+
)
479+
logger.info(
480+
f"User with email {user.email} has been successfully unblocked from the specified projects: {projects}."
481+
)
482+
456483
def get_component_config(self, project: Union[NotEmptyStr, int], component_id: str):
457484
"""
458485
Retrieves the configuration for a given project and component ID.

src/superannotate/lib/core/serviceproviders.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ def set_custom_field_value(
186186
):
187187
raise NotImplementedError
188188

189+
@abstractmethod
190+
def update_user_activity(
191+
self, body_query: Query, action=Literal["resume", "pause"]
192+
) -> ServiceResponse:
193+
raise NotImplementedError
194+
189195

190196
class BaseProjectService(SuperannotateServiceProvider):
191197
@abstractmethod

src/superannotate/lib/infrastructure/controller.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,44 @@ def list_users(self, include: List[Literal["custom_fields"]] = None, **filters):
196196
return response.data
197197
return self.service_provider.work_management.list_users(query).data
198198

199+
def update_user_activity(
200+
self,
201+
user_email: str,
202+
provided_projects: Union[List[int], List[str], Literal["*"]],
203+
action: Literal["resume", "pause"],
204+
):
205+
if isinstance(provided_projects, list):
206+
if not provided_projects:
207+
raise AppException("Provided projects list cannot be empty.")
208+
body_query = EmptyQuery()
209+
if isinstance(provided_projects[0], int):
210+
body_query &= Filter("id", provided_projects, OperatorEnum.IN)
211+
else:
212+
body_query &= Filter("name", provided_projects, OperatorEnum.IN)
213+
exist_projects = self.service_provider.work_management.search_projects(
214+
body_query
215+
).res_data
216+
217+
# project validation
218+
if len(set(provided_projects)) > len(exist_projects):
219+
raise AppException("Invalid project(s) provided.")
220+
else:
221+
exist_projects = self.service_provider.work_management.search_projects(
222+
EmptyQuery()
223+
).res_data
224+
225+
chunked_projects_ids = divide_to_chunks([i.id for i in exist_projects], 50)
226+
for chunk in chunked_projects_ids:
227+
body_query = EmptyQuery()
228+
body_query &= Filter("projects.id", chunk, OperatorEnum.IN)
229+
body_query &= Filter(
230+
"projects.contributors.email", user_email, OperatorEnum.EQ
231+
)
232+
res = self.service_provider.work_management.update_user_activity(
233+
body_query=body_query, action=action
234+
)
235+
res.raise_for_status()
236+
199237

200238
class ProjectManager(BaseManager):
201239
def __init__(self, service_provider: ServiceProvider, team: TeamEntity):

src/superannotate/lib/infrastructure/services/work_management.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from lib.core.jsx_conditions import Filter
1313
from lib.core.jsx_conditions import OperatorEnum
1414
from lib.core.jsx_conditions import Query
15+
from lib.core.pydantic_v1 import Literal
1516
from lib.core.service_types import ListCategoryResponse
1617
from lib.core.service_types import ServiceResponse
1718
from lib.core.service_types import WMCustomFieldResponse
@@ -60,6 +61,7 @@ class WorkManagementService(BaseWorkManagementService):
6061
URL_SEARCH_CUSTOM_ENTITIES = "customentities/search"
6162
URL_SEARCH_TEAM_USERS = "teamusers/search"
6263
URL_SEARCH_PROJECTS = "projects/search"
64+
URL_RESUME_PAUSE_USER = "teams/editprojectsusers"
6365

6466
@staticmethod
6567
def _generate_context(**kwargs):
@@ -362,3 +364,22 @@ def set_custom_field_value(
362364
"parentEntity": parent_entity.value,
363365
},
364366
)
367+
368+
def update_user_activity(
369+
self, body_query: Query, action=Literal["resume", "pause"]
370+
) -> ServiceResponse:
371+
"""resume or pause user by projects"""
372+
body = body_query.body_builder()
373+
body["body"] = {
374+
"projectUsers": {"permissions": {"paused": 1 if action == "pause" else 0}}
375+
}
376+
return self.client.request(
377+
url=self.URL_RESUME_PAUSE_USER,
378+
method="post",
379+
data=body,
380+
headers={
381+
"x-sa-entity-context": self._generate_context(
382+
team_id=self.client.team_id
383+
),
384+
},
385+
)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from lib.core.exceptions import AppException
2+
from src.superannotate import SAClient
3+
from tests.integration.base import BaseTestCase
4+
5+
sa = SAClient()
6+
7+
8+
class TestPauseUserActivity(BaseTestCase):
9+
PROJECT_NAME = "TestPauseUserActivity"
10+
PROJECT_TYPE = "Vector"
11+
PROJECT_DESCRIPTION = "DESCRIPTION"
12+
ATTACHMENT_LIST = [
13+
{
14+
"url": "https://drive.google.com/uc?export=download&id=1vwfCpTzcjxoEA4hhDxqapPOVvLVeS7ZS",
15+
"name": "6022a74d5384c50017c366b3",
16+
},
17+
{
18+
"url": "https://drive.google.com/uc?export=download&id=1geS2YtQiTYuiduEirKVYxBujHJaIWA3V",
19+
"name": "6022a74b5384c50017c366ad",
20+
},
21+
{
22+
"url": "https://drive.google.com/uc?export=download&id=1geS2YtQiTYuiduEirKVYxBujHJaIWA3V",
23+
"name": "6022a74b5384c50017c366ad",
24+
},
25+
]
26+
27+
def setUp(self, *args, **kwargs):
28+
super().setUp(*args, **kwargs)
29+
uploaded, _, _ = sa.attach_items(self.PROJECT_NAME, self.ATTACHMENT_LIST)
30+
users = sa.list_users()
31+
self.scapegoat = [u for u in users if u["role"] == "Contributor"][0]
32+
sa.add_contributors_to_project(
33+
self.PROJECT_NAME, [self.scapegoat["email"]], "QA"
34+
)
35+
36+
def test_pause_and_resume_user_activity(self):
37+
with self.assertLogs("sa", level="INFO") as cm:
38+
sa.pause_user_activity(
39+
pk=self.scapegoat["email"], projects=[self.PROJECT_NAME]
40+
)
41+
assert (
42+
cm.output[0]
43+
== f"INFO:sa:User with email {self.scapegoat['email']} has been successfully paused"
44+
f" from the specified projects: {[self.PROJECT_NAME]}."
45+
)
46+
with self.assertRaisesRegexp(
47+
AppException,
48+
"The user does not have the required permissions for this assignment.",
49+
):
50+
sa.assign_items(
51+
self.PROJECT_NAME,
52+
[i["name"] for i in self.ATTACHMENT_LIST],
53+
self.scapegoat["email"],
54+
)
55+
56+
with self.assertLogs("sa", level="INFO") as cm:
57+
sa.resume_user_activity(
58+
pk=self.scapegoat["email"], projects=[self.PROJECT_NAME]
59+
)
60+
assert (
61+
cm.output[0]
62+
== f"INFO:sa:User with email {self.scapegoat['email']} has been successfully unblocked"
63+
f" from the specified projects: {[self.PROJECT_NAME]}."
64+
)
65+
66+
sa.assign_items(
67+
self.PROJECT_NAME,
68+
[i["name"] for i in self.ATTACHMENT_LIST],
69+
self.scapegoat["email"],
70+
)

0 commit comments

Comments
 (0)