Skip to content

feat: Add PR Dashboard#2464

Closed
aceppaluni wants to merge 1 commit into
hiero-ledger:mainfrom
aceppaluni:prdash
Closed

feat: Add PR Dashboard#2464
aceppaluni wants to merge 1 commit into
hiero-ledger:mainfrom
aceppaluni:prdash

Conversation

@aceppaluni

Copy link
Copy Markdown
Contributor

Description:

This PR adds the PR Dashboard as seen in the C++, and Enterprise Java Repositories. This Dashboard is updated with each new commit so contributors are able to view the PR status in Realtime.

Related issue(s):

Fixes #2463

Notes for reviewer:

Testing done in Hiero-Website

aceppaluni/hiero-website#14
aceppaluni/hiero-website#12

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

Signed-off-by: aceppaluni <aceppaluni@gmail.com>
@aceppaluni
aceppaluni requested review from a team as code owners July 21, 2026 17:36
@github-actions github-actions Bot added approved Issue has been approved by team member skill: advanced requires knowledge of multiple areas in the codebase without defined steps to implement or examples labels Jul 21, 2026
@aceppaluni aceppaluni removed the approved Issue has been approved by team member label Jul 21, 2026
@aceppaluni
aceppaluni requested a review from manishdait July 21, 2026 17:37
@aceppaluni aceppaluni added reviewer: maintainer PR needs a review from the maintainer team reviewer: committer request review help from a committer lang: javascript Uses Javascript programming language lang: yaml Uses Yaml programming language labels Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds a configuration-driven PR dashboard that validates DCO, GPG, mergeability, and issue assignment, posts unified comments, manages labels and assignees, and runs through PR-open and PR-update GitHub Actions workflows.

Changes

PR Dashboard Automation

Layer / File(s) Summary
Configuration and helper foundations
.github/hiero-automation.json, .github/scripts/helpers/config-loader.js, .github/scripts/helpers/constants.js, .github/scripts/helpers/{validation,logger,index}.js
Defines validated automation settings, immutable derived constants, shared validation utilities, logging, and helper exports.
GitHub context and API primitives
.github/scripts/helpers/api.js
Adds validated event context construction and wrappers for GitHub labels, assignees, comments, fetching, milestones, reactions, status labels, and item updates.
PR checks and dashboard rendering
.github/scripts/helpers/{checks,comments}.js
Adds DCO, GPG, mergeability, and issue-link checks, plus unified dashboard comment rendering and status evaluation.
Linked issue and assignment progression
.github/scripts/helpers/api.js
Resolves skill-labeled linked issues, calculates skill levels, counts and lists assigned issues, and detects qualifying needs-review PRs.
PR event handlers and workflows
.github/scripts/bot-on-pr-{open,update}.js, .github/workflows/on-pr*.yml
Runs dashboard checks on PR lifecycle events, assigns authors, skips bot-authored PRs, updates labels, and configures secured workflow execution.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHub
  participant Workflow
  participant PRHandler
  participant GitHubAPI
  participant PRDashboard
  GitHub->>Workflow: emit pull_request event
  Workflow->>PRHandler: invoke handler with github and context
  PRHandler->>GitHubAPI: validate context and fetch PR data
  PRHandler->>GitHubAPI: run PR checks and resolve linked issues
  PRHandler->>PRDashboard: build dashboard result
  PRHandler->>GitHubAPI: update comment, assignee, and status label
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also adds broader issue-progression automation and skill/priority config that goes beyond the minimal dashboard scope in #2463. Split the issue-progression and skill/priority automation into a separate PR, and keep this one focused on the dashboard, signing checks, assignee checks, and guidance.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a PR dashboard.
Description check ✅ Passed The description matches the PR and explains the dashboard purpose and related changes.
Linked Issues check ✅ Passed The changes cover the requested PR dashboard features: DCO/GPG checks, issue-assignee validation, and help/documentation guidance.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
📋 Issue Planner

Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).

View plan for ticket: #2463

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b8ebac8-68c2-49c5-b398-ccd35797cd52

📥 Commits

Reviewing files that changed from the base of the PR and between c75ed0a and 7a6a1db.

📒 Files selected for processing (13)
  • .github/hiero-automation.json
  • .github/scripts/bot-on-pr-open.js
  • .github/scripts/bot-on-pr-update.js
  • .github/scripts/helpers/api.js
  • .github/scripts/helpers/checks.js
  • .github/scripts/helpers/comments.js
  • .github/scripts/helpers/config-loader.js
  • .github/scripts/helpers/constants.js
  • .github/scripts/helpers/index.js
  • .github/scripts/helpers/logger.js
  • .github/scripts/helpers/validation.js
  • .github/workflows/on-pr-update.yml
  • .github/workflows/on-pr.yml

