Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2904,12 +2904,12 @@ def validate_annotations(
@Trackable
@validate_arguments
def add_contributors_to_project(
project_name: NotEmptyStr, emails: List[EmailStr], role: AnnotatorRole
project: NotEmptyStr, emails: List[EmailStr], role: AnnotatorRole
) -> Tuple[List[str], List[str]]:
"""Add contributors to project.

:param project_name: project name
:type project_name: str
:param project: project name
:type project: str

:param emails: users email
:type emails: list
Expand All @@ -2921,7 +2921,7 @@ def add_contributors_to_project(
rtype: tuple (2 members) of lists of strs
"""
response = controller.add_contributors_to_project(
project_name=project_name, emails=emails, role=role
project_name=project, emails=emails, role=role
)
if response.errors:
raise AppException(response.errors)
Expand Down
2 changes: 1 addition & 1 deletion src/superannotate/lib/core/serviceproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def share_project_bulk(self, project_id: int, team_id: int, users: Iterable):
raise NotImplementedError

@abstractmethod
def invite_contributors(self, team_id: int, team_role: int, emails: Iterable) -> bool:
def invite_contributors(self, team_id: int, team_role: int, emails: Iterable) -> Tuple[List[str], List[str]]:
raise NotImplementedError

@abstractmethod
Expand Down
31 changes: 16 additions & 15 deletions src/superannotate/lib/core/usecases/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,8 +981,8 @@ def execute(self):
if user["user_role"] > constances.UserRole.ADMIN.value:
project_users.add(user["email"])

to_add = team_users.intersection(self._emails) - project_users
to_skip = set(self._emails).difference(to_add)
to_add = list(team_users.intersection(self._emails) - project_users)
to_skip = list(set(self._emails).difference(to_add))

if to_skip:
self.reporter.log_warning(
Expand All @@ -1001,7 +1001,7 @@ def execute(self):
if response and not response.get("invalidUsers"):
self.reporter.log_info(
f"Added {len(to_add)}/{len(self._emails)} "
"contributors to the project <project_name> with the <role> role."
f"contributors to the project {self._project.name} with the {self.user_role} role."
)
self._response.data = to_add, to_skip
return self._response
Expand All @@ -1027,34 +1027,35 @@ def __init__(
self._service = service

def execute(self):
team_users = set()
for user in self._team.users:
if user.user_role > constances.UserRole.ADMIN.value:
team_users.add(user.email)
# collecting pending team users which is not admin
for user in self._team.pending_invitations:
if user["user_role"] > constances.UserRole.ADMIN.value:
team_users.add(user["email"])
team_users = {user.email for user in self._team.users}
# collecting pending team users
team_users.update({user["email"] for user in self._team.pending_invitations})

emails = set(self._emails)

to_skip = emails.intersection(team_users)
to_skip = list(emails.intersection(team_users))
to_add = list(emails.difference(to_skip))

if to_skip:
self.reporter.log_warning(
f"Found {len(to_skip)}/{len(self._emails)} existing members of the team."
)
if to_add:
response = self._service.invite_contributors(
invited, failed = self._service.invite_contributors(
team_id=self._team.uuid,
# REMINDER UserRole.VIEWER is the contributor for the teams
team_role=constances.UserRole.ADMIN.value if self._set_admin else constances.UserRole.VIEWER.value,
emails=to_add
)
if response:
if invited:
self.reporter.log_info(
f"Sent team {'admin' if self._set_admin else 'contributor'} invitations"
f" to {len(to_add)}/{len(self._emails)} users."
)
if failed:
self.reporter.log_info(
f"Sent team <admin/contributor> invitations to {len(to_add)}/{len(self._emails)}."
f"Skipped team {'admin' if self._set_admin else 'contributor'} "
f"invitations for {len(failed)}/{len(self._emails)} users."
)
self._response.data = to_add, to_skip
return self._response
6 changes: 3 additions & 3 deletions src/superannotate/lib/infrastructure/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,16 +511,16 @@ def delete_team_invitation(self, team_id: int, token: str, email: str) -> bool:
)
return res.ok

def invite_contributors(self, team_id: int, team_role: int, emails: list) -> bool:
def invite_contributors(self, team_id: int, team_role: int, emails: list) -> Tuple[List[str], List[str]]:
invite_contributors_url = urljoin(
self.api_url, self.URL_INVITE_CONTRIBUTORS.format(team_id)
)
res = self._request(
invite_contributors_url,
"post",
data=dict(emails=emails, team_role=team_role)
)
return res.ok
).json()
return res["success"]["emails"], res["failed"]["emails"]

def update_image(self, image_id: int, team_id: int, project_id: int, data: dict):
update_image_url = urljoin(self.api_url, self.URL_GET_IMAGE.format(image_id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ def test_add_contributors(self, client, get_project_metadata_mock, get_team_mock
self.assertEqual(len(skipped), 7)

@patch("lib.infrastructure.controller.Controller.get_team")
@patch("lib.infrastructure.controller.Controller.backend_client", MagicMock())
def test_invite_contributors(self, get_team_mock):
@patch("lib.infrastructure.controller.Controller.backend_client", new_callable=PropertyMock)
def test_invite_contributors(self, client, get_team_mock):
random_emails = [self.random_email for i in range(20)]

client.return_value.invite_contributors.return_value = random_emails[:3], []
team_users = [UserEntity(email=email, user_role=3) for email in random_emails[: 10]]
to_add_emails = random_emails[8: 18]
pending_users = [dict(email=email, user_role=3) for email in random_emails[15: 20]]
Expand Down