This document contains guidelines for maintaining and developing the Domain Connect Templates repository statistics dashboard.
.
├── docs/ # Statistics dashboard output directory
│ ├── index.html # Main statistics page (static HTML)
│ ├── styles.css # Styling with Domain Connect branding
│ ├── stats.json # Generated statistics data
│ ├── _claude_prompt.txt # Requirements specification for the dashboard
│ └── assets/ # Logos and images
├── scripts/ # Python scripts for statistics generation
│ ├── update_stats.py # Main script to generate stats.json
│ ├── check_syntax_rules.py # Template syntax/field validator
│ ├── requirements.txt # Python dependencies
│ ├── pr_reviews_cache.json # Cache for PR review data (use skip-worktree locally)
│ └── setup_local_dev.sh # Helper to configure local development (sets skip-worktree)
├── .github/workflows/ # CI/CD workflows
│ └── update-stats.yml # Workflow to auto-update statistics
└── [provider].[service].json # Template files (root directory)
- Static HTML: Dashboard is a single static HTML file with embedded JavaScript
- Responsive Design: Must work well on both desktop and mobile devices
- Offline-capable: All data loaded from stats.json, no external API calls from frontend
- Interactive: Graphs support zoom, pan, and data download features
- Primary:
#03263B(dark navy) - Secondary:
#0b3954(lighter navy) - Tertiary:
#bddae6(light blue-gray) - Accent:
#ff6663(coral-red) - Link:
#00bfff(bright cyan) - Text:
#252525(charcoal)
- Plotly.js: Used for all interactive graphs
- Features: Built-in zoom/pan, download options, responsive design
- CDN-loaded for easier maintenance
- Chart bars/donuts link to GitHub code search when clicked
- Search URLs use escaped JSON patterns (e.g.
"\"type\": \"CNAME\"") - Provider template counts link to GitHub code search for
providerId - Feature usage donut percentages link to GitHub code search for that feature
- Last updated date
- Total Templates
- Total Service Providers
- Total Merged PRs
- Total Open PRs
- Total Contributors
- Average Records per Template
-
Templates Growth Over Time
- Monthly data since repository inception
- Dual Y-axis: Cumulative total (line) + Monthly additions (bar)
- Both scales start at zero
-
Provider Growth Over Time
- Monthly data since repository inception
- Dual Y-axis: Cumulative providers (line) + Monthly additions (bar)
- Both scales start at zero
- Uses
providerIdfrom template JSON content (not filename-derived)
-
Pull Requests Activity
- Monthly PRs created vs PRs merged (bar chart)
- Only counts PRs that touch at least one template file
-
Pull Request Time to Merge
- Bar chart: month (of opening) on X-axis, average days from open to merge on Y-axis (1 decimal)
- Only merged template PRs counted; bucket =
created_atmonth - Computed purely from
created_at/merged_aton PR objects — no extra API calls
-
Pull Request Comment Activity
- Bar chart: month on X-axis, average comments per PR on Y-axis (1 decimal)
- Only template PRs; counts issue comments + review comments; excludes bot accounts (
user.type == 'Bot'or login ending in[bot]) - Fetched via
/issues/{number}/commentsand/pulls/{number}/comments, cached undercomment_countskey (integer total per PR)
-
Pull Request Label Activity
- Line chart: month on X-axis, number of PRs on Y-axis, each label as a separate data series
- Only template PRs; counts distinct PRs per label per month (bucket = PR
created_atmonth) - Includes labels ever applied to a PR, even if later removed (uses GitHub Issues Events API
labeledevents) - Data fetched via
/repos/{owner}/{repo}/issues/{number}/events, cached inpr_reviews_cache.jsonunderlabel_eventskey
-
Record Types Distribution
- Horizontal bar chart showing % of templates containing each DNS record type
- Count unique record types per template (e.g. 5 CNAMEs in one template counts as 1)
- Bars show percentage with label "X% (count/total)"
- Clicking a bar opens GitHub code search for that record type
-
Feature Usage
- Row of donut charts below Record Types Distribution
- Each donut shows % of templates using a feature:
syncPubKeyDomain(present and non-empty)syncRedirectDomain(present and non-empty)warnPhishing(present and set totrue)hostRequired(present and set totrue)
- Percentage displayed in center of each donut, clickable to GitHub search
- Two tables: All-time and Recently Added (last 30 new providers)
- Shows provider logo (from first template) to the left of provider name
- Uses
providerNamefrom template content (not filename-derived provider ID) - Number of templates is a clickable link opening GitHub code search for that
providerId - All-time: sorted by template count descending (top 20)
- Recently Added: the 20 most recently first-added providers, ordered by first-added date descending; determined via git history (oldest-first traversal to find when each provider_id first appeared)
- Table showing: PR number, title, status, labels, open date, GitHub link
- Content: All open PRs only
- Each PR shows associated provider/service IDs and logos
- Two side-by-side tables: All-time and Last 30 days
- Shows top 5 reviewers for each period
- Displays: Rank, reviewer avatar, username (linked to GitHub profile), review count
- Review count = number of PRs reviewed (each reviewer counted once per PR)
- Excludes self-reviews (PR author reviewing their own PR)
- Python 3.8+
- Dependencies:
requests - GitHub API token via
GITHUB_TOKENenvironment variable
--folder FOLDER: Path to templates repository folder (default: 'Templates')--repo-owner/--repo-name: Specify GitHub repo directly (must be used together)--remote: Specify git remote name for auto-detection (e.g.upstream)- If neither
--repo-owner/--repo-namenor--remoteprovided, auto-detects from the single git remote (aborts if multiple remotes exist)
- Parse all
*.jsontemplate files in the specified folder - Analyze git history for commits, template additions, and provider growth
- Fetch PR data from GitHub API (using token)
- Calculate all statistics: growth, record types, feature usage, provider metadata
- Generate
docs/stats.jsonwith complete dataset
- Uses
GITHUB_TOKENfrom environment for authentication - Fetches PR data, contributor info, and repository metadata
- Implements rate limiting and error handling
- Caches per-PR data in
scripts/pr_reviews_cache.jsonto avoid repeated API calls- Cache maps PR number → dict with keys:
reviews,label_events,comment_counts,has_templates - Legacy entries (plain list) are migrated on load to
{reviews: [...]}automatically - When adding a new cached field: if the field is absent from ALL cached entries, fetch it for every PR; otherwise fetch only for entries missing that field. Never re-fetch already-cached fields.
- Cache maps PR number → dict with keys:
- Cache is automatically updated when new PRs are processed
- Cache file is committed to repository but can be ignored locally using
git update-index --skip-worktree
export GITHUB_TOKEN="your_token_here"
# Using default folder (Templates):
python scripts/update_stats.py --repo-owner Domain-Connect --repo-name Templates
# Using custom folder:
python scripts/update_stats.py --folder /path/to/templates --repo-owner Domain-Connect --repo-name Templates
# Auto-detect remote (requires exactly one git remote):
python scripts/update_stats.py --remote upstream
# Specify owner/name directly (skips git remote detection):
python scripts/update_stats.py --repo-owner Domain-Connect --repo-name TemplatesTo prevent accidentally committing cache file changes:
# Run once to configure local development
./scripts/setup_local_dev.shThis uses git update-index --skip-worktree to tell git to ignore local changes to the cache file. The cache will still be pulled from the repository but your local modifications won't show up in git status.
- Triggered on push to main and on schedule (daily at midnight UTC)
- Uses
secrets.GITHUB_TOKENautomatically provided by GitHub Actions - Automatically enables cache file tracking with
--no-skip-worktree - Commits updated
stats.jsonandpr_reviews_cache.jsonback to repository
Validates every *.json template file in the Templates folder against a set of
typed syntax rules derived from the Domain Connect specification. Exits with
code 1 if any rule violations are found; warnings (deprecations) are printed but
do not affect the exit code.
- Python 3.8+ (no third-party dependencies)
--folder FOLDER: Path to templates folder (default:Templates)--list-unchecked: Print all top-level template properties not yet covered by any rule, then exit. Useful when reviewing spec changes.
# Check all templates in the default folder:
python scripts/check_syntax_rules.py
# Check a custom folder:
python scripts/check_syntax_rules.py --folder /path/to/templates
# List properties not yet covered by any rule:
python scripts/check_syntax_rules.py --list-uncheckedFAIL <filename>— file has one or more rule violations, followed by indented error messages.WARN <filename>— file has deprecation warnings but no errors, followed by indented warning messages. Files with both errors and warnings show theFAILheader; warnings are printed underneath.- Summary line:
N files checked, F with errors, W with warnings only.
| # | Field(s) | Constraint |
|---|---|---|
| 1 | syncRedirectDomain |
dc-host-list = domain-name *( *SP "," *SP domain-name ) — comma-separated list of RFC 5890 domain names; spaces around commas allowed |
| 2 | providerId, serviceId, groupId (per record) |
dc-id = 1*63( ALPHA / DIGIT / "-" / "_" / "." ) |
| 3 | syncPubKeyDomain |
dc-pubkey-domain = *( dc-underscore-label "." ) domain-name — optional RFC 8552 underscore labels prepended to an RFC 5890 domain name |
| 4 | providerName, serviceName |
dc-display-name = 1*255unicode-assignable — non-empty, ≤ 255 Unicode Assignable code points (RFC 9839 §4.3) |
| 5 | sharedProviderName, sharedServiceName, syncBlock, multiInstance, hostRequired, shared, warnPhishing |
JSON boolean (true / false) |
| 6 | description, variableDescription |
dc-description-text = 0*2048unicode-assignable — empty or ≤ 2048 Unicode Assignable code points (RFC 9839 §4.3) |
| 7 | version |
dc-version = %x31-39 *DIGIT — positive integer ≥ 1, no leading zeros, when present |
| 8 | logoUrl |
Valid RFC 3986 URI with scheme https or http, or empty string, when present (http triggers a deprecation warning) |
| Trigger | Message |
|---|---|
shared is present but sharedProviderName is absent |
Migrate shared → sharedProviderName |
logoUrl uses http scheme |
Migrate logoUrl to https |
- Add or update the validator function (e.g.
is_valid_*) in the appropriate section ofcheck_syntax_rules.py. - Add or update the
rule_*function and append it toRULES. - Add the covered field name(s) to
_CHECKED_PROPERTIESso they disappear from--list-uncheckedoutput. - Update the module docstring rules list and this CLAUDE.md table.
For deprecation warnings (non-fatal), add a warn_* function to WARNINGS
instead of RULES. Warning functions have the same signature but their messages
do not cause a non-zero exit code.
- Update
update_stats.pyto calculate new metric - Add data to the stats.json schema
- Update
docs/index.htmlto display the new data - Update
docs/_claude_prompt.txtto reflect the new requirements - Update this
CLAUDE.mdto document the change
Important: docs/_claude_prompt.txt and CLAUDE.md must always be kept in sync with the actual implementation. Any change to dashboard features, chart types, data sources, script arguments, or interactive behavior must be reflected in both files.
- Naming:
[providerId].[serviceId].json - Required fields:
providerId,providerName,serviceId,serviceName,records - Optional fields:
logoUrl,description,variableDescription,version,syncPubKeyDomain,syncRedirectDomain,hostRequired,warnPhishing,syncBlock,multiInstance,shared,sharedProviderName,sharedServiceName
- Python: PEP 8 compliant, type hints where appropriate
- JavaScript: Modern ES6+, async/await for clarity
- HTML/CSS: Semantic HTML5, mobile-first responsive design
- Comments: Explain "why", not "what"
- Run
update_stats.pylocally to generate stats.json - Open
docs/index.htmlin browser to verify display - Test responsive design at various screen widths
- Verify all interactive features (zoom, download, etc.)
- Keep template changes separate from dashboard changes
- Test statistics generation before submitting PR
- Ensure CI/CD passes (template linting + stats generation)
- Update CLAUDE.md if architecture changes
scripts/update_stats.py: Statistics calculation logicscripts/check_syntax_rules.py: Template field syntax rules and deprecation warningsdocs/index.html: Layout and JavaScript for visualizationdocs/styles.css: Styling and responsive design.github/workflows/update-stats.yml: CI/CD automation
docs/stats.json: Auto-generated by update_stats.py- Template files: Follow template contribution guidelines in README.md
- GitHub token must have read access to repository
- Never commit tokens or credentials to repository
- stats.json contains only public repository data
- No sensitive information exposed in dashboard
- Modern browsers (Chrome, Firefox, Safari, Edge)
- ES6+ JavaScript support required
- Plotly.js compatible browsers
- Mobile browsers (iOS Safari, Chrome Mobile)