From 6c7c98735ca92691871f848d695c293d9550e47d Mon Sep 17 00:00:00 2001
From: Sergey <83376337+freak12techno@users.noreply.github.com>
Date: Mon, 24 Apr 2023 23:32:51 +0300
Subject: [PATCH] feat: refactor events (#45)
* feat: refactor events
* chore: fix linting
* chore: fix tests
* chore: serialize errors to json correctly
* chore: fix tests
---
pkg/events/not_voted.go | 32 ++++++
pkg/events/proposals_query_error.go | 18 ++++
pkg/events/revoted.go | 34 ++++++
pkg/events/vote_query_error.go | 20 ++++
pkg/events/voted.go | 33 ++++++
pkg/report/entry/entry.go | 18 ++++
pkg/report/generator.go | 65 ++++++------
pkg/report/generator_test.go | 53 ++++++----
pkg/reporters/pagerduty/pagerduty.go | 100 ++++++++++--------
pkg/reporters/reporter.go | 48 +--------
pkg/reporters/telegram/add_mute.go | 2 +-
pkg/reporters/telegram/help.go | 2 +-
pkg/reporters/telegram/list_mutes.go | 2 +-
pkg/reporters/telegram/list_proposals.go | 2 +-
pkg/reporters/telegram/telegram.go | 57 +++++-----
pkg/state/generator.go | 2 +-
pkg/state/state.go | 12 +--
pkg/state/state_test.go | 4 +-
pkg/types/error.go | 24 +++++
pkg/types/responses.go | 4 +
pkg/types/types.go | 10 --
templates/telegram/not_voted.html | 8 +-
..._error.html => proposals_query_error.html} | 2 +-
templates/telegram/revoted.html | 12 +--
templates/telegram/vote_query_error.html | 4 +-
templates/telegram/voted.html | 10 +-
26 files changed, 368 insertions(+), 210 deletions(-)
create mode 100644 pkg/events/not_voted.go
create mode 100644 pkg/events/proposals_query_error.go
create mode 100644 pkg/events/revoted.go
create mode 100644 pkg/events/vote_query_error.go
create mode 100644 pkg/events/voted.go
create mode 100644 pkg/report/entry/entry.go
create mode 100644 pkg/types/error.go
rename templates/telegram/{proposal_query_error.html => proposals_query_error.html} (80%)
diff --git a/pkg/events/not_voted.go b/pkg/events/not_voted.go
new file mode 100644
index 0000000..e8d28ec
--- /dev/null
+++ b/pkg/events/not_voted.go
@@ -0,0 +1,32 @@
+package events
+
+import (
+ configTypes "main/pkg/config/types"
+ "main/pkg/types"
+)
+
+type NotVotedEvent struct {
+ Chain *configTypes.Chain
+ Wallet *configTypes.Wallet
+ Proposal types.Proposal
+}
+
+func (e NotVotedEvent) Name() string {
+ return "not_voted"
+}
+
+func (e NotVotedEvent) IsAlert() bool {
+ return true
+}
+
+func (e NotVotedEvent) GetChain() *configTypes.Chain {
+ return e.Chain
+}
+
+func (e NotVotedEvent) GetProposal() types.Proposal {
+ return e.Proposal
+}
+
+func (e NotVotedEvent) GetWallet() *configTypes.Wallet {
+ return e.Wallet
+}
diff --git a/pkg/events/proposals_query_error.go b/pkg/events/proposals_query_error.go
new file mode 100644
index 0000000..25c71c3
--- /dev/null
+++ b/pkg/events/proposals_query_error.go
@@ -0,0 +1,18 @@
+package events
+
+import (
+ configTypes "main/pkg/config/types"
+)
+
+type ProposalsQueryErrorEvent struct {
+ Chain *configTypes.Chain
+ Error error
+}
+
+func (e ProposalsQueryErrorEvent) Name() string {
+ return "proposals_query_error"
+}
+
+func (e ProposalsQueryErrorEvent) IsAlert() bool {
+ return false
+}
diff --git a/pkg/events/revoted.go b/pkg/events/revoted.go
new file mode 100644
index 0000000..ad2b1ae
--- /dev/null
+++ b/pkg/events/revoted.go
@@ -0,0 +1,34 @@
+package events
+
+import (
+ configTypes "main/pkg/config/types"
+ "main/pkg/types"
+)
+
+type RevotedEvent struct {
+ Chain *configTypes.Chain
+ Wallet *configTypes.Wallet
+ Proposal types.Proposal
+ Vote *types.Vote
+ OldVote *types.Vote
+}
+
+func (e RevotedEvent) Name() string {
+ return "revoted"
+}
+
+func (e RevotedEvent) IsAlert() bool {
+ return true
+}
+
+func (e RevotedEvent) GetChain() *configTypes.Chain {
+ return e.Chain
+}
+
+func (e RevotedEvent) GetProposal() types.Proposal {
+ return e.Proposal
+}
+
+func (e RevotedEvent) GetWallet() *configTypes.Wallet {
+ return e.Wallet
+}
diff --git a/pkg/events/vote_query_error.go b/pkg/events/vote_query_error.go
new file mode 100644
index 0000000..c87925c
--- /dev/null
+++ b/pkg/events/vote_query_error.go
@@ -0,0 +1,20 @@
+package events
+
+import (
+ configTypes "main/pkg/config/types"
+ "main/pkg/types"
+)
+
+type VoteQueryError struct {
+ Chain *configTypes.Chain
+ Proposal types.Proposal
+ Error error
+}
+
+func (e VoteQueryError) Name() string {
+ return "vote_query_error"
+}
+
+func (e VoteQueryError) IsAlert() bool {
+ return true
+}
diff --git a/pkg/events/voted.go b/pkg/events/voted.go
new file mode 100644
index 0000000..d3b1599
--- /dev/null
+++ b/pkg/events/voted.go
@@ -0,0 +1,33 @@
+package events
+
+import (
+ configTypes "main/pkg/config/types"
+ "main/pkg/types"
+)
+
+type VotedEvent struct {
+ Chain *configTypes.Chain
+ Wallet *configTypes.Wallet
+ Proposal types.Proposal
+ Vote *types.Vote
+}
+
+func (e VotedEvent) Name() string {
+ return "voted"
+}
+
+func (e VotedEvent) IsAlert() bool {
+ return true
+}
+
+func (e VotedEvent) GetChain() *configTypes.Chain {
+ return e.Chain
+}
+
+func (e VotedEvent) GetProposal() types.Proposal {
+ return e.Proposal
+}
+
+func (e VotedEvent) GetWallet() *configTypes.Wallet {
+ return e.Wallet
+}
diff --git a/pkg/report/entry/entry.go b/pkg/report/entry/entry.go
new file mode 100644
index 0000000..870824c
--- /dev/null
+++ b/pkg/report/entry/entry.go
@@ -0,0 +1,18 @@
+package entry
+
+import (
+ configTypes "main/pkg/config/types"
+ "main/pkg/types"
+)
+
+type ReportEntry interface {
+ Name() string
+ IsAlert() bool // only voted/not_voted are alerts, required for PagerDuty
+}
+
+type ReportEntryNotError interface {
+ ReportEntry
+ GetChain() *configTypes.Chain
+ GetWallet() *configTypes.Wallet
+ GetProposal() types.Proposal
+}
diff --git a/pkg/report/generator.go b/pkg/report/generator.go
index 612bbd0..e16d14b 100644
--- a/pkg/report/generator.go
+++ b/pkg/report/generator.go
@@ -1,13 +1,12 @@
package report
import (
- "time"
-
configTypes "main/pkg/config/types"
+ "main/pkg/events"
+ "main/pkg/report/entry"
"main/pkg/reporters"
"main/pkg/state"
"main/pkg/tendermint"
- "main/pkg/types"
"github.com/rs/zerolog"
)
@@ -32,20 +31,17 @@ func NewReportGenerator(
}
func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Report {
- entries := []reporters.ReportEntry{}
+ entries := []entry.ReportEntry{}
for chainName, chainInfo := range newState.ChainInfos {
if chainInfo.HasProposalsError() {
g.Logger.Debug().
Str("chain", chainName).
Msg("Error querying for proposals - sending an alert")
- entry := reporters.ReportEntry{
- Chain: g.Chains.FindByName(chainName),
- ProposalVoteEndingTime: time.Now(),
- Type: types.ProposalQueryError,
- Value: chainInfo.ProposalsError,
- }
- entries = append(entries, entry)
+ entries = append(entries, events.ProposalsQueryErrorEvent{
+ Chain: g.Chains.FindByName(chainName),
+ Error: chainInfo.ProposalsError,
+ })
continue
}
@@ -60,15 +56,6 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
oldVote, _, _ := oldState.GetVoteAndProposal(chainName, proposalID, wallet)
newVote, proposal, _ := newState.GetVoteAndProposal(chainName, proposalID, wallet)
- entry := reporters.ReportEntry{
- Chain: g.Chains.FindByName(chainName),
- Wallet: newVote.Wallet,
- ProposalID: proposalID,
- ProposalTitle: proposal.Content.Title,
- ProposalDescription: proposal.Content.Description,
- ProposalVoteEndingTime: proposal.VotingEndTime,
- }
-
// Error querying for vote - need to notify via Telegram.
if newVote.IsError() {
g.Logger.Debug().
@@ -76,10 +63,12 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
Str("proposal", proposalID).
Str("wallet", wallet).
Msg("Error querying for vote - sending an alert")
- entry.Type = types.VoteQueryError
- entry.Value = newVote.Error
+ entries = append(entries, events.VoteQueryError{
+ Chain: g.Chains.FindByName(chainName),
+ Proposal: proposal,
+ Error: newVote.Error,
+ })
- entries = append(entries, entry)
continue
}
@@ -90,25 +79,28 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
Str("proposal", proposalID).
Str("wallet", wallet).
Msg("Wallet hasn't voted now - sending an alert")
- entry.Type = types.NotVoted
- entries = append(entries, entry)
+ entries = append(entries, events.NotVotedEvent{
+ Chain: g.Chains.FindByName(chainName),
+ Wallet: newVote.Wallet,
+ Proposal: proposal,
+ })
continue
}
// Hasn't voted before but voted now - need to close alert/notify about new vote.
if newVote.HasVoted() && !oldVote.HasVoted() {
- vote := *newVote.Vote
-
g.Logger.Debug().
Str("chain", chainName).
Str("proposal", proposal.ProposalID).
Str("wallet", wallet).
Msg("Wallet hasn't voted before but voted now - closing an alert")
- entry.Type = types.Voted
- entry.Value = vote.Option
-
- entries = append(entries, entry)
+ entries = append(entries, events.VotedEvent{
+ Chain: g.Chains.FindByName(chainName),
+ Wallet: newVote.Wallet,
+ Proposal: proposal,
+ Vote: newVote.Vote,
+ })
continue
}
@@ -119,11 +111,14 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
Str("proposal", proposal.ProposalID).
Str("wallet", wallet).
Msg("Wallet changed its vote - sending an alert")
- entry.Type = types.Revoted
- entry.Value = newVote.Vote.Option
- entry.OldValue = oldVote.Vote.Option
- entries = append(entries, entry)
+ entries = append(entries, events.RevotedEvent{
+ Chain: g.Chains.FindByName(chainName),
+ Wallet: newVote.Wallet,
+ Proposal: proposal,
+ Vote: newVote.Vote,
+ OldVote: oldVote.Vote,
+ })
}
}
}
diff --git a/pkg/report/generator_test.go b/pkg/report/generator_test.go
index b60c68e..0d5c408 100644
--- a/pkg/report/generator_test.go
+++ b/pkg/report/generator_test.go
@@ -1,6 +1,7 @@
package report
import (
+ "main/pkg/events"
"testing"
configTypes "main/pkg/config/types"
@@ -20,7 +21,7 @@ func TestReportGeneratorWithProposalError(t *testing.T) {
newState := state.State{
ChainInfos: map[string]*state.ChainInfo{
"chain": {
- ProposalsError: "test error",
+ ProposalsError: types.NewJSONError("test error"),
},
},
}
@@ -31,8 +32,10 @@ func TestReportGeneratorWithProposalError(t *testing.T) {
report := generator.GenerateReport(oldState, newState)
assert.Equal(t, len(report.Entries), 1, "Expected to have 1 entry!")
- assert.Equal(t, report.Entries[0].Type, types.ProposalQueryError, "Expected to have a proposal query error!")
- assert.Equal(t, report.Entries[0].Value, "test error", "Error text mismatch!")
+
+ entry, ok := report.Entries[0].(events.ProposalsQueryErrorEvent)
+ assert.True(t, ok, "Expected to have a proposal query error!")
+ assert.Equal(t, entry.Error.Error(), "test error", "Error text mismatch!")
}
func TestReportGeneratorWithVoteError(t *testing.T) {
@@ -47,11 +50,12 @@ func TestReportGeneratorWithVoteError(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {
- Error: "test error",
+ Error: types.NewJSONError("test error"),
},
},
},
@@ -66,8 +70,10 @@ func TestReportGeneratorWithVoteError(t *testing.T) {
report := generator.GenerateReport(oldState, newState)
assert.Equal(t, len(report.Entries), 1, "Expected to have 1 entry!")
- assert.Equal(t, report.Entries[0].Type, types.VoteQueryError, "Expected to have a vote query error!")
- assert.Equal(t, report.Entries[0].ProposalID, "proposal", "Proposal ID mismatch!")
+
+ entry, ok := report.Entries[0].(events.VoteQueryError)
+ assert.True(t, ok, "Expected to have a vote query error!")
+ assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithNotVoted(t *testing.T) {
@@ -82,7 +88,8 @@ func TestReportGeneratorWithNotVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {},
@@ -99,8 +106,10 @@ func TestReportGeneratorWithNotVoted(t *testing.T) {
report := generator.GenerateReport(oldState, newState)
assert.Equal(t, len(report.Entries), 1, "Expected to have 1 entry!")
- assert.Equal(t, report.Entries[0].Type, types.NotVoted, "Expected to have not voted type!")
- assert.Equal(t, report.Entries[0].ProposalID, "proposal", "Proposal ID mismatch!")
+
+ entry, ok := report.Entries[0].(events.NotVotedEvent)
+ assert.True(t, ok, "Expected to have not voted type!")
+ assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithVoted(t *testing.T) {
@@ -114,7 +123,8 @@ func TestReportGeneratorWithVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {},
@@ -130,7 +140,8 @@ func TestReportGeneratorWithVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -151,8 +162,10 @@ func TestReportGeneratorWithVoted(t *testing.T) {
report := generator.GenerateReport(oldState, newState)
assert.Equal(t, len(report.Entries), 1, "Expected to have 1 entry!")
- assert.Equal(t, report.Entries[0].Type, types.Voted, "Expected to have voted type!")
- assert.Equal(t, report.Entries[0].ProposalID, "proposal", "Proposal ID mismatch!")
+
+ entry, ok := report.Entries[0].(events.VotedEvent)
+ assert.True(t, ok, "Expected to have voted type!")
+ assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithRevoted(t *testing.T) {
@@ -166,7 +179,8 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -186,7 +200,8 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- Content: &types.ProposalContent{},
+ ProposalID: "proposal",
+ Content: &types.ProposalContent{},
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -207,6 +222,8 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
report := generator.GenerateReport(oldState, newState)
assert.Equal(t, len(report.Entries), 1, "Expected to have 1 entry!")
- assert.Equal(t, report.Entries[0].Type, types.Revoted, "Expected to have revoted type!")
- assert.Equal(t, report.Entries[0].ProposalID, "proposal", "Proposal ID mismatch!")
+
+ entry, ok := report.Entries[0].(events.RevotedEvent)
+ assert.True(t, ok, "Expected to have revoted type!")
+ assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
}
diff --git a/pkg/reporters/pagerduty/pagerduty.go b/pkg/reporters/pagerduty/pagerduty.go
index 20fc162..0b64a5f 100644
--- a/pkg/reporters/pagerduty/pagerduty.go
+++ b/pkg/reporters/pagerduty/pagerduty.go
@@ -3,7 +3,10 @@ package pagerduty
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
+ "main/pkg/events"
+ "main/pkg/report/entry"
"net/http"
"os"
"time"
@@ -14,13 +17,13 @@ import (
"github.com/rs/zerolog"
)
-type PagerDutyReporter struct {
+type Reporter struct {
PagerDutyURL string
APIKey string
Logger zerolog.Logger
}
-type PagerDutyAlertPayload struct {
+type AlertPayload struct {
Summary string `json:"summary"`
Timestamp string `json:"timestamp"`
Severity string `json:"severity"`
@@ -28,37 +31,42 @@ type PagerDutyAlertPayload struct {
CustomDetails map[string]string `json:"custom_details"`
}
-type PagerDutyLink struct {
+type Link struct {
Href string `json:"href"`
Text string `json:"text"`
}
-type PagerDutyAlert struct {
- Payload PagerDutyAlertPayload `json:"payload"`
- RoutingKey string `json:"routing_key"`
- EventAction string `json:"event_action"`
- DedupKey string `json:"dedup_key"`
- Client string `json:"client"`
- Links []PagerDutyLink `json:"links"`
- ClientURL string `json:"client_url"`
+type Alert struct {
+ Payload AlertPayload `json:"payload"`
+ RoutingKey string `json:"routing_key"`
+ EventAction string `json:"event_action"`
+ DedupKey string `json:"dedup_key"`
+ Client string `json:"client"`
+ Links []Link `json:"links"`
+ ClientURL string `json:"client_url"`
}
-type PagerDutyResponse struct {
+type Response struct {
Status string
Message string
}
-func (r *PagerDutyReporter) NewPagerDutyAlertFromReportEntry(e reporters.ReportEntry) PagerDutyAlert {
+func (r *Reporter) NewAlertFromReportEntry(eventRaw entry.ReportEntry) (Alert, error) {
+ event, ok := eventRaw.(entry.ReportEntryNotError)
+ if !ok {
+ return Alert{}, errors.New("error converting alert entry")
+ }
+
eventAction := "trigger"
- if e.HasVoted() {
+ if _, ok := event.(events.VotedEvent); ok {
eventAction = "resolve"
}
dedupKey := fmt.Sprintf(
"cosmos-proposals-checker alert chain=%s proposal=%s wallet=%s",
- e.Chain.Name,
- e.ProposalID,
- e.Wallet,
+ event.GetChain().Name,
+ event.GetProposal().ProposalID,
+ event.GetWallet().AddressOrAlias(),
)
hostname, err := os.Hostname()
@@ -66,33 +74,33 @@ func (r *PagerDutyReporter) NewPagerDutyAlertFromReportEntry(e reporters.ReportE
hostname = "unknown-host"
}
- links := []PagerDutyLink{}
- explorerLinks := e.Chain.GetExplorerProposalsLinks(e.ProposalID)
+ links := []Link{}
+ explorerLinks := event.GetChain().GetExplorerProposalsLinks(event.GetProposal().ProposalID)
for _, link := range explorerLinks {
- links = append(links, PagerDutyLink{
+ links = append(links, Link{
Href: link.Href,
Text: link.Name,
})
}
- return PagerDutyAlert{
- Payload: PagerDutyAlertPayload{
+ return Alert{
+ Payload: AlertPayload{
Summary: fmt.Sprintf(
"Wallet %s hasn't voted on proposal %s on %s: %s",
- e.Wallet,
- e.ProposalID,
- e.Chain.GetName(),
- e.ProposalTitle,
+ event.GetWallet().AddressOrAlias(),
+ event.GetProposal().ProposalID,
+ event.GetChain().GetName(),
+ event.GetProposal().Content.Title,
),
Timestamp: time.Now().Format(time.RFC3339),
Severity: "error",
Source: hostname,
CustomDetails: map[string]string{
- "Wallet": e.Wallet.AddressOrAlias(),
- "Chain": e.Chain.GetName(),
- "Proposal ID": e.ProposalID,
- "Proposal title": e.ProposalTitle,
- "Proposal description": e.ProposalDescription,
+ "Wallet": event.GetWallet().AddressOrAlias(),
+ "Chain": event.GetChain().GetName(),
+ "Proposal ID": event.GetProposal().ProposalID,
+ "Proposal title": event.GetProposal().Content.Title,
+ "Proposal description": event.GetProposal().Content.Description,
},
},
Links: links,
@@ -101,37 +109,41 @@ func (r *PagerDutyReporter) NewPagerDutyAlertFromReportEntry(e reporters.ReportE
DedupKey: dedupKey,
Client: "cosmos-proposals-checker",
ClientURL: "https://github.com/QuokkaStake/cosmos-proposals-checker",
- }
+ }, nil
}
-func NewPagerDutyReporter(config config.PagerDutyConfig, logger *zerolog.Logger) PagerDutyReporter {
- return PagerDutyReporter{
+func NewPagerDutyReporter(config config.PagerDutyConfig, logger *zerolog.Logger) Reporter {
+ return Reporter{
PagerDutyURL: config.PagerDutyURL,
APIKey: config.APIKey,
Logger: logger.With().Str("component", "pagerduty_reporter").Logger(),
}
}
-func (r PagerDutyReporter) Init() {
+func (r Reporter) Init() {
}
-func (r PagerDutyReporter) Enabled() bool {
+func (r Reporter) Enabled() bool {
return r.APIKey != ""
}
-func (r PagerDutyReporter) Name() string {
+func (r Reporter) Name() string {
return "pagerduty-reporter"
}
-func (r PagerDutyReporter) SendReport(report reporters.Report) error {
+func (r Reporter) SendReport(report reporters.Report) error {
var err error
- for _, entry := range report.Entries {
- if !entry.IsVoteOrNotVoted() {
+ for _, reportEntry := range report.Entries {
+ if !reportEntry.IsAlert() {
continue
}
- alert := r.NewPagerDutyAlertFromReportEntry(entry)
+ alert, alertCreateErr := r.NewAlertFromReportEntry(reportEntry)
+ if alertCreateErr != nil {
+ err = alertCreateErr
+ continue
+ }
if alertErr := r.SendAlert(alert); alertErr != nil {
err = alertErr
@@ -141,8 +153,8 @@ func (r PagerDutyReporter) SendReport(report reporters.Report) error {
return err
}
-func (r PagerDutyReporter) SendAlert(alert PagerDutyAlert) error {
- var response PagerDutyResponse
+func (r Reporter) SendAlert(alert Alert) error {
+ var response Response
err := r.DoRequest(r.PagerDutyURL+"/v2/enqueue", alert, &response)
if err != nil {
return err
@@ -155,7 +167,7 @@ func (r PagerDutyReporter) SendAlert(alert PagerDutyAlert) error {
return nil
}
-func (r PagerDutyReporter) DoRequest(url string, body interface{}, target interface{}) error {
+func (r Reporter) DoRequest(url string, body interface{}, target interface{}) error {
client := &http.Client{Timeout: 10 * 1000000000}
start := time.Now()
diff --git a/pkg/reporters/reporter.go b/pkg/reporters/reporter.go
index 5187924..1f8c88c 100644
--- a/pkg/reporters/reporter.go
+++ b/pkg/reporters/reporter.go
@@ -1,11 +1,7 @@
package reporters
import (
- "time"
-
- configTypes "main/pkg/config/types"
- "main/pkg/types"
- "main/pkg/utils"
+ "main/pkg/report/entry"
)
type Reporter interface {
@@ -16,49 +12,9 @@ type Reporter interface {
}
type Report struct {
- Entries []ReportEntry
+ Entries []entry.ReportEntry
}
func (r *Report) Empty() bool {
return len(r.Entries) == 0
}
-
-type ReportEntry struct {
- Chain *configTypes.Chain
- Wallet *configTypes.Wallet
- ProposalID string
- ProposalTitle string
- ProposalDescription string
- ProposalVoteEndingTime time.Time
- Type types.ReportEntryType
- Value string
- OldValue string
-}
-
-func (e ReportEntry) HasVoted() bool {
- return e.Value != ""
-}
-
-func (e ReportEntry) HasRevoted() bool {
- return e.Value != "" && e.OldValue != ""
-}
-
-func (e ReportEntry) IsVoteOrNotVoted() bool {
- return e.Type == types.NotVoted || e.Type == types.Voted
-}
-
-func (e ReportEntry) GetProposalTime() string {
- return e.ProposalVoteEndingTime.Format(time.RFC1123)
-}
-
-func (e ReportEntry) GetProposalTimeLeft() string {
- return utils.FormatDuration(time.Until(e.ProposalVoteEndingTime).Round(time.Second))
-}
-
-func (e ReportEntry) GetVote() string {
- return utils.ResolveVote(e.Value)
-}
-
-func (e ReportEntry) GetOldVote() string {
- return utils.ResolveVote(e.OldValue)
-}
diff --git a/pkg/reporters/telegram/add_mute.go b/pkg/reporters/telegram/add_mute.go
index 13e0bb3..93081f2 100644
--- a/pkg/reporters/telegram/add_mute.go
+++ b/pkg/reporters/telegram/add_mute.go
@@ -7,7 +7,7 @@ import (
tele "gopkg.in/telebot.v3"
)
-func (reporter *TelegramReporter) HandleAddMute(c tele.Context) error {
+func (reporter *Reporter) HandleAddMute(c tele.Context) error {
reporter.Logger.Info().
Str("sender", c.Sender().Username).
Str("text", c.Text()).
diff --git a/pkg/reporters/telegram/help.go b/pkg/reporters/telegram/help.go
index 55db765..a280f13 100644
--- a/pkg/reporters/telegram/help.go
+++ b/pkg/reporters/telegram/help.go
@@ -6,7 +6,7 @@ import (
tele "gopkg.in/telebot.v3"
)
-func (reporter *TelegramReporter) HandleHelp(c tele.Context) error {
+func (reporter *Reporter) HandleHelp(c tele.Context) error {
reporter.Logger.Info().
Str("sender", c.Sender().Username).
Str("text", c.Text()).
diff --git a/pkg/reporters/telegram/list_mutes.go b/pkg/reporters/telegram/list_mutes.go
index a13861b..54e1f21 100644
--- a/pkg/reporters/telegram/list_mutes.go
+++ b/pkg/reporters/telegram/list_mutes.go
@@ -8,7 +8,7 @@ import (
tele "gopkg.in/telebot.v3"
)
-func (reporter *TelegramReporter) HandleListMutes(c tele.Context) error {
+func (reporter *Reporter) HandleListMutes(c tele.Context) error {
reporter.Logger.Info().
Str("sender", c.Sender().Username).
Str("text", c.Text()).
diff --git a/pkg/reporters/telegram/list_proposals.go b/pkg/reporters/telegram/list_proposals.go
index 581224c..ddafa1e 100644
--- a/pkg/reporters/telegram/list_proposals.go
+++ b/pkg/reporters/telegram/list_proposals.go
@@ -7,7 +7,7 @@ import (
tele "gopkg.in/telebot.v3"
)
-func (reporter *TelegramReporter) HandleProposals(c tele.Context) error {
+func (reporter *Reporter) HandleProposals(c tele.Context) error {
reporter.Logger.Info().
Str("sender", c.Sender().Username).
Str("text", c.Text()).
diff --git a/pkg/reporters/telegram/telegram.go b/pkg/reporters/telegram/telegram.go
index 76098d9..b9fce88 100644
--- a/pkg/reporters/telegram/telegram.go
+++ b/pkg/reporters/telegram/telegram.go
@@ -5,6 +5,7 @@ import (
"fmt"
"html/template"
mutes "main/pkg/mutes"
+ "main/pkg/report/entry"
"main/pkg/state"
"strings"
"time"
@@ -18,7 +19,7 @@ import (
tele "gopkg.in/telebot.v3"
)
-type TelegramReporter struct {
+type Reporter struct {
TelegramToken string
TelegramChat int64
MutesManager *mutes.Manager
@@ -26,7 +27,7 @@ type TelegramReporter struct {
TelegramBot *tele.Bot
Logger zerolog.Logger
- Templates map[types.ReportEntryType]*template.Template
+ Templates map[string]*template.Template
}
const (
@@ -38,18 +39,18 @@ func NewTelegramReporter(
mutesManager *mutes.Manager,
stateGenerator *state.Generator,
logger *zerolog.Logger,
-) *TelegramReporter {
- return &TelegramReporter{
+) *Reporter {
+ return &Reporter{
TelegramToken: config.TelegramToken,
TelegramChat: config.TelegramChat,
MutesManager: mutesManager,
StateGenerator: stateGenerator,
Logger: logger.With().Str("component", "telegram_reporter").Logger(),
- Templates: make(map[types.ReportEntryType]*template.Template, 0),
+ Templates: make(map[string]*template.Template, 0),
}
}
-func (reporter *TelegramReporter) Init() {
+func (reporter *Reporter) Init() {
if reporter.TelegramToken == "" || reporter.TelegramChat == 0 {
reporter.Logger.Debug().Msg("Telegram credentials not set, not creating Telegram reporter.")
return
@@ -75,17 +76,17 @@ func (reporter *TelegramReporter) Init() {
go reporter.TelegramBot.Start()
}
-func (reporter *TelegramReporter) Enabled() bool {
+func (reporter *Reporter) Enabled() bool {
return reporter.TelegramToken != "" && reporter.TelegramChat != 0
}
-func (reporter *TelegramReporter) GetTemplate(tmlpType types.ReportEntryType) (*template.Template, error) {
+func (reporter *Reporter) GetTemplate(tmlpType string) (*template.Template, error) {
if cachedTemplate, ok := reporter.Templates[tmlpType]; ok {
- reporter.Logger.Trace().Str("type", string(tmlpType)).Msg("Using cached template")
+ reporter.Logger.Trace().Str("type", tmlpType).Msg("Using cached template")
return cachedTemplate, nil
}
- reporter.Logger.Trace().Str("type", string(tmlpType)).Msg("Loading template")
+ reporter.Logger.Trace().Str("type", tmlpType).Msg("Loading template")
filename := fmt.Sprintf("%s.html", tmlpType)
@@ -101,34 +102,38 @@ func (reporter *TelegramReporter) GetTemplate(tmlpType types.ReportEntryType) (*
return t, nil
}
-func (reporter *TelegramReporter) SerializeReportEntry(e reporters.ReportEntry) (string, error) {
- parsedTemplate, err := reporter.GetTemplate(e.Type)
+func (reporter *Reporter) SerializeReportEntry(e entry.ReportEntry) (string, error) {
+ parsedTemplate, err := reporter.GetTemplate(e.Name())
if err != nil {
- reporter.Logger.Error().Err(err).Str("type", string(e.Type)).Msg("Error loading template")
+ reporter.Logger.Error().Err(err).Str("type", e.Name()).Msg("Error loading template")
return "", err
}
var buffer bytes.Buffer
err = parsedTemplate.Execute(&buffer, e)
if err != nil {
- reporter.Logger.Error().Err(err).Str("type", string(e.Type)).Msg("Error rendering template")
+ reporter.Logger.Error().Err(err).Str("type", e.Name()).Msg("Error rendering template")
return "", err
}
return buffer.String(), nil
}
-func (reporter *TelegramReporter) SendReport(report reporters.Report) error {
- for _, entry := range report.Entries {
- if !entry.HasVoted() && reporter.MutesManager.IsMuted(entry.Chain.Name, entry.ProposalID) {
- reporter.Logger.Debug().
- Str("chain", entry.Chain.Name).
- Str("proposal", entry.ProposalID).
- Msg("Notifications are muted, not sending.")
- continue
+func (reporter *Reporter) SendReport(report reporters.Report) error {
+ for _, reportEntry := range report.Entries {
+ if entryConverted, ok := reportEntry.(entry.ReportEntryNotError); ok {
+ chain := entryConverted.GetChain()
+ proposal := entryConverted.GetProposal()
+ if reporter.MutesManager.IsMuted(chain.Name, proposal.ProposalID) {
+ reporter.Logger.Debug().
+ Str("chain", chain.Name).
+ Str("proposal", proposal.ProposalID).
+ Msg("Notifications are muted, not sending.")
+ continue
+ }
}
- serializedEntry, err := reporter.SerializeReportEntry(entry)
+ serializedEntry, err := reporter.SerializeReportEntry(reportEntry)
if err != nil {
reporter.Logger.Err(err).Msg("Could not serialize report entry")
return err
@@ -151,11 +156,11 @@ func (reporter *TelegramReporter) SendReport(report reporters.Report) error {
return nil
}
-func (reporter *TelegramReporter) Name() string {
+func (reporter *Reporter) Name() string {
return "telegram-reporter"
}
-func (reporter *TelegramReporter) BotReply(c tele.Context, msg string) error {
+func (reporter *Reporter) BotReply(c tele.Context, msg string) error {
msgsByNewline := strings.Split(msg, "\n")
var sb strings.Builder
@@ -226,7 +231,7 @@ func ParseMuteOptions(query string, c tele.Context) (*mutes.Mute, string) {
return mute, ""
}
-func (reporter *TelegramReporter) SerializeLink(link types.Link) template.HTML {
+func (reporter *Reporter) SerializeLink(link types.Link) template.HTML {
if link.Href != "" {
return template.HTML(fmt.Sprintf("%s", link.Href, link.Name))
}
diff --git a/pkg/state/generator.go b/pkg/state/generator.go
index becbd1c..6e0f62c 100644
--- a/pkg/state/generator.go
+++ b/pkg/state/generator.go
@@ -125,7 +125,7 @@ func (g *Generator) ProcessProposalAndWallet(
}
if err != nil {
- proposalVote.Error = err.Error()
+ proposalVote.Error = types.NewJSONError(err.Error())
} else {
proposalVote.Vote = voteResponse.Vote
}
diff --git a/pkg/state/state.go b/pkg/state/state.go
index d45b89d..35a3c48 100644
--- a/pkg/state/state.go
+++ b/pkg/state/state.go
@@ -8,15 +8,15 @@ import (
type ProposalVote struct {
Wallet *configTypes.Wallet
Vote *types.Vote
- Error string
+ Error *types.JSONError
}
func (v ProposalVote) HasVoted() bool {
- return v.Vote != nil && v.Error == ""
+ return v.Vote != nil && v.Error == nil
}
func (v ProposalVote) IsError() bool {
- return v.Error != ""
+ return v.Error != nil
}
type WalletVotes struct {
@@ -27,11 +27,11 @@ type WalletVotes struct {
type ChainInfo struct {
Chain *configTypes.Chain
ProposalVotes map[string]WalletVotes
- ProposalsError string
+ ProposalsError *types.JSONError
}
func (c ChainInfo) HasProposalsError() bool {
- return c.ProposalsError != ""
+ return c.ProposalsError != nil
}
type State struct {
@@ -65,7 +65,7 @@ func (s *State) SetVote(chain *configTypes.Chain, proposal types.Proposal, walle
func (s *State) SetChainProposalsError(chain *configTypes.Chain, err error) {
s.ChainInfos[chain.Name] = &ChainInfo{
Chain: chain,
- ProposalsError: err.Error(),
+ ProposalsError: types.NewJSONError(err.Error()),
}
}
diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go
index 1d4ab44..46370f0 100644
--- a/pkg/state/state_test.go
+++ b/pkg/state/state_test.go
@@ -44,7 +44,7 @@ func TestSetProposalErrorWithoutChainInfo(t *testing.T) {
assert.Equal(t, hasError2, true, "Chain info should have a proposal error!")
err := state.ChainInfos["test"].ProposalsError
- assert.Equal(t, err, "test error", "Errors text should match!")
+ assert.Equal(t, err.Error(), "test error", "Errors text should match!")
}
func TestSetVotes(t *testing.T) {
@@ -90,7 +90,7 @@ func TestSetProposalErrorWithChainInfo(t *testing.T) {
assert.Equal(t, hasError2, true, "Chain info should have a proposal error!")
err := state.ChainInfos["test"].ProposalsError
- assert.Equal(t, err, "test error", "Errors text should match!")
+ assert.Equal(t, err.Error(), "test error", "Errors text should match!")
}
func TestGetVoteWithoutChainInfo(t *testing.T) {
diff --git a/pkg/types/error.go b/pkg/types/error.go
new file mode 100644
index 0000000..2196b49
--- /dev/null
+++ b/pkg/types/error.go
@@ -0,0 +1,24 @@
+package types
+
+import "encoding/json"
+
+type JSONError struct {
+ error string
+}
+
+func NewJSONError(err string) *JSONError {
+ return &JSONError{error: err}
+}
+
+func (e *JSONError) Error() string {
+ return e.error
+}
+
+func (e *JSONError) MarshalJSON() ([]byte, error) {
+ return json.Marshal(e.error)
+}
+
+func (e *JSONError) UnmarshalJSON(data []byte) error {
+ e.error = string(data)
+ return nil
+}
diff --git a/pkg/types/responses.go b/pkg/types/responses.go
index c6c9899..9237057 100644
--- a/pkg/types/responses.go
+++ b/pkg/types/responses.go
@@ -17,6 +17,10 @@ func (p Proposal) GetTimeLeft() string {
return utils.FormatDuration(time.Until(p.VotingEndTime).Round(time.Second))
}
+func (p Proposal) GetProposalTime() string {
+ return p.VotingEndTime.Format(time.RFC1123)
+}
+
type ProposalContent struct {
Title string `json:"title"`
Description string `json:"description"`
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 3f96b4f..9627c33 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -16,13 +16,3 @@ func (l Link) Serialize() string {
return fmt.Sprintf("%s", l.Href, l.Name)
}
-
-type ReportEntryType string
-
-const (
- NotVoted ReportEntryType = "not_voted"
- Voted ReportEntryType = "voted"
- Revoted ReportEntryType = "revoted"
- ProposalQueryError ReportEntryType = "proposal_query_error"
- VoteQueryError ReportEntryType = "vote_query_error"
-)
diff --git a/templates/telegram/not_voted.html b/templates/telegram/not_voted.html
index 7ce8a2d..822f7e1 100644
--- a/templates/telegram/not_voted.html
+++ b/templates/telegram/not_voted.html
@@ -1,9 +1,9 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-🔴 Wallet {{ SerializeLink $walletLink }} hasn't voted on proposal {{ .ProposalID }} on {{ .Chain.GetName }}
-{{ .ProposalTitle }}
+🔴 Wallet {{ SerializeLink $walletLink }} hasn't voted on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
+{{ .Proposal.Content.Title }}
-Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})
+Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/proposal_query_error.html b/templates/telegram/proposals_query_error.html
similarity index 80%
rename from templates/telegram/proposal_query_error.html
rename to templates/telegram/proposals_query_error.html
index e59ba26..628d9a0 100644
--- a/templates/telegram/proposal_query_error.html
+++ b/templates/telegram/proposals_query_error.html
@@ -1,4 +1,4 @@
❌ There was an error querying proposals on {{ .Chain.GetName }}.
-Error text: {{ .Value }}
+Error text: {{ .Error }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/revoted.html b/templates/telegram/revoted.html
index 1e156a8..3e18eb3 100644
--- a/templates/telegram/revoted.html
+++ b/templates/telegram/revoted.html
@@ -1,11 +1,11 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-↔️ Wallet {{ SerializeLink $walletLink }} has changed its vote on proposal {{ .ProposalID }} on {{ .Chain.GetName }}
-{{ .ProposalTitle }}
+↔️ Wallet {{ SerializeLink $walletLink }} has changed its vote on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
+{{ .Proposal.Content.Title }}
-Vote: {{ .GetVote }}
-Old vote: {{ .GetOldVote }}
-Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})
+Vote: {{ .Vote.ResolveVote }}
+Old vote: {{ .OldVote.ResolveVote }}
+Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/vote_query_error.html b/templates/telegram/vote_query_error.html
index 3ba526b..ffd6dc5 100644
--- a/templates/telegram/vote_query_error.html
+++ b/templates/telegram/vote_query_error.html
@@ -1,5 +1,5 @@
❌ There was an error querying proposal on {{ .Chain.GetName }}
-Proposal ID: {{ .ProposalID }}
-Error text: {{ .Value }}
+Proposal ID: {{ .Proposal.ProposalID }}
+Error text: {{ .Error }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/voted.html b/templates/telegram/voted.html
index df5ceb5..3f2d1cf 100644
--- a/templates/telegram/voted.html
+++ b/templates/telegram/voted.html
@@ -1,10 +1,10 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-✅ Wallet {{ SerializeLink $walletLink }} has voted on proposal {{ .ProposalID }} on {{ .Chain.GetName }}
-{{ .ProposalTitle }}
+✅ Wallet {{ SerializeLink $walletLink }} has voted on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
+{{ .Proposal.Content.Title }}
-Vote: {{ .GetVote }}
-Voting ends at: {{ .GetProposalTime }} (in {{ .GetProposalTimeLeft }})
+Vote: {{ .Vote.ResolveVote }}
+Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file