diff --git a/data/tables/README.md b/data/tables/README.md new file mode 100644 index 0000000000..1595c428b4 --- /dev/null +++ b/data/tables/README.md @@ -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. \ No newline at end of file diff --git a/src/data-directory/middleware/data-tables.ts b/src/data-directory/middleware/data-tables.ts new file mode 100644 index 0000000000..a29086e9f0 --- /dev/null +++ b/src/data-directory/middleware/data-tables.ts @@ -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 | 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() +} diff --git a/src/fixtures/fixtures/data/tables/supported-languages.yml b/src/fixtures/fixtures/data/tables/supported-languages.yml new file mode 100644 index 0000000000..60a1b56d8e --- /dev/null +++ b/src/fixtures/fixtures/data/tables/supported-languages.yml @@ -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' diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index b52591590e..bf4fc11b46 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -35,6 +35,7 @@ import robots from './robots' import earlyAccessLinks from '@/early-access/middleware/early-access-links' import categoriesForSupport from './categories-for-support' import triggerError from '@/observability/middleware/trigger-error' +import dataTables from '@/data-directory/middleware/data-tables' import secretScanning from '@/secret-scanning/middleware/secret-scanning' import ghesReleaseNotes from '@/release-notes/middleware/ghes-release-notes' import whatsNewChangelog from './context/whats-new-changelog' @@ -256,6 +257,7 @@ export default function (app: Express) { app.head('/*path', fastHead) // *** Preparation for render-page: contextualizers *** + app.use(asyncMiddleware(dataTables)) app.use(asyncMiddleware(secretScanning)) app.use(asyncMiddleware(ghesReleaseNotes)) app.use(asyncMiddleware(whatsNewChangelog))