Skip to content

Commit ffa7bc2

Browse files
authored
feat: Teams and Users API: support more parameters (#201)
1 parent 88a38c9 commit ffa7bc2

File tree

6 files changed

+223
-7
lines changed

6 files changed

+223
-7
lines changed

crowdin_api/api_resources/teams/resource.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from crowdin_api.api_resources.abstract.resources import BaseResource
44
from crowdin_api.api_resources.teams.types \
55
import Permissions, TeamPatchRequest, TeamByProjectRole, GroupTeamPatchRequest
6+
from crowdin_api.api_resources.users.enums import ProjectRole
67
from crowdin_api.sorting import Sorting
8+
from crowdin_api.utils import convert_to_query_string, convert_enum_to_string_if_exists
79

810

911
class TeamsResource(BaseResource):
@@ -125,9 +127,14 @@ def add_team_to_project(
125127

126128
def list_teams(
127129
self,
128-
orderBy: Optional[Sorting] = None,
130+
order_by: Optional[Sorting] = None,
129131
offset: Optional[int] = None,
130-
limit: Optional[int] = None
132+
limit: Optional[int] = None,
133+
search: Optional[str] = None,
134+
project_ids: Optional[Iterable[int]] = None,
135+
project_roles: Optional[Iterable[ProjectRole]] = None,
136+
language_ids: Optional[Iterable[str]] = None,
137+
group_ids: Optional[Iterable[int]] = None,
131138
):
132139
"""
133140
List Teams.
@@ -136,7 +143,14 @@ def list_teams(
136143
https://developer.crowdin.com/enterprise/api/v2/#operation/api.teams.getMany
137144
"""
138145

139-
params = {"orderBy": orderBy}
146+
params = {
147+
"orderBy": order_by,
148+
"search": search,
149+
"projectIds": convert_to_query_string(project_ids, lambda project_id: str(project_id)),
150+
"projectRoles": convert_to_query_string(project_roles, lambda role: convert_enum_to_string_if_exists(role)),
151+
"languageIds": convert_to_query_string(language_ids, lambda language_id: str(language_id)),
152+
"groupIds": convert_to_query_string(group_ids, lambda group_id: str(group_id))
153+
}
140154
params.update(self.get_page_params(offset=offset, limit=limit))
141155

142156
return self._get_entire_data(

crowdin_api/api_resources/teams/tests/test_teams_resources.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from crowdin_api.api_resources import TeamsResource
66
from crowdin_api.api_resources.enums import PatchOperation
77
from crowdin_api.api_resources.teams.enums import ListTeamsOrderBy, TeamPatchPath
8-
from crowdin_api.api_resources.users.enums import ListGroupTeamsOrderBy
8+
from crowdin_api.api_resources.users.enums import ListGroupTeamsOrderBy, ProjectRole
99
from crowdin_api.requester import APIRequester
1010
from crowdin_api.sorting import Sorting, SortingOrder, SortingRule
1111

@@ -302,22 +302,41 @@ def test_add_team_to_project(self, m_request, incoming_data, request_data, base_
302302
{},
303303
{
304304
"orderBy": None,
305+
"search": None,
306+
"projectIds": None,
307+
"projectRoles": None,
308+
"languageIds": None,
309+
"groupIds": None,
305310
"limit": 25,
306311
"offset": 0,
307312
},
308313
),
309314
(
310315
{
311-
"orderBy": Sorting(
316+
"order_by": Sorting(
312317
[SortingRule(ListTeamsOrderBy.ID, SortingOrder.DESC)]
313318
),
319+
"search": "Alex",
320+
"project_ids": [1, 2, 3],
321+
"project_roles": [
322+
ProjectRole.MEMBER,
323+
ProjectRole.TRANSLATOR,
324+
ProjectRole.PROOFREADER
325+
],
326+
"language_ids": ["uk", "es", "it"],
327+
"group_ids": [10, 11, 12],
314328
"limit": 10,
315329
"offset": 2,
316330
},
317331
{
318332
"orderBy": Sorting(
319333
[SortingRule(ListTeamsOrderBy.ID, SortingOrder.DESC)]
320334
),
335+
"search": "Alex",
336+
"projectIds": "1,2,3",
337+
"projectRoles": "member,translator,proofreader",
338+
"languageIds": "uk,es,it",
339+
"groupIds": "10,11,12",
321340
"limit": 10,
322341
"offset": 2,
323342
},

crowdin_api/api_resources/users/enums.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ class UserPatchPath(Enum):
1717

1818

1919
class ProjectRole(Enum):
20+
MANAGER = "manager"
21+
DEVELOPER = "developer"
2022
TRANSLATOR = "translator"
2123
PROOFREADER = "proofreader"
24+
LANGUAGE_COORDINATOR = "language_coordinator"
25+
MEMBER = "member"
2226

2327

2428
class ListProjectMembersCrowdinOrderBy(Enum):
@@ -27,6 +31,17 @@ class ListProjectMembersCrowdinOrderBy(Enum):
2731
FULL_NAME = "fullName"
2832

2933

34+
class ListUsersOrderBy(Enum):
35+
ID = "id"
36+
USERNAME = "username"
37+
FIRST_NAME = "firstName"
38+
LAST_NAME = "lastName"
39+
EMAIL = "email"
40+
STATUS = "status"
41+
CREATED_AT = "createdAt"
42+
LAST_SEEN = "lastSeen"
43+
44+
3045
class ListProjectMembersEnterpriseOrderBy(Enum):
3146
ID = "id"
3247
USERNAME = "username"
@@ -48,3 +63,21 @@ class ListGroupTeamsOrderBy(Enum):
4863
NAME = "name"
4964
CREATED_AT = "createdAt"
5065
UPDATED_AT = "updatedAt"
66+
67+
68+
class OrganizationRole(Enum):
69+
ADMIN = "admin"
70+
MANAGER = "manager"
71+
VENDOR = "vendor"
72+
CLIENT = "client"
73+
74+
75+
class UserStatus(Enum):
76+
ACTIVE = "active"
77+
PENDING = "pending"
78+
BLOCKED = "blocked"
79+
80+
81+
class UserTwoFactorAuthStatus(Enum):
82+
ENABLED = "enabled"
83+
DISABLED = "disabled"

crowdin_api/api_resources/users/resource.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from datetime import datetime
12
from typing import Dict, Iterable, Optional
23

34
from crowdin_api.api_resources.abstract.resources import BaseResource
4-
from crowdin_api.api_resources.users.enums import UserRole
5+
from crowdin_api.api_resources.users.enums import UserRole, OrganizationRole, UserStatus
56
from crowdin_api.api_resources.users.types import UserPatchRequest, ProjectMemberRole, GroupManagerPatchRequest
67
from crowdin_api.sorting import Sorting
8+
from crowdin_api.utils import convert_to_query_string, convert_enum_to_string_if_exists
79

810

911
class BaseUsersResource(BaseResource):
@@ -341,3 +343,57 @@ def delete_user(self, userId: int):
341343
method="delete",
342344
path=self.get_users_path(userId=userId)
343345
)
346+
347+
def list_users(
348+
self,
349+
limit: Optional[int] = None,
350+
offset: Optional[int] = None,
351+
order_by: Optional[Sorting] = None,
352+
status: Optional[UserStatus] = None,
353+
search: Optional[str] = None,
354+
two_factor: Optional[str] = None,
355+
organization_roles: Optional[Iterable[OrganizationRole]] = None,
356+
team_id: Optional[int] = None,
357+
project_ids: Optional[Iterable[int]] = None,
358+
project_roles: Optional[Iterable[str]] = None,
359+
language_ids: Optional[Iterable[str]] = None,
360+
group_ids: Optional[Iterable[int]] = None,
361+
last_seen_from: Optional[datetime] = None,
362+
last_seen_to: Optional[datetime] = None
363+
):
364+
"""
365+
List Users
366+
367+
Link to documentation:
368+
https://support.crowdin.com/developer/enterprise/api/v2/#tag/Users/operation/api.users.getMany
369+
"""
370+
371+
params = {
372+
"limit": limit,
373+
"offset": offset,
374+
"orderBy": order_by,
375+
"status": convert_enum_to_string_if_exists(status),
376+
"search": search,
377+
"twoFactor": convert_enum_to_string_if_exists(two_factor),
378+
"organizationRoles": convert_to_query_string(
379+
organization_roles,
380+
lambda role: convert_enum_to_string_if_exists(role)
381+
),
382+
"teamId": team_id,
383+
"projectIds": convert_to_query_string(project_ids, lambda project_id: str(project_id)),
384+
"projectRoles": convert_to_query_string(
385+
project_roles,
386+
lambda role: convert_enum_to_string_if_exists(role)
387+
),
388+
"languageIds": convert_to_query_string(language_ids),
389+
"groupIds": convert_to_query_string(group_ids),
390+
"lastSeenFrom": last_seen_from.isoformat() if last_seen_from is not None else None,
391+
"lastSeenTo": last_seen_to.isoformat() if last_seen_to is not None else None
392+
}
393+
params.update(self.get_page_params(offset=offset, limit=limit))
394+
395+
return self.requester.request(
396+
method="get",
397+
path=self.get_users_path(),
398+
params=params
399+
)

crowdin_api/api_resources/users/tests/test_users_resources.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import timezone, datetime
12
from unittest import mock
23

34
import pytest
@@ -8,7 +9,12 @@
89
ListProjectMembersEnterpriseOrderBy,
910
UserRole,
1011
UserPatchPath,
11-
ListGroupManagersOrderBy
12+
ListGroupManagersOrderBy,
13+
ListUsersOrderBy,
14+
UserStatus,
15+
UserTwoFactorAuthStatus,
16+
OrganizationRole,
17+
ProjectRole
1218
)
1319
from crowdin_api.api_resources.users.resource import (
1420
UsersResource,
@@ -612,3 +618,72 @@ def test_delete_user(self, m_request, base_absolut_url):
612618
resource = self.get_resource(base_absolut_url)
613619
assert resource.delete_user(userId=1) == "response"
614620
m_request.assert_called_once_with(method="delete", path="users/1")
621+
622+
@pytest.mark.parametrize(
623+
"in_params, request_params",
624+
(
625+
(
626+
{
627+
"limit": 10,
628+
"offset": 2,
629+
"order_by": Sorting(
630+
[
631+
SortingRule(ListUsersOrderBy.CREATED_AT, SortingOrder.DESC),
632+
SortingRule(ListUsersOrderBy.USERNAME)
633+
]
634+
),
635+
"status": UserStatus.ACTIVE,
636+
"search": "Alex",
637+
"two_factor": UserTwoFactorAuthStatus.ENABLED,
638+
"organization_roles": [
639+
OrganizationRole.MANAGER,
640+
OrganizationRole.VENDOR,
641+
OrganizationRole.CLIENT
642+
],
643+
"team_id": 123,
644+
"project_ids": [11, 22, 33],
645+
"project_roles": [
646+
ProjectRole.MANAGER,
647+
ProjectRole.DEVELOPER,
648+
ProjectRole.LANGUAGE_COORDINATOR
649+
],
650+
"language_ids": ["uk", "es", "it"],
651+
"group_ids": [4, 5, 6],
652+
"last_seen_from": datetime(2024, 1, 10, 10, 41, 33, tzinfo=timezone.utc),
653+
"last_seen_to": datetime(2024, 1, 11, 10, 41, 33, tzinfo=timezone.utc)
654+
},
655+
{
656+
"limit": 10,
657+
"offset": 2,
658+
"orderBy": Sorting(
659+
[
660+
SortingRule(ListUsersOrderBy.CREATED_AT, SortingOrder.DESC),
661+
SortingRule(ListUsersOrderBy.USERNAME)
662+
]
663+
),
664+
"status": "active",
665+
"search": "Alex",
666+
"twoFactor": "enabled",
667+
"organizationRoles": "manager,vendor,client",
668+
"teamId": 123,
669+
"projectIds": "11,22,33",
670+
"projectRoles": "manager,developer,language_coordinator",
671+
"languageIds": "uk,es,it",
672+
"groupIds": "4,5,6",
673+
"lastSeenFrom": "2024-01-10T10:41:33+00:00",
674+
"lastSeenTo": "2024-01-11T10:41:33+00:00"
675+
}
676+
),
677+
)
678+
)
679+
@mock.patch("crowdin_api.requester.APIRequester.request")
680+
def test_list_users(self, m_request, in_params, request_params, base_absolut_url):
681+
m_request.return_value = "response"
682+
683+
resource = self.get_resource(base_absolut_url)
684+
assert resource.list_users(**in_params)
685+
m_request.assert_called_once_with(
686+
method="get",
687+
path="users",
688+
params=request_params
689+
)

crowdin_api/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from enum import Enum
2+
from typing import Optional, Iterable, Callable
3+
4+
5+
def convert_to_query_string(
6+
collection: Optional[Iterable],
7+
converter: Optional[Callable[[object], str]] = None
8+
) -> Optional[str]:
9+
if not collection:
10+
return None
11+
12+
if converter is not None:
13+
return ','.join(converter(item) for item in collection)
14+
else:
15+
return ','.join(str(item) for item in collection)
16+
17+
18+
def convert_enum_to_string_if_exists(value: Optional[Enum]) -> Optional[str]:
19+
return value.value if value is not None else None

0 commit comments

Comments
 (0)