From 91504546c48515a28a573f334ab90d873238150f Mon Sep 17 00:00:00 2001 From: angiejones Date: Mon, 20 Oct 2025 19:04:17 -0700 Subject: [PATCH 1/4] Add GitHub Health Dashboard workflow --- .github/workflows/update-health-dashboard.yml | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 .github/workflows/update-health-dashboard.yml diff --git a/.github/workflows/update-health-dashboard.yml b/.github/workflows/update-health-dashboard.yml new file mode 100644 index 00000000..26edf9fb --- /dev/null +++ b/.github/workflows/update-health-dashboard.yml @@ -0,0 +1,147 @@ +name: Update GitHub Health Dashboard + +# Trigger configuration +on: + # Run daily at midnight UTC + schedule: + - cron: '0 0 * * *' + + # Allow manual triggering from GitHub UI + workflow_dispatch: + +# Define permissions for the GITHUB_TOKEN +permissions: + # Required to read repository contents + contents: read + # Required to read issues and PRs + issues: read + pull-requests: read + # Required to update discussions + discussions: write + +jobs: + update-dashboard: + name: 'Update Health Dashboard' + runs-on: ubuntu-latest + + steps: + - name: 'Update Dashboard' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const discussionNumber = 5285; + + console.log(`Fetching health metrics for ${owner}/${repo}`); + + // Fetch open issues + const { data: openIssues } = await github.rest.issues.listForRepo({ + owner, + repo, + state: 'open', + per_page: 100 + }); + + // Filter out PRs (issues API includes PRs) + const issues = openIssues.filter(issue => !issue.pull_request); + const totalOpenIssues = issues.length; + + // Count issues without labels (recent ones) + const issuesWithoutLabels = issues.filter(issue => issue.labels.length === 0); + + // Count issues by specific labels + const labelCounts = {}; + const targetLabels = ['P0', 'P1', 'Compaction', 'Windows', 'Provider', 'MCP/Extensions', 'Linux']; + + targetLabels.forEach(label => { + labelCounts[label] = issues.filter(issue => + issue.labels.some(l => l.name === label) + ).length; + }); + + // Fetch open PRs + const { data: openPRs } = await github.rest.pulls.list({ + owner, + repo, + state: 'open', + per_page: 100 + }); + + const totalOpenPRs = openPRs.length; + + // Format the dashboard markdown + const timestamp = new Date().toUTCString(); + + const dashboardBody = `# GitHub Health + +**Updated:** ${new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} + +## Issues + +**Open:** ${totalOpenIssues} + +**No labels:** ${issuesWithoutLabels.length} (these are recent) + +### Label Breakdown + +| Label | Count | +|-------|-------| +| [P0](https://github.com/${owner}/${repo}/labels/P0) | ${labelCounts['P0'] || 0} | +| [P1](https://github.com/${owner}/${repo}/labels/P1) | ${labelCounts['P1'] || 0} | +| [Compaction](https://github.com/${owner}/${repo}/labels/Compaction) | ${labelCounts['Compaction'] || 0} | +| [Windows](https://github.com/${owner}/${repo}/labels/Windows) | ${labelCounts['Windows'] || 0} | +| [Provider](https://github.com/${owner}/${repo}/labels/Provider) | ${labelCounts['Provider'] || 0} | +| [MCP/Extensions](https://github.com/${owner}/${repo}/labels/MCP%2FExtensions) | ${labelCounts['MCP/Extensions'] || 0} | +| [Linux](https://github.com/${owner}/${repo}/labels/Linux) | ${labelCounts['Linux'] || 0} | + +## Open PRs + +**Total:** ${totalOpenPRs} + +--- + +*Last updated: ${timestamp}* +*This dashboard is automatically updated daily by [GitHub Actions](https://github.com/${owner}/${repo}/actions/workflows/update-health-dashboard.yml)*`; + + console.log('Dashboard generated, updating discussion...'); + + // Get the discussion node ID using GraphQL + const discussionQuery = ` + query GetDiscussion($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + discussion(number: $number) { + id + } + } + } + `; + + const discussionResult = await github.graphql(discussionQuery, { + owner, + repo, + number: discussionNumber + }); + + const discussionId = discussionResult.repository.discussion.id; + console.log(`Found discussion ID: ${discussionId}`); + + // Update the discussion using GraphQL mutation + const updateMutation = ` + mutation UpdateDiscussion($discussionId: ID!, $body: String!) { + updateDiscussion(input: {discussionId: $discussionId, body: $body}) { + discussion { + id + number + } + } + } + `; + + await github.graphql(updateMutation, { + discussionId, + body: dashboardBody + }); + + console.log(`Successfully updated discussion #${discussionNumber}`); From d5d92919e0d8f622779780bb9564222d6a0e9770 Mon Sep 17 00:00:00 2001 From: angiejones Date: Mon, 20 Oct 2025 19:45:29 -0700 Subject: [PATCH 2/4] fixing syntax --- .github/workflows/update-health-dashboard.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-health-dashboard.yml b/.github/workflows/update-health-dashboard.yml index 26edf9fb..c919282f 100644 --- a/.github/workflows/update-health-dashboard.yml +++ b/.github/workflows/update-health-dashboard.yml @@ -73,10 +73,11 @@ jobs: // Format the dashboard markdown const timestamp = new Date().toUTCString(); + const formattedDate = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); const dashboardBody = `# GitHub Health -**Updated:** ${new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} +**Updated:** ${formattedDate} ## Issues From ef67d88d688725b4cf84ac29a62f8d2ecf813738 Mon Sep 17 00:00:00 2001 From: angiejones Date: Mon, 20 Oct 2025 20:03:34 -0700 Subject: [PATCH 3/4] escaping markdown --- .github/workflows/update-health-dashboard.yml | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/.github/workflows/update-health-dashboard.yml b/.github/workflows/update-health-dashboard.yml index c919282f..b627dd77 100644 --- a/.github/workflows/update-health-dashboard.yml +++ b/.github/workflows/update-health-dashboard.yml @@ -75,36 +75,26 @@ jobs: const timestamp = new Date().toUTCString(); const formattedDate = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); - const dashboardBody = `# GitHub Health - -**Updated:** ${formattedDate} - -## Issues - -**Open:** ${totalOpenIssues} - -**No labels:** ${issuesWithoutLabels.length} (these are recent) - -### Label Breakdown - -| Label | Count | -|-------|-------| -| [P0](https://github.com/${owner}/${repo}/labels/P0) | ${labelCounts['P0'] || 0} | -| [P1](https://github.com/${owner}/${repo}/labels/P1) | ${labelCounts['P1'] || 0} | -| [Compaction](https://github.com/${owner}/${repo}/labels/Compaction) | ${labelCounts['Compaction'] || 0} | -| [Windows](https://github.com/${owner}/${repo}/labels/Windows) | ${labelCounts['Windows'] || 0} | -| [Provider](https://github.com/${owner}/${repo}/labels/Provider) | ${labelCounts['Provider'] || 0} | -| [MCP/Extensions](https://github.com/${owner}/${repo}/labels/MCP%2FExtensions) | ${labelCounts['MCP/Extensions'] || 0} | -| [Linux](https://github.com/${owner}/${repo}/labels/Linux) | ${labelCounts['Linux'] || 0} | - -## Open PRs - -**Total:** ${totalOpenPRs} - ---- - -*Last updated: ${timestamp}* -*This dashboard is automatically updated daily by [GitHub Actions](https://github.com/${owner}/${repo}/actions/workflows/update-health-dashboard.yml)*`; + const dashboardBody = '# GitHub Health\n\n' + + '**Updated:** ' + formattedDate + '\n\n' + + '## Issues\n\n' + + '**Open:** ' + totalOpenIssues + '\n\n' + + '**No labels:** ' + issuesWithoutLabels.length + ' (these are recent)\n\n' + + '### Label Breakdown\n\n' + + '| Label | Count |\n' + + '|-------|-------|\n' + + '| [P0](https://github.com/' + owner + '/' + repo + '/labels/P0) | ' + (labelCounts['P0'] || 0) + ' |\n' + + '| [P1](https://github.com/' + owner + '/' + repo + '/labels/P1) | ' + (labelCounts['P1'] || 0) + ' |\n' + + '| [Compaction](https://github.com/' + owner + '/' + repo + '/labels/Compaction) | ' + (labelCounts['Compaction'] || 0) + ' |\n' + + '| [Windows](https://github.com/' + owner + '/' + repo + '/labels/Windows) | ' + (labelCounts['Windows'] || 0) + ' |\n' + + '| [Provider](https://github.com/' + owner + '/' + repo + '/labels/Provider) | ' + (labelCounts['Provider'] || 0) + ' |\n' + + '| [MCP/Extensions](https://github.com/' + owner + '/' + repo + '/labels/MCP%2FExtensions) | ' + (labelCounts['MCP/Extensions'] || 0) + ' |\n' + + '| [Linux](https://github.com/' + owner + '/' + repo + '/labels/Linux) | ' + (labelCounts['Linux'] || 0) + ' |\n\n' + + '## Open PRs\n\n' + + '**Total:** ' + totalOpenPRs + '\n\n' + + '---\n\n' + + '*Last updated: ' + timestamp + '*\n' + + '*This dashboard is automatically updated daily by [GitHub Actions](https://github.com/' + owner + '/' + repo + '/actions/workflows/update-health-dashboard.yml)*'; console.log('Dashboard generated, updating discussion...'); From 254fe5e4c6e59147e5a0608a18948c9dd1aae6b4 Mon Sep 17 00:00:00 2001 From: angiejones Date: Mon, 20 Oct 2025 23:55:28 -0700 Subject: [PATCH 4/4] Fix label case sensitivity - use lowercase labels --- .github/workflows/update-health-dashboard.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-health-dashboard.yml b/.github/workflows/update-health-dashboard.yml index b627dd77..39dec9d8 100644 --- a/.github/workflows/update-health-dashboard.yml +++ b/.github/workflows/update-health-dashboard.yml @@ -53,7 +53,7 @@ jobs: // Count issues by specific labels const labelCounts = {}; - const targetLabels = ['P0', 'P1', 'Compaction', 'Windows', 'Provider', 'MCP/Extensions', 'Linux']; + const targetLabels = ['p0', 'p1', 'compaction', 'windows', 'provider', 'mcp', 'linux']; targetLabels.forEach(label => { labelCounts[label] = issues.filter(issue => @@ -83,13 +83,13 @@ jobs: '### Label Breakdown\n\n' + '| Label | Count |\n' + '|-------|-------|\n' + - '| [P0](https://github.com/' + owner + '/' + repo + '/labels/P0) | ' + (labelCounts['P0'] || 0) + ' |\n' + - '| [P1](https://github.com/' + owner + '/' + repo + '/labels/P1) | ' + (labelCounts['P1'] || 0) + ' |\n' + - '| [Compaction](https://github.com/' + owner + '/' + repo + '/labels/Compaction) | ' + (labelCounts['Compaction'] || 0) + ' |\n' + - '| [Windows](https://github.com/' + owner + '/' + repo + '/labels/Windows) | ' + (labelCounts['Windows'] || 0) + ' |\n' + - '| [Provider](https://github.com/' + owner + '/' + repo + '/labels/Provider) | ' + (labelCounts['Provider'] || 0) + ' |\n' + - '| [MCP/Extensions](https://github.com/' + owner + '/' + repo + '/labels/MCP%2FExtensions) | ' + (labelCounts['MCP/Extensions'] || 0) + ' |\n' + - '| [Linux](https://github.com/' + owner + '/' + repo + '/labels/Linux) | ' + (labelCounts['Linux'] || 0) + ' |\n\n' + + '| [P0](https://github.com/' + owner + '/' + repo + '/labels/p0) | ' + (labelCounts['p0'] || 0) + ' |\n' + + '| [P1](https://github.com/' + owner + '/' + repo + '/labels/p1) | ' + (labelCounts['p1'] || 0) + ' |\n' + + '| [Compaction](https://github.com/' + owner + '/' + repo + '/labels/compaction) | ' + (labelCounts['compaction'] || 0) + ' |\n' + + '| [Windows](https://github.com/' + owner + '/' + repo + '/labels/windows) | ' + (labelCounts['windows'] || 0) + ' |\n' + + '| [Provider](https://github.com/' + owner + '/' + repo + '/labels/provider) | ' + (labelCounts['provider'] || 0) + ' |\n' + + '| [MCP](https://github.com/' + owner + '/' + repo + '/labels/mcp) | ' + (labelCounts['mcp'] || 0) + ' |\n' + + '| [Linux](https://github.com/' + owner + '/' + repo + '/labels/linux) | ' + (labelCounts['linux'] || 0) + ' |\n\n' + '## Open PRs\n\n' + '**Total:** ' + totalOpenPRs + '\n\n' + '---\n\n' +