From 293a7d9e306d4d75e45240f6d6086e5c9ef29b8a Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Thu, 4 Apr 2024 19:04:06 +0200 Subject: [PATCH] [counter] Allow `counterTopList` to specify how to sort Signed-off-by: Knut Ahlers --- docs/content/configuration/templating.md | 6 ++-- internal/actors/counter/actor.go | 8 +++--- internal/actors/counter/database.go | 36 ++++++++++++++++++++++-- internal/actors/counter/database_test.go | 29 +++++++++++++++++++ 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/docs/content/configuration/templating.md b/docs/content/configuration/templating.md index 720828b..6a0615a 100644 --- a/docs/content/configuration/templating.md +++ b/docs/content/configuration/templating.md @@ -128,9 +128,9 @@ Example: ### `counterTopList` -Returns the top n counters for the given prefix as objects with Name and Value fields +Returns the top n counters for the given prefix as objects with Name and Value fields. Can be ordered by `name` / `value` / `first_seen` / `last_modified` ascending (`ASC`) or descending (`DESC`): i.e. `last_modified DESC` defaults to `value DESC` -Syntax: `counterTopList ` +Syntax: `counterTopList [orderBy]` Example: @@ -467,7 +467,7 @@ Example: ``` # Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}% -< Your int this hour: 70% +< Your int this hour: 88% ``` ### `spotifyCurrentPlaying` diff --git a/internal/actors/counter/actor.go b/internal/actors/counter/actor.go index dca7914..367650e 100644 --- a/internal/actors/counter/actor.go +++ b/internal/actors/counter/actor.go @@ -166,11 +166,11 @@ func Register(args plugins.RegistrationArguments) (err error) { }, }) - args.RegisterTemplateFunction("counterTopList", plugins.GenericTemplateFunctionGetter(func(prefix string, n int) ([]counter, error) { - return getCounterTopList(db, prefix, n) + args.RegisterTemplateFunction("counterTopList", plugins.GenericTemplateFunctionGetter(func(prefix string, n int, orderBy string) ([]counter, error) { + return getCounterTopList(db, prefix, n, orderBy) }), plugins.TemplateFuncDocumentation{ - Description: "Returns the top n counters for the given prefix as objects with Name and Value fields", - Syntax: `counterTopList `, + Description: "Returns the top n counters for the given prefix as objects with Name and Value fields. Can be ordered by `name` / `value` / `first_seen` / `last_modified` ascending (`ASC`) or descending (`DESC`): i.e. `last_modified DESC` defaults to `value DESC`", + Syntax: `counterTopList [orderBy]`, Example: &plugins.TemplateFuncDocumentationExample{ Template: `{{ range (counterTopList (list .channel "test" "" | join ":") 3) }}{{ .Name }}: {{ .Value }} - {{ end }}`, FakedOutput: "#example:test:foo: 5 - #example:test:bar: 4 - ", diff --git a/internal/actors/counter/database.go b/internal/actors/counter/database.go index 9a37494..bcfecd6 100644 --- a/internal/actors/counter/database.go +++ b/internal/actors/counter/database.go @@ -1,12 +1,15 @@ package counter import ( + "fmt" + "strings" "time" "github.com/pkg/errors" "gorm.io/gorm" "gorm.io/gorm/clause" + "github.com/Luzifer/go_helpers/v2/str" "github.com/Luzifer/twitch-bot/v3/internal/helpers" "github.com/Luzifer/twitch-bot/v3/pkg/database" ) @@ -79,12 +82,39 @@ func getCounterRank(db database.Connector, prefix, name string) (rank, count int return rank, count, nil } -func getCounterTopList(db database.Connector, prefix string, n int) ([]counter, error) { - var cc []counter +func getCounterTopList(db database.Connector, prefix string, n int, orderBy ...string) ([]counter, error) { + var ( + cc []counter + + order string + validOrderCols = []string{"first_seen", "last_modified", "name", "value"} + validOrderDirs = []string{"ASC", "DESC"} + ) + + if len(orderBy) == 0 || orderBy[0] == "" { + order = "value DESC" + } else { + order = orderBy[0] + } + + col, dir, _ := strings.Cut(order, " ") + if col == "" { + col = "value" + } + if dir == "" { + dir = "ASC" + } + + if !str.StringInSlice(col, validOrderCols) { + return nil, fmt.Errorf("invalid orderBy column") + } + if !str.StringInSlice(dir, validOrderDirs) { + return nil, fmt.Errorf("invalid orderBy direction") + } err := helpers.Retry(func() error { return db.DB(). - Order("value DESC"). + Order(strings.Join([]string{col, dir}, " ")). Limit(n). Find(&cc, "name LIKE ?", prefix+"%"). Error diff --git a/internal/actors/counter/database_test.go b/internal/actors/counter/database_test.go index f4c75e5..f9c5bff 100644 --- a/internal/actors/counter/database_test.go +++ b/internal/actors/counter/database_test.go @@ -64,6 +64,35 @@ func TestCounterTopListAndRank(t *testing.T) { {Name: "#example:test:3", Value: 3, FirstSeen: testTime, LastModified: testTime}, }, cc) + cc, err = getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3, "name DESC") + require.NoError(t, err) + assert.Len(t, cc, 3) + + assert.Equal(t, []counter{ + {Name: "#example:test:5", Value: 5, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:4", Value: 4, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:3", Value: 3, FirstSeen: testTime, LastModified: testTime}, + }, cc) + + cc, err = getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3, "name") + require.NoError(t, err) + assert.Len(t, cc, 3) + + assert.Equal(t, []counter{ + {Name: "#example:test:0", Value: 0, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:1", Value: 1, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:2", Value: 2, FirstSeen: testTime, LastModified: testTime}, + }, cc) + + _, err = getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3, "foobar") + assert.Error(t, err) + + _, err = getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3, "name foo") + assert.Error(t, err) + + _, err = getCounterTopList(dbc, fmt.Sprintf(counterTemplate, ""), 3, "name ASC; DROP TABLE counters;") + assert.Error(t, err) + rank, count, err := getCounterRank(dbc, fmt.Sprintf(counterTemplate, ""), fmt.Sprintf(counterTemplate, 4))