From 3e90e848182c1d70be9b8c72d55a0d81e7ce65e8 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Sun, 3 Jul 2022 16:37:55 +0545 Subject: [PATCH 1/6] Add field for team join notification settings --- backend/config.py | 1 + backend/models/dtos/team_dto.py | 3 + backend/models/dtos/user_dto.py | 4 +- backend/models/postgis/team.py | 5 +- backend/models/postgis/user.py | 8 ++- backend/services/messaging/message_service.py | 7 ++- backend/services/team_service.py | 10 +-- migrations/versions/bcb474128817_.py | 61 +++++++++++++++++++ tests/backend/helpers/test_helpers.py | 6 +- .../services/messaging/test_smtp_service.py | 6 ++ .../integration/services/test_team_service.py | 61 ++++++++++++++++++- .../messaging/test_messaging_service.py | 7 ++- .../unit/services/test_mapping_service.py | 6 +- 13 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 migrations/versions/bcb474128817_.py diff --git a/backend/config.py b/backend/config.py index bcb1f33346..523b49abb7 100644 --- a/backend/config.py +++ b/backend/config.py @@ -152,3 +152,4 @@ class TestEnvironmentConfig(EnvironmentConfig): + f"{POSTGRES_PORT}" + f"/test_{POSTGRES_DB}" ) + LOG_LEVEL = "DEBUG" diff --git a/backend/models/dtos/team_dto.py b/backend/models/dtos/team_dto.py index 634baf442f..365b89c9de 100644 --- a/backend/models/dtos/team_dto.py +++ b/backend/models/dtos/team_dto.py @@ -41,6 +41,9 @@ class TeamMembersDTO(Model): username = StringType(required=True) function = StringType(required=True, validators=[validate_team_member_function]) active = StringType() + join_request_notifications = BooleanType( + default=False, serialized_name="joinRequestNotifications" + ) picture_url = StringType(serialized_name="pictureUrl") diff --git a/backend/models/dtos/user_dto.py b/backend/models/dtos/user_dto.py index 6ce72e8260..f0a6ff6db9 100644 --- a/backend/models/dtos/user_dto.py +++ b/backend/models/dtos/user_dto.py @@ -79,7 +79,9 @@ class UserDTO(Model): tasks_comments_notifications = BooleanType( serialized_name="taskCommentsNotifications" ) - teams_notifications = BooleanType(serialized_name="teamsNotifications") + teams_announcement_notifications = BooleanType( + serialized_name="teamsAnnouncementNotifications" + ) # these are read only gender = StringType( diff --git a/backend/models/postgis/team.py b/backend/models/postgis/team.py index 6d18e1fbdc..2eb2ca9fe1 100644 --- a/backend/models/postgis/team.py +++ b/backend/models/postgis/team.py @@ -26,7 +26,9 @@ class TeamMembers(db.Model): ) function = db.Column(db.Integer, nullable=False) # either 'editor' or 'manager' active = db.Column(db.Boolean, default=False) - + join_request_notifications = db.Column( + db.Boolean, nullable=False, default=False + ) # Managers can turn notifications on/off for team join requests member = db.relationship( User, backref=db.backref("teams", cascade="all, delete-orphan") ) @@ -196,6 +198,7 @@ def as_dto_team_member(self, member) -> TeamMembersDTO: member_dto.function = member_function member_dto.picture_url = user.picture_url member_dto.active = member.active + member_dto.join_request_notifications = member.join_request_notifications return member_dto def as_dto_team_project(self, project) -> TeamProjectDTO: diff --git a/backend/models/postgis/user.py b/backend/models/postgis/user.py index c10a431df5..c28625aa31 100644 --- a/backend/models/postgis/user.py +++ b/backend/models/postgis/user.py @@ -61,7 +61,9 @@ class User(db.Model): projects_notifications = db.Column(db.Boolean, default=True, nullable=False) tasks_notifications = db.Column(db.Boolean, default=True, nullable=False) tasks_comments_notifications = db.Column(db.Boolean, default=False, nullable=False) - teams_notifications = db.Column(db.Boolean, default=True, nullable=False) + teams_announcement_notifications = db.Column( + db.Boolean, default=True, nullable=False + ) date_registered = db.Column(db.DateTime, default=timestamp) # Represents the date the user last had one of their tasks validated last_validation_date = db.Column(db.DateTime, default=timestamp) @@ -364,7 +366,9 @@ def as_dto(self, logged_in_username: str) -> UserDTO: user_dto.projects_comments_notifications = self.projects_comments_notifications user_dto.tasks_notifications = self.tasks_notifications user_dto.tasks_comments_notifications = self.tasks_comments_notifications - user_dto.teams_notifications = self.teams_notifications + user_dto.teams_announcement_notifications = ( + self.teams_announcement_notifications + ) if self.username == logged_in_username: # Only return email address and gender information when logged in user is looking at their own profile diff --git a/backend/services/messaging/message_service.py b/backend/services/messaging/message_service.py index 6b7ff6190c..55020c3043 100644 --- a/backend/services/messaging/message_service.py +++ b/backend/services/messaging/message_service.py @@ -169,7 +169,7 @@ def _push_messages(messages): ): continue if ( - user.teams_notifications is False + user.teams_announcement_notifications is False and obj.message_type == MessageType.TEAM_BROADCAST.value ): messages_objs.append(obj) @@ -321,8 +321,9 @@ def send_request_to_join_team( MessageService.get_user_link(from_username), MessageService.get_team_link(team_name, team_id, True), ) - message.add_message() - message.save() + MessageService._push_messages( + [dict(message=message, user=User.query.get(to_user))] + ) @staticmethod def accept_reject_request_to_join_team( diff --git a/backend/services/team_service.py b/backend/services/team_service.py index 671e83baa0..165b9a87cc 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -81,13 +81,13 @@ def join_team(team_id: int, requesting_user: int, username: str, role: str = Non active = True TeamService.add_team_member(team_id, user.id, role, active) - if team.invite_only: team_managers = team.get_team_managers() - for member in team_managers: - MessageService.send_request_to_join_team( - user.id, user.username, member.user_id, team.name, team_id - ) + for manager in team_managers: + if manager.join_request_notifications: + MessageService.send_request_to_join_team( + user.id, user.username, manager.user_id, team.name, team_id + ) @staticmethod def send_invite(team_id, from_user_id, username): diff --git a/migrations/versions/bcb474128817_.py b/migrations/versions/bcb474128817_.py new file mode 100644 index 0000000000..815b1e5c29 --- /dev/null +++ b/migrations/versions/bcb474128817_.py @@ -0,0 +1,61 @@ +"""empty message + +Revision ID: bcb474128817 +Revises: 8b61ac59bc57 +Create Date: 2022-07-03 10:46:56.075805 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "bcb474128817" +down_revision = "8b61ac59bc57" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_index( + "idx_task_validation_validator_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.add_column( + "team_members", + sa.Column("join_request_notifications", sa.Boolean(), nullable=True), + ) + op.add_column( + "users", + sa.Column("teams_announcement_notifications", sa.Boolean(), nullable=True), + ) + op.execute("UPDATE team_members SET join_request_notifications = false") + op.execute("UPDATE users SET teams_announcement_notifications = false") + op.alter_column("team_members", "join_request_notifications", nullable=False) + op.alter_column("users", "teams_announcement_notifications", nullable=False) + op.drop_column("users", "teams_notifications") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column( + "teams_notifications", + sa.BOOLEAN(), + server_default=sa.text("true"), + autoincrement=False, + nullable=False, + ), + ) + op.drop_column("users", "teams_announcement_notifications") + op.drop_column("team_members", "join_request_notifications") + op.drop_index( + "idx_task_validation_validator_status_composite", + table_name="task_invalidation_history", + ) + # ### end Alembic commands ### diff --git a/tests/backend/helpers/test_helpers.py b/tests/backend/helpers/test_helpers.py index 441c711473..5a4ff378ef 100644 --- a/tests/backend/helpers/test_helpers.py +++ b/tests/backend/helpers/test_helpers.py @@ -93,11 +93,11 @@ def get_canned_simplified_osm_user_details(): return data -def return_canned_user() -> User: +def return_canned_user(username=TEST_USERNAME, id=TEST_USER_ID) -> User: """Returns a canned user""" test_user = User() - test_user.username = TEST_USERNAME - test_user.id = TEST_USER_ID + test_user.username = username + test_user.id = id test_user.mapping_level = MappingLevel.BEGINNER.value test_user.email_address = None diff --git a/tests/backend/integration/services/messaging/test_smtp_service.py b/tests/backend/integration/services/messaging/test_smtp_service.py index b74d6723e9..94ef38ccee 100644 --- a/tests/backend/integration/services/messaging/test_smtp_service.py +++ b/tests/backend/integration/services/messaging/test_smtp_service.py @@ -17,6 +17,7 @@ def setUp(self): self.from_username = "Aadesh Baral" self.message_id = 1 self.project_id = 1 + self.project_name = "Test Project" self.task_id = 1 self.subject = "test subject" self.content = "test content" @@ -43,6 +44,7 @@ def test_send_alert(self): message_id=self.message_id, from_username=self.from_username, project_id=self.project_id, + project_name=self.project_name, task_id=self.task_id, subject=self.subject, content=self.content, @@ -63,6 +65,7 @@ def test_send_alert_message_limits(self): message_id=self.message_id, from_username=self.from_username, project_id=self.project_id, + project_name=self.project_name, task_id=self.task_id, subject=self.subject, content=self.content, @@ -81,6 +84,7 @@ def test_alert_not_sent_if_email_not_supplied(self): message_id=self.message_id, from_username=self.from_username, project_id=self.project_id, + project_name=self.project_name, task_id=self.task_id, subject=self.subject, content=self.content, @@ -96,6 +100,7 @@ def test_does_not_send_if_user_not_verified(self): message_id=self.message_id, from_username=self.from_username, project_id=self.project_id, + project_name=self.project_name, task_id=self.task_id, subject=self.subject, content=self.content, @@ -115,6 +120,7 @@ def test_does_send_if_user_verified(self, mock_send_message): message_id=self.message_id, from_username=self.from_username, project_id=self.project_id, + project_name=self.project_name, task_id=self.task_id, subject=self.subject, content=self.content, diff --git a/tests/backend/integration/services/test_team_service.py b/tests/backend/integration/services/test_team_service.py index 0095be78c5..41d4d5e687 100644 --- a/tests/backend/integration/services/test_team_service.py +++ b/tests/backend/integration/services/test_team_service.py @@ -1,11 +1,19 @@ +from unittest.mock import patch + from backend.models.postgis.statuses import TeamMemberFunctions, TeamRoles -from backend.services.team_service import TeamService +from backend.services.team_service import ( + TeamService, + TeamJoinNotAllowed, + MessageService + ) from tests.backend.base import BaseTestCase from tests.backend.helpers.test_helpers import ( add_user_to_team, assign_team_to_project, create_canned_project, create_canned_team, + create_canned_user, + return_canned_user ) @@ -86,3 +94,54 @@ def test_get_project_teams_as_dto(self): # Assert self.assertIn("teams", teams_dto) self.assertNotEqual(len(teams_dto["teams"]), 0) + + @patch.object(TeamService, "is_user_team_member") + def test_join_team_raises_error_if_user_already_team_member( + self, mock_is_team_member + ): + # Arrange + test_team = create_canned_team() + test_user = create_canned_user() + mock_is_team_member.return_value = True + # Act/Assert + with self.assertRaises(TeamJoinNotAllowed): + TeamService.join_team( + test_team.id, test_user.id, test_user.username, "MEMBER" + ) + + @patch.object(TeamService, "is_user_team_member") + @patch.object(MessageService, "_push_messages") + def test_join_team_sends_notification_if_team_is_invite_only_and_manager_has_allowed_notification( + self, mock_send_notification, mock_is_team_member + ): + # Arrange + test_team = create_canned_team() + test_team.invite_only = True + test_user = create_canned_user() + mock_is_team_member.return_value = False + test_manager = return_canned_user("test manager", 1234) + test_manager = add_user_to_team(test_team, test_manager, 1, True) + test_manager.join_request_notifications = True + + # Act + TeamService.join_team(test_team.id, test_user.id, test_user.username, "MEMBER") + # Assert + mock_send_notification.assert_called() + + @patch.object(TeamService, "is_user_team_member") + @patch.object(MessageService, "_push_messages") + def test_join_team_doesnt_send_notification_if_manager_has_disallowed_notification( + self, mock_send_notification, mock_is_team_member + ): + # Arrange + test_team = create_canned_team() + test_team.invite_only = True + test_user = create_canned_user() + mock_is_team_member.return_value = False + test_manager = return_canned_user("test manager", 1234) + test_manager = add_user_to_team(test_team, test_manager, 1, True) + test_manager.join_request_notifications = False + # Act + TeamService.join_team(test_team.id, test_user.id, test_user.username, "MEMBER") + # Assert + mock_send_notification.assert_not_called() diff --git a/tests/backend/unit/services/messaging/test_messaging_service.py b/tests/backend/unit/services/messaging/test_messaging_service.py index 2bee527e8b..f392c15c76 100644 --- a/tests/backend/unit/services/messaging/test_messaging_service.py +++ b/tests/backend/unit/services/messaging/test_messaging_service.py @@ -26,19 +26,20 @@ def test_message_service_generates_correct_task_link(self): def test_message_service_generates_correct_chat_link(self): # Act link = MessageService.get_project_link( - 1, "http://test.com", include_chat_section=True + 1, "Test Project", "http://test.com", include_chat_section=True ) self.assertEqual( link, - 'Project 1', + 'Test Project', ) link = MessageService.get_project_link( 1, + "Test Project", "http://test.com", ) self.assertEqual( link, - 'Project 1', + 'Test Project', ) diff --git a/tests/backend/unit/services/test_mapping_service.py b/tests/backend/unit/services/test_mapping_service.py index 0ddff7a478..5064bcfa26 100644 --- a/tests/backend/unit/services/test_mapping_service.py +++ b/tests/backend/unit/services/test_mapping_service.py @@ -1,3 +1,4 @@ +from unittest.mock import patch, MagicMock from backend.services.mapping_service import ( MappingService, Task, @@ -11,7 +12,7 @@ ) from backend.models.dtos.mapping_dto import MappedTaskDTO, LockTaskDTO from backend.models.postgis.task import TaskHistory, TaskAction, User -from unittest.mock import patch, MagicMock +from backend.services.messaging.message_service import MessageService from tests.backend.base import BaseTestCase @@ -127,10 +128,12 @@ def test_if_new_state_not_acceptable_raise_error(self, mock_task): @patch.object(Task, "update") @patch.object(TaskHistory, "get_last_status") @patch.object(TaskHistory, "update_task_locked_with_duration") + @patch.object(MessageService, "send_message_after_comment") @patch.object(MappingService, "get_task") def test_unlock_with_comment_sets_history( self, mock_task, + mock_send_message, mock_history, mock_update, mock_stats, @@ -147,6 +150,7 @@ def test_unlock_with_comment_sets_history( test_task = MappingService.unlock_task_after_mapping(self.mapped_task_dto) # Assert + mock_send_message.assert_called() self.assertEqual(TaskAction.COMMENT.name, test_task.task_history[0].action) self.assertEqual(test_task.task_history[0].action_text, "Test comment") From 979884279f0d9ae0fae5f270aaf2af18bd30d6ad Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Sun, 3 Jul 2022 16:40:09 +0545 Subject: [PATCH 2/6] Modify teamJoinRequests switch --- frontend/src/components/user/forms/notifications.js | 2 +- tests/backend/integration/services/test_team_service.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/user/forms/notifications.js b/frontend/src/components/user/forms/notifications.js index 641d493569..318cf11d48 100644 --- a/frontend/src/components/user/forms/notifications.js +++ b/frontend/src/components/user/forms/notifications.js @@ -16,7 +16,7 @@ export function UserNotificationsForm(props) { - + diff --git a/tests/backend/integration/services/test_team_service.py b/tests/backend/integration/services/test_team_service.py index 41d4d5e687..351aeef3a4 100644 --- a/tests/backend/integration/services/test_team_service.py +++ b/tests/backend/integration/services/test_team_service.py @@ -4,8 +4,8 @@ from backend.services.team_service import ( TeamService, TeamJoinNotAllowed, - MessageService - ) + MessageService, +) from tests.backend.base import BaseTestCase from tests.backend.helpers.test_helpers import ( add_user_to_team, @@ -13,7 +13,7 @@ create_canned_project, create_canned_team, create_canned_user, - return_canned_user + return_canned_user, ) From 352465152dd4421609b72f7adb24e643d8a02258 Mon Sep 17 00:00:00 2001 From: Hel Nershing Thapa Date: Tue, 5 Jul 2022 14:03:01 +0545 Subject: [PATCH 3/6] Add switch component --- .../src/components/teamsAndOrgs/members.js | 41 ++++++++++++++++++- .../src/components/teamsAndOrgs/messages.js | 5 +++ frontend/src/views/teams.js | 3 ++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/teamsAndOrgs/members.js b/frontend/src/components/teamsAndOrgs/members.js index e0bd266bbf..c2d276a904 100644 --- a/frontend/src/components/teamsAndOrgs/members.js +++ b/frontend/src/components/teamsAndOrgs/members.js @@ -8,6 +8,7 @@ import messages from './messages'; import { UserAvatar } from '../user/avatar'; import { EditModeControl } from './editMode'; import { Button } from '../button'; +import { SwitchToggle } from '../formInputs'; import { fetchLocalJSONAPI, pushToLocalJSONAPI } from '../../network/genericJSONRequest'; import { Alert } from '../alert'; import { useOnClickOutside } from '../../hooks/UseOnClickOutside'; @@ -152,8 +153,20 @@ export function Members({ ); } -export function JoinRequests({ requests, teamId, addMembers, updateRequests }: Object) { +export function JoinRequests({ + requests, + teamId, + addMembers, + updateRequests, + members, + updateTeam, + isTeamInviteOnly, +}: Object) { const token = useSelector((state) => state.auth.get('token')); + const loggedInUsername = useSelector((state) => state.auth.get('userDetails')).username; + const isJoinRequestEnabled = members?.filter((member) => member.username === loggedInUsername)[0] + ?.joinRequestNotifications; + const [isChecked, setIsChecked] = useState(isJoinRequestEnabled); const acceptRejectRequest = useCallback( (user, action) => { @@ -173,6 +186,18 @@ export function JoinRequests({ requests, teamId, addMembers, updateRequests }: O [teamId, requests, updateRequests, addMembers, token], ); + const handleJoinRequestNotificationsChange = (e) => { + const { checked } = e.target; + setIsChecked(checked); + let tempMembers = members; + const memberIndex = tempMembers.findIndex((member) => member.username === loggedInUsername); + Object.assign(tempMembers[memberIndex], { + joinRequestNotifications: checked, + active: checked.toString(), + }); + updateTeam({ members }); + }; + return (
@@ -180,6 +205,18 @@ export function JoinRequests({ requests, teamId, addMembers, updateRequests }: O
+ {isTeamInviteOnly && ( +
+ +
+ handleJoinRequestNotificationsChange(e)} + labelPosition="right" + /> +
+
+ )}
{requests.map((user, n) => (
@@ -210,7 +247,7 @@ export function JoinRequests({ requests, teamId, addMembers, updateRequests }: O
))} {requests.length === 0 && ( -
+
)} diff --git a/frontend/src/components/teamsAndOrgs/messages.js b/frontend/src/components/teamsAndOrgs/messages.js index 7fa351ed9e..d1036e2669 100644 --- a/frontend/src/components/teamsAndOrgs/messages.js +++ b/frontend/src/components/teamsAndOrgs/messages.js @@ -409,6 +409,11 @@ export default defineMessages({ id: 'management.teams.invite_only.description', defaultMessage: "Managers need to approve a member's request to join.", }, + newJoinRequestNotification: { + id: 'management.teams.newJoinRequestNotification', + defaultMessage: + 'Enable for team managers to receive (email) notifications each time a new join request is made', + }, waitingApproval: { id: 'teamsAndOrgs.management.teams.messages.waiting_approval', defaultMessage: 'Your request to join this team is waiting for approval.', diff --git a/frontend/src/views/teams.js b/frontend/src/views/teams.js index 19006c8a76..8b719aab2a 100644 --- a/frontend/src/views/teams.js +++ b/frontend/src/views/teams.js @@ -339,6 +339,9 @@ export function EditTeam(props) { teamId={team.teamId} addMembers={addMembers} updateRequests={setRequests} + members={members} + updateTeam={updateTeam} + isTeamInviteOnly={team.inviteOnly} />
From e38c0696c862417a7f9ed574ce46f2665e4a22f1 Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Tue, 5 Jul 2022 17:00:41 +0545 Subject: [PATCH 4/6] Fix issue while updating team members --- backend/models/dtos/team_dto.py | 2 +- backend/models/postgis/team.py | 34 ++++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/backend/models/dtos/team_dto.py b/backend/models/dtos/team_dto.py index 365b89c9de..9273464deb 100644 --- a/backend/models/dtos/team_dto.py +++ b/backend/models/dtos/team_dto.py @@ -142,7 +142,7 @@ class UpdateTeamDTO(Model): name = StringType() logo = StringType() description = StringType() - invite_only = BooleanType(default=False, serialized_name="inviteOnly") + invite_only = BooleanType(serialized_name="inviteOnly") visibility = StringType( validators=[validate_team_visibility], serialize_when_none=False ) diff --git a/backend/models/postgis/team.py b/backend/models/postgis/team.py index 2eb2ca9fe1..564c7c0074 100644 --- a/backend/models/postgis/team.py +++ b/backend/models/postgis/team.py @@ -46,6 +46,15 @@ def delete(self): db.session.delete(self) db.session.commit() + def update(self): + """ Updates the current model in the DB """ + db.session.commit() + + @staticmethod + def get(team_id: int, user_id: int): + """ Returns a team member by team_id and user_id """ + return TeamMembers.query.filter_by(team_id=team_id, user_id=user_id).first() + class Team(db.Model): """ Describes a team """ @@ -124,18 +133,25 @@ def update(self, team_dto: TeamDTO): if team_dto.members != self._get_team_members() and team_dto.members: for member in self.members: - db.session.delete(member) - + member_name = User.get_by_id(member.user_id).username + if member_name not in [i["username"] for i in team_dto.members]: + member.delete() for member in team_dto.members: - user = User.get_by_username(member["userName"]) - + user = User.get_by_username(member["username"]) if user is None: raise NotFound("User not found") - - new_team_member = TeamMembers() - new_team_member.team = self - new_team_member.member = user - new_team_member.function = TeamMemberFunctions[member["function"]].value + team_member = TeamMembers.get(self.id, user.id) + if team_member: + team_member.join_request_notifications = member[ + "join_request_notifications" + ] + else: + new_team_member = TeamMembers() + new_team_member.team = self + new_team_member.member = user + new_team_member.function = TeamMemberFunctions[ + member["function"] + ].value db.session.commit() From b4c6462ee891d037c3144097f4264d824dbebde9 Mon Sep 17 00:00:00 2001 From: Hel Nershing Thapa Date: Wed, 6 Jul 2022 14:44:14 +0545 Subject: [PATCH 5/6] responsive rerendering on team details update --- .../src/components/teamsAndOrgs/members.js | 31 +++++++++++++------ .../teamsAndOrgs/tests/members.test.js | 6 ++-- frontend/src/views/teams.js | 9 ++++-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/teamsAndOrgs/members.js b/frontend/src/components/teamsAndOrgs/members.js index c2d276a904..1e77f68de2 100644 --- a/frontend/src/components/teamsAndOrgs/members.js +++ b/frontend/src/components/teamsAndOrgs/members.js @@ -158,15 +158,26 @@ export function JoinRequests({ teamId, addMembers, updateRequests, - members, + managers, updateTeam, isTeamInviteOnly, }: Object) { const token = useSelector((state) => state.auth.get('token')); - const loggedInUsername = useSelector((state) => state.auth.get('userDetails')).username; - const isJoinRequestEnabled = members?.filter((member) => member.username === loggedInUsername)[0] - ?.joinRequestNotifications; - const [isChecked, setIsChecked] = useState(isJoinRequestEnabled); + const { username: loggedInUsername } = useSelector((state) => state.auth.get('userDetails')); + + const showJoinRequestSwitch = + isTeamInviteOnly && + managers?.filter( + (manager) => manager.username === loggedInUsername && manager.function === 'MANAGER', + ).length > 0; + const [isChecked, setIsChecked] = useState(false); + + useEffect(() => { + const isJoinRequestEnabled = managers.filter( + (manager) => manager.username === loggedInUsername, + )[0]?.joinRequestNotifications; + setIsChecked(isJoinRequestEnabled); + }, [loggedInUsername, managers]); const acceptRejectRequest = useCallback( (user, action) => { @@ -189,13 +200,13 @@ export function JoinRequests({ const handleJoinRequestNotificationsChange = (e) => { const { checked } = e.target; setIsChecked(checked); - let tempMembers = members; - const memberIndex = tempMembers.findIndex((member) => member.username === loggedInUsername); - Object.assign(tempMembers[memberIndex], { + let member = managers.find((member) => member.username === loggedInUsername); + + Object.assign(member, { joinRequestNotifications: checked, active: checked.toString(), }); - updateTeam({ members }); + updateTeam({ members: [member] }); }; return ( @@ -205,7 +216,7 @@ export function JoinRequests({
- {isTeamInviteOnly && ( + {showJoinRequestSwitch && (
diff --git a/frontend/src/components/teamsAndOrgs/tests/members.test.js b/frontend/src/components/teamsAndOrgs/tests/members.test.js index 30a5ab5594..7779c01129 100644 --- a/frontend/src/components/teamsAndOrgs/tests/members.test.js +++ b/frontend/src/components/teamsAndOrgs/tests/members.test.js @@ -19,7 +19,7 @@ describe('test JoinRequest list', () => { ]; const element = createComponentWithIntl( - + , ); const testInstance = element.root; @@ -53,7 +53,7 @@ describe('test JoinRequest list', () => { describe('test JoinRequest list without requests', () => { const element = createComponentWithIntl( - + , ); const testInstance = element.root; @@ -92,7 +92,7 @@ describe('test JoinRequest list without requests', () => { ); }); it('no requests message is present', () => { - expect(testInstance.findByProps({ className: 'tc' }).children[0].props.id).toBe( + expect(testInstance.findByProps({ className: 'tc mt3' }).children[0].props.id).toBe( 'management.teams.join_requests.empty', ); }); diff --git a/frontend/src/views/teams.js b/frontend/src/views/teams.js index 8b719aab2a..8e814a039d 100644 --- a/frontend/src/views/teams.js +++ b/frontend/src/views/teams.js @@ -8,6 +8,7 @@ import messages from './messages'; import { useFetch } from '../hooks/UseFetch'; import { useEditTeamAllowed } from '../hooks/UsePermissions'; import { useSetTitleTag } from '../hooks/UseMetaTags'; +import useForceUpdate from '../hooks/UseForceUpdate'; import { fetchLocalJSONAPI, pushToLocalJSONAPI } from '../network/genericJSONRequest'; import { getMembersDiff, @@ -210,7 +211,8 @@ export function CreateTeam() { export function EditTeam(props) { const userDetails = useSelector((state) => state.auth.get('userDetails')); const token = useSelector((state) => state.auth.get('token')); - const [error, loading, team] = useFetch(`teams/${props.id}/`); + const [forceUpdated, forceUpdate] = useForceUpdate(); + const [error, loading, team] = useFetch(`teams/${props.id}/`, forceUpdated); const [initManagers, setInitManagers] = useState(false); const [managers, setManagers] = useState([]); const [members, setMembers] = useState([]); @@ -218,7 +220,7 @@ export function EditTeam(props) { const [canUserEditTeam] = useEditTeamAllowed(team); const [memberJoinTeamError, setMemberJoinTeamError] = useState(null); const [managerJoinTeamError, setManagerJoinTeamError] = useState(null); - + useEffect(() => { if (!initManagers && team && team.members) { setManagers(filterActiveManagers(team.members)); @@ -276,6 +278,7 @@ export function EditTeam(props) { const updateTeam = (payload) => { pushToLocalJSONAPI(`teams/${props.id}/`, JSON.stringify(payload), token, 'PATCH'); + forceUpdate(); }; if (team && team.teamId && !canUserEditTeam) { @@ -339,7 +342,7 @@ export function EditTeam(props) { teamId={team.teamId} addMembers={addMembers} updateRequests={setRequests} - members={members} + managers={managers} updateTeam={updateTeam} isTeamInviteOnly={team.inviteOnly} /> From 45ec7b2881b689fdd4d7a05c8568e7f417e6a45d Mon Sep 17 00:00:00 2001 From: Aadesh-Baral Date: Sun, 24 Jul 2022 14:37:39 +0545 Subject: [PATCH 6/6] Fix failing tests after merge --- frontend/src/views/teams.js | 1 - .../integration/services/messaging/test_smtp_service.py | 5 ----- tests/backend/integration/services/test_team_service.py | 1 + 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/views/teams.js b/frontend/src/views/teams.js index 3d1cd437c0..b8c53d5d3b 100644 --- a/frontend/src/views/teams.js +++ b/frontend/src/views/teams.js @@ -10,7 +10,6 @@ import { useEditTeamAllowed } from '../hooks/UsePermissions'; import { useSetTitleTag } from '../hooks/UseMetaTags'; import useForceUpdate from '../hooks/UseForceUpdate'; import { fetchLocalJSONAPI, pushToLocalJSONAPI } from '../network/genericJSONRequest'; -import { useForceUpdate } from '../hooks/UseForceUpdate'; import { getMembersDiff, filterActiveMembers, diff --git a/tests/backend/integration/services/messaging/test_smtp_service.py b/tests/backend/integration/services/messaging/test_smtp_service.py index 405e7dc475..00a7d27df4 100644 --- a/tests/backend/integration/services/messaging/test_smtp_service.py +++ b/tests/backend/integration/services/messaging/test_smtp_service.py @@ -49,7 +49,6 @@ def test_send_alert(self): subject=self.subject, content=self.content, message_type=self.message_type, - project_name=self.project_name, ) self.assertTrue(sent_alert) @@ -71,7 +70,6 @@ def test_send_alert_message_limits(self): subject=self.subject, content=self.content, message_type=self.message_type, - project_name=self.project_name, ) self.assertTrue(sent_alert) @@ -91,7 +89,6 @@ def test_alert_not_sent_if_email_not_supplied(self): subject=self.subject, content=self.content, message_type=self.message_type, - project_name=self.project_name, ) self.assertFalse(sent_alert) @@ -108,7 +105,6 @@ def test_does_not_send_if_user_not_verified(self): subject=self.subject, content=self.content, message_type=self.message_type, - project_name=self.project_name, ) self.assertFalse(sent_alert) @@ -129,7 +125,6 @@ def test_does_send_if_user_verified(self, mock_send_message): subject=self.subject, content=self.content, message_type=self.message_type, - project_name=self.project_name, ) # Assert self.assertTrue(sent_alert) diff --git a/tests/backend/integration/services/test_team_service.py b/tests/backend/integration/services/test_team_service.py index 351aeef3a4..9f812f201f 100644 --- a/tests/backend/integration/services/test_team_service.py +++ b/tests/backend/integration/services/test_team_service.py @@ -102,6 +102,7 @@ def test_join_team_raises_error_if_user_already_team_member( # Arrange test_team = create_canned_team() test_user = create_canned_user() + add_user_to_team(test_team, test_user, TeamMemberFunctions.MEMBER.value, True) mock_is_team_member.return_value = True # Act/Assert with self.assertRaises(TeamJoinNotAllowed):