Comment on lines +71 to +83
function checkGPG(commits) {
const failures = [];
for (const c of commits) {
const shortSha = (c.sha || '').slice(0, 7);
const message = c.commit?.message || '';
const firstLine = message.split('\n')[0] || '(no message)';
if (!hasVerifiedGPGSignature(c)) {
failures.push({ sha: shortSha, message: firstLine });
}
}
getLogger().log(`GPG check: ${commits.length - failures.length}/${commits.length} passed`);
return { passed: failures.length === 0, failures };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

GPG check doesn't exempt merge commits, unlike DCO check.

checkDCO explicitly skips merge commits via isMergeCommit (Line 50-53), but checkGPG runs hasVerifiedGPGSignature on every commit including merge commits. Auto-generated merge commits (e.g. from merging base into a feature branch) are typically not GPG-signed by the contributor, so this will produce false GPG failures for PRs containing such commits.

🐛 Proposed fix
 function checkGPG(commits) {
   const failures = [];
   for (const c of commits) {
+    if (isMergeCommit(c)) continue;
     const shortSha = (c.sha || '').slice(0, 7);
     const message = c.commit?.message || '';
     const firstLine = message.split('\n')[0] || '(no message)';
     if (!hasVerifiedGPGSignature(c)) {
       failures.push({ sha: shortSha, message: firstLine });
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function checkGPG(commits) {
const failures = [];
for (const c of commits) {
const shortSha = (c.sha || '').slice(0, 7);
const message = c.commit?.message || '';
const firstLine = message.split('\n')[0] || '(no message)';
if (!hasVerifiedGPGSignature(c)) {
failures.push({ sha: shortSha, message: firstLine });
}
}
getLogger().log(`GPG check: ${commits.length - failures.length}/${commits.length} passed`);
return { passed: failures.length === 0, failures };
}
function checkGPG(commits) {
const failures = [];
for (const c of commits) {
if (isMergeCommit(c)) continue;
const shortSha = (c.sha || '').slice(0, 7);
const message = c.commit?.message || '';
const firstLine = message.split('\n')[0] || '(no message)';
if (!hasVerifiedGPGSignature(c)) {
failures.push({ sha: shortSha, message: firstLine });
}
}
getLogger().log(`GPG check: ${commits.length - failures.length}/${commits.length} passed`);
return { passed: failures.length === 0, failures };
}

Comment on lines +93 to +94
const maxAttempts = 5;
const delayMs = 2000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hoist retry configuration to top-level constants.

maxAttempts/delayMs are function-local; per path instructions, configuration values should be top-level constants rather than embedded inside function bodies.

As per path instructions, "Use top-level constants for configuration — avoid hardcoded values scattered through the script."

Source: Path instructions

Comment on lines +147 to +161
* Identifies the issue numbers referenced in the PR title so they can be excluded from the GraphQL fallback results.
* @param {string} value
* @returns {Set<number>}
*/
function extractNumbersFromTitle(value) {
const numbers = new Set();
const pattern = /#(\d+)/g;
if (!value) return numbers;

let match;
while ((match = pattern.exec(value)) !== null) {
numbers.add(parseInt(match[1], 10));
}
return numbers;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '120,240p' .github/scripts/helpers/checks.js

Repository: hiero-ledger/hiero-sdk-python

Length of output: 4269


🏁 Script executed:

set -e
printf '\n## checks.js excerpt\n'
sed -n '120,240p' .github/scripts/helpers/checks.js

printf '\n## call sites / references\n'
rg -n "extractNumbersFromTitle|closingIssuesReferences|no_issue_linked|title" .github/scripts/helpers/checks.js .github/scripts -g '!**/node_modules/**'

Repository: hiero-ledger/hiero-sdk-python

Length of output: 14246


🏁 Script executed:

set -e
sed -n '1,260p' .github/scripts/helpers/checks.js

Repository: hiero-ledger/hiero-sdk-python

Length of output: 8521


🌐 Web query:

GitHub GraphQL closingIssuesReferences PR title issue reference title text closing keywords semantics

💡 Result:

In GitHub, the semantics for automatically closing issues via pull requests (PRs) are based on the presence of specific keywords followed by an issue reference in the PR's description or commit messages [1][2][3]. When these conditions are met, GitHub establishes a formal link between the PR and the issue [1]. The GraphQL API provides the closingIssuesReferences field on a PullRequest object, which allows you to programmatically retrieve a list of all issues that are linked to be closed by a given pull request [4][5][6]. Conversely, an Issue object provides the closedByPullRequestsReferences field, which lists the pull requests that, when merged, will close that issue [7][8]. Key details regarding this functionality include: 1. Supported Keywords: The system recognizes a specific set of keywords to trigger the linking mechanism: close, closes, closed, fix, fixes, fixed, resolve, resolves, and resolved [1][2][9][3]. These are case-insensitive and can be followed by a colon (e.g., Closes: #10) [1][9][3]. 2. Linking Requirement: These keywords must be included in the PR's description (the body text) or in commit messages [1][2][3]. If the keyword is in a commit message, the issue will close upon merging that commit to the default branch, but the PR itself will not be listed as a "linked" pull request in the issue's metadata [1][9]. 3. Repository Scope: Linking works for issues in the same repository (e.g., Closes #10) or in different repositories (e.g., Fixes owner/repo#10) [1][9][3]. 4. Default Branch Restriction: The automatic closing behavior is active only when the pull request targets the repository's default branch [1][9]. 5. GraphQL Usage: The closingIssuesReferences field is a connection that returns a list of issues [4]. Developers often use this API to replace manual regex-based parsing of PR bodies when building custom automation or integrations, as it provides a reliable, natively supported way to identify these relationships [5][6]. While these keywords are part of the platform's standard text parsing logic, they are not "semantics" in the sense of a dedicated GraphQL input field; rather, the GraphQL API exposes the result of this parsing logic as a queryable relationship [4][1][5].

Citations:


Don't filter closingIssuesReferences by PR-title numbers. In .github/scripts/helpers/checks.js:208-214, extractNumbersFromTitle() can drop a real GraphQL-linked issue whenever the PR title also mentions the same #NNN, which makes a correctly linked PR look unlinked. If the goal is to ignore title-only mentions, keep the GraphQL results intact and dedupe against body parsing instead.

🧰 Tools
🪛 OpenGrep (1.25.0)

[ERROR] 157-157: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

Comment on lines +172 to +187
async function fetchAndCheckAssignees(botContext, fetchIssue, issueNumbers, prAuthor) {
const logger = getLogger();
const results = [];
for (const num of issueNumbers) {
try {
const issue = await fetchIssue(botContext, num);
const isAssigned = (issue.assignees || []).some(
a => a.login.toLowerCase() === prAuthor.toLowerCase()
);
results.push({ number: num, title: issue.title, isAssigned });
} catch (err) {
logger.log(`Issue link check: could not fetch issue #${num}: ${err.message}`);
}
}
return results;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fetch failures are silently dropped instead of surfaced, weakening the assignment check.

When fetchIssue throws for a linked issue number (rate limit, transient error, etc.), the issue is simply excluded from results (Lines 182-184). If some issues fail to fetch while others succeed, checkIssueLink will only validate the ones that succeeded and can report passed: true even though the un-fetched issue's assignment was never actually verified. If all issues fail to fetch, the reason returned is 'no_issue_linked' (Line 224-226) which is misleading — the PR is linked, the fetch just failed — and will tell an already-compliant author to "link an issue" they already linked.

Consider surfacing fetch failures as an error state (so the dashboard shows the maintainer-review message) rather than silently treating them as absent.

Comment on lines +178 to +180
const isAssigned = (issue.assignees || []).some(
a => a.login.toLowerCase() === prAuthor.toLowerCase()
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against missing prAuthor before calling .toLowerCase().

prAuthor (from botContext.pr?.user?.login, Line 203) can be undefined if the PR author's account was deleted (user is null in that case). prAuthor.toLowerCase() here will throw; it's caught by the outer try/catch in runAllChecksAndComment, but the check just degrades to a generic "error" state instead of a clear message. Add an explicit guard.

🛡️ Proposed fix
       const issue = await fetchIssue(botContext, num);
+      if (!prAuthor) {
+        throw new Error('PR author is unknown (deleted account?)');
+      }
       const isAssigned = (issue.assignees || []).some(

As per path instructions, "Validate context.payload fields before use."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isAssigned = (issue.assignees || []).some(
a => a.login.toLowerCase() === prAuthor.toLowerCase()
);
const issue = await fetchIssue(botContext, num);
if (!prAuthor) {
throw new Error('PR author is unknown (deleted account?)');
}
const isAssigned = (issue.assignees || []).some(
a => a.login.toLowerCase() === prAuthor.toLowerCase()
);

Source: Path instructions

Comment on lines +51 to +62
function buildDCOSection(dco) {
const common = buildSection({ title: 'DCO Sign-off', result: dco, passMessage: 'All commits have valid sign-offs. Nice work!' });
if (common) return common;

const failList = (dco.failures || []).map(f => `- \`${f.sha}\` ${f.message}`).join('\n');
return [
':x: **DCO Sign-off** -- Uh oh! The following commits are missing the required DCO sign-off:',
failList,
'',
`No worries, this is an easy fix! Add \`Signed-off-by: Your Name <email>\` to each commit (e.g. \`git commit -s\`). See the [Signing Guide](${SIGNING_GUIDE}).`,
].join('\n');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unescaped commit-message text is rendered directly into the bot's markdown comment.

f.message (the first line of a commit message — fully attacker/contributor-controlled free-form text) is interpolated into the comment body without any escaping or code-fencing. A crafted commit message containing markdown/HTML could break the dashboard's rendering, inject misleading links, or spoof content in an automated, semi-trusted comment. This pattern is duplicated in buildGPGSection below (Lines 72-79).

🛡️ Proposed fix
-  const failList = (dco.failures || []).map(f => `- \`${f.sha}\` ${f.message}`).join('\n');
+  const failList = (dco.failures || []).map(f => `- \`${f.sha}\` \`${f.message.replace(/`/g, '\u02CB')}\``).join('\n');

As per path instructions, "Do not trust free-form text from issues, PRs, or comments."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function buildDCOSection(dco) {
const common = buildSection({ title: 'DCO Sign-off', result: dco, passMessage: 'All commits have valid sign-offs. Nice work!' });
if (common) return common;
const failList = (dco.failures || []).map(f => `- \`${f.sha}\` ${f.message}`).join('\n');
return [
':x: **DCO Sign-off** -- Uh oh! The following commits are missing the required DCO sign-off:',
failList,
'',
`No worries, this is an easy fix! Add \`Signed-off-by: Your Name <email>\` to each commit (e.g. \`git commit -s\`). See the [Signing Guide](${SIGNING_GUIDE}).`,
].join('\n');
}
function buildDCOSection(dco) {
const common = buildSection({ title: 'DCO Sign-off', result: dco, passMessage: 'All commits have valid sign-offs. Nice work!' });
if (common) return common;
const failList = (dco.failures || []).map(f => `- \`${f.sha}\` \`${f.message.replace(/`/g, '\u02CB')}\``).join('\n');
return [
':x: **DCO Sign-off** -- Uh oh! The following commits are missing the required DCO sign-off:',
failList,
'',
`No worries, this is an easy fix! Add \`Signed-off-by: Your Name <email>\` to each commit (e.g. \`git commit -s\`). See the [Signing Guide](${SIGNING_GUIDE}).`,
].join('\n');
}

Source: Path instructions

Comment on lines +68 to +79
function buildGPGSection(gpg) {
const common = buildSection({ title: 'GPG Signature', result: gpg, passMessage: 'All commits have verified GPG signatures. Locked and loaded!' });
if (common) return common;

const failList = (gpg.failures || []).map(f => `- \`${f.sha}\` ${f.message}`).join('\n');
return [
':x: **GPG Signature** -- Heads up! The following commits don\'t have a verified GPG signature:',
failList,
'',
`You'll need to sign your commits with GPG (e.g. \`git commit -S\`). See the [Signing Guide](${SIGNING_GUIDE}) for a step-by-step walkthrough.`,
].join('\n');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Same unsanitized commit-message rendering as buildDCOSection.

See the comment on buildDCOSection (Lines 51-62) — the same root cause applies here to f.message.

@github-actions github-actions Bot added open to community review PR is open for community review and feedback queue:junior-committer PR awaiting initial quality review labels Jul 21, 2026

@exploreriii exploreriii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aceppaluni
I do not want to block this PR because there are some useful things in this.

However, the scope of the PR is much broader and deeper than the issue stated (create a PR dashboard). The changes proposed here create many new features, some of which we already have in the automation suite and will cause duplication and also some conflicts (mergeable state here is different because we have more reviewers). Additionally, we have another PR that will conflict with yours, even though they have solved their issue #2458, since you are adding assignment checks and label things.

Overall, this creates a surface area like the C++ but does not work to consolidate what we currently have, to allow for that transition. The result will be some new functionalities and robsustness, but also some technical debt that will linger and conflict.

I would propose creating a slim PR dashboard that is tailored to what the python SDK conventions currently have, work to delete and remove other scripts that do similar things, consolidate the things that will now need to be reconciled with this new proposal, then we can proceed with it. Let me know what you agree/disagree with and appreciate the time and effort into making this

@exploreriii exploreriii removed reviewer: maintainer PR needs a review from the maintainer team reviewer: committer request review help from a committer queue:junior-committer PR awaiting initial quality review open to community review PR is open for community review and feedback labels Jul 22, 2026
@exploreriii
exploreriii marked this pull request as draft July 22, 2026 11:39
@aceppaluni

Copy link
Copy Markdown
Contributor Author

Will close and reopen fresh when ready

@aceppaluni aceppaluni closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lang: javascript Uses Javascript programming language lang: yaml Uses Yaml programming language skill: advanced requires knowledge of multiple areas in the codebase without defined steps to implement or examples

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a PR Dashboard

4 participants