diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 4eb89f6f5..7b5ceba00 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -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 @@ -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) diff --git a/src/superannotate/lib/core/serviceproviders.py b/src/superannotate/lib/core/serviceproviders.py index 8446fdea5..a4d5beecb 100644 --- a/src/superannotate/lib/core/serviceproviders.py +++ b/src/superannotate/lib/core/serviceproviders.py @@ -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 diff --git a/src/superannotate/lib/core/usecases/projects.py b/src/superannotate/lib/core/usecases/projects.py index c2bd019dd..16b40b071 100644 --- a/src/superannotate/lib/core/usecases/projects.py +++ b/src/superannotate/lib/core/usecases/projects.py @@ -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( @@ -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 with the 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 @@ -1027,18 +1027,13 @@ 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: @@ -1046,15 +1041,21 @@ def execute(self): 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 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 diff --git a/src/superannotate/lib/infrastructure/services.py b/src/superannotate/lib/infrastructure/services.py index 7a1587b4f..d840f8ed5 100644 --- a/src/superannotate/lib/infrastructure/services.py +++ b/src/superannotate/lib/infrastructure/services.py @@ -511,7 +511,7 @@ 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) ) @@ -519,8 +519,8 @@ def invite_contributors(self, team_id: int, team_role: int, emails: list) -> boo 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)) diff --git a/tests/integration/projects/test_add_contributors_to_project.py b/tests/integration/projects/test_add_contributors_to_project.py index de363c6df..d649d0b97 100644 --- a/tests/integration/projects/test_add_contributors_to_project.py +++ b/tests/integration/projects/test_add_contributors_to_project.py @@ -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]]