Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions src/integrations/project-tracker-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,17 +302,19 @@ type ProjectTrackerMatches = {
project: ProjectTrackerMatch | null;
};

function describeMatch(match: ProjectTrackerMatch, noun: "milestone" | "project"): string {
function describeMatch(match: ProjectTrackerMatch, noun: "milestone" | "project", revealTitle: boolean): string {
const title = revealTitle ? ` ${codeFormat(match.item.title)}` : "";
if (match.source === "native") {
return `This PR is linked to the ${codeFormat(match.item.title)} ${noun} (confirmed via Linear's GitHub integration).`;
return `This PR is linked to the${title} ${noun} (confirmed via Linear's GitHub integration).`;
}
return `This PR looks like it's part of the ${codeFormat(match.item.title)} ${noun} (${Math.round(match.score * 100)}% title/body term overlap).`;
const confidence = revealTitle ? ` (${Math.round(match.score * 100)}% title/body term overlap)` : "";
return `This PR looks like it's part of a matching${title} ${noun}${confidence}.`;
}

function renderSuggestionComment(matches: ProjectTrackerMatches): string {
function renderSuggestionComment(matches: ProjectTrackerMatches, revealTitles: boolean): string {
const lines = [PROJECT_TRACKER_SUGGEST_COMMENT_MARKER];
if (matches.milestone) lines.push(describeMatch(matches.milestone, "milestone"));
if (matches.project) lines.push(describeMatch(matches.project, "project"));
if (matches.milestone) lines.push(describeMatch(matches.milestone, "milestone", revealTitles));
if (matches.project) lines.push(describeMatch(matches.project, "project", revealTitles));
lines.push("", "This is an advisory suggestion only — nothing has been attached automatically.");
return lines.join("\n");
}
Expand Down Expand Up @@ -397,7 +399,9 @@ export async function maybeSuggestProjectOrMilestoneMatch(
}
if (alreadyPosted) return { suggested: false };

await createIssueComment(ctx.env, ctx.installationId, ctx.repoFullName, pullNumber, renderSuggestionComment(matches));
// Linear API keys are workspace-scoped, so project/milestone names may be internal even when the GitHub
// repository is public. Keep the public suggestion useful without echoing Linear tracker titles (#3290).
await createIssueComment(ctx.env, ctx.installationId, ctx.repoFullName, pullNumber, renderSuggestionComment(matches, backend !== "linear"));
return { suggested: true };
}

Expand Down
13 changes: 8 additions & 5 deletions test/unit/linear-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", ()
if (url === "https://api.linear.app/graphql") {
const body = JSON.parse(String(init?.body ?? "{}")) as { query: string };
if (body.query.includes("attachmentsForURL")) {
return Response.json({ data: { attachmentsForURL: { nodes: [{ issue: { project: { id: "proj-1", name: "Self-host reliability roadmap" }, projectMilestone: null } }] } } });
return Response.json({ data: { attachmentsForURL: { nodes: [{ issue: { project: { id: "proj-1", name: "Self-host reliability roadmap" }, projectMilestone: { id: "mile-1", name: "Stealth Launch M3" } } }] } } });
}
projectsListed = true;
return Response.json({ data: { projects: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } } } });
Expand All @@ -204,8 +204,10 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", ()
);
expect(result).toEqual({ suggested: true });
expect(projectsListed).toBe(false);
expect(posted[0]).toContain("linked to");
expect(posted[0]).toContain("Self-host reliability roadmap");
expect(posted[0]).toContain("linked to the project");
expect(posted[0]).toContain("linked to the milestone");
expect(posted[0]).not.toContain("Self-host reliability roadmap");
expect(posted[0]).not.toContain("Stealth Launch M3");
expect(posted[0]).not.toContain("term overlap");
});

Expand Down Expand Up @@ -239,8 +241,9 @@ describe("maybeSuggestProjectOrMilestoneMatch with backend: linear (#3186)", ()
PR_URL,
);
expect(result).toEqual({ suggested: true });
expect(posted[0]).toContain("term overlap");
expect(posted[0]).toContain("Self-host reliability roadmap");
expect(posted[0]).toContain("matching project");
expect(posted[0]).not.toContain("term overlap");
expect(posted[0]).not.toContain("Self-host reliability roadmap");
});

it("API-error best-effort path: a Linear outage propagates to the caller instead of silently mismatching", async () => {
Expand Down