From 67a635b9e7d5e5e1fa1dab8696a072192af5bc45 Mon Sep 17 00:00:00 2001 From: MishkaRogachev Date: Tue, 25 Jul 2023 17:13:15 +0400 Subject: [PATCH] feat: proposal for collecting community metrics https://github.com/status-im/status-desktop/issues/11152 --- protocol/messenger_community_metrics.go | 26 ++++++++ protocol/messenger_community_metrics_test.go | 30 +++++++++ protocol/persistence_metrics.go | 65 +++++++++++++++++++ protocol/persistence_metrics_test.go | 1 + .../requests/community_metrics_request.go | 42 ++++++++++++ services/ext/api.go | 4 ++ 6 files changed, 168 insertions(+) create mode 100644 protocol/messenger_community_metrics.go create mode 100644 protocol/messenger_community_metrics_test.go create mode 100644 protocol/persistence_metrics.go create mode 100644 protocol/persistence_metrics_test.go create mode 100644 protocol/requests/community_metrics_request.go diff --git a/protocol/messenger_community_metrics.go b/protocol/messenger_community_metrics.go new file mode 100644 index 00000000000..0a9184422d6 --- /dev/null +++ b/protocol/messenger_community_metrics.go @@ -0,0 +1,26 @@ +package protocol + +import ( + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/requests" +) + +type CommunityMetricsResponse struct { + Type requests.CommunityMetricsRequestType `json:"type"` + CommunityID types.HexBytes `json:"communityId"` + Entries map[uint64]int32 `json:"entries"` +} + +func (m *Messenger) CollectCommunityMetrics(request *requests.CommunityMetricsRequest) (*CommunityMetricsResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + response := &CommunityMetricsResponse{ + Type: request.Type, + CommunityID: request.CommunityID, + // TODO: collect entries + } + + return response, nil +} diff --git a/protocol/messenger_community_metrics_test.go b/protocol/messenger_community_metrics_test.go new file mode 100644 index 00000000000..fe40e76625a --- /dev/null +++ b/protocol/messenger_community_metrics_test.go @@ -0,0 +1,30 @@ +package protocol + +import ( + "testing" + + "github.com/status-im/status-go/protocol/requests" + "github.com/stretchr/testify/suite" +) + +func TestMessengerCommunityMetricsSuite(t *testing.T) { + suite.Run(t, new(MessengerCommunityMetricsSuite)) +} + +type MessengerCommunityMetricsSuite struct { + MessengerBaseTestSuite +} + +func (s *MessengerCommunityMetricsSuite) TestCollectCommunityMessageMetrics() { + request := &requests.CommunityMetricsRequest{ + CommunityID: []byte("0x654321"), + Type: requests.CommunityMetricsRequestMessages, + StartTimestamp: 1690279200, + EndTimestamp: 1690282800, // one hour + MaxCount: 10, + } + // Send contact request + resp, err := s.m.CollectCommunityMetrics(request) + s.Require().NoError(err) + s.Require().NotNil(resp) +} diff --git a/protocol/persistence_metrics.go b/protocol/persistence_metrics.go new file mode 100644 index 00000000000..9780f369c0a --- /dev/null +++ b/protocol/persistence_metrics.go @@ -0,0 +1,65 @@ +package protocol + +import ( + "context" + "database/sql" +) + +func (db sqlitePersistence) fetchMessagesCountForPeriod(tx *sql.Tx, chatID string, startTimestamp uint64, endTimestamp uint64) (int, error) { + var result int + err := tx.QueryRow(` + SELECT COUNT(*) FROM user_messages + WHERE local_chat_id = ? AND + timestamp >= ? AND + timestamp <= ?`, + chatID, + startTimestamp, + endTimestamp).Scan(&result) + if err != nil { + return 0, err + } + return result, nil +} + +func (db sqlitePersistence) FetchMessagesCountForPeriod(chatID string, startTimestamp uint64, endTimestamp uint64) (int, error) { + tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + return 0, err + } + defer func() { + if err == nil { + err = tx.Commit() + return + } + // don't shadow original error + _ = tx.Rollback() + }() + + return db.fetchMessagesCountForPeriod(tx, chatID, startTimestamp, endTimestamp) +} + +func (db sqlitePersistence) FetchMessagesCountSequenceForPeriod(chatID string, startTimestamp uint64, endTimestamp uint64, timestampStep uint64) ([]int, error) { + tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + return []int{}, err + } + defer func() { + if err == nil { + err = tx.Commit() + return + } + // don't shadow original error + _ = tx.Rollback() + }() + + var sequence []int + for i := startTimestamp; i <= endTimestamp; i += timestampStep { + count, err := db.fetchMessagesCountForPeriod(tx, chatID, startTimestamp, endTimestamp) + if err != nil { + return []int{}, err + } + + sequence = append(sequence, count) + } + return sequence, nil +} diff --git a/protocol/persistence_metrics_test.go b/protocol/persistence_metrics_test.go new file mode 100644 index 00000000000..a8caf75edcf --- /dev/null +++ b/protocol/persistence_metrics_test.go @@ -0,0 +1 @@ +package protocol \ No newline at end of file diff --git a/protocol/requests/community_metrics_request.go b/protocol/requests/community_metrics_request.go new file mode 100644 index 00000000000..9b169f1eb42 --- /dev/null +++ b/protocol/requests/community_metrics_request.go @@ -0,0 +1,42 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrNoCommunityId = errors.New("community metrics request has no community id") +var ErrInvalidTimeInterval = errors.New("community metrics request invalid time interval") +var ErrInvalidMaxCount = errors.New("community metrics request max count should be gratear than zero") + +type CommunityMetricsRequestType uint + +const ( + CommunityMetricsRequestMessages CommunityMetricsRequestType = iota + 1 + CommunityMetricsRequestMembers + CommunityMetricsRequestControlNodeUptime +) + +type CommunityMetricsRequest struct { + CommunityID types.HexBytes `json:"communityId"` + Type CommunityMetricsRequestType `json:"type"` + StartTimestamp uint64 `json:"startTimestamp"` + EndTimestamp uint64 `json:"endTimestamp"` + MaxCount uint `json:"maxCount"` +} + +func (r *CommunityMetricsRequest) Validate() error { + if len(r.CommunityID) == 0 { + return ErrNoCommunityId + } + + if r.StartTimestamp == 0 || r.EndTimestamp == 0 || r.StartTimestamp >= r.EndTimestamp { + return ErrInvalidTimeInterval + } + + if r.MaxCount < 1 { + return ErrInvalidMaxCount + } + return nil +} diff --git a/services/ext/api.go b/services/ext/api.go index 248154c3ff6..449ca57aaae 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -1357,6 +1357,10 @@ func (api *PublicAPI) CheckAllCommunityChannelsPermissions(request *requests.Che return api.service.messenger.CheckAllCommunityChannelsPermissions(request) } +func (api *PublicAPI) CollectCommunityMetrics(request *requests.CommunityMetricsRequest) (*protocol.CommunityMetricsResponse, error) { + return api.service.messenger.CollectCommunityMetrics(request) +} + func (api *PublicAPI) ShareCommunityURLWithChatKey(communityID types.HexBytes) (string, error) { return api.service.messenger.ShareCommunityURLWithChatKey(communityID) }