mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-09 13:56:10 +00:00
9be2a8d799
This commit includes the following changes: 1. Request from backend the messages count in a specific interval as opposed to all messages timestamps in that interval. 2. Update the chart end date before refreshing the data 3. Fix metrics type parsing in community service 4. Fix a bug where the new incoming data was not processed by ChartJs without a hover event on the chart. The fix here is to manually request paint() on model changes.d Issues found and not handled here: 1. On large communities the backend request can take 3 minutes to complete 2. CPU usage can easily go to 100% when switching chart tabs on large communities. All the requests are processed by the backend.
415 lines
18 KiB
QML
415 lines
18 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
import QtQml 2.15
|
|
|
|
import StatusQ.Popups 0.1
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Components 0.1
|
|
|
|
import utils 1.0
|
|
|
|
StatusChartPanel {
|
|
id: root
|
|
/**
|
|
* Flat model to use for the chart containing timestamps
|
|
* type: {Array}
|
|
* default: []
|
|
* example: {"communityId": "", "metricsType": "MessagesCount", "intervals": [{"startTimestamp": 1691047800000, "endTimestamp": 1691062200000, "timestamps": [], "count": 0}]}
|
|
*/
|
|
property var model: []
|
|
|
|
signal collectCommunityMetricsMessagesCount(var intervals)
|
|
|
|
function reset() {
|
|
d.now = Date.now()
|
|
d.requestCommunityMetrics()
|
|
}
|
|
|
|
onVisibleChanged: if(visible) d.resetWithSpamProtection()
|
|
onTimeRangeTabBarIndexChanged: reset()
|
|
onModelChanged: chart.updateToNewData()
|
|
onCollectCommunityMetricsMessagesCount: d.lastRequestModelMetadata = d.selectedTabInfo.modelItems
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
//visual properties
|
|
readonly property string baseColor1: Theme.palette.baseColor1
|
|
readonly property string twentyPercentBaseColor1: Theme.palette.alphaColor(baseColor1, 0.2)
|
|
readonly property string barColor: Theme.palette.primaryColor2
|
|
readonly property string barBorderColor: Theme.palette.primaryColor1
|
|
readonly property string messagesLabel: qsTr("Messages")
|
|
|
|
property int hoveredBarIndex: 0
|
|
property int hoveredBarValue: 0
|
|
readonly property var hoveredModelMetadata: modelMetadata[root.timeRangeTabBarIndex].modelItems[hoveredBarIndex]
|
|
readonly property var tooltipConfig: modelMetadata[root.timeRangeTabBarIndex].tooltipConfig
|
|
readonly property var graphTabsModel: [{text: messagesLabel, enabled: true}]
|
|
property var now: Date.now()
|
|
property var lastRequestModelMetadata: null
|
|
|
|
readonly property var chartData: selectedTabInfo.modelItems.map(x => d.itemsCountInRange(root.model, x.start, x.end))
|
|
readonly property var labels: selectedTabInfo.modelItems.map(x => x.label)
|
|
readonly property var selectedTabInfo: modelMetadata[root.timeRangeTabBarIndex]
|
|
readonly property var modelMetadata: [
|
|
{
|
|
text: qsTr("1H"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.minutes(60, now), end: LocaleUtils.minutes(50, now), label: minutesStr(55)},
|
|
{ start: LocaleUtils.minutes(50, now), end: LocaleUtils.minutes(40, now), label: minutesStr(45)},
|
|
{ start: LocaleUtils.minutes(40, now), end: LocaleUtils.minutes(30, now), label: minutesStr(35)},
|
|
{ start: LocaleUtils.minutes(30, now), end: LocaleUtils.minutes(20, now), label: minutesStr(25)},
|
|
{ start: LocaleUtils.minutes(20, now), end: LocaleUtils.minutes(10, now), label: minutesStr(15)},
|
|
{ start: LocaleUtils.minutes(10, now), end: LocaleUtils.minutes(0, now, false), label: minutesStr(5)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Time period"),
|
|
timeRangeFormatter: d.hoursRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("1D"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.hours(24, now), end: LocaleUtils.hours(20, now), label: hourStr(22)},
|
|
{ start: LocaleUtils.hours(20, now), end: LocaleUtils.hours(16, now), label: hourStr(18)},
|
|
{ start: LocaleUtils.hours(16, now), end: LocaleUtils.hours(12, now), label: hourStr(14)},
|
|
{ start: LocaleUtils.hours(12, now), end: LocaleUtils.hours(8, now), label: hourStr(10)},
|
|
{ start: LocaleUtils.hours(8, now), end: LocaleUtils.hours(4, now), label: hourStr(6)},
|
|
{ start: LocaleUtils.hours(4, now), end: LocaleUtils.hours(0, now, false), label: hourStr(2)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Time period"),
|
|
timeRangeFormatter: d.hoursRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("7D"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.days(6, now), end: LocaleUtils.days(5, now), label: dayStr(6)},
|
|
{ start: LocaleUtils.days(5, now), end: LocaleUtils.days(4, now), label: dayStr(5)},
|
|
{ start: LocaleUtils.days(4, now), end: LocaleUtils.days(3, now), label: dayStr(4)},
|
|
{ start: LocaleUtils.days(3, now), end: LocaleUtils.days(2, now), label: dayStr(3)},
|
|
{ start: LocaleUtils.days(2, now), end: LocaleUtils.days(1, now), label: dayStr(2)},
|
|
{ start: LocaleUtils.days(1, now), end: LocaleUtils.days(0, now), label: dayStr(1)},
|
|
{ start: LocaleUtils.days(0, now), end: LocaleUtils.days(0, now, false), label: dayStr(0)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Date"),
|
|
timeRangeFormatter: d.daysRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("1M"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.days(30, now), end: LocaleUtils.days(25, now), label: dayStr(30)},
|
|
{ start: LocaleUtils.days(25, now), end: LocaleUtils.days(20, now), label: dayStr(25)},
|
|
{ start: LocaleUtils.days(20, now), end: LocaleUtils.days(15, now), label: dayStr(20)},
|
|
{ start: LocaleUtils.days(15, now), end: LocaleUtils.days(10, now), label: dayStr(15)},
|
|
{ start: LocaleUtils.days(10, now), end: LocaleUtils.days(5, now), label: dayStr(10)},
|
|
{ start: LocaleUtils.days(5, now), end: LocaleUtils.days(0, now, false), label: dayStr(5)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Time period"),
|
|
timeRangeFormatter: d.daysRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("6M"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.months(5, now), end: LocaleUtils.months(4, now), label: monthStr(5)},
|
|
{ start: LocaleUtils.months(4, now), end: LocaleUtils.months(3, now), label: monthStr(4)},
|
|
{ start: LocaleUtils.months(3, now), end: LocaleUtils.months(2, now), label: monthStr(3)},
|
|
{ start: LocaleUtils.months(2, now), end: LocaleUtils.months(1, now), label: monthStr(2)},
|
|
{ start: LocaleUtils.months(1, now), end: LocaleUtils.months(0, now), label: monthStr(1)},
|
|
{ start: LocaleUtils.months(0, now), end: LocaleUtils.months(0, now, false), label: monthStr(0)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Month"),
|
|
timeRangeFormatter: d.monthsRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("1Y"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.months(12, now), end: LocaleUtils.months(10, now), label: monthStr(11)},
|
|
{ start: LocaleUtils.months(10, now), end: LocaleUtils.months(8, now), label: monthStr(9)},
|
|
{ start: LocaleUtils.months(8, now), end: LocaleUtils.months(6, now), label: monthStr(7)},
|
|
{ start: LocaleUtils.months(6, now), end: LocaleUtils.months(4, now), label: monthStr(5)},
|
|
{ start: LocaleUtils.months(4, now), end: LocaleUtils.months(2, now), label: monthStr(3)},
|
|
{ start: LocaleUtils.months(2, now), end: LocaleUtils.months(0, now, false), label: monthStr(1)}
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Time period"),
|
|
timeRangeFormatter: d.monthsRangeStr
|
|
},
|
|
},
|
|
{
|
|
text: qsTr("ALL"),
|
|
modelItems: [
|
|
{ start: LocaleUtils.years(7, now), end: LocaleUtils.years(6, now), label: yearsStr(7) },
|
|
{ start: LocaleUtils.years(6, now), end: LocaleUtils.years(5, now), label: yearsStr(6) },
|
|
{ start: LocaleUtils.years(5, now), end: LocaleUtils.years(4, now), label: yearsStr(5) },
|
|
{ start: LocaleUtils.years(4, now), end: LocaleUtils.years(3, now), label: yearsStr(4) },
|
|
{ start: LocaleUtils.years(3, now), end: LocaleUtils.years(2, now), label: yearsStr(3) },
|
|
{ start: LocaleUtils.years(2, now), end: LocaleUtils.years(1, now), label: yearsStr(2) },
|
|
{ start: LocaleUtils.years(1, now), end: LocaleUtils.years(0, now), label: yearsStr(1) },
|
|
{ start: LocaleUtils.years(0, now), end: LocaleUtils.years(0, now, false), label: yearsStr(0) }
|
|
],
|
|
tooltipConfig: {
|
|
timeRangeString: qsTr("Year"),
|
|
timeRangeFormatter: d.yearsRangeStr
|
|
},
|
|
}
|
|
]
|
|
|
|
function resetWithSpamProtection() {
|
|
if(Date.now() - d.now > LocaleUtils.minutesToMs(5) || d.lastRequestModelMetadata != selectedTabInfo.modelItems) {
|
|
root.reset()
|
|
}
|
|
}
|
|
|
|
function requestCommunityMetrics() {
|
|
let intervals = d.selectedTabInfo.modelItems.map(item => {
|
|
return {
|
|
startTimestamp: item.start,
|
|
endTimestamp: item.end
|
|
}
|
|
})
|
|
root.collectCommunityMetricsMessagesCount(JSON.stringify(intervals))
|
|
}
|
|
|
|
function itemsCountInRange(model, start, end) {
|
|
if (model == undefined || model.intervals == undefined)
|
|
return 0
|
|
|
|
const interval = model.intervals.find(x => x.startTimestamp == start && x.endTimestamp == end)
|
|
|
|
if (!interval)
|
|
return 0
|
|
|
|
if(model.metricsType === "MessagesTimestamps")
|
|
return interval.timestamps.length
|
|
|
|
return interval.count
|
|
}
|
|
|
|
function minutesStr(before = 0, timeReference = now, roundCurrentTime = true) {
|
|
return LocaleUtils.formatTime(LocaleUtils.minutes(before, timeReference, roundCurrentTime), Locale.ShortFormat)
|
|
}
|
|
|
|
function hourStr(before = 0, timeReference = now, roundCurrentTime = true) {
|
|
return LocaleUtils.formatTime(LocaleUtils.hours(before, timeReference, roundCurrentTime), Locale.ShortFormat)
|
|
}
|
|
|
|
function dayStr(before = 0, timeReference = now, roundCurrentTime = true) {
|
|
return LocaleUtils.getDayMonth(LocaleUtils.days(before, timeReference, roundCurrentTime), Locale.ShortFormat)
|
|
}
|
|
|
|
function monthStr(before = 0, timeReference = now, roundCurrentTime = true, shortFormat = true) {
|
|
const format = shortFormat ? "MMM" : "MMMM"
|
|
const timeStamp = LocaleUtils.months(before, timeReference, roundCurrentTime)
|
|
return LocaleUtils.formatDate(timeStamp, format)
|
|
}
|
|
|
|
function yearsStr(before = 0, timeReference = now, roundCurrentTime = true) {
|
|
return LocaleUtils.formatDate(LocaleUtils.years(before, timeReference, roundCurrentTime), "yyyy");
|
|
}
|
|
|
|
function hoursRangeStr(start, end) {
|
|
return "%1 - %2".arg(hourStr(0, start, false)).arg(hourStr(0, end, false))
|
|
}
|
|
|
|
function daysRangeStr(start, end) {
|
|
return (end - start > LocaleUtils.daysToMs(1)) ?
|
|
"%1 - %2".arg(dayStr(0, start, false)).arg(dayStr(0, end, false)) :
|
|
dayStr(0, start, false)
|
|
}
|
|
|
|
function monthsRangeStr(start, end) {
|
|
//End date excluded
|
|
//Adjust by one ms to exclude the end date
|
|
//To avoid considering the end date as a new month
|
|
end = end - 1
|
|
const startDate = monthStr(0, start, false)
|
|
const endDate = monthStr(0, end, false)
|
|
return (startDate !== endDate) ?
|
|
"%1 - %2".arg(startDate).arg(endDate) :
|
|
monthStr(0, start, false, false)
|
|
}
|
|
|
|
function yearsRangeStr(start, end) {
|
|
//End date excluded
|
|
//Adjust by one ms to exclude the end date
|
|
//To avoid considering the end date as a new year
|
|
end = end - 1
|
|
const startYear = yearsStr(0, start, false)
|
|
const endYear = yearsStr(0, end, false)
|
|
return (startYear !== endYear) ?
|
|
"%1 - %2".arg(startYear).arg(endYear) :
|
|
startYear
|
|
}
|
|
|
|
function getAdjustedTooltipPosition(event) {
|
|
// By defaullt the popup is displayed on the right of the cursor
|
|
// If there is not enough space on the right, display it on the left
|
|
const relativeMousePoint = event.target.mapToItem(toolTip.parent, event.x, event.y) // relative to tooltip parent
|
|
const leftPositon = (toolTip.parent.width - (toolTip.width + toolTip.rightPadding + relativeMousePoint.x + 15)) < 0
|
|
return leftPositon ? Qt.point(relativeMousePoint.x - toolTip.width - 15, relativeMousePoint.y - 5)
|
|
: Qt.point(relativeMousePoint.x + 15, relativeMousePoint.y - 5)
|
|
}
|
|
}
|
|
|
|
headerLeftPadding: 0
|
|
headerBottomPadding: Style.current.bigPadding
|
|
graphsModel: d.graphTabsModel
|
|
timeRangeModel: d.modelMetadata
|
|
onHeaderTabClicked: {
|
|
root.chart.animateToNewData();
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Chartjs configuration //
|
|
/////////////////////////////
|
|
chart.chartType: 'bar'
|
|
chart.chartData: {
|
|
return {
|
|
labels: d.labels,
|
|
datasets: [{
|
|
xAxisId: 'x-axis-1',
|
|
yAxisId: 'y-axis-1',
|
|
backgroundColor: d.barColor,
|
|
pointRadius: 0,
|
|
hoverBackgroundColor: d.barColor,
|
|
hoverBorderColor: d.barBorderColor,
|
|
hoverBorderWidth: 2,
|
|
data: d.chartData
|
|
}]
|
|
}
|
|
}
|
|
|
|
chart.chartOptions: {
|
|
return {
|
|
maintainAspectRatio: false,
|
|
responsive: true,
|
|
legend: {
|
|
display: false
|
|
},
|
|
// Popup follows the cursor
|
|
onHover: function(arg1, hoveredItems, event) {
|
|
if(!event || hoveredItems.length == 0) {
|
|
toolTip.close()
|
|
return
|
|
}
|
|
|
|
d.hoveredBarIndex = hoveredItems[0]._index
|
|
d.hoveredBarValue = hoveredItems[0]._chart.config.data.datasets[0].data[hoveredItems[0]._index]
|
|
const position = d.getAdjustedTooltipPosition(event)
|
|
toolTip.popup(position.x, position.y)
|
|
},
|
|
tooltips: {
|
|
enabled: false,
|
|
},
|
|
scales: {
|
|
xAxes: [{
|
|
id: 'x-axis-1',
|
|
position: 'bottom',
|
|
stacked: false,
|
|
gridLines: {
|
|
drawOnChartArea: false,
|
|
drawBorder: false,
|
|
drawTicks: false,
|
|
},
|
|
ticks: {
|
|
fontSize: Style.current.asideTextFontSize,
|
|
fontColor: d.baseColor1,
|
|
padding: Style.current.padding,
|
|
}
|
|
}],
|
|
yAxes: [{
|
|
position: 'left',
|
|
id: 'y-axis-1',
|
|
stacked: false,
|
|
gridLines: {
|
|
borderDash: [5, 3],
|
|
lineWidth: 1,
|
|
drawBorder: false,
|
|
drawTicks: false,
|
|
color: d.twentyPercentBaseColor1,
|
|
},
|
|
beforeDataLimits: (axis) => {
|
|
axis.paddingTop = 25;
|
|
axis.paddingBottom = 0;
|
|
},
|
|
ticks: {
|
|
fontSize: 10,
|
|
fontColor: d.baseColor1,
|
|
padding: Style.current.halfPadding,
|
|
maxTicksLimit: Style.current.asideTextFontSize,
|
|
beginAtZero: true,
|
|
stepSize: 1,
|
|
callback: function(value, index, values) {
|
|
return LocaleUtils.numberToLocaleString(value)
|
|
}
|
|
}
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
|
|
StatusMenu {
|
|
id: toolTip
|
|
width: 243 //By design
|
|
topPadding: Style.current.padding
|
|
bottomPadding: topPadding
|
|
leftPadding: topPadding
|
|
rightPadding: topPadding
|
|
parent: Overlay.overlay
|
|
|
|
ColumnLayout {
|
|
spacing: Style.current.padding
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
StatusBaseText {
|
|
elide: Qt.ElideRight
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
|
color: Theme.palette.baseColor1
|
|
text: d.tooltipConfig.timeRangeString
|
|
}
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
StatusBaseText {
|
|
Layout.alignment: Qt.AlignRight
|
|
elide: Qt.ElideRight
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
|
color: Theme.palette.directColor1
|
|
text: d.hoveredModelMetadata ? d.tooltipConfig.timeRangeFormatter(d.hoveredModelMetadata.start, d.hoveredModelMetadata.end)
|
|
: ""
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
StatusBaseText {
|
|
elide: Qt.ElideRight
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
|
color: Theme.palette.baseColor1
|
|
text: qsTr("No. of Messages")
|
|
}
|
|
Item { Layout.fillWidth: true }
|
|
StatusBaseText {
|
|
Layout.alignment: Qt.AlignRight
|
|
elide: Qt.ElideRight
|
|
font.pixelSize: Style.current.primaryTextFontSize
|
|
color: Theme.palette.directColor1
|
|
text: LocaleUtils.numberToLocaleString(d.hoveredBarValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|