Support data-driven tables (#57806)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
99
data/tables/README.md
Normal file
99
data/tables/README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Data-driven tables
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
GitHub Docs uses YAML files to manage some complex reference tables instead of hard-to-maintain Markdown tables. This approach provides:
|
||||||
|
|
||||||
|
- **Maintainable format**: Stakeholders can easily update readable YAML files
|
||||||
|
- **Single source of truth**: Centralized data prevents inconsistencies
|
||||||
|
- **Accurate information**: Reduces errors and outdated content
|
||||||
|
- **Self-service process**: Minimal engineering support needed
|
||||||
|
|
||||||
|
> **Important**: The `.yml` files in this directory are maintained **manually**. Tables that need automatic updates from external sources require engineering work.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
- [When to use this approach](#when-to-use-this-approach)
|
||||||
|
- [How it works](#how-it-works)
|
||||||
|
- [Step-by-step guide](#step-by-step-guide)
|
||||||
|
- [Testing and validation](#testing-and-validation)
|
||||||
|
- [Next steps](#next-steps)
|
||||||
|
|
||||||
|
## When to use this approach
|
||||||
|
|
||||||
|
Use data-driven tables when you have:
|
||||||
|
- Complex reference tables with multiple columns
|
||||||
|
- Data that needs regular updates by different stakeholders
|
||||||
|
- Structured information that benefits from validation
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Every data-driven table needs **three files** that work together:
|
||||||
|
|
||||||
|
| File type | Location | Purpose |
|
||||||
|
|-----------|----------|---------|
|
||||||
|
| **Data file** | `data/tables/` | Stores the table content in YAML format |
|
||||||
|
| **Content file** | `content/` | Displays the table using Liquid templating |
|
||||||
|
| **Schema file** | `src/data-directory/lib/data-schemas/` | Validates the YAML structure |
|
||||||
|
|
||||||
|
**Estimated time**: 30-60 minutes for a new table
|
||||||
|
|
||||||
|
## Step-by-step guide
|
||||||
|
|
||||||
|
### Step 1: Create the data file
|
||||||
|
|
||||||
|
Create a new `.yml` file in `data/tables/` with a descriptive name.
|
||||||
|
|
||||||
|
**Copilot prompt template:**
|
||||||
|
```
|
||||||
|
Create a YAML structure that will allow me to generate a table that looks like:
|
||||||
|
[describe your table headers, rows, and columns OR attach an example]
|
||||||
|
|
||||||
|
See src/secret-scanning/data/public-docs.yml for an example.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create the content display
|
||||||
|
|
||||||
|
In your content file, add Liquid code to render the table. Access your data at `{% data tables.TABLE_NAME %}` (where `TABLE_NAME` is your filename without `.yml`).
|
||||||
|
|
||||||
|
**Copilot prompt template:**
|
||||||
|
```
|
||||||
|
Create a Markdown table that is dynamically rendered using Liquid code.
|
||||||
|
Pull data from data/tables/TABLE_NAME.yml.
|
||||||
|
The table should look like: [describe your desired output OR attach an example]
|
||||||
|
|
||||||
|
See content/code-security/secret-scanning/introduction/supported-secret-scanning-patterns.md for an example.
|
||||||
|
Liquid docs: https://shopify.github.io/liquid
|
||||||
|
```
|
||||||
|
|
||||||
|
**💡 Tip**: Iterate between Steps 1 and 2 until the table renders correctly.
|
||||||
|
|
||||||
|
### Step 3: Create the schema file
|
||||||
|
|
||||||
|
Create a `.ts` file in `src/data-directory/lib/data-schemas/` with the same name as your YAML file.
|
||||||
|
|
||||||
|
**Copilot prompt template:**
|
||||||
|
```
|
||||||
|
Create a TypeScript schema following prior art under data-schemas that enforces
|
||||||
|
the structure of the data/TABLE_NAME.yml file.
|
||||||
|
|
||||||
|
See src/data-directory/lib/data-schemas/learning-tracks.ts for an example.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing and validation
|
||||||
|
|
||||||
|
After creating all three files:
|
||||||
|
|
||||||
|
1. **Build the site**: Run `npm run build`
|
||||||
|
2. **Test schemas**: Run `npm test -- src/data-directory/tests`
|
||||||
|
3. **Fix any errors**: If you get failures in `src/data-directory/tests/data-schemas.js`:
|
||||||
|
- Copy the error message
|
||||||
|
- In VS Code Copilot Chat, type: "When I ran the schema test, I got this error:" and paste the error
|
||||||
|
- Update your schema file based on Copilot's suggestions
|
||||||
|
4. **Repeat testing** until all tests pass
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
Once your table is working and tests pass, create a pull request for review.
|
||||||
|
|
||||||
|
The `docs-engineering` team must review and approve your implementation.
|
||||||
26
src/data-directory/middleware/data-tables.ts
Normal file
26
src/data-directory/middleware/data-tables.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { NextFunction, Response } from 'express'
|
||||||
|
import { ExtendedRequest } from '@/types'
|
||||||
|
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data'
|
||||||
|
|
||||||
|
let tablesCache: Record<string, unknown> | null = null
|
||||||
|
|
||||||
|
// Lazy loading function
|
||||||
|
const getTables = () => {
|
||||||
|
if (!tablesCache) {
|
||||||
|
// Keep product-name-heavy reference tables in English only for now
|
||||||
|
tablesCache = getDeepDataByLanguage('tables', 'en')
|
||||||
|
}
|
||||||
|
return tablesCache
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that loads data-driven table content into the request context.
|
||||||
|
* Tables are sourced from YAML files in data/tables/ directory.
|
||||||
|
*/
|
||||||
|
export default async function dataTables(req: ExtendedRequest, res: Response, next: NextFunction) {
|
||||||
|
if (!req.context) throw new Error('request not contextualized')
|
||||||
|
|
||||||
|
req.context.tables = getTables()
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
13
src/fixtures/fixtures/data/tables/supported-languages.yml
Normal file
13
src/fixtures/fixtures/data/tables/supported-languages.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
features:
|
||||||
|
foo:
|
||||||
|
name: 'Foo Feature'
|
||||||
|
fptAndGhec: true
|
||||||
|
ghes: false
|
||||||
|
bar:
|
||||||
|
name: 'Bar Feature'
|
||||||
|
fptAndGhec: true
|
||||||
|
ghes: true
|
||||||
|
supported:
|
||||||
|
BeepBoop:
|
||||||
|
foo: 'supported'
|
||||||
|
bar: 'not-supported'
|
||||||
@@ -35,6 +35,7 @@ import robots from './robots'
|
|||||||
import earlyAccessLinks from '@/early-access/middleware/early-access-links'
|
import earlyAccessLinks from '@/early-access/middleware/early-access-links'
|
||||||
import categoriesForSupport from './categories-for-support'
|
import categoriesForSupport from './categories-for-support'
|
||||||
import triggerError from '@/observability/middleware/trigger-error'
|
import triggerError from '@/observability/middleware/trigger-error'
|
||||||
|
import dataTables from '@/data-directory/middleware/data-tables'
|
||||||
import secretScanning from '@/secret-scanning/middleware/secret-scanning'
|
import secretScanning from '@/secret-scanning/middleware/secret-scanning'
|
||||||
import ghesReleaseNotes from '@/release-notes/middleware/ghes-release-notes'
|
import ghesReleaseNotes from '@/release-notes/middleware/ghes-release-notes'
|
||||||
import whatsNewChangelog from './context/whats-new-changelog'
|
import whatsNewChangelog from './context/whats-new-changelog'
|
||||||
@@ -256,6 +257,7 @@ export default function (app: Express) {
|
|||||||
app.head('/*path', fastHead)
|
app.head('/*path', fastHead)
|
||||||
|
|
||||||
// *** Preparation for render-page: contextualizers ***
|
// *** Preparation for render-page: contextualizers ***
|
||||||
|
app.use(asyncMiddleware(dataTables))
|
||||||
app.use(asyncMiddleware(secretScanning))
|
app.use(asyncMiddleware(secretScanning))
|
||||||
app.use(asyncMiddleware(ghesReleaseNotes))
|
app.use(asyncMiddleware(ghesReleaseNotes))
|
||||||
app.use(asyncMiddleware(whatsNewChangelog))
|
app.use(asyncMiddleware(whatsNewChangelog))
|
||||||
|
|||||||
Reference in New Issue
Block a user