diff --git a/internal/bot/job_create_round_test.go b/internal/bot/job_create_round_test.go index 8a887d2..56d5cce 100644 --- a/internal/bot/job_create_round_test.go +++ b/internal/bot/job_create_round_test.go @@ -133,7 +133,7 @@ func (s *CreateRoundSuite) Test_CreateRound() { s.mock, reportStatsParams, models.JobTypeReportStats.String(), - models.JobPriorityStandard, + models.JobPriorityLow, ) // Mock query to queue CREATE_ROUND job diff --git a/internal/bot/job_end_round.go b/internal/bot/job_end_round.go index 07426b5..02294c9 100644 --- a/internal/bot/job_end_round.go +++ b/internal/bot/job_end_round.go @@ -19,7 +19,7 @@ type EndRoundParams struct { NextRound time.Time `json:"next_round"` } -// EndRound completes a running chat-roulette round for a Slack channel. +// EndRound concludes a running chat-roulette round for a Slack channel. func EndRound(ctx context.Context, db *gorm.DB, client *slack.Client, p *EndRoundParams) error { logger := hclog.FromContext(ctx).With( @@ -28,7 +28,7 @@ func EndRound(ctx context.Context, db *gorm.DB, client *slack.Client, p *EndRoun logger.Info("ending chat-roulette round if one is in progress") - // End the previous chat-roulette round for this Slack channel + // End the current chat-roulette round for this Slack channel dbCtx, cancel := context.WithTimeout(ctx, 300*time.Millisecond) defer cancel() @@ -39,12 +39,12 @@ func EndRound(ctx context.Context, db *gorm.DB, client *slack.Client, p *EndRoun Update("has_ended", true) if result.Error != nil { - message := "failed to end chat-roulette round" + message := "failed to end current chat-roulette round" logger.Error(message, "error", result.Error) return errors.Wrap(result.Error, message) } - logger.Info("ended the last chat-roulette round") + logger.Info("ended the current chat-roulette round") return nil } @@ -55,7 +55,7 @@ func QueueEndRoundJob(ctx context.Context, db *gorm.DB, p *EndRoundParams) error JobType: models.JobTypeEndRound, Priority: models.JobPriorityStandard, Params: p, - ExecAt: p.NextRound.Add(-(1 * time.Hour)), + ExecAt: p.NextRound.Add(-(4 * time.Hour)), // 4 hours before the start of the next round } return QueueJob(ctx, db, job) diff --git a/internal/bot/job_end_round_test.go b/internal/bot/job_end_round_test.go index e77c5d2..97a18b8 100644 --- a/internal/bot/job_end_round_test.go +++ b/internal/bot/job_end_round_test.go @@ -56,7 +56,7 @@ func (s *EndRoundSuite) Test_EndRound() { err := EndRound(s.ctx, s.db, nil, p) r.NoError(err) - r.Contains(s.buffer.String(), "ended the last chat-roulette round") + r.Contains(s.buffer.String(), "ended the current chat-roulette round") } func (s *EndRoundSuite) Test_QueueEndRoundJob() { diff --git a/internal/bot/job_report_stats.go b/internal/bot/job_report_stats.go index c8a0d99..e702579 100644 --- a/internal/bot/job_report_stats.go +++ b/internal/bot/job_report_stats.go @@ -20,11 +20,16 @@ const ( // reportStatsTemplate is used with reportStatsTemplateFilename type reportStatsTemplate struct { - Matches float64 + Pairs float64 Met float64 Percent float64 } +type roundStats struct { + Total int + Met int +} + // ReportStatsParams are the parameters for the REPORT_STATS job. type ReportStatsParams struct { ChannelID string `json:"channel_id"` @@ -40,25 +45,20 @@ func ReportStats(ctx context.Context, db *gorm.DB, client *slack.Client, p *Repo "round_id", p.RoundID, ) - // Retrieve the number of matches that were made and how many actually met - type Matches struct { - Total int64 - Met int64 - } - - var matches Matches + // Retrieve the number of pairs that were made and how many actually met + var stats roundStats dbCtx, cancel := context.WithTimeout(ctx, 300*time.Millisecond) defer cancel() result := db.WithContext(dbCtx). Model(&models.Match{}). - Select( - `COUNT(*) as total`, - `SUM(CASE WHEN has_met = true then 1 else 0 end) AS met`, - ). + Select(` + COUNT(*) AS total, + COUNT(*) FILTER (WHERE has_met) AS met + `). Where("round_id = ?", p.RoundID). - Find(&matches) + Find(&stats) if result.Error != nil { message := "failed to retrieve match results" @@ -67,12 +67,12 @@ func ReportStats(ctx context.Context, db *gorm.DB, client *slack.Client, p *Repo } // Calculate matches percent - percent := (float64(matches.Met) / float64(matches.Total)) * 100 + percent := (float64(stats.Met) / float64(stats.Total)) * 100 // Render template t := reportStatsTemplate{ - Matches: float64(matches.Total), - Met: float64(matches.Met), + Pairs: float64(stats.Total), + Met: float64(stats.Met), Percent: percent, } @@ -104,10 +104,9 @@ func ReportStats(ctx context.Context, db *gorm.DB, client *slack.Client, p *Repo func QueueReportStatsJob(ctx context.Context, db *gorm.DB, p *ReportStatsParams) error { job := models.GenericJob[*ReportStatsParams]{ JobType: models.JobTypeReportStats, - Priority: models.JobPriorityStandard, + Priority: models.JobPriorityLow, Params: p, - // This should execute before the start of the next chat-roulette round. - ExecAt: p.NextRound.Add(-(1 * time.Hour)), + ExecAt: p.NextRound.Add(-(4 * time.Hour)), // 4 hours before the start of the next round } return QueueJob(ctx, db, job) diff --git a/internal/bot/job_report_stats_test.go b/internal/bot/job_report_stats_test.go index 7bebf38..a7e63ac 100644 --- a/internal/bot/job_report_stats_test.go +++ b/internal/bot/job_report_stats_test.go @@ -71,7 +71,7 @@ func (s *ReportStatsSuite) Test_ReportStats() { w.Write([]byte(`{"ok":false}`)) } - r.Len(blocks.BlockSet, 5) + r.Len(blocks.BlockSet, 9) w.Write([]byte(`{ "ok": true, @@ -102,13 +102,14 @@ func Test_reportStatsTemplate(t *testing.T) { testCases := []struct { name string - matches float64 + pairs float64 met float64 percent float64 contains []string }{ {"all met", 10, 10, 100, []string{}}, {"half met", 10, 5, 50, []string{ + "This round had *20* participants", "*5* groups met", "*50%* of the *10* intros made", }}, @@ -117,12 +118,13 @@ func Test_reportStatsTemplate(t *testing.T) { "*25%* of the *4* intros made", }}, {"none met", 20, 0, 0, []string{ + "This round had *40* participants", "*0* groups met", "*0%* of the *20* intros made", }}, - {"no matches", 0, 0, 0, []string{ - "No matches were made in the last round of Chat Roulette! :cry:", - "To ensure matches can be made in the next round, participants must opt-in to Chat Roulette.", + {"no pairs", 0, 0, 0, []string{ + "No intros were made in the last round :sob:", + "To ensure intros can be made in the next round, you must opt-in to Chat Roulette.", }}, } @@ -130,7 +132,7 @@ func Test_reportStatsTemplate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { p := reportStatsTemplate{ - Matches: tc.matches, + Pairs: tc.pairs, Met: tc.met, Percent: tc.percent, } diff --git a/internal/bot/template.go b/internal/bot/template.go index 743089e..be88f3b 100644 --- a/internal/bot/template.go +++ b/internal/bot/template.go @@ -17,9 +17,10 @@ var ( // funcMap is a map of custom template functions funcMap = template.FuncMap{ - "capitalize": templatex.Capitalize, - "prettyDate": templatex.PrettierDate, - "prettyURL": templatex.PrettyURL, + "capitalize": templatex.Capitalize, + "prettyDate": templatex.PrettierDate, + "prettyURL": templatex.PrettyURL, + "prettyPercent": templatex.PrettyPercent, } templates = template.New("custom").Funcs(funcMap).Funcs(sprig.TxtFuncMap()) diff --git a/internal/bot/templates/report_matches.json.tmpl b/internal/bot/templates/report_matches.json.tmpl index 962c733..f945b8a 100644 --- a/internal/bot/templates/report_matches.json.tmpl +++ b/internal/bot/templates/report_matches.json.tmpl @@ -90,7 +90,7 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*{{ .Pairs }}* matches were made {{ $matchesEmoji }}" + "text": "*{{ .Pairs }}* intros were made {{ $matchesEmoji }}" } } {{- if and ( gt .Pairs 0 ) ( not .IsAdmin ) }} diff --git a/internal/bot/templates/report_stats.json.tmpl b/internal/bot/templates/report_stats.json.tmpl index e671ab5..df7e7d1 100644 --- a/internal/bot/templates/report_stats.json.tmpl +++ b/internal/bot/templates/report_stats.json.tmpl @@ -1,29 +1,55 @@ +{{- $participants := mul .Pairs 2 }} +{{- $groupsKeyword := "groups" }} +{{- if eq .Met 1.0 }} + {{- $groupsKeyword = "group" }} +{{- end -}} { "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hi all :wave:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "The current round of Chat Roulette has now come to an end :smile:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "The next round will begin soon. But before that, let's review how we did!" + } + }, { "type": "header", "text": { "type": "plain_text", - "text": ":bar_chart: Stats for Chat Roulette", + "text": ":bar_chart: Round Stats", "emoji": true } }, { "type": "divider" }, - {{- if (eq .Matches 0.0) }} + {{- if eq .Pairs 0.0 }} { "type": "section", "text": { "type": "mrkdwn", - "text": "No matches were made in the last round of Chat Roulette! :cry:" + "text": "No intros were made in the last round :sob:" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "To ensure matches can be made in the next round, participants must opt-in to Chat Roulette." + "text": "To ensure intros can be made in the next round, you must opt-in to Chat Roulette." } } {{- else }} @@ -31,17 +57,24 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*{{ .Met }}* group{{ if or (eq .Met 0.0) (gt .Met 1.0) }}s{{ end }} met from the last round of Chat Roulette!" + "text": "This round had *{{ $participants }}* participants :tada:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*{{ .Met }}* {{ $groupsKeyword }} met :partying_face:" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "That's *{{ .Percent }}%* of the *{{ .Matches }}* intros made." + "text": "That's *{{ .Percent | prettyPercent }}* of the *{{ .Pairs }}* intros made :confetti_ball:" } }, - {{- if (eq .Percent 100.0) }} + {{- if eq .Percent 100.0 }} { "type": "section", "text": { diff --git a/internal/bot/testdata/report_matches_admin.json.golden b/internal/bot/testdata/report_matches_admin.json.golden index 90cce0d..1930ce7 100644 --- a/internal/bot/testdata/report_matches_admin.json.golden +++ b/internal/bot/testdata/report_matches_admin.json.golden @@ -64,7 +64,7 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*25* matches were made :raised_hands:" + "text": "*25* intros were made :raised_hands:" } } ] diff --git a/internal/bot/testdata/report_matches_channel.json.golden b/internal/bot/testdata/report_matches_channel.json.golden index e6476f7..b925484 100644 --- a/internal/bot/testdata/report_matches_channel.json.golden +++ b/internal/bot/testdata/report_matches_channel.json.golden @@ -43,7 +43,7 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*6* matches were made :raised_hands:" + "text": "*6* intros were made :raised_hands:" } } ,{ diff --git a/internal/bot/testdata/report_matches_channel_zero.json.golden b/internal/bot/testdata/report_matches_channel_zero.json.golden index abdae53..c7739c7 100644 --- a/internal/bot/testdata/report_matches_channel_zero.json.golden +++ b/internal/bot/testdata/report_matches_channel_zero.json.golden @@ -43,7 +43,7 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*0* matches were made :sob:" + "text": "*0* intros were made :sob:" } } ] diff --git a/internal/bot/testdata/report_stats.json.golden b/internal/bot/testdata/report_stats.json.golden index 3f5192e..49d9679 100644 --- a/internal/bot/testdata/report_stats.json.golden +++ b/internal/bot/testdata/report_stats.json.golden @@ -1,10 +1,31 @@ { "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hi all :wave:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "The current round of Chat Roulette has now come to an end :smile:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "The next round will begin soon. But before that, let's review how we did!" + } + }, { "type": "header", "text": { "type": "plain_text", - "text": ":bar_chart: Stats for Chat Roulette", + "text": ":bar_chart: Round Stats", "emoji": true } }, @@ -15,14 +36,21 @@ "type": "section", "text": { "type": "mrkdwn", - "text": "*10* groups met from the last round of Chat Roulette!" + "text": "This round had *20* participants :tada:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*10* groups met :partying_face:" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "That's *100%* of the *10* intros made." + "text": "That's *100%* of the *10* intros made :confetti_ball:" } }, { diff --git a/internal/templatex/templatex.go b/internal/templatex/templatex.go index 8803c31..486c4cc 100644 --- a/internal/templatex/templatex.go +++ b/internal/templatex/templatex.go @@ -50,6 +50,15 @@ func PrettyURL(s string) string { return re.ReplaceAllString(s, "") } +// PrettyPercent returns a pretty-formatted percent without trailing zeros +func PrettyPercent(v float64) string { + if v == float64(int(v)) { + return fmt.Sprintf("%.0f%%", v) + } + + return fmt.Sprintf("%.2f%%", v) +} + // DerefBool derefences a pointer to a boolean. func DerefBool(b *bool) bool { return *b diff --git a/internal/templatex/templatex_test.go b/internal/templatex/templatex_test.go index 37529ac..e6477d3 100644 --- a/internal/templatex/templatex_test.go +++ b/internal/templatex/templatex_test.go @@ -72,6 +72,24 @@ func Test_PrettyURL(t *testing.T) { } } +func Test_PrettyPercent(t *testing.T) { + testCases := []struct { + name string + input float64 + expected string + }{ + {"no decimals", 100.0, "100%"}, + {"decimals", 66.66666666, "66.67%"}, + {"one third", 33.3333, "33.33%"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, PrettyPercent(tc.input)) + }) + } +} + func Test_DerefBool(t *testing.T) { b := true