Skip to content

Commit

Permalink
feat: add RenderedState and use it when rendering data (#83)
Browse files Browse the repository at this point in the history
* feat: add RenderedState and use it when rendering data

* chore: add tests for rendered state

* chore: no preview on Telegram messages

* chore: fixed linting
  • Loading branch information
freak12techno authored May 9, 2024
1 parent c01f43c commit 89b29a3
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 4 deletions.
6 changes: 4 additions & 2 deletions pkg/reporters/telegram/list_proposals.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ func (reporter *Reporter) HandleProposals(c tele.Context) error {
Msg("Got proposals list query")

state := reporter.StateGenerator.GetState(statePkg.NewState())
renderedState := state.ToRenderedState()

template, _ := reporter.GetTemplate("proposals")
var buffer bytes.Buffer
if err := template.Execute(&buffer, state); err != nil {
reporter.Logger.Error().Err(err).Msg("Error rendering votes template")
if err := template.Execute(&buffer, renderedState); err != nil {
reporter.Logger.Error().Err(err).Msg("Error rendering proposals template")
return err
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/reporters/telegram/telegram.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (reporter *Reporter) BotReply(c tele.Context, msg string) error {

for _, line := range msgsByNewline {
if sb.Len()+len(line) > MaxMessageSize {
if err := c.Reply(sb.String(), tele.ModeHTML); err != nil {
if err := c.Reply(sb.String(), tele.ModeHTML, tele.NoPreview); err != nil {
reporter.Logger.Error().Err(err).Msg("Could not send Telegram message")
return err
}
Expand All @@ -195,7 +195,7 @@ func (reporter *Reporter) BotReply(c tele.Context, msg string) error {
sb.WriteString(line + "\n")
}

if err := c.Reply(sb.String(), tele.ModeHTML); err != nil {
if err := c.Reply(sb.String(), tele.ModeHTML, tele.NoPreview); err != nil {
reporter.Logger.Error().Err(err).Msg("Could not send Telegram message")
return err
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/state/rendered_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package state

import "main/pkg/types"

type RenderedState struct {
ChainInfos []RenderedChainInfo
}

type RenderedChainInfo struct {
Chain *types.Chain
ProposalVotes []RenderedProposalVotes
ProposalsError *types.QueryError
}

type RenderedProposalVotes struct {
Proposal types.Proposal
Votes []RenderedWalletVote
}

type RenderedWalletVote struct {
Wallet *types.Wallet
Vote *types.Vote
Error *types.QueryError
}

func (v RenderedWalletVote) HasVoted() bool {
return v.Vote != nil && v.Error == nil
}

func (v RenderedWalletVote) IsError() bool {
return v.Error != nil
}

func (c RenderedChainInfo) HasProposalsError() bool {
return c.ProposalsError != nil
}
165 changes: 165 additions & 0 deletions pkg/state/rendered_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package state

import (
"main/pkg/types"
"testing"

"github.com/stretchr/testify/assert"
)

func TestToRenderedStateFilteredChain(t *testing.T) {
t.Parallel()

state := State{
ChainInfos: map[string]*ChainInfo{
"chain": {
ProposalVotes: map[string]WalletVotes{},
},
},
}

renderedState := state.ToRenderedState()
assert.Empty(t, renderedState.ChainInfos)
}

func TestToRenderedStateFilterProposalsNotInVoting(t *testing.T) {
t.Parallel()

state := State{
ChainInfos: map[string]*ChainInfo{
"chain": {
ProposalVotes: map[string]WalletVotes{
"proposal": {
Proposal: types.Proposal{
ID: "proposal",
Status: types.ProposalStatusPassed,
},
},
},
},
},
}

renderedState := state.ToRenderedState()
assert.Empty(t, renderedState.ChainInfos)
}

func TestToRenderedStateSortProposals(t *testing.T) {
t.Parallel()

chain := &types.Chain{Name: "chain"}
state := NewState()
state.SetProposal(chain, types.Proposal{ID: "15", Status: types.ProposalStatusVoting})
state.SetProposal(chain, types.Proposal{ID: "231", Status: types.ProposalStatusVoting})
state.SetProposal(chain, types.Proposal{ID: "2", Status: types.ProposalStatusVoting})

renderedState := state.ToRenderedState()
assert.Len(t, renderedState.ChainInfos, 1)

renderedChain := renderedState.ChainInfos[0]
assert.Len(t, renderedChain.ProposalVotes, 3)
assert.Equal(t, "231", renderedChain.ProposalVotes[0].Proposal.ID)
assert.Equal(t, "15", renderedChain.ProposalVotes[1].Proposal.ID)
assert.Equal(t, "2", renderedChain.ProposalVotes[2].Proposal.ID)
}

func TestToRenderedStateSortProposalsInvalid(t *testing.T) {
t.Parallel()

chain := &types.Chain{Name: "chain"}
state := NewState()
state.SetProposal(chain, types.Proposal{ID: "1", Status: types.ProposalStatusVoting})
state.SetProposal(chain, types.Proposal{ID: "a", Status: types.ProposalStatusVoting})
state.SetProposal(chain, types.Proposal{ID: "b", Status: types.ProposalStatusVoting})

renderedState := state.ToRenderedState()
assert.Len(t, renderedState.ChainInfos, 1)

renderedChain := renderedState.ChainInfos[0]
assert.Len(t, renderedChain.ProposalVotes, 3)

// we don't know the sorting
}

func TestToRenderedStateSortChains(t *testing.T) {
t.Parallel()

state := State{
ChainInfos: map[string]*ChainInfo{
"cosmos": {
Chain: &types.Chain{Name: "cosmos"},
ProposalVotes: map[string]WalletVotes{
"proposal": {Proposal: types.Proposal{ID: "1", Status: types.ProposalStatusVoting}},
},
},
"sentinel": {
Chain: &types.Chain{Name: "sentinel"},
ProposalVotes: map[string]WalletVotes{
"proposal": {Proposal: types.Proposal{ID: "1", Status: types.ProposalStatusVoting}},
},
},
"bitsong": {
Chain: &types.Chain{Name: "bitsong"},
ProposalVotes: map[string]WalletVotes{
"proposal": {Proposal: types.Proposal{ID: "1", Status: types.ProposalStatusVoting}},
},
},
},
}

renderedState := state.ToRenderedState()
assert.Len(t, renderedState.ChainInfos, 3)
assert.Equal(t, "bitsong", renderedState.ChainInfos[0].Chain.Name)
assert.Equal(t, "cosmos", renderedState.ChainInfos[1].Chain.Name)
assert.Equal(t, "sentinel", renderedState.ChainInfos[2].Chain.Name)
}

func TestToRenderedStateSortWallets(t *testing.T) {
t.Parallel()

chain := &types.Chain{Name: "chain"}
proposal := types.Proposal{ID: "proposal", Status: types.ProposalStatusVoting}
state := NewState()

wallets := []string{"21", "352", "2"}

for _, addr := range wallets {
wallet := &types.Wallet{Address: addr}
state.SetVote(chain, proposal, wallet, ProposalVote{Wallet: wallet})
}

renderedState := state.ToRenderedState()
assert.Len(t, renderedState.ChainInfos, 1)

renderedChain := renderedState.ChainInfos[0]
assert.Len(t, renderedChain.ProposalVotes, 1)

renderedVotes := renderedChain.ProposalVotes[0]
assert.Len(t, renderedVotes.Votes, 3)

assert.Equal(t, "2", renderedVotes.Votes[0].Wallet.Address)
assert.Equal(t, "21", renderedVotes.Votes[1].Wallet.Address)
assert.Equal(t, "352", renderedVotes.Votes[2].Wallet.Address)
}

func TestRenderedWalletVoteHasVoted(t *testing.T) {
t.Parallel()

assert.True(t, RenderedWalletVote{Vote: &types.Vote{}}.HasVoted())
assert.False(t, RenderedWalletVote{Vote: &types.Vote{}, Error: &types.QueryError{}}.HasVoted())
assert.False(t, RenderedWalletVote{Error: &types.QueryError{}}.HasVoted())
}

func TestRenderedWalletVoteIsError(t *testing.T) {
t.Parallel()

assert.False(t, RenderedWalletVote{}.IsError())
assert.True(t, RenderedWalletVote{Error: &types.QueryError{}}.IsError())
}

func TestRenderedChainInfoHasError(t *testing.T) {
t.Parallel()

assert.False(t, RenderedChainInfo{}.HasProposalsError())
assert.True(t, RenderedChainInfo{ProposalsError: &types.QueryError{}}.HasProposalsError())
}
85 changes: 85 additions & 0 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package state

import (
"main/pkg/types"
"sort"
"strconv"
)

type ProposalVote struct {
Expand Down Expand Up @@ -158,3 +160,86 @@ func (s *State) HasVoted(chain, proposal, wallet string) bool {

return s.ChainInfos[chain].ProposalVotes[proposal].Votes[wallet].HasVoted()
}

func (s *State) ToRenderedState() RenderedState {
keys := make([]string, 0)
renderedChainInfos := map[string]RenderedChainInfo{}

for chainName, chainInfo := range s.ChainInfos {
proposalsKeys := make([]string, 0)
renderedProposals := map[string]RenderedProposalVotes{}

for proposalID, proposalVotes := range chainInfo.ProposalVotes {
if !proposalVotes.Proposal.IsInVoting() {
continue
}

votesKeys := make([]string, 0)
renderedVotes := map[string]RenderedWalletVote{}

for wallet, walletVote := range proposalVotes.Votes {
votesKeys = append(votesKeys, wallet)
renderedVotes[wallet] = RenderedWalletVote{
Wallet: walletVote.Wallet,
Vote: walletVote.Vote,
Error: walletVote.Error,
}
}

// sorting wallets votes by wallet name desc
sort.Strings(votesKeys)

proposalsKeys = append(proposalsKeys, proposalID)
renderedProposals[proposalID] = RenderedProposalVotes{
Proposal: proposalVotes.Proposal,
Votes: make([]RenderedWalletVote, len(votesKeys)),
}

for index, key := range votesKeys {
renderedProposals[proposalID].Votes[index] = renderedVotes[key]
}
}

// might be all the proposals are not in voting
if !chainInfo.HasProposalsError() && len(renderedProposals) == 0 {
continue
}

keys = append(keys, chainName)
renderedChainInfos[chainName] = RenderedChainInfo{
Chain: chainInfo.Chain,
ProposalsError: chainInfo.ProposalsError,
ProposalVotes: make([]RenderedProposalVotes, len(proposalsKeys)),
}

// sorting proposals by ID desc
sort.Slice(proposalsKeys, func(i, j int) bool {
first, firstErr := strconv.Atoi(proposalsKeys[i])
second, secondErr := strconv.Atoi(proposalsKeys[j])

// if it's faulty - doesn't matter how we sort it out
if firstErr != nil || secondErr != nil {
return true
}

return first > second
})

for index, key := range proposalsKeys {
renderedChainInfos[chainName].ProposalVotes[index] = renderedProposals[key]
}
}

// sorting chains by chain name desc
sort.Strings(keys)

renderedState := RenderedState{
ChainInfos: make([]RenderedChainInfo, len(keys)),
}

for index, key := range keys {
renderedState.ChainInfos[index] = renderedChainInfos[key]
}

return renderedState
}

0 comments on commit 89b29a3

Please sign in to comment.