forked from codeGROOVE-dev/slacker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport_handler.go
More file actions
239 lines (212 loc) · 7.57 KB
/
report_handler.go
File metadata and controls
239 lines (212 loc) · 7.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package slack
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/codeGROOVE-dev/slacker/pkg/dailyreport"
"github.com/codeGROOVE-dev/slacker/pkg/github"
"github.com/codeGROOVE-dev/slacker/pkg/home"
"github.com/codeGROOVE-dev/slacker/pkg/state"
"github.com/codeGROOVE-dev/slacker/pkg/usermapping"
gogithub "github.com/google/go-github/v50/github"
)
// ReportHandler handles manual daily report generation via slash command.
type ReportHandler struct {
slackManager *Manager
githubManager *github.Manager
stateStore state.Store
reverseMapping *usermapping.ReverseService
}
// NewReportHandler creates a new report handler.
func NewReportHandler(
slackManager *Manager,
githubManager *github.Manager,
stateStore state.Store,
reverseMapping *usermapping.ReverseService,
) *ReportHandler {
return &ReportHandler{
slackManager: slackManager,
githubManager: githubManager,
stateStore: stateStore,
reverseMapping: reverseMapping,
}
}
// HandleReportCommand handles the /goose report slash command.
// It generates and sends a daily report for the requesting user, bypassing time window and interval checks.
func (h *ReportHandler) HandleReportCommand(ctx context.Context, teamID, slackUserID string) error {
slog.Info("handling manual daily report request",
"team_id", teamID,
"slack_user", slackUserID)
// Get Slack client for the workspace
slackClient, err := h.slackManager.Client(ctx, teamID)
if err != nil {
slog.Error("failed to get Slack client for report",
"team_id", teamID,
"error", err)
return fmt.Errorf("failed to get Slack client: %w", err)
}
// Try to map Slack user to GitHub username
// We need to check all orgs since we don't know which org the user belongs to
var githubUsername string
var foundOrg string
// Get workspace info to determine workspace name
workspaceInfo, err := slackClient.WorkspaceInfo(ctx)
if err != nil {
slog.Error("failed to get workspace info for user mapping",
"team_id", teamID,
"error", err)
return fmt.Errorf("failed to get workspace info: %w", err)
}
workspaceName := workspaceInfo.Domain
// Get underlying Slack API client for user mapping
slackAPI := slackClient.API()
if slackAPI == nil {
slog.Error("failed to get Slack API client for user mapping",
"team_id", teamID)
return errors.New("failed to get Slack API client")
}
allOrgs := h.githubManager.AllOrgs()
slog.Info("attempting to map Slack user to GitHub",
"slack_user", slackUserID,
"workspace", workspaceName,
"available_orgs", allOrgs,
"org_count", len(allOrgs))
// Collect all orgs where this user is a member
var userOrgs []string
for _, org := range allOrgs {
mapping, err := h.reverseMapping.LookupGitHub(ctx, slackAPI, slackUserID, org, workspaceName)
if err != nil || mapping == nil || mapping.GitHubUsername == "" {
slog.Info("org did not match user mapping",
"slack_user", slackUserID,
"org", org,
"error", err)
continue
}
// First match establishes the GitHub username
if githubUsername == "" {
githubUsername = mapping.GitHubUsername
foundOrg = org
slog.Info("mapped Slack user to GitHub for report",
"slack_user", slackUserID,
"github_user", githubUsername,
"org", org,
"match_method", mapping.MatchMethod,
"confidence", mapping.Confidence)
userOrgs = append(userOrgs, org)
continue
}
// Check if subsequent matches have the same GitHub username
if mapping.GitHubUsername != githubUsername {
// Different GitHub username in this org - skip it
slog.Warn("user maps to different GitHub username in org, skipping",
"slack_user", slackUserID,
"org", org,
"expected_github_user", githubUsername,
"found_github_user", mapping.GitHubUsername)
continue
}
// Same username in another org - include it
slog.Info("found user in additional org",
"slack_user", slackUserID,
"github_user", githubUsername,
"org", org,
"match_method", mapping.MatchMethod,
"confidence", mapping.Confidence)
userOrgs = append(userOrgs, org)
}
if githubUsername == "" {
slog.Warn("cannot generate report: Slack user not mapped to any GitHub user",
"slack_user", slackUserID,
"workspace", workspaceName,
"checked_orgs", allOrgs)
return fmt.Errorf("could not find GitHub username for Slack user %s", slackUserID)
}
slog.Info("collected user orgs for report",
"slack_user", slackUserID,
"github_user", githubUsername,
"orgs", userOrgs,
"org_count", len(userOrgs))
// Get GitHub client for the org
ghClient, ok := h.githubManager.ClientForOrg(foundOrg)
if !ok {
slog.Error("no GitHub client for org", "org", foundOrg)
return fmt.Errorf("no GitHub client for org %s", foundOrg)
}
// Get GitHub token
token := ghClient.InstallationToken(ctx)
// Create dashboard fetcher - need to cast to *github.Client from go-github package
goGitHubClient, ok := ghClient.Client().(*gogithub.Client)
if !ok {
slog.Error("failed to cast GitHub client", "org", foundOrg)
return fmt.Errorf("failed to cast GitHub client for org %s", foundOrg)
}
fetcher := home.NewFetcher(goGitHubClient, h.stateStore, token, "reviewgoose[bot]")
// Fetch dashboard for user across all orgs where they're a member
slog.Info("fetching dashboard for manual report",
"slack_user", slackUserID,
"github_user", githubUsername,
"orgs", userOrgs)
dashboard, err := fetcher.FetchDashboard(ctx, githubUsername, userOrgs)
if err != nil {
slog.Error("failed to fetch dashboard for manual report",
"slack_user", slackUserID,
"github_user", githubUsername,
"error", err)
return fmt.Errorf("failed to fetch dashboard: %w", err)
}
slog.Info("dashboard fetched successfully",
"slack_user", slackUserID,
"github_user", githubUsername,
"incoming_prs", len(dashboard.IncomingPRs),
"outgoing_prs", len(dashboard.OutgoingPRs),
"workspace_orgs", dashboard.WorkspaceOrgs)
// If user has no PRs, send a friendly message
if len(dashboard.IncomingPRs) == 0 && len(dashboard.OutgoingPRs) == 0 {
slog.Info("user has no PRs for report",
"slack_user", slackUserID,
"github_user", githubUsername)
_, _, err := slackClient.SendDirectMessage(ctx, slackUserID,
"🎉 You're all caught up! No pending PR reviews or outgoing PRs right now.")
if err != nil {
slog.Error("failed to send empty report message",
"slack_user", slackUserID,
"error", err)
return fmt.Errorf("failed to send message: %w", err)
}
// Record that we sent a report (even if empty)
if err := h.stateStore.RecordReportSent(ctx, slackUserID, time.Now()); err != nil {
slog.Warn("failed to record empty report timestamp",
"slack_user", slackUserID,
"error", err)
}
return nil
}
// Build report blocks
blocks := dailyreport.BuildReportBlocks(dashboard.IncomingPRs, dashboard.OutgoingPRs)
// Send the report
_, _, err = slackClient.SendDirectMessageWithBlocks(ctx, slackUserID, blocks)
if err != nil {
slog.Error("failed to send manual daily report",
"slack_user", slackUserID,
"github_user", githubUsername,
"incoming_prs", len(dashboard.IncomingPRs),
"outgoing_prs", len(dashboard.OutgoingPRs),
"error", err)
return fmt.Errorf("failed to send report: %w", err)
}
slog.Info("successfully sent manual daily report",
"slack_user", slackUserID,
"github_user", githubUsername,
"incoming_prs", len(dashboard.IncomingPRs),
"outgoing_prs", len(dashboard.OutgoingPRs))
// Record that we sent a report
if err := h.stateStore.RecordReportSent(ctx, slackUserID, time.Now()); err != nil {
slog.Warn("failed to record report timestamp",
"slack_user", slackUserID,
"error", err)
// Don't fail the whole operation if we can't record the timestamp
}
return nil
}