Skip to content

Conversation

@wise-king-sullyman
Copy link
Contributor

@wise-king-sullyman wise-king-sullyman commented Jan 20, 2026

Closes #187

Summary by CodeRabbit

  • Documentation

    • Enhanced API documentation clarifying flattened subsection page naming using underscores (e.g., forms_checkbox).
    • Expanded endpoint descriptions and parameter documentation with additional usage examples.
    • Updated OpenAPI specification with clarified examples and descriptions for subsection pages.
  • Tests

    • Added comprehensive test coverage for flattened subsection support and new data structures.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Walkthrough

This PR implements support for flattened subsections in the API structure. Subsections are encoded into page names with underscores (e.g., forms_checkbox), requiring changes to the API index generation, route validation, content matching utilities, and endpoint documentation.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
astro.config.mjs, package.json, pf-docs.config.mjs
Added fs/promises and path to SSR external modules; added PatternFly react-user-feedback and react-data-view dev dependencies; added corresponding content config entries
API Index Generation
src/utils/apiIndex/generate.ts, src/utils/__tests__/apiIndex.test.ts
Flattened subsections into page names (e.g., forms_checkbox); refactored key schemas for pages, tabs, and examples; expanded test coverage to validate flattened structure and enforce 3-part/4-part key formats
New Utility Modules
src/utils/apiRoutes/collections.ts, src/utils/apiRoutes/contentMatching.ts, src/utils/apiRoutes/exampleParsing.ts
Added getEnrichedCollections for fetching and enriching content by version; added findContentEntry and findContentEntryFilePath for matching entries against section/page/tab criteria; added extractImports and extractExampleFilePath for parsing example file references
API Route Refactoring
src/pages/api/[version]/[section].ts, src/pages/api/[version]/[section]/[page].ts, src/pages/api/[version]/[section]/[page]/[tab].ts, src/pages/api/[version]/[section]/[page]/[tab]/examples.ts, src/pages/api/[version]/[section]/[page]/[tab]/examples/[example].ts, src/pages/api/[version]/[section]/[page]/[tab]/text.ts
Updated routes to use new enriched collections utilities and adjusted key naming for clarity; refactored validation logic to account for flattened subsections; simplified content retrieval by delegating to content matching helpers
API Documentation & Tests
src/pages/api/index.ts, src/pages/api/openapi.json.ts, src/__tests__/pages/api/__tests__/[version]/[section]/[page]/[tab].test.ts
Updated endpoint descriptions and examples to reflect underscore-separated subsection pages; expanded test mocks to include empty examples object

Sequence Diagram(s)

sequenceDiagram
    participant Client as API Client
    participant Route as API Route<br/>(text/examples)
    participant Index as API Index
    participant Collections as Enriched<br/>Collections
    participant ContentMatch as Content<br/>Matching

    Client->>Route: GET /api/v6/components/forms_checkbox
    Route->>Index: Validate section in index
    Index-->>Route: Section valid
    Route->>Collections: getEnrichedCollections(v6)
    Collections-->>Route: EnrichedContentEntry[]
    Route->>ContentMatch: findContentEntry(entries, {section, page, tab})
    ContentMatch->>ContentMatch: Match entry by:<br/>- section<br/>- page (forms_checkbox<br/>from id + subsection)<br/>- tab (with addDemosOrDeprecated)
    ContentMatch-->>Route: EnrichedContentEntry
    Route-->>Client: Content response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • dlabaj
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Changes in package.json dependencies (@patternfly/react-user-feedback, @patternfly/react-data-view) and astro.config.mjs SSR externals appear tangential to subsection support but may be related to documentation infrastructure; however, their necessity is unclear from the linked issue context. Clarify whether the PatternFly dependencies and Astro SSR configuration changes are necessary for the subsection support feature or should be separated into a different pull request.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(API): add subsection support' accurately and concisely describes the main objective of the pull request, which is to add subsection support to the API.
Linked Issues check ✅ Passed The pull request successfully implements subsection support in the API by flattening subsections into page names (e.g., forms_checkbox), updating the ApiIndex structure, and providing new utilities to properly handle and retrieve subsection-aware content.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link

Deploying patternfly-doc-core with  Cloudflare Pages  Cloudflare Pages

Latest commit: 783b4ad
Status: ✅  Deploy successful!
Preview URL: https://eae88f95.patternfly-doc-core.pages.dev
Branch Preview URL: https://add-subsection-support.patternfly-doc-core.pages.dev

