diff --git a/.github/scripts/review-checklist.js b/.github/scripts/review-checklist.js index 20f9f87fa47..e2e0f0bf7b8 100644 --- a/.github/scripts/review-checklist.js +++ b/.github/scripts/review-checklist.js @@ -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; diff --git a/.github/scripts/review-checklist.test.js b/.github/scripts/review-checklist.test.js index b7977892f5e..02e8df5ebd3 100644 --- a/.github/scripts/review-checklist.test.js +++ b/.github/scripts/review-checklist.test.js @@ -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({ diff --git a/.github/workflows/update-badge.sh b/.github/workflows/update-badge.sh index 6763d854427..c8075c22e49 100755 --- a/.github/workflows/update-badge.sh +++ b/.github/workflows/update-badge.sh @@ -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 diff --git a/.github/workflows/update-progress.py b/.github/workflows/update-progress.py index 4e8e75dc8b2..511c9655089 100644 --- a/.github/workflows/update-progress.py +++ b/.github/workflows/update-progress.py @@ -77,8 +77,23 @@ class GitHubProjectTracker: ... on ProjectV2ItemFieldNumberValue { number, field { ... on ProjectV2Field { name } } } - ... on ProjectV2ItemFieldDateValue { - date, field { ... on ProjectV2Field { name } } + ... 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 } + } } } } @@ -102,15 +117,24 @@ class GitHubProjectTracker: def _extract_field_value(self, field_data: Dict[str, Any]) -> Optional[str]: """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", + "ProjectV2ItemFieldIterationValue": "title", "ProjectV2ItemFieldTextValue": "text", "ProjectV2ItemFieldNumberValue": "number", "ProjectV2ItemFieldDateValue": "date" } - + value_key = value_map.get(type_name) return field_data.get(value_key) if value_key else None diff --git a/README.md b/README.md index 53585880b47..ea915477b67 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ The badges below track the release **currently in development**. [![Low Priority](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/HDFGroup-Bot/0ad2eabb63b28eb90d69f5e5b2c1496f/raw/release-low-hdf5.json)](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: diff --git a/release_docs/img/release-schedule.plantuml b/release_docs/img/release-schedule.plantuml index 27289b4ff2f..15e5088632a 100644 --- a/release_docs/img/release-schedule.plantuml +++ b/release_docs/img/release-schedule.plantuml @@ -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 diff --git a/release_docs/img/release-schedule.png b/release_docs/img/release-schedule.png index 1b6a50cb75c..650f0bafe9f 100644 Binary files a/release_docs/img/release-schedule.png and b/release_docs/img/release-schedule.png differ