4601dbc2d2
* feat(tasks): search tasks by PR number in command panel Typing a PR number (e.g. 1243 or #1243) in the Cmd+K panel now surfaces the task that PR is associated with, alongside the existing title/description/repo search. The github store gains a workspace-scoped ListTaskIDsByPRNumber lookup over github_task_prs; the task service resolves it through an injected PRTaskResolver interface (github service wired in cmd/kandev) and merges PR matches into search results, deduped and respecting the archived/ephemeral/config filters. Keeping the lookup at the service layer avoids coupling the task repository to the github schema, so existing search queries and tests are untouched. * fix(tasks): scope PR-number search to page 1 and unfiltered queries Address review feedback on PR-number search: - Only augment results on the first page of an unscoped search. Running on every page re-prepended the PR match and truncated a real result off each subsequent page; running under a workflow/repository filter leaked tasks that don't satisfy the filter. - Add idx_github_task_prs_pr_number so the lookup is index-backed instead of a full scan (pr_number is the 3rd column of the existing UNIQUE constraint, unusable as a leading key). - Log instead of silently dropping a task when GetTask fails in the PR-match loop. * test(github): dedup PR-lookup test setup into shared newTestStore Add workspace_id to newTestStore's tasks table and drop the near-identical newTestStoreWithWorkspaces helper, per review feedback. * perf(tasks): batch-fetch PR-matched tasks instead of N+1 GetTask Add a repository GetTasksByIDs and use it in augmentWithPRMatches so PR-number search loads all matched tasks in one query instead of one GetTask per id, addressing the N+1 noted in review. Fetch+filter is extracted into a fetchPRMatchedTasks helper that preserves resolver order and dedup.
62 lines
1.7 KiB
Go
62 lines
1.7 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestListTaskIDsByPRNumber(t *testing.T) {
|
|
// newTestStore's tasks table carries workspace_id, which the
|
|
// workspace-scoped PR-number lookup needs.
|
|
store := newTestStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
// task-1 (ws-1) has PR #1243, task-2 (ws-1) has PR #99,
|
|
// task-3 (ws-2) also has PR #1243 — must not leak into ws-1.
|
|
exec := store.db
|
|
for _, q := range []string{
|
|
`INSERT INTO tasks (id, workspace_id) VALUES ('task-1', 'ws-1')`,
|
|
`INSERT INTO tasks (id, workspace_id) VALUES ('task-2', 'ws-1')`,
|
|
`INSERT INTO tasks (id, workspace_id) VALUES ('task-3', 'ws-2')`,
|
|
} {
|
|
if _, err := exec.Exec(q); err != nil {
|
|
t.Fatalf("seed task: %v", err)
|
|
}
|
|
}
|
|
mkPR := func(id, taskID string, num int) *TaskPR {
|
|
return &TaskPR{
|
|
ID: id, TaskID: taskID, RepositoryID: "repo-" + id,
|
|
Owner: "kdlbs", Repo: "kandev", PRNumber: num,
|
|
PRURL: "https://github.com/kdlbs/kandev/pull/1", PRTitle: "x",
|
|
HeadBranch: "feat/x", BaseBranch: "main", State: "open", CreatedAt: now,
|
|
}
|
|
}
|
|
for _, pr := range []*TaskPR{
|
|
mkPR("p1", "task-1", 1243),
|
|
mkPR("p2", "task-2", 99),
|
|
mkPR("p3", "task-3", 1243),
|
|
} {
|
|
if err := store.CreateTaskPR(ctx, pr); err != nil {
|
|
t.Fatalf("create PR: %v", err)
|
|
}
|
|
}
|
|
|
|
got, err := store.ListTaskIDsByPRNumber(ctx, "ws-1", 1243)
|
|
if err != nil {
|
|
t.Fatalf("lookup: %v", err)
|
|
}
|
|
if len(got) != 1 || got[0] != "task-1" {
|
|
t.Errorf("expected [task-1] for ws-1 PR #1243, got %v", got)
|
|
}
|
|
|
|
none, err := store.ListTaskIDsByPRNumber(ctx, "ws-1", 7777)
|
|
if err != nil {
|
|
t.Fatalf("lookup unknown: %v", err)
|
|
}
|
|
if len(none) != 0 {
|
|
t.Errorf("expected no tasks for unknown PR number, got %v", none)
|
|
}
|
|
}
|