-
Notifications
You must be signed in to change notification settings - Fork 19
Initial attempt at adding an option for grouping related issues #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds a new action input to optionally create “tracking” issues that group multiple newly filed issues sharing the same problemShort.
Changes:
- Introduces a new
open_tracking_issuesinput (defaultfalse) on the top-level action and forwards it into thefilesub-action. - Updates the
fileaction implementation to collect newly-created issues byproblemShortand (when enabled) open a tracking issue per group.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
action.yml |
Adds/forwards the open_tracking_issues input to the file step. |
.github/actions/file/action.yml |
Declares the new open_tracking_issues input for the file action. |
.github/actions/file/src/index.ts |
Implements grouping logic and tracking-issue creation behind the new flag. |
Comments suppressed due to low confidence (1)
.github/actions/file/src/index.ts:61
trackingIssueUrlsis populated later but never read anywhere. Either use it to implement the intended back-linking from newly created issues to the tracking issue, or remove the variable to avoid dead code.
const newIssuesByProblemShort: Record<string, { url: string; id: number }[]> =
{};
const trackingIssueUrls: Record<string, string> = {};
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| newIssuesByProblemShort[problemShort].push({ | ||
| url: response.data.html_url, | ||
| id: response.data.number, | ||
| }); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The collected issue object uses { url, id: response.data.number }, but response.data.number is the issue number (not the GitHub issue id). Rename this field to something like issueNumber to avoid confusion with response.data.id (which you later store into filing.issue.id).
| import { openIssue } from "./openIssue.js"; | ||
| import { reopenIssue } from "./reopenIssue.js"; | ||
| import { updateFilingsWithNewFindings } from "./updateFilingsWithNewFindings.js"; | ||
| import { OctokitResponse } from "@octokit/types"; |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OctokitResponse is only used as a type. Use a type-only import (import type { OctokitResponse } ...) to avoid accidentally generating a runtime import if TS compiler options change (e.g., verbatimModuleSyntax).
| import { OctokitResponse } from "@octokit/types"; | |
| import type { OctokitResponse } from "@octokit/types"; |
| // Open tracking issues for each root cause and link back from each newly-created issue | ||
| if (shouldOpenTrackingIssues) { | ||
| for (const [problemShort, issues] of Object.entries( | ||
| newIssuesByProblemShort, | ||
| ) as [string, { url: string; id: number }[]][]) { | ||
| if (issues.length > 1) { | ||
| const title: string = `${problemShort} issues`; | ||
| const body: string = | ||
| `# ${problemShort} issues\n\n` + | ||
| issues.map((issue) => `- [ ] ${issue.url}`).join("\n"); | ||
| try { | ||
| const trackingResponse = await octokit.request( | ||
| `POST /repos/${repoWithOwner}/issues`, | ||
| { | ||
| owner: repoWithOwner.split("/")[0], | ||
| repo: repoWithOwner.split("/")[1], | ||
| title, | ||
| body, | ||
| }, | ||
| ); | ||
| const trackingUrl: string = trackingResponse.data.html_url; | ||
| trackingIssueUrls[problemShort] = trackingUrl; | ||
| core.info( | ||
| `Opened tracking issue for '${problemShort}': ${issues.length} issues.`, | ||
| ); | ||
| } catch (error) { | ||
| core.warning( | ||
| `Failed to open tracking issue for '${problemShort}': ${error}`, | ||
| ); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New tracking-issue behavior is behind a flag but has no test coverage. Consider adding/adjusting a Vitest test to assert that when open_tracking_issues is enabled and multiple new issues share the same problemShort, exactly one tracking issue is created and includes links to those issues (can be gated behind describe.runIf(!!process.env.GITHUB_TOKEN) like existing GitHub API tests).
| core.setOutput("filings", JSON.stringify(filings)); | ||
| core.debug(`Output: 'filings: ${JSON.stringify(filings)}'`); | ||
| core.info("Finished 'file' action"); | ||
| // Open tracking issues for each root cause and link back from each newly-created issue |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says tracking issues are opened "and link back from each newly-created issue", but the implementation only creates tracking issues and never updates the newly created issues with a backlink. Update the comment or implement the backlinking to avoid misleading future maintainers.
This issue also appears on line 58 of the same file.
| // Open tracking issues for each root cause and link back from each newly-created issue | |
| // Open tracking issues for each root cause based on newly-created issues |
| // Open tracking issues for each root cause and link back from each newly-created issue | ||
| if (shouldOpenTrackingIssues) { | ||
| for (const [problemShort, issues] of Object.entries( | ||
| newIssuesByProblemShort, | ||
| ) as [string, { url: string; id: number }[]][]) { | ||
| if (issues.length > 1) { | ||
| const title: string = `${problemShort} issues`; | ||
| const body: string = | ||
| `# ${problemShort} issues\n\n` + | ||
| issues.map((issue) => `- [ ] ${issue.url}`).join("\n"); | ||
| try { | ||
| const trackingResponse = await octokit.request( | ||
| `POST /repos/${repoWithOwner}/issues`, | ||
| { | ||
| owner: repoWithOwner.split("/")[0], | ||
| repo: repoWithOwner.split("/")[1], | ||
| title, | ||
| body, | ||
| }, | ||
| ); | ||
| const trackingUrl: string = trackingResponse.data.html_url; | ||
| trackingIssueUrls[problemShort] = trackingUrl; | ||
| core.info( | ||
| `Opened tracking issue for '${problemShort}': ${issues.length} issues.`, | ||
| ); | ||
| } catch (error) { | ||
| core.warning( | ||
| `Failed to open tracking issue for '${problemShort}': ${error}`, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| core.setOutput("filings", JSON.stringify(filings)); | ||
| core.debug(`Output: 'filings: ${JSON.stringify(filings)}'`); | ||
| core.info("Finished 'file' action"); | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
core.setOutput("filings", ...) (and the "Finished 'file' action" log) now run only when shouldOpenTrackingIssues is true. When the flag is false, the action produces no filings output at all, which will break downstream steps that rely on steps.file.outputs.filings (and also changes behavior from before this PR). Move the output/log statements outside the conditional so they always run, and keep the tracking-issue logic inside the flag guard.
| description: "Cached filings from previous runs, as stringified JSON. Without this, duplicate issues may be filed." | ||
| required: false | ||
| open_tracking_issues: | ||
| description: "In the 'file' step, also open tracking issues which link to all issues with the same problem" |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
core.getBooleanInput("open_tracking_issues") throws if the input is missing or an empty string. Since this action’s action.yml declares the input as optional and has no default, running the file action standalone without specifying open_tracking_issues will fail. Add a default of "false" in .github/actions/file/action.yml (or read it as a string input with a fallback).
| description: "In the 'file' step, also open tracking issues which link to all issues with the same problem" | |
| description: "In the 'file' step, also open tracking issues which link to all issues with the same problem" | |
| default: "false" |
| // Track for grouping | ||
| if (shouldOpenTrackingIssues) { | ||
| const problemShort: string = filing.findings[0].problemShort; | ||
| if (!newIssuesByProblemShort[problemShort]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was originally gonna say: looks like if this if condition is not met, we don't end up pushing anything to the list, but then I realized this line:
newIssuesByProblemShort[problemShort] = [];
is indented more than the rest, and only that line belongs to the condition. For single line conditions like this, my personal preference is either keeping everything on 1 line:
if (x) doY();
or wrapping in brackets
if (x) {
doY();
}
otherwise it can be easy to miss the intention of the logic.
also, if the problem here was with the length of the line, maybe another approach could help break it up into multiple lines:
const newIssues = newIssuesByProblemShort[problemShort] || [];
newIssues.push({...});
newIssuesByProblemShort[problemShort] = newIssues;
🤷 your call, I'm just thinking out loud mostly
| if (shouldOpenTrackingIssues) { | ||
| for (const [problemShort, issues] of Object.entries( | ||
| newIssuesByProblemShort, | ||
| ) as [string, { url: string; id: number }[]][]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should declare a type for this: { url: string; id: number } - that can make these statements a little easier to read. maybe: type FindingGroupIssue? and then it can be reused on line 58 above too. To take it further, we can also declare a type for the wrapper object: string, { url: string; id: number }[]
type FindingGroupIssue {
url: string
id: number
}
type IssuesByProblemShort {
problemShort: string
issues: FindingGroupIssue[]
}
again, not a blocker, just a nice to have for readability
| body, | ||
| }, | ||
| ); | ||
| const trackingUrl: string = trackingResponse.data.html_url; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we will want to wrap this in a model in the near future (especially if we start working with other issue types), specifically this part: trackingResponse.data.html_url;. and some of the code around interfacing with octokit (something like this for models, and something like this for a 'client' of some kind - these are just examples, I'm open to other approaches too).
abdulahmad307
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general approach looks good! Left some comments for things to consider.
No description provided.