View logs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/utils/apiRoutes/exampleParsing.ts`:
- Around line 24-37: The current extractExampleFilePath uses imports.find(imp =>
imp.includes(exampleName)) which can match substrings (e.g., AlertBasic vs
AlertBasicExpanded); change the selection to use a word-boundary regex with the
exampleName escaped: build an escapedName by replacing special regex chars,
create a RegExp like new RegExp(`\\b${escapedName}\\b`) and use imports.find(imp
=> wordRegex.test(imp)) to locate exampleImport; keep the existing
path-extraction logic (match on /['"](\.[^'"]+)['"]/i) and ensure the escape
helper is defined in this module.
🧹 Nitpick comments (1)
src/utils/apiRoutes/contentMatching.ts (1)

24-51: Consider extracting the shared matching logic.

Both findContentEntry and findContentEntryFilePath contain nearly identical matching logic (lines 30-48 and 70-86): section check, tab comparison with addDemosOrDeprecated, and page construction with subsection handling.

Extract a shared predicate function to reduce duplication and ensure consistent matching behavior.

♻️ Proposed refactor
+function matchesParams(entry: EnrichedContentEntry, params: ContentMatchParams): boolean {
+  const { section, page, tab } = params
+
+  if (entry.data.section !== section) {
+    return false
+  }
+
+  const entryTab = addDemosOrDeprecated(entry.data.tab, entry.filePath)
+  if (entryTab !== tab) {
+    return false
+  }
+
+  const entryId = kebabCase(entry.data.id)
+  const entryPage = entry.data.subsection
+    ? `${entry.data.subsection}_${entryId}`
+    : entryId
+
+  return entryPage === page
+}

 export function findContentEntry(
   entries: EnrichedContentEntry[],
   params: ContentMatchParams
 ): EnrichedContentEntry | null {
-  const { section, page, tab } = params
-
-  const matchingEntry = entries.find((entry) => {
-    // Match section and tab
-    if (entry.data.section !== section) {
-      return false
-    }
-
-    const entryTab = addDemosOrDeprecated(entry.data.tab, entry.filePath)
-    if (entryTab !== tab) {
-      return false
-    }
-
-    // Match page (handling flattened subsection names)
-    const entryId = kebabCase(entry.data.id)
-    const entryPage = entry.data.subsection
-      ? `${entry.data.subsection}_${entryId}`
-      : entryId
-
-    return entryPage === page
-  })
-
-  return matchingEntry || null
+  return entries.find((entry) => matchesParams(entry, params)) || null
 }

 export function findContentEntryFilePath(
   entries: EnrichedContentEntry[],
   params: ContentMatchParams
 ): string | null {
-  const { section, page, tab } = params
-
-  // Find all matching entries
-  const matchingEntries = entries.filter((entry) => {
-    if (entry.data.section !== section) {
-      return false
-    }
-
-    const entryTab = addDemosOrDeprecated(entry.data.tab, entry.filePath)
-    if (entryTab !== tab) {
-      return false
-    }
-
-    const entryId = kebabCase(entry.data.id)
-    const entryPage = entry.data.subsection
-      ? `${entry.data.subsection}_${entryId}`
-      : entryId
-
-    return entryPage === page
-  })
+  const matchingEntries = entries.filter((entry) => matchesParams(entry, params))

   if (matchingEntries.length === 0) {
     return null
   }

   // Prefer .mdx over .md (mdx files contain LiveExample components)
   const mdxEntry = matchingEntries.find((entry) => entry.filePath.endsWith('.mdx'))
   const selectedEntry = mdxEntry || matchingEntries[0]

   return selectedEntry.filePath
 }

Also applies to: 63-97

Comment on lines +24 to +37
export function extractExampleFilePath(imports: string[], exampleName: string): string | null {
const exampleImport = imports.find((imp) => imp.includes(exampleName))
if (!exampleImport) {
console.error('No import path found for example', exampleName)
return null
}

// Extract path from import statement, handling query parameters like ?raw
// Matches: "./path" or "../path" with optional file extensions and query params
const match = exampleImport.match(/['"](\.[^'"]+)['"]/i)
if (!match || !match[1]) {
return null
}
return match[1]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid substring collisions when selecting the example import.
includes(exampleName) can match unintended imports (e.g., AlertBasic vs AlertBasicExpanded). A word-boundary regex with escaping is safer.

🔧 Suggested fix
-  const exampleImport = imports.find((imp) => imp.includes(exampleName))
+  const escaped = exampleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+  const exampleRegex = new RegExp(`\\b${escaped}\\b`)
+  const exampleImport = imports.find((imp) => exampleRegex.test(imp))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function extractExampleFilePath(imports: string[], exampleName: string): string | null {
const exampleImport = imports.find((imp) => imp.includes(exampleName))
if (!exampleImport) {
console.error('No import path found for example', exampleName)
return null
}
// Extract path from import statement, handling query parameters like ?raw
// Matches: "./path" or "../path" with optional file extensions and query params
const match = exampleImport.match(/['"](\.[^'"]+)['"]/i)
if (!match || !match[1]) {
return null
}
return match[1]
export function extractExampleFilePath(imports: string[], exampleName: string): string | null {
const escaped = exampleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const exampleRegex = new RegExp(`\\b${escaped}\\b`)
const exampleImport = imports.find((imp) => exampleRegex.test(imp))
if (!exampleImport) {
console.error('No import path found for example', exampleName)
return null
}
// Extract path from import statement, handling query parameters like ?raw
// Matches: "./path" or "../path" with optional file extensions and query params
const match = exampleImport.match(/['"](\.[^'"]+)['"]/i)
if (!match || !match[1]) {
return null
}
return match[1]
}
🤖 Prompt for AI Agents
In `@src/utils/apiRoutes/exampleParsing.ts` around lines 24 - 37, The current
extractExampleFilePath uses imports.find(imp => imp.includes(exampleName)) which
can match substrings (e.g., AlertBasic vs AlertBasicExpanded); change the
selection to use a word-boundary regex with the exampleName escaped: build an
escapedName by replacing special regex chars, create a RegExp like new
RegExp(`\\b${escapedName}\\b`) and use imports.find(imp => wordRegex.test(imp))
to locate exampleImport; keep the existing path-extraction logic (match on
/['"](\.[^'"]+)['"]/i) and ensure the escape helper is defined in this module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for subsections in the API

2 participants