Skip to content
Source
Sign inSign up

threadPullRequests.ts

65 lines2.7 KB

1import type { ThreadLinkedPullRequest, ThreadPullRequestLink } from "@t3tools/contracts";
2​
3/** Tombstones prevent sync from re-adding hidden members. */
4export function visibleThreadPullRequests(
5 links: ReadonlyArray<ThreadPullRequestLink>,
6): ReadonlyArray<ThreadPullRequestLink> {
7 return links.filter((link) => link.source !== "stack-dismissed");
8}
9​
10function latestUpdatedAt(link: ThreadPullRequestLink): number {
11 const value = link.snapshot?.updatedAt ?? link.linkedAt;
12 const ms = Date.parse(value);
13 return Number.isNaN(ms) ? 0 : ms;
15​
16export interface ThreadSearchPullRequest {
17 readonly number: number;
18 readonly title: string | null;
19 /** `owner/repo#123` and the pull request URL, matched only by a whole query. */
20 readonly references: ReadonlyArray<string>;
22​
23const SEARCHED_PULL_REQUEST_TITLES = 3;
24​
25/** Pull requests thread search matches by number, title, and reference. */
26export function threadSearchPullRequests(thread: {
27 readonly pullRequests?: ReadonlyArray<ThreadPullRequestLink> | undefined;
28 readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined;
29 readonly branchPullRequest?: ThreadLinkedPullRequest | null | undefined;
30}): ThreadSearchPullRequest[] {
31 const entry = (
32 link: Pick<ThreadLinkedPullRequest, "number" | "repository" | "url">,
33 title: string | null,
34 ): ThreadSearchPullRequest => ({
35 number: link.number,
36 title,
37 references: [`${link.repository}#${link.number}`, link.url],
38 });
39 const branch = thread.branchPullRequest ? [entry(thread.branchPullRequest, null)] : [];
40 if (thread.pullRequests !== undefined && thread.pullRequests.length > 0) {
41 // Every pull request matches by number and link, but only the newest few
42 // by title: a triage thread linked to dozens would match almost any word.
43 const newestFirst = [...visibleThreadPullRequests(thread.pullRequests)].sort(
44 (left, right) => latestUpdatedAt(right) - latestUpdatedAt(left),
45 );
46 return branch.concat(
47 newestFirst.map((link, index) =>
48 entry(link, index < SEARCHED_PULL_REQUEST_TITLES ? (link.snapshot?.title ?? null) : null),
49 ),
50 );
51 }
52 return thread.linkedPullRequest ? branch.concat(entry(thread.linkedPullRequest, null)) : branch;
54​
55export function threadPullRequestSearchTerms(thread: {
56 readonly pullRequests?: ReadonlyArray<ThreadPullRequestLink> | undefined;
57 readonly linkedPullRequest?: ThreadLinkedPullRequest | null | undefined;
58 readonly branchPullRequest?: ThreadLinkedPullRequest | null | undefined;
59}): string[] {
60 return threadSearchPullRequests(thread).flatMap((pullRequest) => [
61 `#${pullRequest.number}`,
62 ...pullRequest.references,
63 ...(pullRequest.title ? [pullRequest.title] : []),
64 ]);