From 7dc5fbef7a92e289aa758fea3d21c2a8ed404ae3 Mon Sep 17 00:00:00 2001 From: fsafasff <2624934035@qq.com> Date: Fri, 29 May 2026 11:25:42 +0800 Subject: [PATCH] fix: include merged time in pr view --- shortcuts/pr/pr.go | 56 +++++++++++++++++++++++++++++++++++------ shortcuts/pr/pr_test.go | 49 +++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/shortcuts/pr/pr.go b/shortcuts/pr/pr.go index 5fd983b..dd55b74 100644 --- a/shortcuts/pr/pr.go +++ b/shortcuts/pr/pr.go @@ -85,7 +85,7 @@ func Shortcuts() []*common.Shortcut { if err != nil { return err } - if err := enrichPullRequestClosedAt(ctx, env); err != nil { + if err := enrichPullRequestFinishedAt(ctx, env); err != nil { return err } return ctx.Output(env) @@ -396,13 +396,13 @@ func extractIssueID(env *output.Envelope) (int64, error) { return int64(idFloat), nil } -func enrichPullRequestClosedAt(ctx *common.RuntimeContext, env *output.Envelope) error { +func enrichPullRequestFinishedAt(ctx *common.RuntimeContext, env *output.Envelope) error { data, ok := env.Data.(map[string]interface{}) if !ok { return nil } pr, ok := data["pull_request"].(map[string]interface{}) - if !ok || !isClosedPullRequest(pr) || stringField(pr, "closed_at") != "" { + if !ok || (!isClosedPullRequest(pr) && !isMergedPullRequest(pr)) { return nil } issue, ok := data["issue"].(map[string]interface{}) @@ -417,12 +417,20 @@ func enrichPullRequestClosedAt(ctx *common.RuntimeContext, env *output.Envelope) if err != nil { return err } - closedAt := extractPullRequestClosedAt(journalsEnv) - if closedAt == "" { - return nil + if isMergedPullRequest(pr) && stringField(pr, "merged_at") == "" { + mergedAt := extractPullRequestMergedAt(journalsEnv) + if mergedAt != "" { + pr["merged_at"] = mergedAt + data["merged_at"] = mergedAt + } + } + if isClosedPullRequest(pr) && stringField(pr, "closed_at") == "" { + closedAt := extractPullRequestClosedAt(journalsEnv) + if closedAt != "" { + pr["closed_at"] = closedAt + data["closed_at"] = closedAt + } } - pr["closed_at"] = closedAt - data["closed_at"] = closedAt return nil } @@ -434,6 +442,14 @@ func isClosedPullRequest(pr map[string]interface{}) bool { return ok && int(status) == 2 } +func isMergedPullRequest(pr map[string]interface{}) bool { + if stringField(pr, "pull_request_staus") == "merged" { + return true + } + status, ok := numberField(pr, "status") + return ok && int(status) == 1 +} + func extractPullRequestClosedAt(env *output.Envelope) string { data, ok := env.Data.(map[string]interface{}) if !ok { @@ -462,6 +478,30 @@ func extractPullRequestClosedAt(env *output.Envelope) string { return "" } +func extractPullRequestMergedAt(env *output.Envelope) string { + data, ok := env.Data.(map[string]interface{}) + if !ok { + return "" + } + rawJournals, ok := data["journals"].([]interface{}) + if !ok { + return "" + } + for i := len(rawJournals) - 1; i >= 0; i-- { + journal, ok := rawJournals[i].(map[string]interface{}) + if !ok || stringField(journal, "operate_category") != "status" { + continue + } + if updatedAt := stringField(journal, "updated_at"); updatedAt != "" { + return updatedAt + } + if createdAt := stringField(journal, "created_at"); createdAt != "" { + return createdAt + } + } + return "" +} + func isPullRequestCloseOperation(content string) bool { content = strings.ToLower(content) return strings.Contains(content, "合并请求") && diff --git a/shortcuts/pr/pr_test.go b/shortcuts/pr/pr_test.go index 530fc9a..669b2d0 100644 --- a/shortcuts/pr/pr_test.go +++ b/shortcuts/pr/pr_test.go @@ -106,6 +106,53 @@ func TestPRViewAddsClosedAtFromIssueJournal(t *testing.T) { assertEqual(t, prData["closed_at"], "2026-05-25 08:58") } +func TestPRViewAddsMergedAtFromIssueJournal(t *testing.T) { + var issueJournalCalled bool + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET" && r.URL.Path == "/owner/repo/pulls/38.json": + writeJSON(t, w, map[string]interface{}{ + "issue": map[string]interface{}{ + "id": float64(142757), + }, + "pull_request": map[string]interface{}{ + "status": float64(1), + "pull_request_staus": "merged", + }, + }) + case r.Method == "GET" && r.URL.Path == "/v1/owner/repo/issues/142757/journals.json": + issueJournalCalled = true + writeJSON(t, w, map[string]interface{}{ + "journals": []map[string]interface{}{ + { + "operate_category": "status", + "operate_content": "merged pull request", + "created_at": "2026-05-25 09:10", + "updated_at": "2026-05-25 09:10", + }, + }, + }) + default: + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + } + })) + defer server.Close() + + env, err := runPRShortcutWithOutput(t, server, "view", map[string]string{ + "id": "38", + }) + if err != nil { + t.Fatalf("view shortcut failed: %v", err) + } + if !issueJournalCalled { + t.Fatal("issue journal endpoint was not called") + } + data := env.Data.(map[string]interface{}) + assertEqual(t, data["merged_at"], "2026-05-25 09:10") + prData := data["pull_request"].(map[string]interface{}) + assertEqual(t, prData["merged_at"], "2026-05-25 09:10") +} + func TestPRViewDoesNotFetchJournalsForOpenPR(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" || r.URL.Path != "/owner/repo/pulls/45.json" { @@ -400,7 +447,7 @@ func runPRShortcutWithOutput(t *testing.T, server *httptest.Server, name string, if err != nil { return nil, err } - if err := enrichPullRequestClosedAt(ctx, env); err != nil { + if err := enrichPullRequestFinishedAt(ctx, env); err != nil { return nil, err } return env, nil