-
Notifications
You must be signed in to change notification settings - Fork 11
feat(API): add subsection support #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Deploying patternfly-doc-core with
|
| 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 |
There was a problem hiding this 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
findContentEntryandfindContentEntryFilePathcontain nearly identical matching logic (lines 30-48 and 70-86): section check, tab comparison withaddDemosOrDeprecated, 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
| 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Closes #187
Summary by CodeRabbit
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.