Automate setting closed issue milestones#37872
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the existing GitHub Actions automation which labels issues closed by merged PRs, extending it to also move those closed issues from the Backlog milestone (or no milestone) to the milestone for the current major version derived from eng/Versions.props.
Changes:
- Read
VersionPrefixfromeng/Versions.propson the PR target branch to determine the major-version milestone (e.g.11.0.0). - Query closing issues via GraphQL including their milestone, and update milestones when needed.
- Improve logging and aggregate per-issue failures into a single workflow failure.
Comments suppressed due to low confidence (3)
.github/workflows/label-and-milestone-issues.yml:118
- The workflow now throws when no matching preview/RC
release/*branches are found (or when the latest major has no preview/RC branches). Since this job runs on every merged PR tomain, this change can turn a previously benign 'skip' case into a failing workflow run, creating noisy failures during periods without preview/RC branches. Consider logging and returning (skip) instead of throwing here, unless a failing workflow run is explicitly desired.
.github/workflows/label-and-milestone-issues.yml:168 listMilestonesis called withstate: 'open'and only the first page (per_page: 100) is fetched. This can cause the workflow to fail for servicing branches where the${majorVersion}.0.0milestone may already be closed (or if there are >100 milestones), even though GitHub still allows assigning a closed milestone to issues. Consider listing milestones withstate: 'all'and paginating (or usinggithub.paginate) before searching by title.
.github/workflows/label-and-milestone-issues.yml:50parseInt(versionPrefixMatch[1])should pass a radix (e.g. 10) for consistent parsing and to avoid edge cases if the string ever includes a leading zero or other prefix.
You can also share your feedback on Copilot code review. Take the survey.
|
@AndriySvyryd I did a bit of a bigger rewrite/simplification here - we now simply always look at eng/Versions.props to know which version the PR is going into (both for release and for preview/RC versions - no more of the version sniffing via which branches exist). Importantly we now always assign the milestone based on this - even if a milestone is already assigned (so we clobber any existing milestone assignment). I think this should be safe: the contents of Versions.props for the PR's merge commit should always determine the milestone and preview/RC label. So maybe take another quick look if you want and let me know if you see a problem. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
You can also share your feedback on Copilot code review. Take the survey.
That means that if an issue was fixed in servicing and then merged to main it will be marked as fixed in 11. It should set the |
|
@AndriySvyryd good catch, pushed an update to do this. Existing milestones which don't parse as an X.Y.Z version (e.g. Backlog, MQ...) are still always clobbered. Take a look, obviously we'll need to pay close attention on the first few invocations to make sure this is doing the right thing, but seems legit. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
You can also share your feedback on Copilot code review. Take the survey.
| // Look up the target milestone (e.g. "11.0.0", "10.0.5") via GraphQL | ||
| const targetMilestoneName = versionPrefix; | ||
| const milestoneResult = await github.graphql(` | ||
| query($owner: String!, $repo: String!, $title: String!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| milestones(query: $title, first: 1) { | ||
| nodes { | ||
| number | ||
| title | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, repo, title: targetMilestoneName }); | ||
| const milestoneNode = milestoneResult.repository.milestones.nodes | ||
| .find(m => m.title === targetMilestoneName); | ||
| if (!milestoneNode) { | ||
| throw new Error(`Milestone '${targetMilestoneName}' not found`); | ||
| } |
There was a problem hiding this comment.
Milestone lookup can miss the exact milestone and fail the workflow: the GraphQL query requests only first: 1, but query: $title is a search and may return a different milestone (e.g. a milestone whose title contains the version string) as the first result. Fetch more results (or paginate) and then select the exact title match; also consider including both OPEN and CLOSED milestones so the lookup still works if the target milestone was closed.
| query($owner: String!, $repo: String!, $prNumber: Int!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| pullRequest(number: $prNumber) { | ||
| closingIssuesReferences(first: 50) { | ||
| nodes { | ||
| number | ||
| milestone { | ||
| title | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| const result = await github.graphql(query, { owner, repo, prNumber }); | ||
| const closingIssues = result.repository.pullRequest.closingIssuesReferences.nodes; | ||
|
|
There was a problem hiding this comment.
closingIssuesReferences(first: 50) will only process the first 50 issues closed by a PR; any additional linked closing issues won’t get labeled/milestoned. Consider paginating this connection (e.g. request pageInfo { hasNextPage endCursor } and loop) so all closing issues are handled.
| query($owner: String!, $repo: String!, $prNumber: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $prNumber) { | |
| closingIssuesReferences(first: 50) { | |
| nodes { | |
| number | |
| milestone { | |
| title | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| const result = await github.graphql(query, { owner, repo, prNumber }); | |
| const closingIssues = result.repository.pullRequest.closingIssuesReferences.nodes; | |
| query($owner: String!, $repo: String!, $prNumber: Int!, $after: String) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $prNumber) { | |
| closingIssuesReferences(first: 50, after: $after) { | |
| nodes { | |
| number | |
| milestone { | |
| title | |
| } | |
| } | |
| pageInfo { | |
| hasNextPage | |
| endCursor | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| let closingIssues = []; | |
| let hasNextPage = true; | |
| let cursor = null; | |
| while (hasNextPage) { | |
| const result = await github.graphql(query, { owner, repo, prNumber, after: cursor }); | |
| const page = result.repository.pullRequest.closingIssuesReferences; | |
| closingIssues = closingIssues.concat(page.nodes); | |
| hasNextPage = page.pageInfo.hasNextPage; | |
| cursor = page.pageInfo.endCursor; | |
| } |
Continues the previous work for applying preview version labels: this now also sets the closed issue's milestone, for both vnext on main (e.g. 11.0.0) and for release (servicing) PRs.