dashboard.yml

Telemetry dashboard build and publish. Lives in the redline-metrics repo.

What this is

Rebuilds the telemetry dashboard and publishes it to GitHub Pages, via scripts/build-dashboard.mjs.

How to onboard it

Copy this file into the redline-metrics repository, set REDLINE_ORG_READ_TOKEN and acknowledge Pages visibility there. Coverage additionally needs registry.json readable from the source repo — which the Redline Registry workflow now publishes nightly.

  • Lives in: The redline-metrics repo — not this one.
  • Trigger: Daily at 05:30 UTC, plus workflow_dispatch, plus automatically once Redline Collect or Redline Seed Canary finishes.

How to use it

Nothing, normally — it runs itself daily. Run scripts/build-dashboard.mjs locally against a copy of data/ to preview a metric or chart change.

What a run does, in order:

  • Same Pages-visibility gate as the inbox.
  • Counts onboarded repos by reading registry.json from the source repo, for a coverage figure. A failed read omits the figure rather than reporting zero.
  • Builds dist/index.html with scripts/build-dashboard.mjs, then deploys, then opens or updates a tracking issue on failure.

Works, but only where installed: same Pages-visibility gate as the inbox, plus REDLINE_ORG_READ_TOKEN and collected telemetry to summarise. The coverage figure works again now that registry.json exists — but this workflow file lives in the metrics repo, so the fix reaches the live dashboard only once someone copies it across; redline sync would do that, but it targets registered product repositories, not the metrics repo.

Expected output

A static dashboard on GitHub Pages: acted-on rate, weekly trend, seed recall history, and the rules most worth tuning. Coverage is reported as instrumented-against-onboarded, read from registry.json; if that read fails the figure is omitted rather than shown as zero, because zero would look like a finding.

How to edit it

  1. Edit the YAML in workflows/ or .github/workflows/workflows/ holds files destined for other repositories; .github/workflows/ is this repository's own CI. The two are not interchangeable — check where this one lives before editing.
  2. actionlintCI lints .github/workflows/*.yml, workflows/*.yml and templates/redline.yml together. workflows/ is pointed at explicitly because actionlint's own discovery would skip it.
  3. node scripts/check-pins.mjsIf you add a third-party action, pin it to a 40-character commit SHA with a trailing # vX.Y.Z comment. First-party actions/* are referenced by tag. The pin checker re-resolves the SHA against the tag the comment claims.
  4. node scripts/validate.mjsAsserts the workflow files the bundle depends on still exist, and that the gate's job ids still match the required check name derived from them.

The full file

workflows/dashboard.yml · 120 lines · 4.4 KB
# Lives in the redline-metrics repo. Rebuilds the Redline dashboard from collected
# telemetry and publishes it to GitHub Pages.
#
# SECURITY: the page lists repo names, rule ids and finding counts. Set the Pages site to
# private or internal visibility BEFORE enabling this. The build refuses to run until
# PAGES_VISIBILITY_ACKNOWLEDGED is set as a repository variable.
name: Redline Dashboard

on:
  schedule:
    - cron: '30 5 * * *'
  workflow_dispatch:
  workflow_run:
    # Rebuild as soon as new telemetry lands, so the page is never a day behind the data.
    workflows: [Redline Collect, Redline Seed Canary]
    types: [completed]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: dashboard
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Confirm Pages visibility was reviewed
        env:
          ACK: ${{ vars.PAGES_VISIBILITY_ACKNOWLEDGED }}
        run: |
          if [[ "${ACK:-}" != "private" && "${ACK:-}" != "internal" ]]; then
            echo "::error::Set the repository variable PAGES_VISIBILITY_ACKNOWLEDGED to 'private' or 'internal' after confirming the Pages site is not public. This page exposes org-wide delivery detail."
            exit 1
          fi

      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Count onboarded repos
        id: coverage
        env:
          GH_TOKEN: ${{ secrets.REDLINE_ORG_READ_TOKEN }}
          SOURCE: ${{ vars.REDLINE_SOURCE || format('{0}/redline', github.repository_owner) }}
        run: |
          set -euo pipefail
          # registry.json is the derived register of onboarded repositories, refreshed
          # nightly by the source repo's Redline Registry workflow. Coverage is
          # instrumented-vs-onboarded, so partial coverage cannot read as health. A
          # failed read omits the figure rather than reporting zero — absent is honest,
          # zero is a lie that looks like a finding.
          # Written to a file rather than counted here: the dashboard reads the whole
          # register for the enforcement ladder, not just its length, and a count
          # alone cannot say how much of the estate is actually enforcing.
          if gh api "repos/$SOURCE/contents/registry.json" --jq '.content' 2>/dev/null | base64 -d > registry.json; then
            echo "found=true" >> "$GITHUB_OUTPUT"
          else
            rm -f registry.json
            echo "found=false" >> "$GITHUB_OUTPUT"
            echo "::warning::Could not read registry.json from $SOURCE — coverage and the ladder will be omitted."
          fi

      # No checkout of the Redline source, and no copy of its scripts in this
      # repository. The command ships in the package; this repo owns its data and
      # nothing else.
      - name: Build dashboard
        env:
          STANDARDS_VERSION: ${{ vars.STANDARDS_VERSION }}
        run: |
          npx --yes "redlinegate@0.0.3" metrics dashboard \
            --org "${{ github.repository_owner }}" \
            --data data \
            --days 90 \
            --out dist \
            --registry registry.json

      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

  alert:
    needs: [build, deploy]
    if: failure()
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - name: Open or update a failure issue
        env:
          GH_TOKEN: ${{ github.token }}
          REPO: ${{ github.repository }}
          RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        run: |
          set -euo pipefail
          title="Redline dashboard build is failing"
          existing=$(gh issue list --repo "$REPO" --state open --search "$title in:title" --json number --jq '.[0].number // ""')
          if [[ -n "$existing" ]]; then
            gh issue comment "$existing" --repo "$REPO" --body "Still failing: $RUN"
          else
            gh issue create --repo "$REPO" --title "$title" \
              --body "The dashboard build failed, so the published page is now stale. Run: $RUN"
          fi