Fix some of the freezes experienced by the admins when the community updates (#16384)

* fix: force focus on search inputs in permissions and sort members
* fix(admin): fix freezes when community gets updated
* fix(airdrop): use FastExpressionFilter to speed up

Iterates #16043

When the community gets updated by any means, we reconstruct the section item, which is already bad, but we also re-fetch all tokens and all shared addresses, which in turn re-updates models for no reason.

Instead, I make sure to only fetch those on first section build, then, I get the new shared addresses when members join using the request to join response that comes from status-go
This commit is contained in:
Jonathan Rainville 2024-10-10 13:01:47 -04:00 committed by GitHub
parent 70fccb3835
commit 0bb2bc0e03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 149 additions and 72 deletions

View File

@ -309,6 +309,10 @@ proc init*(self: Controller) =
var args = CommunityRequestArgs(e)
self.delegate.newCommunityMembershipRequestReceived(args.communityRequest)
self.events.on(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY_ACCEPTED) do(e: Args):
var args = CommunityRequestArgs(e)
self.delegate.communityMemberRevealedAccountsAdded(args.communityRequest)
self.events.on(SIGNAL_NETWORK_CONNECTED) do(e: Args):
self.delegate.onNetworkConnected()
@ -595,6 +599,9 @@ proc getColorId*(self: Controller, pubkey: string): int =
proc asyncGetRevealedAccountsForAllMembers*(self: Controller, communityId: string) =
self.communityService.asyncGetRevealedAccountsForAllMembers(communityId)
proc asyncGetRevealedAccountsForMember*(self: Controller, communityId, memberPubkey: string) =
self.communityService.asyncGetRevealedAccountsForMember(communityId, memberPubkey)
proc asyncReevaluateCommunityMembersPermissions*(self: Controller, communityId: string) =
self.communityService.asyncReevaluateCommunityMembersPermissions(communityId)

View File

@ -391,6 +391,9 @@ method windowDeactivated*(self: AccessInterface) {.base.} =
method communityMembersRevealedAccountsLoaded*(self: AccessInterface, communityId: string, membersRevealedAccounts: MembersRevealedAccounts) {.base.} =
raise newException(ValueError, "No implementation available")
method communityMemberRevealedAccountsAdded*(self: AccessInterface, request: CommunityMembershipRequestDto) {.base.} =
raise newException(ValueError, "No implementation available")
## Used in test env only, for testing keycard flows
method registerMockedKeycard*(self: AccessInterface, cardIndex: int, readerState: int, keycardState: int,
mockedKeycard: string, mockedKeycardHelper: string) {.base.} =

View File

@ -315,21 +315,24 @@ method onCommunityTokensDetailsLoaded[T](self: Module[T], communityId: string,
)
self.view.model().setTokenItems(communityId, communityTokensItems)
proc createCommunitySectionItem[T](self: Module[T], communityDetails: CommunityDto): SectionItem =
proc createCommunitySectionItem[T](self: Module[T], communityDetails: CommunityDto, isEdit: bool = false): SectionItem =
var communityTokensItems: seq[TokenItem]
var communityMembersAirdropAddress: Table[string, string]
let existingCommunity = self.view.model().getItemById(communityDetails.id)
if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster:
self.controller.getCommunityTokensDetailsAsync(communityDetails.id)
if not isEdit:
# When first creating the section, we load the community tokens and the members revealed accounts
self.controller.getCommunityTokensDetailsAsync(communityDetails.id)
# Get community members' revealed accounts
# We will update the model later when we finish loading the accounts
self.controller.asyncGetRevealedAccountsForAllMembers(communityDetails.id)
# Get community members' revealed accounts
# We will update the model later when we finish loading the accounts
self.controller.asyncGetRevealedAccountsForAllMembers(communityDetails.id)
# If there are tokens already in the model, we should keep the existing community tokens, until
# getCommunityTokensDetailsAsync will trigger onCommunityTokensDetailsLoaded
let existingCommunity = self.view.model().getItemById(communityDetails.id)
if not existingCommunity.isEmpty() and not existingCommunity.communityTokens.isNil:
communityTokensItems = existingCommunity.communityTokens.items
communityTokensItems = existingCommunity.communityTokens.items
let (unviewedCount, notificationsCount) = self.controller.sectionUnreadMessagesAndMentionsCount(
communityDetails.id,
@ -394,8 +397,16 @@ proc createCommunitySectionItem[T](self: Module[T], communityDetails: CommunityD
state = memberState
elif not member.joined:
state = MembershipRequestState.AwaitingAddress
result = self.createMemberItem(member.id, "", state, member.role)
var airdropAddress = ""
if not existingCommunity.isEmpty() and not existingCommunity.communityTokens.isNil:
airdropAddress = existingCommunity.members.getAirdropAddressForMember(member.id)
result = self.createMemberItem(
member.id,
requestId = "",
state,
member.role,
airdropAddress,
)
),
# pendingRequestsToJoin
communityDetails.pendingRequestsToJoin.map(x => pending_request_item.initItem(
@ -1093,7 +1104,7 @@ method communityEdited*[T](
community: CommunityDto) =
if(not self.chatSectionModules.contains(community.id)):
return
var communitySectionItem = self.createCommunitySectionItem(community)
var communitySectionItem = self.createCommunitySectionItem(community, isEdit = true)
# We need to calculate the unread counts because the community update doesn't come with it
let (unviewedMessagesCount, unviewedMentionsCount) = self.controller.sectionUnreadMessagesAndMentionsCount(
communitySectionItem.id,
@ -1667,6 +1678,20 @@ method communityMembersRevealedAccountsLoaded*[T](self: Module[T], communityId:
self.view.model.setMembersAirdropAddress(communityId, communityMembersAirdropAddress)
method communityMemberRevealedAccountsAdded*[T](self: Module[T], request: CommunityMembershipRequestDto) =
var airdropAddress = ""
for revealedAccount in request.revealedAccounts:
if revealedAccount.isAirdropAddress:
airdropAddress = revealedAccount.address
discard
if airdropAddress == "":
warn "Request to join doesn't have an airdrop address", requestId = request.id, communityId = request.communityId
return
let communityMembersAirdropAddress = {request.publicKey: airdropAddress}.toTable
self.view.model.setMembersAirdropAddress(request.communityId, communityMembersAirdropAddress)
## Used in test env only, for testing keycard flows
method registerMockedKeycard*[T](self: Module[T], cardIndex: int, readerState: int, keycardState: int,
mockedKeycard: string, mockedKeycardHelper: string) =
@ -1700,7 +1725,14 @@ method updateRequestToJoinState*[T](self: Module[T], sectionId: string, requestT
if sectionId in self.chatSectionModules:
self.chatSectionModules[sectionId].updateRequestToJoinState(requestToJoinState)
proc createMemberItem*[T](self: Module[T], memberId: string, requestId: string, state: MembershipRequestState, role: MemberRole): MemberItem =
proc createMemberItem*[T](
self: Module[T],
memberId: string,
requestId: string,
state: MembershipRequestState,
role: MemberRole,
airdropAddress: string = "",
): MemberItem =
let contactDetails = self.controller.getContactDetails(memberId)
let status = self.controller.getStatusForContactWithId(memberId)
return initMemberItem(
@ -1718,7 +1750,8 @@ proc createMemberItem*[T](self: Module[T], memberId: string, requestId: string,
isVerified = contactDetails.dto.isContactVerified(),
memberRole = role,
membershipRequestState = state,
requestToJoinId = requestId
requestToJoinId = requestId,
airdropAddress = airdropAddress,
)
{.pop.}

View File

@ -396,6 +396,13 @@ QtObject:
ModelRole.AirdropAddress.int
])
proc getAirdropAddressForMember*(self: Model, pubKey: string): string =
let idx = self.findIndexForMember(pubKey)
if idx == -1:
return ""
return self.items[idx].airdropAddress
# TODO: rename me to removeItemByPubkey
proc removeItemById*(self: Model, pubKey: string) =
let ind = self.findIndexForMember(pubKey)
@ -437,3 +444,13 @@ QtObject:
self.dataChanged(index, index, @[
ModelRole.MembershipRequestState.int
])
proc getNewMembers*(self: Model, pubkeys: seq[string]): seq[string] =
for pubkey in pubkeys:
var found = false
for item in self.items:
if item.pubKey == pubkey:
found = true
break
if not found:
result.add(pubkey)

View File

@ -473,8 +473,8 @@ QtObject:
if (index == -1):
return
for pubkey, revealedAccounts in communityMembersAirdropAddress.pairs:
self.items[index].members.setAirdropAddress(pubkey, revealedAccounts)
for pubkey, airdropAddress in communityMembersAirdropAddress.pairs:
self.items[index].members.setAirdropAddress(pubkey, airdropAddress)
proc setTokenItems*(self: SectionModel, id: string, communityTokensItems: seq[TokenItem]) =
let index = self.getItemIndex(id)

View File

@ -48,6 +48,13 @@ type
proc isBanned*(state: CommunityMemberPendingBanOrKick): bool =
return state == CommunityMemberPendingBanOrKick.Banned or state == CommunityMemberPendingBanOrKick.BannedWithAllMessagesDelete
type RevealedAccount* = object
address*: string
chainIds*: seq[int]
isAirdropAddress*: bool
type MembersRevealedAccounts* = Table[string, seq[RevealedAccount]]
type CommunityMembershipRequestDto* = object
id*: string
publicKey*: string
@ -55,6 +62,7 @@ type CommunityMembershipRequestDto* = object
communityId*: string
state*: int
our*: string #FIXME: should be bool
revealedAccounts*: seq[RevealedAccount]
type CommunitySettingsDto* = object
id*: string
@ -126,13 +134,6 @@ type CommunityMetricsDto* = object
metricsType*: CommunityMetricsType
intervals*: seq[MetricsIntervalDto]
type RevealedAccount* = object
address*: string
chainIds*: seq[int]
isAirdropAddress*: bool
type MembersRevealedAccounts* = Table[string, seq[RevealedAccount]]
type CommunityDto* = object
id*: string
memberRole*: MemberRole
@ -388,6 +389,24 @@ proc toCommunityMetricsDto*(jsonObj: JsonNode): CommunityMetricsDto =
for interval in intervalsObj:
result.intervals.add(interval.toMetricsIntervalDto)
proc toRevealedAccount*(revealedAccountObj: JsonNode): RevealedAccount =
var chainIdsObj: JsonNode
var chainIds: seq[int] = @[]
if revealedAccountObj.getProp("chain_ids", chainIdsObj):
for chainIdObj in chainIdsObj:
chainIds.add(chainIdObj.getInt)
result = RevealedAccount(
address: revealedAccountObj["address"].getStr,
chainIds: chainIds,
isAirdropAddress: revealedAccountObj{"isAirdropAddress"}.getBool,
)
proc toRevealedAccounts*(revealedAccountsObj: JsonNode): seq[RevealedAccount] =
result = @[]
for revealedAccountObj in revealedAccountsObj:
result.add(revealedAccountObj.toRevealedAccount())
proc toCommunityMembershipRequestDto*(jsonObj: JsonNode): CommunityMembershipRequestDto =
result = CommunityMembershipRequestDto()
discard jsonObj.getProp("id", result.id)
@ -397,6 +416,10 @@ proc toCommunityMembershipRequestDto*(jsonObj: JsonNode): CommunityMembershipReq
discard jsonObj.getProp("communityId", result.communityId)
discard jsonObj.getProp("our", result.our)
var revealedAccountObj: JsonNode
if(jsonObj.getProp("revealedAccounts", revealedAccountObj)):
result.revealedAccounts = toRevealedAccounts(revealedAccountObj)
proc toCollapsedCategoryDto*(jsonObj: JsonNode, isCollapsed: bool = false): Category =
result = Category()
discard jsonObj.getProp("categoryId", result.id)
@ -579,24 +602,6 @@ proc parseDiscordChannels*(response: JsonNode): seq[DiscordChannelDto] =
for channel in response["discordChannels"].items():
result.add(channel.toDiscordChannelDto())
proc toRevealedAccount*(revealedAccountObj: JsonNode): RevealedAccount =
var chainIdsObj: JsonNode
var chainIds: seq[int] = @[]
if revealedAccountObj.getProp("chain_ids", chainIdsObj):
for chainIdObj in chainIdsObj:
chainIds.add(chainIdObj.getInt)
result = RevealedAccount(
address: revealedAccountObj["address"].getStr,
chainIds: chainIds,
isAirdropAddress: revealedAccountObj{"isAirdropAddress"}.getBool,
)
proc toRevealedAccounts*(revealedAccountsObj: JsonNode): seq[RevealedAccount] =
result = @[]
for revealedAccountObj in revealedAccountsObj:
result.add(revealedAccountObj.toRevealedAccount())
proc toMembersRevealedAccounts*(membersRevealedAccountsObj: JsonNode): MembersRevealedAccounts =
result = initTable[string, seq[RevealedAccount]]()
for (pubkey, revealedAccountsObj) in membersRevealedAccountsObj.pairs:

View File

@ -205,6 +205,7 @@ const SIGNAL_COMMUNITY_MEMBER_STATUS_CHANGED* = "communityMemberStatusChanged"
const SIGNAL_COMMUNITY_MEMBERS_CHANGED* = "communityMembersChanged"
const SIGNAL_COMMUNITY_KICKED* = "communityKicked"
const SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY* = "newRequestToJoinCommunity"
const SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY_ACCEPTED* = "requestToJoinCommunityAccepted"
const SIGNAL_REQUEST_TO_JOIN_COMMUNITY_CANCELED* = "requestToJoinCommunityCanceled"
const SIGNAL_WAITING_ON_NEW_COMMUNITY_OWNER_TO_CONFIRM_REQUEST_TO_REJOIN* = "waitingOnNewCommunityOwnerToConfirmRequestToRejoin"
const SIGNAL_CURATED_COMMUNITY_FOUND* = "curatedCommunityFound"
@ -769,29 +770,31 @@ QtObject:
proc handleCommunitiesRequestsToJoin(self: Service, membershipRequests: seq[CommunityMembershipRequestDto]) =
for membershipRequest in membershipRequests:
if (not self.communities.contains(membershipRequest.communityId)):
error "Received a membership request for an unknown community", communityId=membershipRequest.communityId
continue
if (not self.communities.contains(membershipRequest.communityId)):
error "Received a membership request for an unknown community", communityId=membershipRequest.communityId
continue
let requestToJoinState = RequestToJoinType(membershipRequest.state)
let noAwaitingIndex = self.getWaitingForSharedAddressesRequestIndex(membershipRequest.communityId, membershipRequest.id) == -1
if requestToJoinState == RequestToJoinType.AwaitingAddress and noAwaitingIndex:
self.communities[membershipRequest.communityId].waitingForSharedAddressesRequestsToJoin.add(membershipRequest)
let myPublicKey = singletonInstance.userProfile.getPubKey()
if myPublicKey == membershipRequest.publicKey:
self.events.emit(SIGNAL_WAITING_ON_NEW_COMMUNITY_OWNER_TO_CONFIRM_REQUEST_TO_REJOIN, CommunityIdArgs(communityId: membershipRequest.communityId))
elif RequestToJoinType.Pending == requestToJoinState and self.getPendingRequestIndex(membershipRequest.communityId, membershipRequest.id) == -1:
self.communities[membershipRequest.communityId].pendingRequestsToJoin.add(membershipRequest)
self.events.emit(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY,
CommunityRequestArgs(communityRequest: membershipRequest))
else:
try:
self.updateMembershipRequestToNewState(membershipRequest.communityId, membershipRequest.id, self.communities[membershipRequest.communityId],
requestToJoinState)
except Exception as e:
error "Unknown request", msg = e.msg
self.events.emit(SIGNAL_COMMUNITY_EDITED, CommunityArgs(community: self.communities[membershipRequest.communityId]))
let requestToJoinState = RequestToJoinType(membershipRequest.state)
let noAwaitingIndex = self.getWaitingForSharedAddressesRequestIndex(membershipRequest.communityId, membershipRequest.id) == -1
if requestToJoinState == RequestToJoinType.AwaitingAddress and noAwaitingIndex:
self.communities[membershipRequest.communityId].waitingForSharedAddressesRequestsToJoin.add(membershipRequest)
let myPublicKey = singletonInstance.userProfile.getPubKey()
if myPublicKey == membershipRequest.publicKey:
self.events.emit(SIGNAL_WAITING_ON_NEW_COMMUNITY_OWNER_TO_CONFIRM_REQUEST_TO_REJOIN, CommunityIdArgs(communityId: membershipRequest.communityId))
elif requestToJoinState == RequestToJoinType.Pending and self.getPendingRequestIndex(membershipRequest.communityId, membershipRequest.id) == -1:
self.communities[membershipRequest.communityId].pendingRequestsToJoin.add(membershipRequest)
self.events.emit(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY,
CommunityRequestArgs(communityRequest: membershipRequest))
elif requestToJoinState == RequestToJoinType.Accepted:
# Request was accepted, update the member's airdrop address
self.events.emit(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY_ACCEPTED,
CommunityRequestArgs(communityRequest: membershipRequest))
else:
try:
self.updateMembershipRequestToNewState(membershipRequest.communityId, membershipRequest.id, self.communities[membershipRequest.communityId],
requestToJoinState)
except Exception as e:
error "Unknown request", msg = e.msg
proc init*(self: Service) =
self.doConnect()

View File

@ -53,8 +53,11 @@ StatusDropdown {
listView.positionViewAtBeginning()
}
onAboutToShow: listView.Layout.preferredHeight = Math.min(
onAboutToShow: {
searcher.input.edit.forceActiveFocus()
listView.Layout.preferredHeight = Math.min(
listView.implicitHeight, 420)
}
// only channels (no entries representing categories), sorted according to
// category position and position

View File

@ -67,6 +67,7 @@ StatusDropdown {
onOpened: {
listView.positionViewAtBeginning()
filterInput.text = ""
filterInput.input.edit.forceActiveFocus()
}
QtObject {

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
@ -512,27 +513,31 @@ StatusScrollView {
model: SortFilterProxyModel {
sourceModel: membersModel
sorters : [
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
filters: [
ExpressionFilter {
FastExpressionFilter {
enabled: membersDropdown.searchText !== ""
function matchesAlias(name, filter) {
return name.split(" ").some(p => p.startsWith(filter))
}
expression: {
membersDropdown.searchText
const filter = membersDropdown.searchText.toLowerCase()
return matchesAlias(model.alias.toLowerCase(), filter)
return model.alias.toLowerCase().includes(filter)
|| model.displayName.toLowerCase().includes(filter)
|| model.ensName.toLowerCase().includes(filter)
|| model.localNickname.toLowerCase().includes(filter)
|| model.pubKey.toLowerCase().includes(filter)
}
expectedRoles: ["alias", "displayName", "ensName", "localNickname", "pubKey"]
},
ExpressionFilter {
expression: !!model.airdropAddress
ValueFilter {
roleName: "airdropAddress"
value: ""
inverted: true
}
]
}