feat(Community): Community messaging statistics chart (#11696)
* feat(Community): Community messaging statistics chart Close 11152 - Use se `collectCommunityMessageMetrics` for messaging statistics chart in community overview * feat(Community): Transfer community metrics with dto objects * feat: impl simple string-based model for community metrics * fix(Community): Review fixes and fix for changing community when chat is open * Update src/app/modules/main/chat_section/controller.nim Co-authored-by: Jonathan Rainville <rainville.jonathan@gmail.com> --------- Co-authored-by: Jonathan Rainville <rainville.jonathan@gmail.com>
This commit is contained in:
parent
dd346319ff
commit
edba946a71
|
@ -169,6 +169,16 @@ proc init*(self: Controller) =
|
|||
self.nodeConfigurationService, self.contactService, self.chatService, self.communityService,
|
||||
self.messageService, self.gifService, self.mailserversService, setChatAsActive = true)
|
||||
|
||||
self.events.on(SIGNAL_COMMUNITY_METRICS_UPDATED) do(e: Args):
|
||||
let args = CommunityMetricsArgs(e)
|
||||
if args.communityId == self.sectionId:
|
||||
let metrics = self.communityService.getCommunityMetrics(args.communityId, args.metricsType)
|
||||
var strings: seq[string]
|
||||
for interval in metrics.intervals:
|
||||
for timestamp in interval.timestamps:
|
||||
strings.add($timestamp)
|
||||
self.delegate.setOverviewChartData("[" & join(strings, ", ") & "]")
|
||||
|
||||
self.events.on(SIGNAL_COMMUNITY_CHANNEL_DELETED) do(e:Args):
|
||||
let args = CommunityChatIdArgs(e)
|
||||
if (args.communityId == self.sectionId):
|
||||
|
@ -652,3 +662,6 @@ proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int,
|
|||
|
||||
proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] =
|
||||
return self.communityTokensService.getCommunityTokens(self.getMySectionId())
|
||||
|
||||
proc collectCommunityMetricsMessagesTimestamps*(self: Controller, intervals: string) =
|
||||
self.communityService.collectCommunityMetricsMessagesTimestamps(self.getMySectionId(), intervals)
|
|
@ -349,6 +349,12 @@ method createOrEditCommunityTokenPermission*(self: AccessInterface, communityId:
|
|||
method deleteCommunityTokenPermission*(self: AccessInterface, communityId: string, permissionId: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method collectCommunityMetricsMessagesTimestamps*(self: AccessInterface, intervals: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setOverviewChartData*(self: AccessInterface, metrics: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onCommunityTokenPermissionCreated*(self: AccessInterface, communityId: string, tokenPermission: CommunityTokenPermissionDto) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
|
|
|
@ -1322,3 +1322,9 @@ method deleteCommunityTokenPermission*(self: Module, communityId: string, permis
|
|||
|
||||
method onDeactivateChatLoader*(self: Module, chatId: string) =
|
||||
self.view.chatsModel().disableChatLoader(chatId)
|
||||
|
||||
method collectCommunityMetricsMessagesTimestamps*(self: Module, intervals: string) =
|
||||
self.controller.collectCommunityMetricsMessagesTimestamps(intervals)
|
||||
|
||||
method setOverviewChartData*(self: Module, metrics: string) =
|
||||
self.view.setOverviewChartData(metrics)
|
||||
|
|
|
@ -27,6 +27,8 @@ QtObject:
|
|||
requiresTokenPermissionToJoin: bool
|
||||
amIMember: bool
|
||||
chatsLoaded: bool
|
||||
communityMetrics: string # NOTE: later this should be replaced with QAbstractListModel-based model
|
||||
|
||||
|
||||
proc delete*(self: View) =
|
||||
self.model.delete
|
||||
|
@ -64,6 +66,7 @@ QtObject:
|
|||
result.amIMember = false
|
||||
result.requiresTokenPermissionToJoin = false
|
||||
result.chatsLoaded = false
|
||||
result.communityMetrics = "[]"
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
@ -409,3 +412,19 @@ QtObject:
|
|||
QtProperty[bool] allTokenRequirementsMet:
|
||||
read = getAllTokenRequirementsMet
|
||||
notify = allTokenRequirementsMetChanged
|
||||
|
||||
proc getOverviewChartData*(self: View): QVariant {.slot.} =
|
||||
return newQVariant(self.communityMetrics)
|
||||
|
||||
proc overviewChartDataChanged*(self: View) {.signal.}
|
||||
|
||||
QtProperty[QVariant] overviewChartData:
|
||||
read = getOverviewChartData
|
||||
notify = overviewChartDataChanged
|
||||
|
||||
proc setOverviewChartData*(self: View, communityMetrics: string) =
|
||||
self.communityMetrics = communityMetrics
|
||||
self.overviewChartDataChanged()
|
||||
|
||||
proc collectCommunityMetricsMessagesTimestamps*(self: View, intervals: string) {.slot.} =
|
||||
self.delegate.collectCommunityMetricsMessagesTimestamps(intervals)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import stint
|
||||
import stint, std/strutils
|
||||
import ./io_interface
|
||||
|
||||
import ../../../core/signals/types
|
||||
|
|
|
@ -24,6 +24,29 @@ const asyncLoadCommunitiesDataTask: Task = proc(argEncoded: string) {.gcsafe, ni
|
|||
"error": e.msg,
|
||||
})
|
||||
|
||||
type
|
||||
AsyncCollectCommunityMetricsTaskArg = ref object of QObjectTaskArg
|
||||
communityId: string
|
||||
metricsType: CommunityMetricsType
|
||||
intervals: JsonNode
|
||||
|
||||
const asyncCollectCommunityMetricsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[AsyncCollectCommunityMetricsTaskArg](argEncoded)
|
||||
try:
|
||||
let response = status_go.collectCommunityMetrics(arg.communityId, arg.metricsType.int, arg.intervals)
|
||||
arg.finish(%* {
|
||||
"communityId": arg.communityId,
|
||||
"metricsType": arg.metricsType,
|
||||
"response": response,
|
||||
"error": "",
|
||||
})
|
||||
except Exception as e:
|
||||
arg.finish(%* {
|
||||
"communityId": arg.communityId,
|
||||
"metricsType": arg.metricsType,
|
||||
"error": e.msg,
|
||||
})
|
||||
|
||||
type
|
||||
AsyncRequestCommunityInfoTaskArg = ref object of QObjectTaskArg
|
||||
communityId: string
|
||||
|
|
|
@ -25,6 +25,13 @@ type MutedType* {.pure.}= enum
|
|||
For1min = 6,
|
||||
Unmuted = 7
|
||||
|
||||
type
|
||||
CommunityMetricsType* {.pure.} = enum
|
||||
MessagesTimestamps = 0,
|
||||
MessagesCount,
|
||||
Members,
|
||||
ControlNodeUptime
|
||||
|
||||
type CommunityMembershipRequestDto* = object
|
||||
id*: string
|
||||
publicKey*: string
|
||||
|
@ -88,6 +95,17 @@ type CheckPermissionsToJoinResponseDto* = object
|
|||
permissions*: Table[string, CheckPermissionsResultDto]
|
||||
validCombinations*: seq[AccountChainIDsCombinationDto]
|
||||
|
||||
type MetricsIntervalDto* = object
|
||||
startTimestamp*: uint64
|
||||
endTimestamp*: uint64
|
||||
timestamps*: seq[uint64]
|
||||
count*: int
|
||||
|
||||
type CommunityMetricsDto* = object
|
||||
communityId*: string
|
||||
metricsType*: CommunityMetricsType
|
||||
intervals*: seq[MetricsIntervalDto]
|
||||
|
||||
type CommunityDto* = object
|
||||
id*: string
|
||||
memberRole*: MemberRole
|
||||
|
@ -301,6 +319,34 @@ proc toCheckAllChannelsPermissionsResponseDto*(jsonObj: JsonNode): CheckAllChann
|
|||
for channelId, permissionResponse in channelsObj:
|
||||
result.channels[channelId] = permissionResponse.toCheckChannelPermissionsResponseDto()
|
||||
|
||||
proc toMetricsIntervalDto*(jsonObj: JsonNode): MetricsIntervalDto =
|
||||
result = MetricsIntervalDto()
|
||||
discard jsonObj.getProp("startTimestamp", result.startTimestamp)
|
||||
discard jsonObj.getProp("endTimestamp", result.endTimestamp)
|
||||
|
||||
var timestampsObj: JsonNode
|
||||
if (jsonObj.getProp("timestamps", timestampsObj) and timestampsObj.kind == JArray):
|
||||
for timestamp in timestampsObj:
|
||||
result.timestamps.add(uint64(timestamp.getInt))
|
||||
|
||||
discard jsonObj.getProp("count", result.count)
|
||||
|
||||
proc toCommunityMetricsDto*(jsonObj: JsonNode): CommunityMetricsDto =
|
||||
result = CommunityMetricsDto()
|
||||
|
||||
discard jsonObj.getProp("communityId", result.communityId)
|
||||
|
||||
result.metricsType = CommunityMetricsType.MessagesTimestamps
|
||||
var metricsTypeInt: int
|
||||
if (jsonObj.getProp("metricsType", metricsTypeInt) and (metricsTypeInt >= ord(low(CommunityMetricsType)) and
|
||||
metricsTypeInt <= ord(high(CommunityMetricsType)))):
|
||||
result.metricsType = CommunityMetricsType(metricsTypeInt)
|
||||
|
||||
var intervalsObj: JsonNode
|
||||
if (jsonObj.getProp("intervals", intervalsObj) and intervalsObj.kind == JArray):
|
||||
for interval in intervalsObj:
|
||||
result.intervals.add(interval.toMetricsIntervalDto)
|
||||
|
||||
proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
|
||||
result = CommunityDto()
|
||||
discard jsonObj.getProp("id", result.id)
|
||||
|
|
|
@ -121,6 +121,10 @@ type
|
|||
communityId*: string
|
||||
checkPermissionsToJoinResponse*: CheckPermissionsToJoinResponseDto
|
||||
|
||||
CommunityMetricsArgs* = ref object of Args
|
||||
communityId*: string
|
||||
metricsType*: CommunityMetricsType
|
||||
|
||||
# Signals which may be emitted by this service:
|
||||
const SIGNAL_COMMUNITY_DATA_LOADED* = "communityDataLoaded"
|
||||
const SIGNAL_COMMUNITY_JOINED* = "communityJoined"
|
||||
|
@ -189,6 +193,8 @@ const SIGNAL_CHECK_PERMISSIONS_TO_JOIN_RESPONSE* = "checkPermissionsToJoinRespon
|
|||
|
||||
const SIGNAL_COMMUNITY_PRIVATE_KEY_REMOVED* = "communityPrivateKeyRemoved"
|
||||
|
||||
const SIGNAL_COMMUNITY_METRICS_UPDATED* = "communityMetricsUpdated"
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Service* = ref object of QObject
|
||||
|
@ -202,6 +208,7 @@ QtObject:
|
|||
myCommunityRequests*: seq[CommunityMembershipRequestDto]
|
||||
historyArchiveDownloadTaskCommunityIds*: HashSet[string]
|
||||
requestedCommunityIds*: HashSet[string]
|
||||
communityMetrics: Table[string, CommunityMetricsDto]
|
||||
|
||||
# Forward declaration
|
||||
proc asyncLoadCuratedCommunities*(self: Service)
|
||||
|
@ -237,6 +244,7 @@ QtObject:
|
|||
result.myCommunityRequests = @[]
|
||||
result.historyArchiveDownloadTaskCommunityIds = initHashSet[string]()
|
||||
result.requestedCommunityIds = initHashSet[string]()
|
||||
result.communityMetrics = initTable[string, CommunityMetricsDto]()
|
||||
|
||||
proc getFilteredJoinedCommunities(self: Service): Table[string, CommunityDto] =
|
||||
result = initTable[string, CommunityDto]()
|
||||
|
@ -1359,6 +1367,18 @@ QtObject:
|
|||
except Exception as e:
|
||||
error "Error reordering category channel", msg = e.msg, communityId, categoryId, position
|
||||
|
||||
proc asyncCommunityMetricsLoaded*(self: Service, rpcResponse: string) {.slot.} =
|
||||
let rpcResponseObj = rpcResponse.parseJson
|
||||
if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "":
|
||||
error "Error collecting community metrics", msg = rpcResponseObj{"error"}
|
||||
return
|
||||
|
||||
let communityId = rpcResponseObj{"communityId"}.getStr()
|
||||
let metricsType = rpcResponseObj{"metricsType"}.getInt()
|
||||
|
||||
var metrics = rpcResponseObj{"response"}{"result"}.toCommunityMetricsDto()
|
||||
self.communityMetrics[communityId] = metrics
|
||||
self.events.emit(SIGNAL_COMMUNITY_METRICS_UPDATED, CommunityMetricsArgs(communityId: communityId, metricsType: metrics.metricsType))
|
||||
|
||||
proc asyncCommunityInfoLoaded*(self: Service, communityIdAndRpcResponse: string) {.slot.} =
|
||||
let rpcResponseObj = communityIdAndRpcResponse.parseJson
|
||||
|
@ -1551,6 +1571,34 @@ QtObject:
|
|||
error "error loading curated communities: ", errMsg
|
||||
self.events.emit(SIGNAL_CURATED_COMMUNITIES_LOADING_FAILED, Args())
|
||||
|
||||
proc getCommunityMetrics*(self: Service, communityId: string, metricsType: CommunityMetricsType): CommunityMetricsDto =
|
||||
# NOTE: use metricsType when other metrics types added
|
||||
if self.communityMetrics.hasKey(communityId):
|
||||
return self.communityMetrics[communityId]
|
||||
return CommunityMetricsDto()
|
||||
|
||||
proc collectCommunityMetricsMessagesTimestamps*(self: Service, communityId: string, intervals: string) =
|
||||
let arg = AsyncCollectCommunityMetricsTaskArg(
|
||||
tptr: cast[ByteAddress](asyncCollectCommunityMetricsTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "asyncCommunityMetricsLoaded",
|
||||
communityId: communityId,
|
||||
metricsType: CommunityMetricsType.MessagesTimestamps,
|
||||
intervals: parseJson(intervals)
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
proc collectCommunityMetricsMessagesCount*(self: Service, communityId: string, intervals: string) =
|
||||
let arg = AsyncCollectCommunityMetricsTaskArg(
|
||||
tptr: cast[ByteAddress](asyncCollectCommunityMetricsTask),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "asyncCommunityMetricsLoaded",
|
||||
communityId: communityId,
|
||||
metricsType: CommunityMetricsType.MessagesCount,
|
||||
intervals: parseJson(intervals)
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
proc requestCommunityInfo*(self: Service, communityId: string, importing = false) =
|
||||
|
||||
if communityId in self.requestedCommunityIds:
|
||||
|
|
|
@ -352,6 +352,15 @@ proc deleteCommunityCategory*(
|
|||
"categoryId": categoryId
|
||||
}])
|
||||
|
||||
proc collectCommunityMetrics*(communityId: string, metricsType: int, intervals: JsonNode
|
||||
):RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
result = callPrivateRPC("collectCommunityMetrics".prefix, %*[
|
||||
{
|
||||
"communityId": communityId,
|
||||
"type": metricsType,
|
||||
"intervals": intervals
|
||||
}])
|
||||
|
||||
proc requestCommunityInfo*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
result = callPrivateRPC("requestCommunityInfoFromMailserver".prefix, %*[communityId])
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
readonly property string overviewChartData: chatCommunitySectionModule.overviewChartData
|
||||
|
||||
readonly property bool isUserAllowedToSendMessage: _d.isUserAllowedToSendMessage
|
||||
readonly property string chatInputPlaceHolderText: _d.chatInputPlaceHolderText
|
||||
readonly property var oneToOneChatContact: _d.oneToOneChatContact
|
||||
|
@ -416,6 +418,11 @@ QtObject {
|
|||
return communitiesList.getSectionByIdJson(id)
|
||||
}
|
||||
|
||||
// intervals is a string containing json array [{startTimestamp: 1690548852, startTimestamp: 1690547684}, {...}]
|
||||
function collectCommunityMetricsMessagesTimestamps(intervals) {
|
||||
chatCommunitySectionModule.collectCommunityMetricsMessagesTimestamps(intervals)
|
||||
}
|
||||
|
||||
function requestCommunityInfo(id, importing = false) {
|
||||
communitiesModuleInst.requestCommunityInfo(id, importing)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,20 @@ StatusChartPanel {
|
|||
*/
|
||||
property var model: []
|
||||
|
||||
signal collectCommunityMetricsMessagesTimestamps(var intervals)
|
||||
|
||||
function requestCommunityMetrics() {
|
||||
let intervals = d.selectedTabInfo.modelItems.map(item => {
|
||||
return {
|
||||
startTimestamp: item.start,
|
||||
endTimestamp: item.end
|
||||
}
|
||||
})
|
||||
collectCommunityMetricsMessagesTimestamps(JSON.stringify(intervals))
|
||||
}
|
||||
|
||||
onVisibleChanged: if (visible) requestCommunityMetrics()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
@ -219,6 +233,8 @@ StatusChartPanel {
|
|||
return leftPositon ? Qt.point(relativeMousePoint.x - toolTip.width - 15, relativeMousePoint.y - 5)
|
||||
: Qt.point(relativeMousePoint.x + 15, relativeMousePoint.y - 5)
|
||||
}
|
||||
|
||||
onSelectedTabInfoChanged: root.requestCommunityMetrics()
|
||||
}
|
||||
headerLeftPadding: 0
|
||||
headerBottomPadding: Style.current.bigPadding
|
||||
|
|
|
@ -40,6 +40,8 @@ StackLayout {
|
|||
property int loginType: Constants.LoginType.Password
|
||||
property bool communitySettingsDisabled
|
||||
|
||||
property string overviewChartData: ""
|
||||
|
||||
function navigateBack() {
|
||||
if (editSettingsPanelLoader.item.dirty)
|
||||
settingsDirtyToastMessage.notifyDirty()
|
||||
|
@ -47,6 +49,8 @@ StackLayout {
|
|||
root.currentIndex = 0
|
||||
}
|
||||
|
||||
signal collectCommunityMetricsMessagesTimestamps(var intervals)
|
||||
|
||||
signal edited(Item item) // item containing edited fields (name, description, logoImagePath, color, options, etc..)
|
||||
|
||||
signal inviteNewPeopleClicked
|
||||
|
@ -113,11 +117,21 @@ StackLayout {
|
|||
}
|
||||
|
||||
OverviewSettingsChart {
|
||||
model: JSON.parse(root.overviewChartData)
|
||||
onCollectCommunityMetricsMessagesTimestamps: {
|
||||
root.collectCommunityMetricsMessagesTimestamps(intervals)
|
||||
}
|
||||
Layout.topMargin: 16
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
onCommunityIdChanged: requestCommunityMetrics()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
|
@ -176,6 +176,11 @@ StatusSectionLayout {
|
|||
loginType: root.rootStore.loginType
|
||||
isControlNode: root.isControlNode
|
||||
communitySettingsDisabled: root.communitySettingsDisabled
|
||||
overviewChartData: rootStore.overviewChartData
|
||||
|
||||
onCollectCommunityMetricsMessagesTimestamps: {
|
||||
rootStore.collectCommunityMetricsMessagesTimestamps(intervals)
|
||||
}
|
||||
|
||||
onEdited: {
|
||||
const error = root.chatCommunitySectionModule.editCommunity(
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0ae7aa44f00bff345539c1e288705057e7e4574c
|
||||
Subproject commit 4ad84d80cc7b0363f3c8da589d7bb8930fb8a629
|
Loading…
Reference in New Issue