mirror of
https://github.com/HDFGroup/hdf5.git
synced 2026-09-25 04:09:44 +03:00
Fix release-progress badges to read native issue-level Priority field (#6668)
* Fix release-progress badges to read native issue-level Priority field The Project Priority board field was consolidated into a Priority field managed at the issue level (GitHub's org-wide custom issue fields), which the GraphQL API mirrors into projects as ProjectV2ItemIssueFieldValue instead of ProjectV2ItemFieldSingleSelectValue. The badge script's query had no fragment for that type, so it silently found zero priority values and would raise ProjectFieldMissingError on every run. * Show TBD on the Next Release badge when no milestone due date is set Previously the badge silently omitted any due-date annotation when the milestone had none, which read as if the badge just hadn't picked one up. * Update release-schedule image and plantuml source from add-next-release-badge branch * Don't flag CODEOWNERS auto-assignment as a manual reviewer add on a PR's first pass GitHub's own CODEOWNERS engine fires an identical review_requested webhook (sender type User, not Bot) when it auto-assigns owners at PR-open time. If one of those survives the cancel-in-progress race as the run that actually executes, it was mistaken for a human deliberately requesting that reviewer, permanently flagging them "manually added" and requiring separate approval — e.g. a catch-all "*" owner also named on a touched area's CODEOWNERS line got flagged on every PR touching that area, even though no human ever picked them. Gate the manual-add detection and the sticky per-area assignment on !isFirstCoordinationPass (no checklist comment posted yet), since that's exactly the window where GitHub's own auto-assignment is indistinguishable from a real human pick.
This commit is contained in:
@@ -697,7 +697,18 @@ async function coordinateReviewers(github, context, core, {
|
||||
updatedExcluded.add(login);
|
||||
updatedManuallyAdded.delete(login);
|
||||
core.info(`${login} explicitly removed — excluding from future auto-reassignment`);
|
||||
} else if (action === 'review_requested' && context.payload.requested_reviewer && !isBotSender) {
|
||||
} else if (action === 'review_requested' && context.payload.requested_reviewer && !isBotSender && !isFirstCoordinationPass) {
|
||||
// Also gated on !isFirstCoordinationPass: on a PR's first coordination
|
||||
// pass, GitHub's own CODEOWNERS auto-assignment fires this identical
|
||||
// review_requested event, attributed to the PR's own opener (a User, not
|
||||
// a Bot) — isBotSender can't catch that one. Without this guard, every
|
||||
// owner CODEOWNERS auto-assigns at PR-open time gets mistaken for a
|
||||
// deliberate human pick, permanently sticking them as "manually added"
|
||||
// (e.g. a catch-all "*" owner who is also named on a touched area's
|
||||
// CODEOWNERS line ends up flagged for approval on every single PR that
|
||||
// touches that area). Once the checklist has been posted once, a later
|
||||
// review_requested is a real signal again — a maintainer choosing to
|
||||
// add someone, not the initial avalanche.
|
||||
const login = context.payload.requested_reviewer.login;
|
||||
if (updatedExcluded.delete(login)) {
|
||||
core.info(`${login} explicitly re-requested — clearing prior exclusion`);
|
||||
@@ -719,10 +730,12 @@ async function coordinateReviewers(github, context, core, {
|
||||
// must survive this same run: it lands existingRequested at two owners for
|
||||
// that login's area (them plus whoever an earlier pruning pass already
|
||||
// picked), which is indistinguishable from an unpruned CODEOWNERS avalanche
|
||||
// unless this login is carved out. Gated on !isBotSender for the same
|
||||
// reason as updatedManuallyAdded above — the bot's own requestReviewers
|
||||
// calls fire this identical event and aren't a human decision.
|
||||
const justRequestedLogin = (action === 'review_requested' && context.payload.requested_reviewer && !isBotSender)
|
||||
// unless this login is carved out. Gated on !isBotSender and
|
||||
// !isFirstCoordinationPass for the same reasons as updatedManuallyAdded
|
||||
// above — the bot's own requestReviewers calls fire this identical event
|
||||
// and aren't a human decision, and neither is GitHub's own CODEOWNERS
|
||||
// auto-assignment surviving as the first coordination pass's event.
|
||||
const justRequestedLogin = (action === 'review_requested' && context.payload.requested_reviewer && !isBotSender && !isFirstCoordinationPass)
|
||||
? context.payload.requested_reviewer.login
|
||||
: null;
|
||||
|
||||
|
||||
@@ -1049,7 +1049,10 @@ asyncTest('coordinateReviewers: review_requested survives the opened race and st
|
||||
|
||||
const { confirmedRequested } = await coordinateReviewers(github, context, makeCore(), args);
|
||||
|
||||
assert.strictEqual(confirmedRequested.size, 1);
|
||||
// Must prune to the normal load-balanced pick (hyoklee) — not stick with
|
||||
// whichever CODEOWNERS auto-assignment happened to survive as this run's
|
||||
// review_requested event (jhendersonHDF).
|
||||
assert.deepStrictEqual([...confirmedRequested], ['hyoklee']);
|
||||
assert.ok(github.calls.removeRequestedReviewers.length > 0);
|
||||
});
|
||||
|
||||
@@ -1595,6 +1598,29 @@ function makeManualAddContext(senderType, login) {
|
||||
};
|
||||
}
|
||||
|
||||
asyncTest('coordinateReviewers: review_requested surviving the FIRST coordination pass does NOT mark the reviewer manually-added', async () => {
|
||||
// The exact false positive this guards against: GitHub's own CODEOWNERS
|
||||
// auto-assignment fires review_requested (sender type "User", the PR's own
|
||||
// opener) for every owner of a touched area at PR-open time. If the
|
||||
// cancel-in-progress race lets one of those survive as this run's event
|
||||
// instead of "opened" itself (hasExistingComment: false — no checklist
|
||||
// posted yet), it must not be mistaken for a human's deliberate pick, or
|
||||
// every CODEOWNER — including a catch-all "*" owner — ends up permanently
|
||||
// flagged "manually added, approval required" on every PR that happens to
|
||||
// touch their area.
|
||||
const github = makeGithubMock();
|
||||
const args = makeCoordinateBaseArgs({ hasExistingComment: false });
|
||||
|
||||
const { manuallyAdded, confirmedRequested } = await coordinateReviewers(
|
||||
github, makeManualAddContext('User', 'jhendersonHDF'), makeCore(), args
|
||||
);
|
||||
|
||||
assert.ok(!manuallyAdded.has('jhendersonHDF'));
|
||||
// And the sticky-assignment short-circuit must not have hijacked the
|
||||
// area's pick either — it still falls to the normal load-balanced owner.
|
||||
assert.deepStrictEqual([...confirmedRequested], ['hyoklee']);
|
||||
});
|
||||
|
||||
asyncTest('coordinateReviewers: human review_requested for a CODEOWNER marks them manually-added', async () => {
|
||||
const github = makeGithubMock();
|
||||
const args = makeCoordinateBaseArgs({
|
||||
|
||||
@@ -212,7 +212,7 @@ if [ -n "${VERSION:-}" ] && [ "$VERSION" != "all" ]; then
|
||||
if [ -n "${MILESTONE_DUE_DATE:-}" ]; then
|
||||
VERSION_MESSAGE="$VERSION (target: $MILESTONE_DUE_DATE)"
|
||||
else
|
||||
VERSION_MESSAGE="$VERSION"
|
||||
VERSION_MESSAGE="$VERSION (target: TBD)"
|
||||
fi
|
||||
VERSION_COLOR="blue"
|
||||
else
|
||||
|
||||
@@ -80,6 +80,21 @@ class GitHubProjectTracker:
|
||||
... on ProjectV2ItemFieldDateValue {
|
||||
date, field { ... on ProjectV2Field { name } }
|
||||
}
|
||||
... on ProjectV2ItemIssueFieldValue {
|
||||
field {
|
||||
... on ProjectV2Field { name }
|
||||
... on ProjectV2SingleSelectField { name }
|
||||
... on ProjectV2IterationField { name }
|
||||
... on ProjectV2MultiSelectField { name }
|
||||
}
|
||||
issueFieldValue {
|
||||
__typename
|
||||
... on IssueFieldSingleSelectValue { value }
|
||||
... on IssueFieldTextValue { value }
|
||||
... on IssueFieldNumberValue { value }
|
||||
... on IssueFieldDateValue { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
content {
|
||||
@@ -103,6 +118,15 @@ class GitHubProjectTracker:
|
||||
"""Extracts value from a field based on its type."""
|
||||
type_name = field_data.get("__typename")
|
||||
|
||||
# Fields backed by GitHub's native issue-level custom fields (e.g. a
|
||||
# Priority field managed at the org/repo level rather than as a
|
||||
# project-only field) are mirrored into the project as
|
||||
# ProjectV2ItemIssueFieldValue, with the actual value nested under
|
||||
# issueFieldValue instead of alongside __typename like the other cases.
|
||||
if type_name == "ProjectV2ItemIssueFieldValue":
|
||||
issue_value = field_data.get("issueFieldValue") or {}
|
||||
return issue_value.get("value")
|
||||
|
||||
value_map = {
|
||||
"ProjectV2ItemFieldSingleSelectValue": "name",
|
||||
"ProjectV2ItemFieldIterationValue": "title",
|
||||
|
||||
@@ -112,8 +112,8 @@ The badges below track the release **currently in development**.
|
||||
[](https://github.com/orgs/HDFGroup/projects/39/views/24)
|
||||
|
||||
The **Next Release** badge shows the in-development version (derived from `H5_VERS_MAJOR`/`H5_VERS_MINOR` in
|
||||
[src/H5public.h](src/H5public.h)), annotated with the target due date of the matching GitHub milestone when one is
|
||||
set. The **Latest Release** badge shows the most recently published release in that same major series, with its
|
||||
[src/H5public.h](src/H5public.h)), annotated with the target due date of the matching GitHub milestone, or **TBD**
|
||||
when no due date is set. The **Latest Release** badge shows the most recently published release in that same major series, with its
|
||||
release date. The badges below them show the current progress of **critical**, **high**, **medium**, and
|
||||
**low priority** issues for the in-development release, with colors that reflect completion status:
|
||||
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
The release timeline was generated on PlantUML (https://plantuml.com)
|
||||
|
||||
The current script:
|
||||
|
||||
@startgantt
|
||||
|
||||
title HDF5 Release Schedule
|
||||
|
||||
projectscale monthly
|
||||
Project starts 2024-09-01
|
||||
Project starts 2023-01-01
|
||||
|
||||
[1.14] starts at 2024-09-01 and lasts 30 weeks
|
||||
[1.8] starts 2023-01-01 and lasts 5 weeks
|
||||
[1.8.23 (EOL)] happens 2023-01-31
|
||||
[1.8] is colored in #F76969
|
||||
|
||||
[1.10] starts 2023-01-01 and lasts 39 weeks
|
||||
[1.10.10] happens 2023-03-31
|
||||
[1.10.11 (EOL)] happens 2023-09-30
|
||||
[1.10.11 (EOL)] displays on same row as [1.10.10]
|
||||
[1.10] is colored in #F6DD60
|
||||
|
||||
[1.12] starts 2023-01-01 and lasts 48 weeks
|
||||
[1.12.3 (EOL)] happens 2023-11-30
|
||||
[1.12] is colored in #88CCEE
|
||||
|
||||
[1.14] starts at 2023-01-01 and lasts 110 weeks
|
||||
[1.14.1] happens at 2023-04-30
|
||||
[1.14.2] happens at 2023-08-31
|
||||
[1.14.3] happens at 2023-10-31
|
||||
[1.14.5] happens at 2024-09-30
|
||||
[1.14.6 (EOL)] happens at 2025-01-30
|
||||
[1.14.5] displays on same row as [1.14.5]
|
||||
[1.14.6 (EOL)] displays on same row as [1.14.5]
|
||||
[1.14] is colored in #F76969
|
||||
|
||||
[2.x] starts at 2025-01-31 and lasts 90 weeks
|
||||
[2.0.0] happens at 2025-10-25
|
||||
[2.1.0] happens at 2026-03-03
|
||||
[2.2.0] happens at 2026-07-27
|
||||
[2.0.0] displays on same row as [2.0.0]
|
||||
[2.1.0] displays on same row as [2.0.0]
|
||||
[2.2.0] displays on same row as [2.0.0]
|
||||
[2.x] is colored in #44ffaa
|
||||
[1.14.1] displays on same row as [1.14.1]
|
||||
[1.14.2] displays on same row as [1.14.1]
|
||||
[1.14.3] displays on same row as [1.14.1]
|
||||
[1.14.5] displays on same row as [1.14.1]
|
||||
[1.14.6 (EOL)] displays on same row as [1.14.1]
|
||||
[1.14] is colored in #B187CF
|
||||
|
||||
@endgantt
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 9.7 KiB |
Reference in New Issue
Block a user