fix: server-side task ID injection for plan tools and UI polish (#431)
This commit is contained in:
@@ -195,21 +195,30 @@ func convertRawStatusContexts(raw []ghStatusContext) []CheckRun {
|
||||
return checks
|
||||
}
|
||||
|
||||
// mergeChecks deduplicates check runs and commit statuses by normalized name/url.
|
||||
// If both sources refer to the same item, check-run data wins.
|
||||
// mergeChecks deduplicates check runs and commit statuses by normalized name.
|
||||
// Check-run source wins over status-context; among same-source duplicates the
|
||||
// most recent entry (by StartedAt) is kept.
|
||||
func mergeChecks(checkRuns, statusChecks []CheckRun) []CheckRun {
|
||||
merged := make([]CheckRun, 0, len(checkRuns)+len(statusChecks))
|
||||
byKey := make(map[string]int)
|
||||
|
||||
for _, check := range statusChecks {
|
||||
key := checkMergeKey(check)
|
||||
if idx, ok := byKey[key]; ok {
|
||||
if isNewerCheck(check, merged[idx]) {
|
||||
merged[idx] = check
|
||||
}
|
||||
continue
|
||||
}
|
||||
byKey[key] = len(merged)
|
||||
merged = append(merged, check)
|
||||
}
|
||||
for _, check := range checkRuns {
|
||||
key := checkMergeKey(check)
|
||||
if idx, ok := byKey[key]; ok {
|
||||
merged[idx] = check
|
||||
if merged[idx].Source == checkSourceStatusContext || isNewerCheck(check, merged[idx]) {
|
||||
merged[idx] = check
|
||||
}
|
||||
continue
|
||||
}
|
||||
byKey[key] = len(merged)
|
||||
@@ -219,9 +228,18 @@ func mergeChecks(checkRuns, statusChecks []CheckRun) []CheckRun {
|
||||
}
|
||||
|
||||
func checkMergeKey(check CheckRun) string {
|
||||
name := strings.ToLower(strings.TrimSpace(check.Name))
|
||||
url := strings.ToLower(strings.TrimSpace(check.HTMLURL))
|
||||
return name + "|" + url
|
||||
return strings.ToLower(strings.TrimSpace(check.Name))
|
||||
}
|
||||
|
||||
// isNewerCheck reports whether a started more recently than b.
|
||||
func isNewerCheck(a, b CheckRun) bool {
|
||||
if a.StartedAt == nil {
|
||||
return false
|
||||
}
|
||||
if b.StartedAt == nil {
|
||||
return true
|
||||
}
|
||||
return a.StartedAt.After(*b.StartedAt)
|
||||
}
|
||||
|
||||
// convertSearchItemToPR converts common search result fields into a PR struct.
|
||||
|
||||
@@ -264,6 +264,60 @@ func TestConvertRawStatusContextsAndMergeChecks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeChecksDuplicateCheckRunsByName(t *testing.T) {
|
||||
t1 := time.Date(2025, 6, 1, 10, 0, 0, 0, time.UTC)
|
||||
t2 := time.Date(2025, 6, 1, 11, 0, 0, 0, time.UTC)
|
||||
|
||||
// Two check-runs with the same name but different URLs (different check suites).
|
||||
checkRuns := []CheckRun{
|
||||
{Name: "Analyze (go)", Source: checkSourceCheckRun, Status: "in_progress", HTMLURL: "https://github.com/o/r/runs/1", StartedAt: &t1},
|
||||
{Name: "Analyze (go)", Source: checkSourceCheckRun, Status: "in_progress", HTMLURL: "https://github.com/o/r/runs/2", StartedAt: &t2},
|
||||
}
|
||||
merged := mergeChecks(checkRuns, nil)
|
||||
if len(merged) != 1 {
|
||||
t.Fatalf("expected 1 check after dedupe, got %d", len(merged))
|
||||
}
|
||||
if merged[0].HTMLURL != "https://github.com/o/r/runs/2" {
|
||||
t.Errorf("expected newer check-run to win, got URL %s", merged[0].HTMLURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeChecksCheckRunWinsOverStatusDifferentURL(t *testing.T) {
|
||||
t1 := time.Date(2025, 6, 1, 10, 0, 0, 0, time.UTC)
|
||||
|
||||
checkRuns := []CheckRun{
|
||||
{Name: "ci/test", Source: checkSourceCheckRun, Status: "completed", Conclusion: "success", HTMLURL: "https://github.com/o/r/runs/1", StartedAt: &t1},
|
||||
}
|
||||
statuses := []CheckRun{
|
||||
{Name: "ci/test", Source: checkSourceStatusContext, Status: "in_progress", HTMLURL: "https://other-url.com/status/1", StartedAt: &t1},
|
||||
}
|
||||
merged := mergeChecks(checkRuns, statuses)
|
||||
if len(merged) != 1 {
|
||||
t.Fatalf("expected 1 check after dedupe, got %d", len(merged))
|
||||
}
|
||||
if merged[0].Source != checkSourceCheckRun {
|
||||
t.Errorf("expected check_run to win over status_context, got source %s", merged[0].Source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNewerCheck(t *testing.T) {
|
||||
t1 := time.Date(2025, 6, 1, 10, 0, 0, 0, time.UTC)
|
||||
t2 := time.Date(2025, 6, 1, 11, 0, 0, 0, time.UTC)
|
||||
|
||||
if isNewerCheck(CheckRun{StartedAt: &t1}, CheckRun{StartedAt: &t2}) {
|
||||
t.Error("older should not be newer")
|
||||
}
|
||||
if !isNewerCheck(CheckRun{StartedAt: &t2}, CheckRun{StartedAt: &t1}) {
|
||||
t.Error("newer should be newer")
|
||||
}
|
||||
if isNewerCheck(CheckRun{StartedAt: nil}, CheckRun{StartedAt: &t1}) {
|
||||
t.Error("nil StartedAt should not be newer")
|
||||
}
|
||||
if !isNewerCheck(CheckRun{StartedAt: &t1}, CheckRun{StartedAt: nil}) {
|
||||
t.Error("non-nil should be newer than nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertSearchItemToPR(t *testing.T) {
|
||||
now := time.Now()
|
||||
pr := convertSearchItemToPR(
|
||||
|
||||
Reference in New Issue
Block a user