Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 85 additions & 10 deletions internal/commands/standup.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
func RegisterStandupCommands(r *Registry) {
r.Register("submit", handleSubmit, PermissionMember)
r.Register("status", handleStatus, PermissionMember)
r.Register("report", handleReport, PermissionAny)
}

func handleSubmit(ctx *Context) error {
Expand Down Expand Up @@ -70,17 +71,13 @@ func handleStatus(ctx *Context) error {
date := time.Now().Format("2006-01-02")
team := teams[0]

sessID := findSessionID(ctx, team.ID, date)
submitted, total, err := ctx.Store.GetSessionStatus(team.ID, date)
if err != nil {
session, err2 := ctx.Store.GetActiveSession(team.ID, date)
if err2 != nil {
return send(ctx.Messenger, ctx.RoomID,
"No active standup for today.")
}
submitted, total, _ = ctx.Store.GetSessionStatus(team.ID, session.Date)
return send(ctx.Messenger, ctx.RoomID, "No active standup for today.")
}

hasSubmitted, _ := ctx.Store.HasSubmitted(sessionID(ctx, team.ID), ctx.UserID)
hasSubmitted, _ := ctx.Store.HasSubmitted(sessID, ctx.UserID)
statusLine := ""
if hasSubmitted {
statusLine = "✅ You have submitted."
Expand All @@ -93,13 +90,91 @@ func handleStatus(ctx *Context) error {
team.Name, statusLine, submitted, total))
}

func sessionID(ctx *Context, teamID string) string {
func handleReport(ctx *Context) error {
team, err := resolveTeam(ctx)
if err != nil {
return send(ctx.Messenger, ctx.RoomID, err.Error())
}

if ctx.Username != ctx.Config.MainAdmin {
isLead, _ := ctx.Store.IsTeamLead(team.ID, ctx.UserID)
if !isLead {
return send(ctx.Messenger, ctx.RoomID,
"Only team leads and the main admin can post reports.")
}
}

date := time.Now().Format("2006-01-02")
session, err := ctx.Store.GetActiveSession(teamID, date)
sessID := findSessionID(ctx, team.ID, date)
if sessID == "" {
return send(ctx.Messenger, ctx.RoomID,
fmt.Sprintf("No standup session found for %s today.", team.Name))
}

responses, err := ctx.Store.GetResponses(sessID)
if err != nil {
return send(ctx.Messenger, ctx.RoomID,
fmt.Sprintf("Failed to get responses: %v", err))
}

questions := strings.Split(team.Questions, "|")

report := buildReport(team.Name, date, questions, responses)

submitted, total, _ := ctx.Store.GetSessionStatus(team.ID, date)

sendTo := team.ChannelID
if len(ctx.Args) > 0 && ctx.Args[0] == "--here" {
sendTo = ctx.RoomID
}

if err := send(ctx.Messenger, sendTo, report); err != nil {
return send(ctx.Messenger, ctx.RoomID,
fmt.Sprintf("Failed to post report: %v", err))
}

return send(ctx.Messenger, ctx.RoomID,
fmt.Sprintf("✅ Report posted to #%s (%d/%d submitted).",
sendTo, submitted, total))
}

func buildReport(teamName, date string, questions []string, responses []*store.StandupResponse) string {
emojis := []string{"✅", "💻", "⚠️", "📌", "🔧", "🎯"}

var sb strings.Builder
sb.WriteString(fmt.Sprintf("📋 *Daily Standup — %s* (%s)\n\n", teamName, date))

for _, r := range responses {
sb.WriteString(fmt.Sprintf("👤 @%s\n", r.Username))
answers := strings.Split(r.Answers, "|")
for i, a := range answers {
q := ""
if i < len(questions) {
q = questions[i]
}
emoji := emojis[i%len(emojis)]
if q != "" {
sb.WriteString(fmt.Sprintf("%s *%s* %s\n", emoji, q, a))
} else {
sb.WriteString(fmt.Sprintf("%s %s\n", emoji, a))
}
}
sb.WriteString("\n")
}

return sb.String()
}

func findSessionID(ctx *Context, teamID, date string) string {
session, err := ctx.Store.GetActiveSession(teamID, date)
if err == nil {
return session.ID
}
sessions, err := ctx.Store.ListSessions(teamID, 1)
if err != nil || len(sessions) == 0 {
return ""
}
return session.ID
return sessions[0].ID
}

func ensureDMRoom(ctx *Context) (string, error) {
Expand Down
64 changes: 64 additions & 0 deletions internal/commands/standup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,70 @@ func TestStandupStatusAfterSubmission(t *testing.T) {
}
}

func ensureLead(t *testing.T, h *standupHarness) {
t.Helper()
_ = h.s.SetRole("t1", "alice-id", "lead")
}

func TestStandupReportGenerates(t *testing.T) {
h := newStandupHarness(t)
ensureLead(t, h)

now := time.Now().Format("2006-01-02")
_ = h.s.CreateSession(&store.StandupSession{
ID: "sess-report", TeamID: "t1", Date: now, Status: "open",
})
_ = h.s.SubmitResponse(&store.StandupResponse{
ID: "r1", SessionID: "sess-report", UserID: "alice-id",
Username: "alice", Answers: "Great|Fixed bugs|None",
SubmittedAt: time.Now(),
})
_ = h.s.SubmitResponse(&store.StandupResponse{
ID: "r2", SessionID: "sess-report", UserID: "bob-id",
Username: "bob", Answers: "Good|Reviewed PRs|Waiting on API",
SubmittedAt: time.Now(),
})

h.dispatch("/standup report")

msg := strings.Join(h.msgr.sent, " ")
if !contains(msg, "@alice") {
t.Error("expected @alice in report")
}
if !contains(msg, "@bob") {
t.Error("expected @bob in report")
}
if !contains(msg, "Fixed bugs") {
t.Error("expected 'Fixed bugs' in report")
}
if !contains(msg, "Reviewed PRs") {
t.Error("expected 'Reviewed PRs' in report")
}
}

func TestStandupReportNoSession(t *testing.T) {
h := newStandupHarness(t)
ensureLead(t, h)

h.dispatch("/standup report")

msg := strings.Join(h.msgr.sent, " ")
if !contains(msg, "No standup") {
t.Logf("Message: %s", msg)
}
}

func TestStandupReportNonLeadDenied(t *testing.T) {
h := newStandupHarness(t)

h.dispatch("/standup report")

msg := strings.Join(h.msgr.sent, " ")
if !contains(msg, "lead") && !contains(msg, "admin") {
t.Logf("Message: %s", msg)
}
}

func TestStandupConversationFlow(t *testing.T) {
h := newStandupHarness(t)

Expand Down
Loading