diff --git a/bots/src/API/Docs/Responses.hs b/bots/src/API/Docs/Responses.hs index 1c2e4baec3..8d5cb9f348 100644 --- a/bots/src/API/Docs/Responses.hs +++ b/bots/src/API/Docs/Responses.hs @@ -162,6 +162,7 @@ undocumentedResponses = "CRGroupUserChanged", "CRItemsReadForChat", "CRJoinedGroupMember", + "CRMemberSupportChatRead", "CRMemberSupportChatDeleted", "CRMemberSupportChats", "CRNetworkConfig", diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 919274f4d2..d8a6f7a30a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -726,6 +726,7 @@ data ChatResponse | CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]} | CRJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRMemberAccepted {user :: User, groupInfo :: GroupInfo, member :: GroupMember} + | CRMemberSupportChatRead {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRMemberSupportChatDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRMembersRoleUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], toRole :: GroupMemberRole} | CRMembersBlockedForAllUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], blocked :: Bool} diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index a18056362f..67bb4f9194 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1042,12 +1042,23 @@ processChatCommand vr nm = \case gInfo <- getGroupInfo db vr user chatId pure (user, gInfo) ts <- liftIO getCurrentTime - timedItems <- withFastStore' $ \db -> do - timedItems <- getGroupUnreadTimedItems db user chatId scope - updateGroupChatItemsRead db user gInfo scope - setGroupChatItemsDeleteAt db user chatId timedItems ts - forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt - ok user + chatScopeInfo <- mapM (getChatScopeInfo vr user) scope + case chatScopeInfo of + Nothing -> do + timedItems <- withFastStore' $ \db -> do + timedItems <- getGroupUnreadTimedItems db user chatId scope + updateGroupChatItemsRead db user gInfo + setGroupChatItemsDeleteAt db user chatId timedItems ts + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + ok user + Just scopeInfo -> do + (gInfo', m', timedItems) <- withFastStore' $ \db -> do + timedItems <- getGroupUnreadTimedItems db user chatId scope + (gInfo', m') <- updateSupportChatItemsRead db vr user gInfo scopeInfo + timedItems' <- setGroupChatItemsDeleteAt db user chatId timedItems ts + pure (gInfo', m', timedItems') + forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt + pure $ CRMemberSupportChatRead user gInfo' m' CTLocal -> do user <- withFastStore $ \db -> getUserByNoteFolderId db chatId withFastStore' $ \db -> updateLocalChatItemsRead db user chatId diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index b645a57988..d0658b281d 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -80,6 +80,7 @@ module Simplex.Chat.Store.Messages setDirectChatItemRead, setDirectChatItemsDeleteAt, updateGroupChatItemsRead, + updateSupportChatItemsRead, getGroupUnreadTimedItems, updateGroupChatItemsReadList, updateGroupScopeUnreadStats, @@ -2018,20 +2019,23 @@ setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM i (deleteAt, userId, contactId, chatItemId) pure (chatItemId, deleteAt) -updateGroupChatItemsRead :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScope -> IO () -updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope = do +updateGroupChatItemsRead :: DB.Connection -> User -> GroupInfo -> IO () +updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} = do currentTs <- getCurrentTime - case scope of - Nothing -> - DB.execute - db - [sql| - UPDATE chat_items SET item_status = ?, updated_at = ? - WHERE user_id = ? AND group_id = ? - AND item_status = ? - |] - (CISRcvRead, currentTs, userId, groupId, CISRcvNew) - Just GCSMemberSupport {groupMemberId_} -> do + DB.execute + db + [sql| + UPDATE chat_items SET item_status = ?, updated_at = ? + WHERE user_id = ? AND group_id = ? + AND item_status = ? + |] + (CISRcvRead, currentTs, userId, groupId, CISRcvNew) + +updateSupportChatItemsRead :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> IO (GroupInfo, GroupMember) +updateSupportChatItemsRead db vr user@User {userId} g@GroupInfo {groupId, membership} scopeInfo = do + currentTs <- getCurrentTime + case scopeInfo of + GCSIMemberSupport {groupMember_} -> do DB.execute db [sql| @@ -2040,8 +2044,21 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope AND group_scope_tag = ? AND group_scope_group_member_id IS NOT DISTINCT FROM ? AND item_status = ? |] - (CISRcvRead, currentTs, userId, groupId, GCSTMemberSupport_, groupMemberId_, CISRcvNew) - let gmId = fromMaybe (groupMemberId' membership) groupMemberId_ + (CISRcvRead, currentTs, userId, groupId, GCSTMemberSupport_, groupMemberId' <$> groupMember_, CISRcvNew) + case groupMember_ of + Nothing -> do + membership' <- updateGMStats membership + pure (g {membership = membership'}, membership') + Just member -> do + member' <- updateGMStats member + let didRequire = gmRequiresAttention member + nowRequires = gmRequiresAttention member' + if (not nowRequires && didRequire) + then (,member') <$> decreaseGroupMembersRequireAttention db user g + else pure (g, member') + where + updateGMStats m@GroupMember {groupMemberId} = do + currentTs <- getCurrentTime DB.execute db [sql| @@ -2051,7 +2068,9 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope support_chat_items_mentions = 0 WHERE group_member_id = ? |] - (Only gmId) + (Only groupMemberId) + m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId + pure $ either (const m) id m_ -- Left shouldn't happen, but types require it getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> Maybe GroupChatScope -> IO [(ChatItemId, Int)] getGroupUnreadTimedItems db User {userId} groupId scope = diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index a1e7bc25e3..ac757fbfb0 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -231,6 +231,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else [] CRJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m CRMemberAccepted u g m -> ttyUser u $ viewMemberAccepted g m + CRMemberSupportChatRead u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " support chat read"] CRMemberSupportChatDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " support chat deleted"] CRMembersRoleUser u g members r' -> ttyUser u $ viewMemberRoleUserChanged g members r' CRMembersBlockedForAllUser u g members blocked -> ttyUser u $ viewMembersBlockedForAllUser g members blocked diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 686ced1d59..0aca66113d 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -220,7 +220,7 @@ chatGroupTests = do it "should send messages to admins and members" testSupportCLISendCommand it "should correctly maintain unread stats for support chats on reading chat items" testScopedSupportUnreadStatsOnRead it "should correctly maintain unread stats for support chats on deleting chat items" testScopedSupportUnreadStatsOnDelete - fit "should correct member attention stat for support chat on opening it" testScopedSupportUnreadStatsCorrectOnOpen + it "should correct member attention stat for support chat on opening it" testScopedSupportUnreadStatsCorrectOnOpen testGroupCheckMessages :: HasCallStack => TestParams -> IO () testGroupCheckMessages = @@ -8008,19 +8008,22 @@ testScopedSupportUnreadStatsOnRead = bob ##> "/member support chats #team" bob <## "support: unread: 4, require attention: 0, mentions: 1" - alice #$> ("/_read chat #1(_support:2)", id, "ok") + alice ##> "/_read chat #1(_support:2)" + alice <## "#team: bob support chat read" alice ##> "/member support chats #team" alice <## "members require attention: 0" alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0" - dan #$> ("/_read chat #1(_support:3)", id, "ok") + dan ##> "/_read chat #1(_support:3)" + dan <## "#team: bob support chat read" dan ##> "/member support chats #team" - dan <## "members require attention: 1" -- TODO fix mark read: 0 + dan <## "members require attention: 0" dan <## "bob (Bob) (id 3): unread: 0, require attention: 0, mentions: 0" - bob #$> ("/_read chat #1(_support)", id, "ok") + bob ##> "/_read chat #1(_support)" + bob <## "#team: bob support chat read" bob ##> "/member support chats #team" bob <## "support: unread: 0, require attention: 0, mentions: 0" @@ -8081,10 +8084,11 @@ testScopedSupportUnreadStatsCorrectOnOpen = alice <## "members require attention: 1" alice <## "bob (Bob) (id 2): unread: 2, require attention: 2, mentions: 0" - alice #$> ("/_read chat #1(_support:2)", id, "ok") + alice ##> "/_read chat #1(_support:2)" + alice <## "#team: bob support chat read" alice ##> "/member support chats #team" - alice <## "members require attention: 1" -- TODO fix mark read: 0 + alice <## "members require attention: 0" alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0" bob #> "#team (support) 3" @@ -8097,26 +8101,27 @@ testScopedSupportUnreadStatsCorrectOnOpen = alice <# "#team (support: bob) bob> 5" alice ##> "/member support chats #team" - alice <## "members require attention: 2" -- TODO fix mark read: 1 + alice <## "members require attention: 1" alice <## "bob (Bob) (id 2): unread: 3, require attention: 3, mentions: 0" void $ withCCTransaction alice $ \db -> DB.execute db "UPDATE group_members SET support_chat_items_member_attention=100 WHERE group_member_id=?" (Only (2 :: Int64)) alice ##> "/member support chats #team" - alice <## "members require attention: 2" -- TODO fix mark read: 1 + alice <## "members require attention: 1" alice <## "bob (Bob) (id 2): unread: 3, require attention: 100, mentions: 0" alice #$> ("/_get chat #1(_support:2) count=100", chat, [(0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5")]) alice ##> "/member support chats #team" - alice <## "members require attention: 2" -- TODO fix mark read: 1 + alice <## "members require attention: 1" alice <## "bob (Bob) (id 2): unread: 3, require attention: 3, mentions: 0" - alice #$> ("/_read chat #1(_support:2)", id, "ok") + alice ##> "/_read chat #1(_support:2)" + alice <## "#team: bob support chat read" alice ##> "/member support chats #team" - alice <## "members require attention: 2" -- TODO fix mark read: 0 + alice <## "members require attention: 0" alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0" where opts =