Skip to content

Commit d7914b5

Browse files
committed
add missing code
1 parent d74b412 commit d7914b5

4 files changed

Lines changed: 999 additions & 0 deletions

File tree

pkg/prx/types/processing.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package types
2+
3+
import (
4+
"time"
5+
)
6+
7+
// FilterEvents removes non-essential events to reduce noise.
8+
// Currently filters out successful status_check events (keeps failures).
9+
func FilterEvents(events []Event) []Event {
10+
filtered := make([]Event, 0, len(events))
11+
12+
for i := range events {
13+
e := &events[i]
14+
// Include all non-status_check events
15+
if e.Kind != EventKindStatusCheck {
16+
filtered = append(filtered, *e)
17+
continue
18+
}
19+
20+
// For status_check events, only include if outcome is failure
21+
if e.Outcome == "failure" {
22+
filtered = append(filtered, *e)
23+
}
24+
}
25+
26+
return filtered
27+
}
28+
29+
// UpgradeWriteAccess scans through events and upgrades write_access from 1 (likely) to 2 (definitely)
30+
// for actors who have performed actions that require write access.
31+
func UpgradeWriteAccess(events []Event) {
32+
// Track actors who have definitely demonstrated write access
33+
confirmed := make(map[string]bool)
34+
35+
// First pass: identify actors who have performed write-access-requiring actions
36+
for i := range events {
37+
e := &events[i]
38+
switch e.Kind {
39+
case EventKindPRMerged, EventKindLabeled, EventKindUnlabeled,
40+
EventKindAssigned, EventKindUnassigned, EventKindMilestoned, EventKindDemilestoned:
41+
// These actions require write access to the repository
42+
if e.Actor != "" {
43+
confirmed[e.Actor] = true
44+
}
45+
default:
46+
// Other event types don't require write access
47+
}
48+
}
49+
50+
// Second pass: upgrade write_access from 1 to 2 for confirmed actors
51+
for i := range events {
52+
if events[i].WriteAccess == WriteAccessLikely {
53+
if confirmed[events[i].Actor] {
54+
events[i].WriteAccess = WriteAccessDefinitely
55+
}
56+
}
57+
}
58+
}
59+
60+
// CalculateCheckSummary analyzes check/status events and categorizes them by outcome.
61+
func CalculateCheckSummary(events []Event, requiredChecks []string) *CheckSummary {
62+
summary := &CheckSummary{
63+
Success: make(map[string]string),
64+
Failing: make(map[string]string),
65+
Pending: make(map[string]string),
66+
Cancelled: make(map[string]string),
67+
Skipped: make(map[string]string),
68+
Stale: make(map[string]string),
69+
Neutral: make(map[string]string),
70+
}
71+
72+
// Track latest state for each check (deduplicates multiple runs of same check)
73+
type checkInfo struct {
74+
timestamp time.Time
75+
outcome string
76+
description string
77+
}
78+
latestChecks := make(map[string]checkInfo)
79+
80+
// Collect latest state for each check
81+
// Events should be sorted chronologically, but we explicitly track timestamps to be safe
82+
for i := range events {
83+
e := &events[i]
84+
if (e.Kind == EventKindStatusCheck || e.Kind == EventKindCheckRun) && e.Body != "" {
85+
existing, exists := latestChecks[e.Body]
86+
// Update if:
87+
// 1. First occurrence (!exists)
88+
// 2. New event has a later timestamp
89+
// 3. Both timestamps are zero (fallback to slice order - later in slice wins)
90+
shouldUpdate := !exists ||
91+
e.Timestamp.After(existing.timestamp) ||
92+
(e.Timestamp.IsZero() && existing.timestamp.IsZero())
93+
94+
if shouldUpdate {
95+
latestChecks[e.Body] = checkInfo{
96+
outcome: e.Outcome,
97+
description: e.Description,
98+
timestamp: e.Timestamp,
99+
}
100+
}
101+
}
102+
}
103+
104+
// Collect checks and categorize them
105+
seen := make(map[string]bool)
106+
for name, info := range latestChecks {
107+
// Track required checks we've seen
108+
for _, req := range requiredChecks {
109+
if req == name {
110+
seen[req] = true
111+
break
112+
}
113+
}
114+
115+
// Categorize the check (each check goes into exactly one category)
116+
switch info.outcome {
117+
case "success":
118+
summary.Success[name] = info.description
119+
case "failure", "error", "timed_out", "action_required":
120+
summary.Failing[name] = info.description
121+
case "cancelled":
122+
summary.Cancelled[name] = info.description
123+
case "pending", "queued", "in_progress", "waiting":
124+
summary.Pending[name] = info.description
125+
case "skipped":
126+
summary.Skipped[name] = info.description
127+
case "stale":
128+
summary.Stale[name] = info.description
129+
case "neutral":
130+
summary.Neutral[name] = info.description
131+
default:
132+
// Unknown outcome, ignore
133+
}
134+
}
135+
136+
// Add missing required checks as pending
137+
for _, req := range requiredChecks {
138+
if !seen[req] {
139+
summary.Pending[req] = "Expected — Waiting for status to be reported"
140+
}
141+
}
142+
143+
return summary
144+
}
145+
146+
// CalculateApprovalSummary analyzes review events and categorizes approvals by reviewer's write access.
147+
func CalculateApprovalSummary(events []Event) *ApprovalSummary {
148+
summary := &ApprovalSummary{}
149+
150+
// Track the latest review state from each user
151+
latestReviews := make(map[string]Event)
152+
153+
for i := range events {
154+
e := &events[i]
155+
if e.Kind == EventKindReview && e.Outcome != "" {
156+
latestReviews[e.Actor] = *e
157+
}
158+
}
159+
160+
// Check permissions for each reviewer and categorize their reviews
161+
for actor := range latestReviews {
162+
review := latestReviews[actor]
163+
switch review.Outcome {
164+
case "approved":
165+
// Use the WriteAccess field that was already populated in the event
166+
switch review.WriteAccess {
167+
case WriteAccessDefinitely:
168+
// Confirmed write access (OWNER, COLLABORATOR, or verified MEMBER)
169+
summary.ApprovalsWithWriteAccess++
170+
case WriteAccessNo:
171+
// Confirmed no write access (explicitly denied)
172+
summary.ApprovalsWithoutWriteAccess++
173+
default:
174+
// Unknown/uncertain write access or unexpected values - treat as unknown
175+
summary.ApprovalsWithUnknownAccess++
176+
}
177+
case "changes_requested":
178+
summary.ChangesRequested++
179+
default:
180+
// Ignore other review states like "commented"
181+
}
182+
}
183+
184+
return summary
185+
}
186+
187+
// CalculateParticipantAccess builds a map of all PR participants to their write access levels.
188+
// Includes the PR author, assignees, reviewers, and all event actors.
189+
func CalculateParticipantAccess(events []Event, pr *PullRequest) map[string]int {
190+
participants := make(map[string]int)
191+
192+
// Add the PR author
193+
if pr.Author != "" {
194+
participants[pr.Author] = pr.AuthorWriteAccess
195+
}
196+
197+
// Add assignees (write access unknown)
198+
for _, assignee := range pr.Assignees {
199+
if assignee != "" {
200+
if _, exists := participants[assignee]; !exists {
201+
participants[assignee] = WriteAccessNA
202+
}
203+
}
204+
}
205+
206+
// Add reviewers (write access unknown at this point)
207+
for reviewer := range pr.Reviewers {
208+
if reviewer != "" {
209+
if _, exists := participants[reviewer]; !exists {
210+
participants[reviewer] = WriteAccessNA
211+
}
212+
}
213+
}
214+
215+
// Collect all unique actors from events and upgrade write access where known
216+
for i := range events {
217+
e := &events[i]
218+
if e.Actor != "" {
219+
// Keep the highest write access level if we see the same actor multiple times
220+
if existing, ok := participants[e.Actor]; !ok {
221+
// New participant
222+
participants[e.Actor] = e.WriteAccess
223+
} else if e.WriteAccess > existing {
224+
// Upgrade to higher write access level
225+
participants[e.Actor] = e.WriteAccess
226+
}
227+
}
228+
}
229+
230+
return participants
231+
}

0 commit comments

Comments
 (0)