feat(api): Hugo-native API reference rendering

Hugo-native templates for API
reference documentation. Operations render as server-side HTML instead
of client-side Redoc, providing faster page loads, full SEO
indexability, and consistent styling.

Architecture:
- Tag-based navigation: operations grouped by OpenAPI tag, accessed
  via tag pages only (no individual operation URLs)
- Generation script auto-discovers products from .config.yml files,
  deriving Hugo paths and menu keys from directory structure
- Per-tag JSON/YAML chunks for fast Hugo template rendering
- Inline curl examples generated from OpenAPI specs at build time

Templates (layouts/api/, layouts/partials/api/):
- tag-renderer.html: renders all operations for a tag
- operation.html: individual operation with parameters, request body,
  responses, and schema rendering
- code-sample.html: curl examples with Ask AI integration
- section-children.html: tag listing on section index pages
- all-endpoints-list.html: all operations sorted by path

Generation (api-docs/scripts/generate-openapi-articles.ts):
- Auto-discovery from .config.yml replaces hardcoded productConfigs
- Each API section generates independently (no cross-spec merging)
- Frontmatter-driven template data (specDownloadPath, articleDataKey)
- Link transformation: /influxdb/version/ placeholders resolved to
  product-specific paths

Removed:
- Redoc renderer, JavaScript components, and CSS
- Shadow DOM test infrastructure (~160 lines)
- Operation page generation (dead generatePathPages function)
- mergeArticleData() and slugifyDisplayName()

Styles (assets/styles/layouts/_api-*.scss):
- 3-column layout: sidebar, content, sticky TOC
- Theme-aware code blocks for curl examples
- Method badges (GET/POST/PUT/DELETE) with color coding
- Collapsible schema sections with depth tracking

Tests (cypress/e2e/content/api-reference.cy.js):
- Tag page rendering with operation structure
- Download button verification per section
- All-endpoints page with operation cards
- Related links from x-related OpenAPI extension
- Code sample and Ask AI link validation
clean-squash
Jason Stirnaman 2026-03-15 20:38:51 -05:00
parent 2858f6327d
commit f5535c9d5c
123 changed files with 15616 additions and 2070 deletions

View File

@ -9,6 +9,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
## Core Expertise Areas
**InfluxDB 3 Products & Architecture:**
- **Self-hosted products:**
- InfluxDB 3 Core (`influxdata/influxdb/influxdb3*` source code) - open source
- InfluxDB 3 Enterprise - licensed
@ -29,6 +30,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
- `content/shared/` - Shared content across products, versions, and editions
**APIs & Interfaces:**
- InfluxDB 3 HTTP APIs:
- v1 compatibility API (InfluxQL write/query)
- v2 compatibility API (Flux)
@ -42,6 +44,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
- Telegraf integration patterns and plugin ecosystem
**Documentation Standards:**
- Google Developer Documentation Style guidelines
- InfluxData documentation structure and conventions (from CLAUDE.md context)
- Hugo shortcodes and frontmatter requirements
@ -51,6 +54,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
## Your Responsibilities
**Content Creation & Review:**
- Write technically accurate documentation that reflects actual product behavior
- Create comprehensive API documentation with proper OpenAPI specifications
- Develop clear, testable code examples with proper annotations
@ -59,6 +63,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
- Identify when content should be shared vs. product-specific
**Technical Accuracy:**
- Verify code examples work with current product versions
- Cross-reference implementation details with source code when needed
- Validate API endpoints, parameters, and response formats
@ -71,6 +76,7 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
- Cloud: Managed features, quotas, billing
**Style & Standards Compliance:**
- Apply Google Developer Documentation Style consistently
- Use semantic line feeds and proper Markdown formatting
- Implement appropriate shortcodes for product-specific content
@ -80,30 +86,25 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
## Content Development Process
1. **Analyze Requirements:**
1. **Analyze Requirements:**
- Understand the target audience, product version(s), and documentation type
- Determine if content should be shared or product-specific
2. **Research Implementation:**
2. **Research Implementation:**
- Reference source code, APIs, and existing documentation for accuracy
- Identify product-specific behaviors and differences
3. **Structure Content:**
3. **Structure Content:**
- Use appropriate frontmatter, headings, and shortcodes for the content type
- Apply shared content pattern when content applies to multiple products
- Use product-specific conditionals when needed
4. **Create Examples:**
4. **Create Examples:**
- Develop working, testable code examples with proper annotations
- Include examples for relevant products and deployment models
5. **Apply Standards:**
5. **Apply Standards:**
- Ensure compliance with style guidelines and documentation conventions
- Use docs CLI tools for content creation and validation
6. **Cross-Reference:**
6. **Cross-Reference:**
- Verify consistency with related documentation and product variants
- Add alt_links for cross-product navigation
- Add alt\_links for cross-product navigation
- Link related concepts and procedures
## Quality Assurance
@ -114,11 +115,12 @@ You are an expert InfluxDB 3 technical writer with deep knowledge of InfluxData'
- Use placeholder conventions consistently (UPPERCASE for user-replaceable values)
- Ensure proper cross-linking between related concepts and procedures
- Verify shared content works correctly across all target products
- Test cross-product navigation (alt_links)
- Test cross-product navigation (alt\_links)
## Product-Specific Considerations
**When documenting, consider:**
- **Core vs Enterprise:** Feature availability (clustering, HA, RBAC)
- **Self-hosted vs Cloud:** Configuration methods, authentication, quotas
- **Clustered vs Dedicated:** Deployment model, scaling, management

View File

@ -197,7 +197,7 @@ assets/js/
│ ├── api-nav.ts # API navigation behavior
│ ├── api-toc.ts # Table of contents
│ ├── api-tabs.ts # Tab switching
│ └── api-scalar.ts # Scalar/RapiDoc integration
│ └── api-rapidoc.ts # RapiDoc integration
└── utils/
├── dom-helpers.ts # Shared DOM utilities
└── debug-helpers.js # Debugging utilities

View File

@ -353,6 +353,24 @@ The **only** acceptable inline scripts are minimal initialization that MUST run
Everything else belongs in `assets/js/`.
### File Organization for Components
```
assets/
├── js/
│ ├── main.js # Entry point, component registry
│ ├── components/
│ │ ├── api-nav.ts # API navigation behavior
│ │ ├── api-toc.ts # Table of contents
│ │ ├── api-tabs.ts # Tab switching (if needed beyond CSS)
│ │ └── api-rapidoc.ts # RapiDoc integration
│ └── utils/
│ └── dom-helpers.ts # Shared DOM utilities
└── styles/
└── layouts/
└── _api-layout.scss # API-specific styles
```
### TypeScript Component Checklist
When creating a new interactive feature:

View File

@ -102,7 +102,6 @@ level: warning
### Critical: Vale Uses regexp2, Not RE2
Vale uses the [regexp2](https://pkg.go.dev/github.com/dlclark/regexp2) library, **not** Go's standard `regexp` package (which uses RE2). This is a common source of confusion because Vale is written in Go.
### Supported Regex Features
@ -155,11 +154,13 @@ tokens:
### tokens vs raw
**tokens:**
- Automatically wrapped in word boundaries
- Converted to non-capturing groups
- Good for simple patterns
**raw:**
- Full control over the pattern
- No automatic processing
- Use for complex regex
@ -333,16 +334,19 @@ print(matches2) # Should be empty
### Common Issues
**Pattern not matching:**
1. Check if you need `nonword: true` for punctuation
2. Verify scope is appropriate (`sentence`, `heading`, etc.)
3. Test with `raw` instead of `tokens` for complex patterns
**Too many false positives:**
1. Add exceptions using negative lookahead/lookbehind
2. Adjust scope to be more specific
3. Consider using substitution rule with exceptions
**Pattern works in Python but not Vale:**
- Unlikely if you're using PCRE features (Vale supports them)
- Check for differences in whitespace handling
- Try `raw` field for exact pattern control

15
.gitignore vendored
View File

@ -12,9 +12,22 @@ package-lock.json
# Content generation
/content/influxdb*/**/api/**/*.html
/content/influxdb*/**/api/**/*.md
!api-docs/**/.config.yml
/api-docs/redoc-static.html*
/api-docs/_build/
# API documentation generation (generated by api-docs/scripts/)
/content/influxdb/*/api/**
/content/influxdb3/*/api/**
/content/influxdb3/*/reference/api/**
/content/enterprise_influxdb/*/api/**
/static/openapi
/data/article_data
# Exception: hand-crafted API conceptual pages (not generated)
!/content/influxdb3/*/api/administration/
!/content/influxdb3/*/api/administration/_index.md
/helper-scripts/output/*
/telegraf-build
!telegraf-build/templates
@ -39,6 +52,8 @@ tmp
# TypeScript build output
**/dist/
# Exception: include compiled API doc scripts for easier use
!api-docs/scripts/dist/
**/dist-lambda/
# User context files for AI assistant tools

242
AGENTS.md
View File

@ -1,22 +1,36 @@
# InfluxData Documentation (docs-v2)
> **Shared project guidelines for all AI assistants**
> **For general AI assistants (Claude, ChatGPT, Gemini, etc.)**
>
> This guide provides comprehensive instructions for AI assistants helping with the InfluxData documentation repository. It focuses on content creation, writing workflows, and style guidelines.
>
> **Other instruction resources**:
> - [.github/copilot-instructions.md](.github/copilot-instructions.md) - GitHub Copilot (CLI tools, workflows, repo structure)
> - [CLAUDE.md](CLAUDE.md) - Claude with MCP (pointer file)
>
> - [.github/copilot-instructions.md](.github/copilot-instructions.md) - For GitHub Copilot (focused on coding and automation)
> - [CLAUDE.md](CLAUDE.md) - For Claude with MCP (minimal pointer)
> - [.claude/](.claude/) - Claude MCP configuration (commands, agents, skills)
> - [.github/instructions/](.github/instructions/) - File pattern-specific instructions
## Commands
## Project Overview
| Task | Command | Notes |
|------|---------|-------|
| Install | `CYPRESS_INSTALL_BINARY=0 yarn install` | ~4s |
| Build | `npx hugo --quiet` | ~75s — **NEVER CANCEL** |
| Dev server | `npx hugo server` | ~92s, port 1313 |
| Test code blocks | `yarn test:codeblocks:all` | 15-45m — **NEVER CANCEL** |
| Lint | `yarn lint` | ~1m |
This repository powers [docs.influxdata.com](https://docs.influxdata.com), a Hugo-based static documentation site covering InfluxDB 3, InfluxDB v2/v1, Telegraf, and related products.
**Key Characteristics:**
- **Scale**: 5,359+ pages
- **Build time**: \~75 seconds (NEVER cancel Hugo builds)
- **Tech stack**: Hugo, Node.js, Docker, Vale, Pytest, Cypress
- **Test time**: 15-45 minutes for full code block tests
## Quick Commands
| Task | Command | Time |
| -------------------- | --------------------------------------- | ------ |
| Install dependencies | `CYPRESS_INSTALL_BINARY=0 yarn install` | \~4s |
| Build site | `npx hugo --quiet` | \~75s |
| Dev server | `npx hugo server` | \~92s |
| Test code blocks | `yarn test:codeblocks:all` | 15-45m |
| Lint | `yarn lint` | \~1m |
## Repository Structure
@ -31,7 +45,7 @@ docs-v2/
│ └── example.md # Shortcode testing playground
├── layouts/ # Hugo templates and shortcodes
├── assets/ # JS, CSS, TypeScript
├── api-docs/ # InfluxDB OpenAPI specifications, API reference documentation generation scripts
├── api-docs/ # OpenAPI specifications
├── data/ # YAML/JSON data files
├── public/ # Build output (gitignored, ~529MB)
└── .github/
@ -40,18 +54,19 @@ docs-v2/
**Content Paths**: See [copilot-instructions.md](.github/copilot-instructions.md#content-organization)
## Documentation MCP Server
A hosted MCP server provides semantic search over all InfluxDB documentation.
Use it to verify technical accuracy, check API syntax, and find related docs.
See the [InfluxDB documentation MCP server guide](https://docs.influxdata.com/influxdb3/core/admin/mcp-server/) for setup instructions.
## Common Workflows
### Creating/Editing Content
### Editing a page in your browser
1. Navigate to the desired page on [docs.influxdata.com](https://docs.influxdata.com)
2. Click the "Edit this page" link at the bottom
3. Make changes in the GitHub web editor
4. Commit changes via a pull request
### Creating/Editing Content Manually
**Frontmatter** (page metadata):
```yaml
title: Page Title # Required - becomes h1
description: Brief desc # Required - for SEO
@ -63,15 +78,18 @@ weight: 1 # Required - sort order
```
**Shared Content** (avoid duplication):
```yaml
source: /shared/path/to/content.md
```
Shared content files (`/shared/path/to/content.md`):
- Don't store frontmatter
- Can use `{{% show-in %}}`, `{{% hide-in %}}`, and the `version` keyword (`/influxdb3/version/content.md`)
**Common Shortcodes**:
- Callouts: `> [!Note]`, `> [!Warning]`, `> [!Important]`, `> [!Tip]`
- Tabs: `{{< tabs-wrapper >}}` + `{{% tabs %}}` + `{{% tab-content %}}`
- Required: `{{< req >}}` or `{{< req type="key" >}}`
@ -82,6 +100,7 @@ Shared content files (`/shared/path/to/content.md`):
### Testing Changes
**Always test before committing**:
```bash
# Verify server renders (check 200 status)
curl -s -o /dev/null -w "%{http_code}" http://localhost:1313/influxdb3/core/
@ -95,83 +114,140 @@ yarn test:links content/influxdb3/core/**/*.md
**📖 Complete Reference**: [DOCS-TESTING.md](DOCS-TESTING.md)
### Committing Changes
## Constraints
**Commit Message Format**:
- **NEVER cancel** Hugo builds (~75s) or test runs (15-45m) — the site has 5,359+ pages
- Set timeouts: Hugo 180s+, tests 30m+
- Use `python` not `py` for code block language identifiers (pytest won't collect `py` blocks)
- Shared content files (`content/shared/`) have no frontmatter — the consuming page provides it
- Product names and versions come from `data/products.yml` (single source of truth)
- Commit format: `type(scope): description` — see [DOCS-CONTRIBUTING.md](DOCS-CONTRIBUTING.md#commit-guidelines)
- Network-restricted environments: Cypress (`CYPRESS_INSTALL_BINARY=0`), Docker builds, and Alpine packages may fail
```
type(scope): description
## Style Rules
Examples:
- fix(enterprise): correct Docker environment variable
- feat(influxdb3): add new plugin documentation
- docs(core): update configuration examples
```
Follows [Google Developer Documentation Style Guide](https://developers.google.com/style) with these project-specific additions:
**Types**: `fix`, `feat`, `style`, `refactor`, `test`, `chore`
- **Semantic line feeds** — one sentence per line (better diffs)
- **No h1 in content**`title` frontmatter auto-generates h1
- Active voice, present tense, second person
- Long options in CLI examples (`--output` not `-o`)
- Code blocks within 80 characters
**Scopes**: `enterprise`, `influxdb3`, `core`, `cloud`, `telegraf`, etc.
## Content Structure
**Pre-commit hooks** run automatically (Vale, Prettier, tests). Skip with:
**Required frontmatter**: `title`, `description`, `menu`, `weight`
— see [DOCS-FRONTMATTER.md](DOCS-FRONTMATTER.md)
```bash
git commit -m "message" --no-verify
```
**Shared content**: `source: /shared/path/to/content.md`
— shared files use `{{% show-in %}}` / `{{% hide-in %}}` for product-specific content
**📖 Complete Reference**: [DOCS-CONTRIBUTING.md](DOCS-CONTRIBUTING.md#commit-guidelines)
**Shortcodes**: Callouts use `> [!Note]` / `> [!Warning]` syntax
— see [DOCS-SHORTCODES.md](DOCS-SHORTCODES.md) and [content/example.md](content/example.md)
## Key Patterns
## Product Content Paths
### Content Organization
Canonical paths from `data/products.yml`:
- **Product versions**: Managed in `/data/products.yml`
- **Semantic line feeds**: One sentence per line for better diffs
- **Heading hierarchy**: Use h2-h6 only (h1 auto-generated from frontmatter)
- **Image naming**: `project/version-context-description.png`
| Product | Content Path |
|---------|-------------|
| InfluxDB 3 Core | `content/influxdb3/core/` |
| InfluxDB 3 Enterprise | `content/influxdb3/enterprise/` |
| InfluxDB 3 Explorer | `content/influxdb3/explorer/` |
| InfluxDB Cloud Serverless | `content/influxdb3/cloud-serverless/` |
| InfluxDB Cloud Dedicated | `content/influxdb3/cloud-dedicated/` |
| InfluxDB Clustered | `content/influxdb3/clustered/` |
| InfluxDB OSS v2 | `content/influxdb/v2/` |
| InfluxDB OSS v1 | `content/influxdb/v1/` |
| InfluxDB Cloud (TSM) | `content/influxdb/cloud/` |
| InfluxDB Enterprise v1 | `content/enterprise_influxdb/` |
| Telegraf | `content/telegraf/` |
| Chronograf | `content/chronograf/` |
| Kapacitor | `content/kapacitor/` |
| Flux | `content/flux/` |
| Shared content | `content/shared/` |
### Code Examples
## Doc Review Pipeline
**Testable code blocks** (pytest):
Automated PR review for documentation changes.
See [.github/LABEL_GUIDE.md](.github/LABEL_GUIDE.md) for the label taxonomy.
```python
print("Hello, world!")
```
| Resource | Path |
|----------|------|
| Label guide | [.github/LABEL_GUIDE.md](.github/LABEL_GUIDE.md) |
| Triage agent | [.claude/agents/doc-triage-agent.md](.claude/agents/doc-triage-agent.md) |
| Content review instructions | [.github/instructions/content-review.instructions.md](.github/instructions/content-review.instructions.md) |
| Review agent (local) | [.claude/agents/doc-review-agent.md](.claude/agents/doc-review-agent.md) |
| Auto-label workflow | [.github/workflows/auto-label.yml](.github/workflows/auto-label.yml) |
| Doc review workflow | [.github/workflows/doc-review.yml](.github/workflows/doc-review.yml) |
<!--pytest-codeblocks:expected-output-->
## Reference
```
Hello, world!
```
| Document | Purpose |
|----------|---------|
| [DOCS-CONTRIBUTING.md](DOCS-CONTRIBUTING.md) | Style guidelines, commit format, contribution workflow |
| [DOCS-TESTING.md](DOCS-TESTING.md) | Code block testing, link validation, Vale linting |
| [DOCS-SHORTCODES.md](DOCS-SHORTCODES.md) | Complete shortcode reference |
| [DOCS-FRONTMATTER.md](DOCS-FRONTMATTER.md) | Complete frontmatter field reference |
| [api-docs/README.md](api-docs/README.md) | API documentation workflow |
| [content/example.md](content/example.md) | Live shortcode examples |
| [.github/copilot-instructions.md](.github/copilot-instructions.md) | CLI tools, repo structure, workflows |
| [.github/LABEL_GUIDE.md](.github/LABEL_GUIDE.md) | Label taxonomy and review pipeline |
**Language identifiers**: Use `python` not `py`, `bash` not `sh` (for pytest collection)
### API Documentation
- **Location**: `/api-docs/` directory
- **Format**: OpenAPI 3.0 YAML
- **Generation**: Uses Redoc + custom processing
- **📖 Workflow**: [api-docs/README.md](api-docs/README.md)
### JavaScript/TypeScript
- **Entry point**: `assets/js/main.js`
- **Pattern**: Component-based with `data-component` attributes
- **Debugging**: Source maps or debug helpers available
- **📖 Details**: [DOCS-CONTRIBUTING.md](DOCS-CONTRIBUTING.md#javascript-in-the-documentation-ui)
## Important Constraints
### Performance
- **NEVER cancel Hugo builds** - they take \~75s normally
- **NEVER cancel test runs** - code block tests take 15-45 minutes
- **Set timeouts**: Hugo (180s+), tests (30+ minutes)
### Style Guidelines
- Use Google Developer Documentation style
- Active voice, present tense, second person for instructions
- No emojis unless explicitly requested
- Use long options in CLI examples (`--option` vs `-o`)
- Format code blocks within 80 characters
### Network Restrictions
Some operations may fail in restricted environments:
- Docker builds requiring external repos
- `docker compose up local-dev` (Alpine packages)
- Cypress installation (use `CYPRESS_INSTALL_BINARY=0`)
## Documentation References
| Document | Purpose |
| ------------------------------------------------------------------ | ------------------------------------------------ |
| [DOCS-CONTRIBUTING.md](DOCS-CONTRIBUTING.md) | Contribution workflow, style guidelines |
| [DOCS-TESTING.md](DOCS-TESTING.md) | Testing procedures (code blocks, links, linting) |
| [DOCS-SHORTCODES.md](DOCS-SHORTCODES.md) | Complete shortcode reference |
| [DOCS-FRONTMATTER.md](DOCS-FRONTMATTER.md) | Complete frontmatter field reference |
| [.github/copilot-instructions.md](.github/copilot-instructions.md) | Primary AI assistant instructions |
| [api-docs/README.md](api-docs/README.md) | API documentation workflow |
| [content/example.md](content/example.md) | Live shortcode examples for testing |
## Specialized Topics
### Working with Specific Products
| Product | Content Path | Special Notes |
| ------------------------ | ----------------------------------------------------------------------------- | --------------------------------------- |
| InfluxDB 3 Core | `/content/influxdb3/core/` | Latest architecture |
| InfluxDB 3 Enterprise | `/content/influxdb3/enterprise/` | Core + licensed features, clustered |
| InfluxDB Cloud Dedicated | `/content/influxdb3/cloud-dedicated/`, `/content/influxdb3/cloud-serverless/` | Managed and distributed |
| InfluxDB Clustered | `/content/influxdb3/clustered/` | Self-managed and distributed |
| InfluxDB Cloud | `/content/influxdb/cloud/` | Legacy but active |
| InfluxDB v2 | `/content/influxdb/v2/` | Legacy but active |
| InfluxDB Enterprise v1 | `/content/enterprise_influxdb/v1/` | Legacy but active enterprise, clustered |
### Advanced Tasks
- **Vale configuration**: `.ci/vale/styles/` for custom rules
- **Link checking**: Uses custom `link-checker` binary
- **Docker testing**: `compose.yaml` defines test services
- **Lefthook**: Git hooks configuration in `lefthook.yml`
## Troubleshooting
| Issue | Solution |
| ------------------------ | ------------------------------------------------------ |
| Pytest collected 0 items | Use `python` not `py` for code block language |
| Hugo build errors | Check `/config/_default/` configuration |
| Link validation slow | Test specific files: `yarn test:links content/file.md` |
| Vale errors | Check `.ci/vale/styles/config/vocabularies` |
## Critical Reminders
1. **Be a critical thinking partner** - Challenge assumptions, identify issues
2. **Test before committing** - Run relevant tests locally
3. **Reference, don't duplicate** - Link to detailed docs instead of copying
4. **Respect build times** - Don't cancel long-running operations
5. **Follow conventions** - Use established patterns for consistency

View File

@ -1,6 +1,7 @@
# Contributing to InfluxData Documentation
<!-- agent:instruct: essential -->
## Quick Start
Ready to contribute?
@ -14,7 +15,7 @@ Ready to contribute?
For detailed setup and reference information, see the sections below.
---
***
## Legal & Getting Started
@ -27,18 +28,19 @@ What constitutes a "substantial" change is at the discretion of InfluxData docum
[Sign the InfluxData CLA](https://www.influxdata.com/legal/cla/)
_**Note:** Typo and broken link fixes are greatly appreciated and do not require signing the CLA._
***Note:** Typo and broken link fixes are greatly appreciated and do not require signing the CLA.*
_If you're new to contributing or you're looking for an easy update, see [`docs-v2` good-first-issues](https://github.com/influxdata/docs-v2/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue)._
*If you're new to contributing or you're looking for an easy update, see [`docs-v2` good-first-issues](https://github.com/influxdata/docs-v2/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue).*
### Fork and clone InfluxData Documentation Repository
[Fork this repository](https://help.github.com/articles/fork-a-repo/) and
[clone it](https://help.github.com/articles/cloning-a-repository/) to your local machine.
---
***
<!-- agent:instruct: condense -->
## Development Environment Setup
### Prerequisites
@ -76,9 +78,9 @@ dev dependencies used in pre-commit hooks for linting, syntax-checking, and test
Dev dependencies include:
- [Lefthook](https://github.com/evilmartians/lefthook): configures and
manages git pre-commit and pre-push hooks for linting and testing Markdown content.
manages git pre-commit and pre-push hooks for linting and testing Markdown content.
- [prettier](https://prettier.io/docs/en/): formats code, including Markdown, according to style rules for consistency
- [Cypress]: e2e testing for UI elements and URLs in content
- \[Cypress]: e2e testing for UI elements and URLs in content
### Install Vale (style linting)
@ -114,11 +116,11 @@ docs-v2 contains a `./.vscode/settings.json` that configures the following exten
- Vale: shows linter errors and suggestions in the editor.
- YAML Schemas: validates frontmatter attributes.
---
***
<!-- agent:instruct: condense -->
## Making Changes
## Making Changes
### Style Guidelines
@ -130,7 +132,7 @@ Content follows Google Developer Documentation Style Guide and YouTube API docum
Most docs-v2 documentation content uses [Markdown](https://en.wikipedia.org/wiki/Markdown).
_Some parts of the documentation, such as `./api-docs`, contain Markdown within YAML and rely on additional tooling._
*Some parts of the documentation, such as `./api-docs`, contain Markdown within YAML and rely on additional tooling.*
#### Semantic line feeds
@ -270,6 +272,7 @@ Documentation audit tools should:
4. Support both single-line and multi-line exclusion lists
<!-- agent:instruct: essential -->
### Common Shortcodes Reference
#### Callouts (notes and warnings)
@ -327,7 +330,7 @@ For the complete shortcodes reference with all available shortcodes and usage ex
Test shortcodes with working examples in **[content/example.md](content/example.md)**.
---
***
### InfluxDB API documentation
@ -338,11 +341,10 @@ InfluxDB API documentation when documentation is deployed.
For more information about editing and generating InfluxDB API documentation, see the
[API Documentation README](https://github.com/influxdata/docs-v2/tree/master/api-docs#readme).
---
***
## Testing & Quality Assurance
Pre-commit hooks run automatically when you commit changes, testing your staged files with Vale, Prettier, Cypress, and Pytest. To skip hooks if needed:
```sh
@ -364,13 +366,14 @@ yarn test:links content/influxdb3/core/**/*.md
For comprehensive testing information, including code block testing, link validation, style linting, and advanced testing procedures, see **[DOCS-TESTING.md](DOCS-TESTING.md)**.
---
***
<!-- agent:instruct: condense -->
## Submission Process
<!-- agent:instruct: essential -->
### Commit Guidelines
When creating commits, follow these guidelines:
@ -383,6 +386,7 @@ When creating commits, follow these guidelines:
- For multiple issues, use comma separation: `closes influxdata/DAR#517, closes influxdata/DAR#518`
**Examples:**
```
fix(enterprise): correct Docker environment variable name for license email
fix(influxdb3): correct Docker environment variable and compose examples for monolith
@ -394,7 +398,7 @@ chore(ci): update Vale configuration
Push your changes up to your forked repository, then [create a new pull request](https://help.github.com/articles/creating-a-pull-request/).
---
***
## Reference Documentation
@ -404,6 +408,7 @@ For detailed reference documentation, see:
- **[DOCS-SHORTCODES.md](DOCS-SHORTCODES.md)** - Complete shortcodes reference with usage examples for all available shortcodes
<!-- agent:instruct: condense -->
### Advanced Configuration
#### Vale style linting configuration
@ -434,6 +439,7 @@ To add accepted/rejected terms for specific products, configure a style for the
To learn more about configuration and rules, see [Vale configuration](https://vale.sh/docs/topics/config).
<!-- agent:instruct: condense -->
#### JavaScript in the documentation UI
The InfluxData documentation UI uses TypeScript and JavaScript with ES6+ syntax and
@ -450,13 +456,14 @@ If you're adding UI functionality that requires JavaScript, follow these steps:
```html
<div data-component="my-component"></div>
```
```
2. Following the component pattern, create a single-purpose JavaScript module
(`assets/js/components/my-component.js`)
that exports a single function that receives the component element and initializes it.
3. In `assets/js/main.js`, import the module and register the component to ensure
the component is initialized on page load.
the component is initialized on page load.
##### Debugging JavaScript
@ -470,7 +477,7 @@ To debug JavaScript code used in the InfluxData documentation UI, choose one of
1. In VS Code, select Run > Start Debugging.
2. Select the "Debug Docs (source maps)" configuration.
3. Click the play button to start the debugger.
5. Set breakpoints in the JavaScript source files--files in the
4. Set breakpoints in the JavaScript source files--files in the
`assets/js/ns-hugo-imp:` namespace-- in the
VS Code editor or in the Chrome Developer Tools Sources panel:
@ -484,8 +491,9 @@ To debug JavaScript code used in the InfluxData documentation UI, choose one of
1. In your JavaScript module, import debug helpers from `assets/js/utils/debug-helpers.js`.
These helpers provide breakpoints and console logging as a workaround or alternative for
using source maps and the Chrome DevTools debugger.
2. Insert debug statements by calling the helper functions in your code--for example:
```js
import { debugLog, debugBreak, debugInspect } from './utils/debug-helpers.js';
@ -512,4 +520,4 @@ Your system uses the configuration in `launch.json` to launch the site in Chrome
and attach the debugger to the Developer Tools console.
Make sure to remove the debug statements before merging your changes.
The debug helpers are designed to be used in development and should not be used in production.
The debug helpers are designed to be used in development and should not be used in production.

View File

@ -11,13 +11,13 @@ This guide covers all testing procedures for the InfluxData documentation, inclu
## Test Types Overview
| Test Type | Purpose | Command |
| ----------------------- | ----------------------------------- | ---------------------------- |
| **Code blocks** | Validate shell/Python code examples | `yarn test:codeblocks:all` |
| **Link validation** | Check internal/external links | `yarn test:links` |
| **Style linting** | Enforce writing standards | `.ci/vale/vale.sh` |
| **Markdown generation** | Generate LLM-friendly Markdown | `yarn build:md` |
| **E2E tests** | UI and functionality testing | `yarn test:e2e` |
| Test Type | Purpose | Command |
| ----------------------- | ----------------------------------- | -------------------------- |
| **Code blocks** | Validate shell/Python code examples | `yarn test:codeblocks:all` |
| **Link validation** | Check internal/external links | `yarn test:links` |
| **Style linting** | Enforce writing standards | `.ci/vale/vale.sh` |
| **Markdown generation** | Generate LLM-friendly Markdown | `yarn build:md` |
| **E2E tests** | UI and functionality testing | `yarn test:e2e` |
## Code Block Testing

58
PLAN.md Normal file
View File

@ -0,0 +1,58 @@
---
branch: feat-api-uplift
repo: docs-v2
created: 2025-12-02T15:28:32Z
status: in-progress
---
# feat-api-uplift
## Overview
Replace the current API reference documentation implementation (RapiDoc web components) with Hugo-native templates.
## Phase 1: Core Infrastructure (completed)
### Build process
- `yarn build:api` parses OpenAPI specs into Hugo data
- Generates Hugo pages with frontmatter for Algolia search integration
- Static JSON chunks for faster page loads
### OpenAPI tag cleanup
- Removed unused tags from OpenAPI specs
- Updated tags to be consistent and descriptive
### Hugo-native POC
- Implemented Hugo-native templates in `layouts/partials/api/hugo-native/`
- Tested with InfluxDB 3 Core product
## Phase 2: Migration to Hugo-Native (in progress)
**Plan**: @docs/plans/2026-02-13-hugo-native-api-migration.md
### Task Order
1. ✅ **Promote Hugo-native templates** - Move from POC to production
2. ✅ **Remove RapiDoc templates** - Delete templates and partials
3. ✅ **Remove RapiDoc JavaScript** - Delete components
4. ✅ **Remove operation pages** - Delete individual operation page generation
5. ✅ **Update Cypress tests** - Simplify tests for static HTML
6. ✅ **Clean up styles** - Remove RapiDoc CSS and dead auth modal code
7. ✅ **Fix generation script cleanup** - Added `--clean` (default) and `--dry-run` flags
8. ✅ **Add inline code samples** - curl examples and Ask AI links per operation
9. ✅ **Refine API styling** - Theme-aware code blocks, font normalization, layout width, TOC border
10. **Apply Cache Data tag split** - Enterprise spec update (planned)
11. **Migrate remaining products** - Apply to all InfluxDB products (planned)
## Related Files
- Branch: `feat-api-uplift`
- Plan: `plans/2026-02-13-hugo-native-api-migration.md`
## Notes
- Use Chrome devtools and Cypress to debug
- No individual operation pages - operations accessed only via tag pages

View File

@ -1,65 +1,79 @@
<!-- This file is auto-generated from data/products.yml. Do not edit directly. -->
<!-- Run 'npm run build:agent:instructions' to regenerate this file. -->
Use the following information to help determine which InfluxDB version and product the user is asking about:
InfluxDB OSS v2:
- Documentation: https://docs.influxdata.com/influxdb/v2/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
- Documentation: <https://docs.influxdata.com/influxdb/v2/>
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
InfluxDB OSS v1:
- Documentation: https://docs.influxdata.com/influxdb/v1/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
- Documentation: <https://docs.influxdata.com/influxdb/v1/>
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
InfluxDB Enterprise v1:
- Documentation: https://docs.influxdata.com/enterprise_influxdb/v1.12/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
- Documentation: <https://docs.influxdata.com/enterprise_influxdb/v1.12/>
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v1/v2 client libraries
InfluxDB Cloud (TSM):
- Documentation: https://docs.influxdata.com/influxdb/cloud/
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
- Documentation: <https://docs.influxdata.com/influxdb/cloud/>
- Query languages: InfluxQL and Flux
- Clients: Telegraf, influx CLI, v2 client libraries
InfluxDB Cloud Serverless:
- Documentation: https://docs.influxdata.com/influxdb3/cloud-serverless/
- Query languages: SQL and InfluxQL and Flux
- Clients: Telegraf, influxctl CLI, v3 client libraries
- Documentation: <https://docs.influxdata.com/influxdb3/cloud-serverless/>
- Query languages: SQL and InfluxQL and Flux
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Cloud Dedicated:
- Documentation: https://docs.influxdata.com/influxdb3/cloud-dedicated/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
- Documentation: <https://docs.influxdata.com/influxdb3/cloud-dedicated/>
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB Clustered:
- Documentation: https://docs.influxdata.com/influxdb3/clustered/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
- Documentation: <https://docs.influxdata.com/influxdb3/clustered/>
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxctl CLI, v3 client libraries
InfluxDB 3 Core:
- Documentation: https://docs.influxdata.com/influxdb3/core/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxdb3 CLI, v3 client libraries, InfluxDB 3 Explorer
- Documentation: <https://docs.influxdata.com/influxdb3/core/>
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxdb3 CLI, v3 client libraries, InfluxDB 3 Explorer
InfluxDB 3 Enterprise:
- Documentation: https://docs.influxdata.com/influxdb3/enterprise/
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxdb3 CLI, v3 client libraries, InfluxDB 3 Explorer
- Documentation: <https://docs.influxdata.com/influxdb3/enterprise/>
- Query languages: SQL and InfluxQL
- Clients: Telegraf, influxdb3 CLI, v3 client libraries, InfluxDB 3 Explorer
InfluxDB 3 Explorer:
- Documentation: https://docs.influxdata.com/influxdb3/explorer/
- Documentation: <https://docs.influxdata.com/influxdb3/explorer/>
Telegraf:
- Documentation: https://docs.influxdata.com/telegraf/v1.37/
- Documentation: <https://docs.influxdata.com/telegraf/v1.37/>
Chronograf:
- Documentation: https://docs.influxdata.com/chronograf/v1.11/
- Documentation: <https://docs.influxdata.com/chronograf/v1.11/>
Kapacitor:
- Documentation: https://docs.influxdata.com/kapacitor/v1.8/
- Documentation: <https://docs.influxdata.com/kapacitor/v1.8/>
Flux:
- Documentation: https://docs.influxdata.com/flux/v0.x/
- Documentation: <https://docs.influxdata.com/flux/v0.x/>

View File

@ -11,14 +11,14 @@ The docs-v2 repository uses **two complementary spell-checking tools**:
## Tool Comparison
| Feature | Vale | Codespell |
|---------|------|-----------|
| **Purpose** | Document spell checking | Code comment spell checking |
| **Integration** | Pre-commit hooks (Docker) | CI/CD pipeline |
| **False Positives** | Low (comprehensive filters) | Low (clear dictionary only) |
| **Customization** | YAML rules | INI config + dictionary lists |
| **Performance** | Moderate | Fast |
| **True Positive Detection** | Document-level | Code-level |
| Feature | Vale | Codespell |
| --------------------------- | --------------------------- | ----------------------------- |
| **Purpose** | Document spell checking | Code comment spell checking |
| **Integration** | Pre-commit hooks (Docker) | CI/CD pipeline |
| **False Positives** | Low (comprehensive filters) | Low (clear dictionary only) |
| **Customization** | YAML rules | INI config + dictionary lists |
| **Performance** | Moderate | Fast |
| **True Positive Detection** | Document-level | Code-level |
## Vale Configuration
@ -51,17 +51,20 @@ Unlike other documentation style checkers, this configuration **intentionally in
### Filter Patterns Explained
#### 1. camelCase and snake_case Identifiers
#### 1. camelCase and snake\_case Identifiers
```regex
(?:_*[a-z]+(?:[A-Z][a-z0-9]*)+(?:[A-Z][a-zA-Z0-9]*)*|[a-z_][a-z0-9]*_[a-z0-9_]*)
```
**Why**: Prevents false positives on variable/method names while NOT matching normal prose
**Breakdown**:
- **camelCase**: `_*[a-z]+(?:[A-Z][a-z0-9]*)+(?:[A-Z][a-zA-Z0-9]*)*`
- Requires at least one uppercase letter (distinguishes `myVariable` from `provide`)
- Allows leading underscores for private variables (`_privateVar`, `__dunder__`)
- **snake_case**: `[a-z_][a-z0-9]*_[a-z0-9_]*`
- **snake\_case**: `[a-z_][a-z0-9]*_[a-z0-9_]*`
- Requires at least one underscore
- Distinguishes `my_variable` from normal words
@ -69,49 +72,61 @@ Unlike other documentation style checkers, this configuration **intentionally in
**Examples NOT Ignored** (caught by spell-checker): `provide`, `database`, `variable` (normal prose)
#### 2. UPPER_CASE Constants
#### 2. UPPER\_CASE Constants
```regex
[A-Z_][A-Z0-9_]+
```
**Why**: Prevents false positives on environment variables and constants
**Examples Ignored**: `API_KEY`, `AWS_REGION`, `INFLUXDB_TOKEN`
**Note**: Matches AWS, API (even single uppercase acronyms) - acceptable in docs
#### 3. Version Numbers
```regex
\d+\.\d+(?:\.\d+)*
```
**Why**: Version numbers aren't words
**Examples Ignored**: `1.0`, `2.3.1`, `0.101.0`, `1.2.3.4`, `v1.2.3`
**Note**: Handles any number of version parts (2-part, 3-part, 4-part, etc.)
#### 4. Hexadecimal Values
```regex
0[xX][0-9a-fA-F]+
```
**Why**: Hex values appear in code and aren't dictionary words
**Examples Ignored**: `0xFF`, `0xDEADBEEF`, `0x1A`
#### 5. URLs and Paths
```regex
/[a-zA-Z0-9/_\-\.\{\}]+ # Paths: /api/v2/write
https?://[^\s\)\]>"]+ # Full URLs: https://docs.example.com
```
**Why**: URLs contain hyphens, slashes, and special chars
**Examples Ignored**: `/api/v2/write`, `/kapacitor/v1/`, `https://docs.influxdata.com`
#### 6. Shortcode Attributes
```regex
(?:endpoint|method|url|href|src|path)="[^"]+"
```
**Why**: Hugo shortcode attribute values often contain hyphens and special chars
**Examples Ignored**: `endpoint="https://..."`, `method="POST"`
**Future Enhancement**: Add more attributes as needed (name, value, data, etc.)
#### 7. Code Punctuation
```regex
[@#$%^&*()_+=\[\]{};:,.<>?/\\|-]+
```
**Why**: Symbols and special characters aren't words
**Examples Ignored**: `()`, `{}`, `[]`, `->`, `=>`, `|`, etc.
@ -134,15 +149,15 @@ To add a word that should be ignored, edit the appropriate file.
- `clear` - Unambiguous spelling errors only
- Examples: "recieve" → "receive", "occured" → "occurred"
- False positive rate: ~1%
- False positive rate: \~1%
- `rare` - Includes uncommon but valid English words
- Would flag legitimate technical terms
- False positive rate: ~15-20%
- False positive rate: \~15-20%
- `code` - Includes code-specific words
- Too aggressive for documentation
- False positive rate: ~25-30%
- False positive rate: \~25-30%
#### Skip Directories
@ -167,6 +182,7 @@ ignore-words-list = aks,invokable
- **`invokable`** - InfluxData product branding term (scriptable tasks/queries)
**To add more**:
1. Edit `.codespellrc`
2. Add word to `ignore-words-list` (comma-separated)
3. Add inline comment explaining why
@ -178,6 +194,7 @@ ignore-words-list = aks,invokable
Vale automatically runs on files you commit via Lefthook.
**Manual check**:
```bash
# Check all content
docker compose run -T vale content/**/*.md
@ -233,7 +250,8 @@ echo "recieve the data" | codespell
**Problem**: Vale flags a word that should be valid
**Solutions**:
1. Check if it's a code identifier (camelCase, UPPER_CASE, hex, version)
1. Check if it's a code identifier (camelCase, UPPER\_CASE, hex, version)
2. Add to `InfluxDataDocs/Terms/ignore.txt` if it's a technical term
3. Add filter pattern to `.ci/vale/styles/InfluxDataDocs/Spelling.yml` if it's a pattern
@ -242,6 +260,7 @@ echo "recieve the data" | codespell
**Problem**: Codespell flags a legitimate term
**Solutions**:
1. Add to `ignore-words-list` in `.codespellrc`
2. Add skip directory if entire directory should be excluded
3. Use `-i 3` (interactive mode) to review before accepting
@ -251,6 +270,7 @@ echo "recieve the data" | codespell
**Problem**: A real typo isn't caught
**Solutions**:
1. Verify it's actually a typo (not a branding term or intentional)
2. Check if it's in excluded scope (tables, URLs, code identifiers)
3. Report as GitHub issue for tool improvement

View File

@ -0,0 +1,31 @@
title: InfluxDB Enterprise v1 HTTP API
x-influxdata-short-title: InfluxDB Enterprise v1 API
x-influxdata-short-description: >-
The InfluxDB Enterprise v1 HTTP API provides a programmatic interface for writing,
querying, and managing InfluxDB Enterprise v1 clusters.
version: 1.11.6
description: |
The InfluxDB Enterprise v1 HTTP API provides a simple way to interact with the database.
It uses HTTP response codes, authentication with username and password credentials
or API tokens, and JSON-formatted response data.
## Cluster Features
InfluxDB Enterprise includes additional parameters for cluster operations:
- **Write Consistency**: Control write consistency across cluster nodes
## Authentication
InfluxDB Enterprise v1 supports multiple authentication methods:
- **Basic Authentication**: Use HTTP Basic Auth with username and password
- **Query String Authentication**: Pass `u` (username) and `p` (password) as query parameters
- **Token Authentication** (v2-compatible): Use `Authorization: Token username:password` header
Authentication is optional unless [enabled in the configuration](/enterprise_influxdb/v1/administration/authentication_and_authorization/).
license:
name: Proprietary
url: https://www.influxdata.com/legal/slsa/
contact:
name: InfluxData
url: https://www.influxdata.com

View File

@ -0,0 +1,2 @@
- url: http://localhost:8086
description: Local InfluxDB Enterprise data node

File diff suppressed because it is too large Load Diff

View File

@ -222,15 +222,17 @@ for configPath in $(find . -name '.config.yml' -not -path './.config.yml' -not -
done
# ---------------------------------------------------------------------------
# Step 3: Copy specs to static/openapi/ for download
# Step 3: Generate Hugo-native article data and content pages
# ---------------------------------------------------------------------------
# Discovers products from .config.yml, processes specs from _build/,
# generates tag-based article data and Hugo content pages.
echo ""
echo "========================================"
echo "Step 3: Copying specs to static/openapi/"
echo "Step 3: Generating article data and pages"
echo "========================================"
cd ..
node api-docs/scripts/dist/generate-openapi-articles.js --static-only
node api-docs/scripts/dist/generate-openapi-articles.js --skip-fetch
cd api-docs
echo ""

View File

@ -187,9 +187,16 @@ tags:
Supported operations:
x-traitTag: true
description: >
Overview of the common CRUD operations supported by the InfluxDB Cloud
API.
description: |
The InfluxDB Cloud API supports create, read, update, and delete operations
on resources. Most endpoints follow standard HTTP method conventions:
| Operation | HTTP Method | Description |
|:----------|:------------|:------------|
| **Write** | `POST` | Send data or create a resource. |
| **Read** | `GET` | Retrieve a resource or list resources. |
| **Update** | `PUT`, `PATCH` | Replace or modify an existing resource. |
| **Delete** | `DELETE` | Remove a resource. |
System information endpoints:
description: >

View File

@ -0,0 +1,32 @@
title: InfluxDB v1 HTTP API
x-influxdata-short-title: InfluxDB v1 API
x-influxdata-short-description: >-
The InfluxDB v1 HTTP API provides a programmatic interface for writing,
querying, and managing InfluxDB v1 databases.
version: 1.8.10
description: |
The InfluxDB v1 HTTP API provides a simple way to interact with the database.
It uses HTTP response codes, authentication with username and password credentials
or API tokens, and JSON-formatted response data.
## InfluxDB 3 Compatibility
InfluxDB 3 supports the v1 `/write` and `/query` HTTP API endpoints.
If you're getting started with InfluxDB v1, we recommend using the
InfluxDB v1 client libraries and InfluxQL for future compatibility.
## Authentication
InfluxDB v1 supports two authentication methods:
- **Basic Authentication**: Use HTTP Basic Auth with username and password
- **Query String Authentication**: Pass `u` (username) and `p` (password) as query parameters
- **Token Authentication** (v2-compatible): Use `Authorization: Token username:password` header
Authentication is optional unless [enabled in the configuration](/influxdb/v1/administration/authentication_and_authorization/).
license:
name: MIT
url: https://opensource.org/licenses/MIT
contact:
name: InfluxData
url: https://www.influxdata.com

View File

@ -0,0 +1,2 @@
- url: http://localhost:8086
description: Local InfluxDB instance

File diff suppressed because it is too large Load Diff

View File

@ -225,9 +225,16 @@ tags:
Supported operations:
x-traitTag: true
description: >
Overview of the common CRUD operations supported by the InfluxDB OSS v2
API.
description: |
The InfluxDB OSS v2 API supports create, read, update, and delete operations
on resources. Most endpoints follow standard HTTP method conventions:
| Operation | HTTP Method | Description |
|:----------|:------------|:------------|
| **Write** | `POST` | Send data or create a resource. |
| **Read** | `GET` | Retrieve a resource or list resources. |
| **Update** | `PUT`, `PATCH` | Replace or modify an existing resource. |
| **Delete** | `DELETE` | Remove a resource. |
System information endpoints:
description: >

View File

@ -1,9 +1,40 @@
tags:
API compatibility:
x-traitTag: true
description: >
Overview of the InfluxDB v1 and v2 compatible write and query endpoints
available in InfluxDB 3 Cloud Dedicated.
description: |
### Write data
InfluxDB 3 Cloud Dedicated provides the following HTTP API endpoints for writing data:
- **Recommended**: [`/api/v2/write` endpoint](#operation/PostWrite) for new write workloads or for bringing existing InfluxDB v2 write workloads to InfluxDB 3.
- [`/write` endpoint](#operation/PostLegacyWrite) for bringing existing InfluxDB v1 write workloads to InfluxDB 3.
Both endpoints accept the same line protocol format and process data in the same way.
### Query data
InfluxDB 3 Cloud Dedicated provides the following protocols for executing a query:
- **Recommended**: _Flight+gRPC_ request that contains an SQL or InfluxQL query.
- HTTP API [`/query` request](#operation/GetLegacyQuery) that contains an InfluxQL query.
Use this protocol when bringing existing InfluxDB v1 query workloads to InfluxDB 3.
### InfluxDB v2 compatibility
The HTTP API [`/api/v2/write` endpoint](#operation/PostWrite) works with the [`Bearer`](#section/Authentication/BearerAuthentication) and [`Token`](#section/Authentication/TokenAuthentication) authentication schemes and existing InfluxDB 2.x tools and code.
### InfluxDB v1 compatibility
The HTTP API [`/write` endpoint](#operation/PostLegacyWrite) and [`/query` endpoint](#operation/GetLegacyQuery) work with InfluxDB 1.x username/password [authentication schemes](#section/Authentication/) and existing InfluxDB 1.x tools and code.
x-related:
- title: Get started querying InfluxDB
href: /influxdb3/cloud-dedicated/get-started/query/
- title: Write data
href: /influxdb3/cloud-dedicated/write-data/
- title: Use the v2 HTTP API with Cloud Dedicated
href: /influxdb3/cloud-dedicated/guides/api-compatibility/v2/
- title: Use the v1 HTTP API with Cloud Dedicated
href: /influxdb3/cloud-dedicated/guides/api-compatibility/v1/
Authentication:
x-traitTag: true

View File

@ -1,9 +1,41 @@
tags:
API compatibility:
x-traitTag: true
description: >
Overview of the InfluxDB v1 and v2 compatible write and query endpoints
available in InfluxDB 3 Cloud Serverless.
description: |
### Write data
InfluxDB 3 Cloud Serverless provides the following HTTP API endpoints for writing data:
- **Recommended**: [`/api/v2/write` endpoint](#operation/PostWrite)
for new write workloads or for bringing existing InfluxDB v2 write workloads to InfluxDB 3.
- [`/write` endpoint](#operation/PostLegacyWrite) for bringing existing InfluxDB v1 write workloads to InfluxDB 3.
Both endpoints accept the same line protocol format and process data in the same way.
### Query data
InfluxDB 3 Cloud Serverless provides the following protocols for executing a query:
- **Recommended**: _Flight+gRPC_ request that contains an SQL or InfluxQL query.
- HTTP API [`/query` request](#operation/GetLegacyQuery) that contains an InfluxQL query.
Use this protocol when bringing existing InfluxDB v1 query workloads to InfluxDB 3.
### InfluxDB v2 compatibility
The HTTP API [`/api/v2/write` endpoint](#operation/PostWrite) works with the [`Token` authentication scheme](#section/Authentication/TokenAuthentication) and existing InfluxDB 2.x tools and code.
### InfluxDB v1 compatibility
The HTTP API [`/write` endpoint](#operation/PostLegacyWrite) and [`/query` endpoint](#operation/GetLegacyQuery) work with InfluxDB 1.x username/password [authentication schemes](#section/Authentication/) and existing InfluxDB 1.x tools and code.
x-related:
- title: Get started querying InfluxDB
href: /influxdb3/cloud-serverless/get-started/query/
- title: Write data
href: /influxdb3/cloud-serverless/write-data/
- title: Use the v2 HTTP API with Cloud Serverless
href: /influxdb3/cloud-serverless/guides/api-compatibility/v2/
- title: Use the v1 HTTP API with Cloud Serverless
href: /influxdb3/cloud-serverless/guides/api-compatibility/v1/
Authentication:
x-traitTag: true
@ -190,9 +222,16 @@ tags:
Supported operations:
x-traitTag: true
description: >
Overview of the common CRUD operations supported by the InfluxDB 3 Cloud
Serverless API.
description: |
The InfluxDB 3 Cloud Serverless API supports create, read, update, and delete
operations on resources. Most endpoints follow standard HTTP method conventions:
| Operation | HTTP Method | Description |
|:----------|:------------|:------------|
| **Write** | `POST` | Send data or create a resource. |
| **Read** | `GET` | Retrieve a resource or list resources. |
| **Update** | `PUT`, `PATCH` | Replace or modify an existing resource. |
| **Delete** | `DELETE` | Remove a resource. |
System information endpoints:
description: >

View File

@ -1,9 +1,40 @@
tags:
API compatibility:
x-traitTag: true
description: >
Overview of the InfluxDB v1 and v2 compatible write and query endpoints
available in InfluxDB 3 Clustered.
description: |
### Write data
InfluxDB 3 Clustered provides the following HTTP API endpoints for writing data:
- **Recommended**: [`/api/v2/write` endpoint](#operation/PostWrite) for new write workloads or for bringing existing InfluxDB v2 write workloads to InfluxDB 3.
- [`/write` endpoint](#operation/PostLegacyWrite) for bringing existing InfluxDB v1 write workloads to InfluxDB 3.
Both endpoints accept the same line protocol format and process data in the same way.
### Query data
InfluxDB 3 Clustered provides the following protocols for executing a query:
- **Recommended**: _Flight+gRPC_ request that contains an SQL or InfluxQL query.
- HTTP API [`/query` request](#operation/GetLegacyQuery) that contains an InfluxQL query.
Use this protocol when bringing existing InfluxDB v1 query workloads to InfluxDB 3.
### InfluxDB v2 compatibility
The HTTP API [`/api/v2/write` endpoint](#operation/PostWrite) works with the [`Bearer`](#section/Authentication/BearerAuthentication) and [`Token`](#section/Authentication/TokenAuthentication) authentication schemes and existing InfluxDB 2.x tools and code.
### InfluxDB v1 compatibility
The HTTP API [`/write` endpoint](#operation/PostLegacyWrite) and [`/query` endpoint](#operation/GetLegacyQuery) work with InfluxDB 1.x username/password [authentication schemes](#section/Authentication/) and existing InfluxDB 1.x tools and code.
x-related:
- title: Get started querying InfluxDB
href: /influxdb3/clustered/get-started/query/
- title: Write data
href: /influxdb3/clustered/write-data/
- title: Use the v2 HTTP API with Clustered
href: /influxdb3/clustered/guides/api-compatibility/v2/
- title: Use the v1 HTTP API with Clustered
href: /influxdb3/clustered/guides/api-compatibility/v1/
Authentication:
x-traitTag: true

View File

@ -11,28 +11,50 @@ tags:
<!-- ReDoc-Inject: <security-definitions> -->
Cache distinct values:
description: >
Manage the Distinct Value Cache (DVC), an in-memory cache that stores
distinct values for specific columns in a table to improve query
performance for distinct tag and field value lookups.
description: |
The Distinct Value Cache (DVC) lets you cache distinct
values of one or more columns in a table, improving the performance of
queries that return distinct tag and field values.
The DVC is an in-memory cache that stores distinct values for specific columns
in a table. When you create a DVC, you can specify what columns' distinct
values to cache, the maximum number of distinct value combinations to cache, and
the maximum age of cached values. A DVC is associated with a table, which can
have multiple DVCs.
x-related:
- title: Manage the Distinct Value Cache
href: /influxdb3/core/admin/distinct-value-cache/
Cache last value:
description: >
Manage the Last Value Cache (LVC), an in-memory cache that stores the
most recent N values for specific fields in a table to improve query
performance for last-value lookups.
description: |
The Last Value Cache (LVC) lets you cache the most recent
values for specific fields in a table, improving the performance of queries that
return the most recent value of a field for specific series or the last N values
of a field.
The LVC is an in-memory cache that stores the last N number of values for
specific fields of series in a table. When you create an LVC, you can specify
what fields to cache, what tags to use to identify each series, and the
number of values to cache for each unique series.
An LVC is associated with a table, which can have multiple LVCs.
x-related:
- title: Manage the Last Value Cache
href: /influxdb3/core/admin/last-value-cache/
Migrate from InfluxDB v1 or v2:
x-traitTag: true
description: >
Reference information for migrating existing InfluxDB v1 or v2 workloads
to InfluxDB 3 Core using compatibility endpoints.
description: |
Migrate your existing InfluxDB v1 or v2 workloads to InfluxDB 3 Core.
InfluxDB 3 provides compatibility endpoints that work with InfluxDB 1.x and 2.x client libraries and tools.
Operations marked with <span class="api-compat-badge api-compat-badge--v1">v1</span> or <span class="api-compat-badge api-compat-badge--v2">v2</span> badges are compatible with the respective InfluxDB version.
### Migration guides
- [Migrate from InfluxDB v1](/influxdb3/core/guides/migrate/influxdb-1x/) - For users migrating from InfluxDB 1.x
- [Migrate from InfluxDB v2](/influxdb3/core/guides/migrate/influxdb-2x/) - For users migrating from InfluxDB 2.x or Cloud
- [Use compatibility APIs to write data](/influxdb3/core/write-data/http-api/compatibility-apis/) - v1 and v2 write endpoints
- [Use the v1 HTTP query API](/influxdb3/core/query-data/execute-queries/influxdb-v1-api/) - InfluxQL queries via HTTP
Database:
description: >
@ -43,15 +65,37 @@ tags:
Headers and parameters:
x-traitTag: true
description: >
Common HTTP request headers and query parameters used by InfluxDB 3 Core
API endpoints.
description: |
Most InfluxDB API endpoints require parameters in the request--for example, specifying the database to use.
### Common parameters
The following table shows common parameters used by many InfluxDB API endpoints.
Many endpoints may require other parameters in the query string or in the
request body that perform functions specific to those endpoints.
| Query parameter | Value type | Description |
|:------------------------ |:--------------------- |:-------------------------------------------|
| `db` | string | The database name |
InfluxDB HTTP API endpoints use standard HTTP request and response headers.
The following table shows common headers used by many InfluxDB API endpoints.
Some endpoints may use other headers that perform functions more specific to those endpoints--for example,
the write endpoints accept the `Content-Encoding` header to indicate that line protocol is compressed in the request body.
| Header | Value type | Description |
|:------------------------ |:--------------------- |:-------------------------------------------|
| `Accept` | string | The content type that the client can understand. |
| `Authorization` | string | The [authorization scheme and credential](/influxdb/version/api/authentication/). |
| `Content-Length` | integer | The size of the entity-body, in bytes. |
| `Content-Type` | string | The format of the data in the request body. |
Processing engine:
description: >
Manage Processing engine triggers, test plugins, and invoke On Request
plugins. The processing engine is an embedded Python VM that runs plugins
in response to database events.
description: |
Manage Processing engine triggers, test plugins, and send requests to trigger On Request plugins.
InfluxDB 3 Core provides the InfluxDB 3 processing engine, an embedded Python VM that can dynamically load and trigger Python plugins in response to events in your database.
Use Processing engine plugins and triggers to run code and perform tasks for different database events.
x-related:
- title: Processing engine and Python plugins
href: /influxdb3/core/processing-engine/
@ -65,9 +109,49 @@ tags:
Quick start:
x-traitTag: true
description: >
Get started authenticating, writing, and querying data with the
InfluxDB 3 Core API.
description: |
Authenticate, write, and query with the API:
1. Create an admin token to authorize API requests.
```bash
curl -X POST "http://localhost:8181/api/v3/configure/token/admin"
```
2. Check the status of the InfluxDB server.
```bash
curl "http://localhost:8181/health" \
--header "Authorization: Bearer ADMIN_TOKEN"
```
3. Write data to InfluxDB.
```bash
curl "http://localhost:8181/api/v3/write_lp?db=sensors&precision=auto"
--header "Authorization: Bearer ADMIN_TOKEN" \
--data-raw "home,room=Kitchen temp=72.0
home,room=Living\ room temp=71.5"
```
If all data is written, the response is `204 No Content`.
4. Query data from InfluxDB.
```bash
curl -G "http://localhost:8181/api/v3/query_sql" \
--header "Authorization: Bearer ADMIN_TOKEN" \
--data-urlencode "db=sensors" \
--data-urlencode "q=SELECT * FROM home WHERE room='Living room'" \
--data-urlencode "format=jsonl"
```
Output:
```jsonl
{"room":"Living room","temp":71.5,"time":"2025-02-25T20:19:34.984098"}
```
For more information about using InfluxDB 3 Core, see the [Get started](/influxdb3/core/get-started/) guide.
Server information:
description: >
@ -90,9 +174,26 @@ tags:
href: /influxdb3/core/admin/tokens/
Write data:
description: >
Write time series data to InfluxDB 3 Core in line protocol format using
the v1, v2, or v3 write endpoints.
description: |
Write data to InfluxDB 3 Core using line protocol format.
#### Timestamp precision across write APIs
InfluxDB 3 provides multiple write endpoints for compatibility with different InfluxDB versions.
The following table compares timestamp precision support across v1, v2, and v3 write APIs:
| Precision | v1 (`/write`) | v2 (`/api/v2/write`) | v3 (`/api/v3/write_lp`) |
|-----------|---------------|----------------------|-------------------------|
| **Auto detection** | ❌ No | ❌ No | ✅ `auto` (default) |
| **Seconds** | ✅ `s` | ✅ `s` | ✅ `second` |
| **Milliseconds** | ✅ `ms` | ✅ `ms` | ✅ `millisecond` |
| **Microseconds** | ✅ `u` or `µ` | ✅ `us` | ✅ `microsecond` |
| **Nanoseconds** | ✅ `ns` | ✅ `ns` | ✅ `nanosecond` |
| **Minutes** | ✅ `m` | ❌ No | ❌ No |
| **Hours** | ✅ `h` | ❌ No | ❌ No |
| **Default** | Nanosecond | Nanosecond | **Auto** (guessed) |
All timestamps are stored internally as nanoseconds.
x-related:
- title: Write data using HTTP APIs
href: /influxdb3/core/write-data/http-api/

View File

@ -11,10 +11,33 @@ tags:
<!-- ReDoc-Inject: <security-definitions> -->
Cache data:
description: >
Manage in-memory caches for InfluxDB 3 Enterprise, including the Distinct
Value Cache (DVC) for distinct column values and the Last Value Cache
(LVC) for most-recent field values.
description: |
Manage the in-memory cache.
#### Distinct Value Cache
The Distinct Value Cache (DVC) lets you cache distinct
values of one or more columns in a table, improving the performance of
queries that return distinct tag and field values.
The DVC is an in-memory cache that stores distinct values for specific columns
in a table. When you create an DVC, you can specify what columns' distinct
values to cache, the maximum number of distinct value combinations to cache, and
the maximum age of cached values. A DVC is associated with a table, which can
have multiple DVCs.
#### Last value cache
The Last Value Cache (LVC) lets you cache the most recent
values for specific fields in a table, improving the performance of queries that
return the most recent value of a field for specific series or the last N values
of a field.
The LVC is an in-memory cache that stores the last N number of values for
specific fields of series in a table. When you create an LVC, you can specify
what fields to cache, what tags to use to identify each series, and the
number of values to cache for each unique series.
An LVC is associated with a table, which can have multiple LVCs.
x-related:
- title: Manage the Distinct Value Cache
href: /influxdb3/enterprise/admin/distinct-value-cache/
@ -30,15 +53,37 @@ tags:
Headers and parameters:
x-traitTag: true
description: >
Common HTTP request headers and query parameters used by InfluxDB 3
Enterprise API endpoints.
description: |
Most InfluxDB API endpoints require parameters in the request--for example, specifying the database to use.
### Common parameters
The following table shows common parameters used by many InfluxDB API endpoints.
Many endpoints may require other parameters in the query string or in the
request body that perform functions specific to those endpoints.
| Query parameter | Value type | Description |
|:------------------------ |:--------------------- |:-------------------------------------------|
| `db` | string | The database name |
InfluxDB HTTP API endpoints use standard HTTP request and response headers.
The following table shows common headers used by many InfluxDB API endpoints.
Some endpoints may use other headers that perform functions more specific to those endpoints--for example,
the write endpoints accept the `Content-Encoding` header to indicate that line protocol is compressed in the request body.
| Header | Value type | Description |
|:------------------------ |:--------------------- |:-------------------------------------------|
| `Accept` | string | The content type that the client can understand. |
| `Authorization` | string | The authorization scheme and credential. |
| `Content-Length` | integer | The size of the entity-body, in bytes. |
| `Content-Type` | string | The format of the data in the request body. |
Processing engine:
description: >
Manage Processing engine triggers, test plugins, and invoke On Request
plugins. The processing engine is an embedded Python VM that runs plugins
in response to database events.
description: |
Manage Processing engine triggers, test plugins, and send requests to trigger On Request plugins.
InfluxDB 3 Enterprise provides the InfluxDB 3 processing engine, an embedded Python VM that can dynamically load and trigger Python plugins in response to events in your database.
Use Processing engine plugins and triggers to run code and perform tasks for different database events.
x-related:
- title: Processing engine and Python plugins
href: /influxdb3/enterprise/processing-engine/
@ -52,9 +97,49 @@ tags:
Quick start:
x-traitTag: true
description: >
Get started authenticating, writing, and querying data with the
InfluxDB 3 Enterprise API.
description: |
Authenticate, write, and query with the API:
1. Create an admin token to authorize API requests.
```bash
curl -X POST "http://localhost:8181/api/v3/configure/token/admin"
```
2. Check the status of the InfluxDB server.
```bash
curl "http://localhost:8181/health" \
--header "Authorization: Bearer ADMIN_TOKEN"
```
3. Write data to InfluxDB.
```bash
curl "http://localhost:8181/api/v3/write_lp?db=sensors&precision=auto"
--header "Authorization: Bearer ADMIN_TOKEN" \
--data-raw "home,room=Kitchen temp=72.0
home,room=Living\ room temp=71.5"
```
If all data is written, the response is `204 No Content`.
4. Query data from InfluxDB.
```bash
curl -G "http://localhost:8181/api/v3/query_sql" \
--header "Authorization: Bearer ADMIN_TOKEN" \
--data-urlencode "db=sensors" \
--data-urlencode "q=SELECT * FROM home WHERE room='Living room'" \
--data-urlencode "format=jsonl"
```
Output:
```jsonl
{"room":"Living room","temp":71.5,"time":"2025-02-25T20:19:34.984098"}
```
For more information, see the [Get started](/influxdb3/enterprise/get-started/) guide.
Server information:
description: >
@ -77,9 +162,26 @@ tags:
href: /influxdb3/enterprise/admin/tokens/
Write data:
description: >
Write time series data to InfluxDB 3 Enterprise in line protocol format
using the v1, v2, or v3 write endpoints.
description: |
Write data to InfluxDB 3 Enterprise using line protocol format.
#### Timestamp precision across write APIs
InfluxDB 3 provides multiple write endpoints for compatibility with different InfluxDB versions.
The following table compares timestamp precision support across v1, v2, and v3 write APIs:
| Precision | v1 (`/write`) | v2 (`/api/v2/write`) | v3 (`/api/v3/write_lp`) |
|-----------|---------------|----------------------|-------------------------|
| **Auto detection** | ❌ No | ❌ No | ✅ `auto` (default) |
| **Seconds** | ✅ `s` | ✅ `s` | ✅ `second` |
| **Milliseconds** | ✅ `ms` | ✅ `ms` | ✅ `millisecond` |
| **Microseconds** | ✅ `u` or `µ` | ✅ `us` | ✅ `microsecond` |
| **Nanoseconds** | ✅ `ns` | ✅ `ns` | ✅ `nanosecond` |
| **Minutes** | ✅ `m` | ❌ No | ❌ No |
| **Hours** | ✅ `h` | ❌ No | ❌ No |
| **Default** | Nanosecond | Nanosecond | **Auto** (guessed) |
All timestamps are stored internally as nanoseconds.
x-related:
- title: Write data using HTTP APIs
href: /influxdb3/enterprise/write-data/http-api/

View File

@ -0,0 +1,850 @@
#!/usr/bin/env node
"use strict";
/**
* Generate OpenAPI Articles Script
*
* Generates Hugo data files and content pages from OpenAPI specifications
* for all InfluxDB products.
*
* Products are auto-discovered by scanning api-docs/ for .config.yml files.
* Hugo paths, menu keys, and static file names are derived from directory
* structure and existing Hugo frontmatter.
*
* This script:
* 1. Discovers products from .config.yml files
* 2. Cleans output directories (unless --no-clean)
* 3. Transforms documentation links in specs
* 4. Copies specs to static directory for download
* 5. Generates tag-based data fragments (YAML and JSON)
* 6. Generates Hugo content pages from article data
*
* Usage:
* node generate-openapi-articles.js # Clean and generate all products
* node generate-openapi-articles.js influxdb3-core # Clean and generate single product
* node generate-openapi-articles.js --no-clean # Generate without cleaning
* node generate-openapi-articles.js --dry-run # Preview what would be cleaned
* node generate-openapi-articles.js --skip-fetch # Skip getswagger.sh fetch step
* node generate-openapi-articles.js --validate-links # Validate documentation links
*
* @module generate-openapi-articles
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.LINK_PATTERN = exports.MARKDOWN_FIELDS = void 0;
exports.discoverProducts = discoverProducts;
exports.processProduct = processProduct;
exports.processApiSection = processApiSection;
exports.transformDocLinks = transformDocLinks;
exports.validateDocLinks = validateDocLinks;
exports.resolveContentPath = resolveContentPath;
exports.deriveStaticDirName = deriveStaticDirName;
exports.getSectionSlug = getSectionSlug;
exports.parseApiEntry = parseApiEntry;
exports.readMenuKey = readMenuKey;
const child_process_1 = require("child_process");
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
// Import the OpenAPI to Hugo converter
const openapiPathsToHugo = require('./openapi-paths-to-hugo-data/index.js');
// ---------------------------------------------------------------------------
// Constants and CLI flags
// ---------------------------------------------------------------------------
const DOCS_ROOT = '.';
const API_DOCS_ROOT = 'api-docs';
const validateLinks = process.argv.includes('--validate-links');
const skipFetch = process.argv.includes('--skip-fetch');
const noClean = process.argv.includes('--no-clean');
const dryRun = process.argv.includes('--dry-run');
// ---------------------------------------------------------------------------
// Utility functions
// ---------------------------------------------------------------------------
/**
* Load products with API paths from data/products.yml.
* Returns a map of alt_link_key to API path for alt_links generation.
*/
function loadApiProducts() {
const yaml = require('js-yaml');
const productsFile = path.join(DOCS_ROOT, 'data/products.yml');
if (!fs.existsSync(productsFile)) {
console.warn('⚠️ products.yml not found, skipping alt_links generation');
return new Map();
}
const productsContent = fs.readFileSync(productsFile, 'utf8');
const products = yaml.load(productsContent);
const apiProducts = new Map();
for (const [, product] of Object.entries(products)) {
if (product.api_path && product.alt_link_key) {
apiProducts.set(product.alt_link_key, product.api_path);
}
}
return apiProducts;
}
const apiProductsMap = loadApiProducts();
/** Execute a shell command and handle errors */
function execCommand(command, description) {
try {
if (description) {
console.log(`\n${description}...`);
}
console.log(`Executing: ${command}\n`);
(0, child_process_1.execSync)(command, { stdio: 'inherit' });
}
catch (error) {
console.error(`\n❌ Error executing command: ${command}`);
if (error instanceof Error) {
console.error(error.message);
}
process.exit(1);
}
}
// ---------------------------------------------------------------------------
// Auto-discovery functions
// ---------------------------------------------------------------------------
/**
* Recursively find all .config.yml files under api-docs/.
* Excludes the root api-docs/.config.yml and internal directories.
*/
function findConfigFiles(rootDir) {
const configs = [];
const skipDirs = new Set([
'node_modules',
'dist',
'_build',
'scripts',
'openapi',
]);
function scanDir(dir, depth) {
if (depth > 5)
return;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
}
catch {
return;
}
for (const entry of entries) {
if (skipDirs.has(entry.name))
continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
scanDir(fullPath, depth + 1);
}
else if (entry.name === '.config.yml' && dir !== rootDir) {
configs.push(fullPath);
}
}
}
scanDir(rootDir, 0);
return configs.sort();
}
/**
* Parse an API entry key like 'v3@3' into apiKey and version.
*/
function parseApiEntry(entry) {
const atIdx = entry.indexOf('@');
if (atIdx === -1) {
return { apiKey: entry, version: '0' };
}
return {
apiKey: entry.substring(0, atIdx),
version: entry.substring(atIdx + 1),
};
}
/**
* Determine Hugo section slug from API key.
* 'management' 'management-api', everything else 'api'.
*/
function getSectionSlug(apiKey) {
if (apiKey === 'management')
return 'management-api';
return 'api';
}
/**
* Derive a clean static directory name from a product directory path.
* Replaces path separators and underscores with hyphens.
*
* @example 'influxdb3/core' 'influxdb3-core'
* @example 'enterprise_influxdb/v1' 'enterprise-influxdb-v1'
*/
function deriveStaticDirName(productDir) {
return productDir.replace(/[/_]/g, '-');
}
/**
* Read the cascade.product field from a product's _index.md frontmatter.
* This value serves as the Hugo menu key.
*/
function readMenuKey(pagesDir) {
const yaml = require('js-yaml');
const indexFile = path.join(pagesDir, '_index.md');
if (!fs.existsSync(indexFile)) {
console.warn(`⚠️ Product index not found: ${indexFile}`);
return '';
}
const content = fs.readFileSync(indexFile, 'utf8');
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!fmMatch)
return '';
try {
const fm = yaml.load(fmMatch[1]);
const cascade = fm.cascade;
if (cascade?.product)
return cascade.product;
// Fallback: first key of the menu map
if (fm.menu && typeof fm.menu === 'object') {
const keys = Object.keys(fm.menu);
if (keys.length > 0)
return keys[0];
}
}
catch {
console.warn(`⚠️ Could not parse frontmatter in ${indexFile}`);
}
return '';
}
/**
* Check whether a hand-maintained api/_index.md already has a menu entry.
* If so, the generator should skip adding its own parent menu entry.
*
* Only detects genuinely hand-maintained files files previously generated
* by this script (which have articleDataKey in frontmatter) are ignored,
* since they'll be regenerated during this run.
*/
function hasExistingApiMenu(pagesDir) {
const yaml = require('js-yaml');
const apiIndex = path.join(pagesDir, 'api', '_index.md');
if (!fs.existsSync(apiIndex))
return false;
const content = fs.readFileSync(apiIndex, 'utf8');
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (!fmMatch)
return false;
try {
const fm = yaml.load(fmMatch[1]);
// Skip files generated by this script (they have articleDataKey)
if (fm.articleDataKey)
return false;
return !!fm.menu;
}
catch {
return false;
}
}
/**
* Discover all products by scanning api-docs/ for .config.yml files.
* Derives Hugo paths from directory structure and existing frontmatter.
*/
function discoverProducts() {
const yaml = require('js-yaml');
const products = [];
const configFiles = findConfigFiles(API_DOCS_ROOT);
for (const configPath of configFiles) {
const configDir = path.dirname(configPath);
const productDir = path.relative(API_DOCS_ROOT, configDir);
let config;
try {
const raw = fs.readFileSync(configPath, 'utf8');
config = yaml.load(raw);
}
catch (err) {
console.warn(`⚠️ Could not parse ${configPath}: ${err}`);
continue;
}
if (!config.apis || Object.keys(config.apis).length === 0) {
continue;
}
const pagesDir = path.join(DOCS_ROOT, 'content', productDir);
const staticDirName = deriveStaticDirName(productDir);
const menuKey = readMenuKey(pagesDir);
const skipParentMenu = hasExistingApiMenu(pagesDir);
// Parse API entries, skipping compatibility specs
const apis = [];
for (const [entryKey, entry] of Object.entries(config.apis)) {
const { apiKey, version } = parseApiEntry(entryKey);
// Skip v1-compatibility entries (being removed in pipeline restructure)
if (apiKey.includes('compatibility'))
continue;
// Prefer post-processed spec from _build/ (has overlays and tag configs),
// fall back to source spec for standalone usage
const sourceSpec = path.join(configDir, entry.root);
const buildSpec = path.join(API_DOCS_ROOT, '_build', productDir, entry.root);
const specFile = fs.existsSync(buildSpec) ? buildSpec : sourceSpec;
const sectionSlug = getSectionSlug(apiKey);
apis.push({ apiKey, version, specFile, sectionSlug });
}
if (apis.length === 0)
continue;
products.push({
configDir,
productDir,
productName: config['x-influxdata-product-name'] || productDir,
pagesDir,
menuKey,
skipParentMenu,
staticDirName,
apis,
});
}
return products;
}
// ---------------------------------------------------------------------------
// Cleanup functions
// ---------------------------------------------------------------------------
/**
* Get all paths that would be cleaned for a product.
*
* @param product - The product to clean
* @param allStaticDirNames - Names of all products (to avoid prefix collisions)
*/
function getCleanupPaths(product, allStaticDirNames) {
const staticPath = path.join(DOCS_ROOT, 'static/openapi');
const directories = [];
const files = [];
// Tag specs directory: static/openapi/{staticDirName}/
const tagSpecsDir = path.join(staticPath, product.staticDirName);
if (fs.existsSync(tagSpecsDir)) {
directories.push(tagSpecsDir);
}
// Article data directory: data/article_data/influxdb/{staticDirName}/
const articleDataDir = path.join(DOCS_ROOT, `data/article_data/influxdb/${product.staticDirName}`);
if (fs.existsSync(articleDataDir)) {
directories.push(articleDataDir);
}
// Content pages: content/{pagesDir}/{sectionSlug}/ for each API
for (const api of product.apis) {
const contentDir = path.join(product.pagesDir, api.sectionSlug);
if (fs.existsSync(contentDir)) {
directories.push(contentDir);
}
}
// Root spec files: static/openapi/{staticDirName}*.yml and .json
// Avoid matching files that belong to products with longer names
// (e.g., 'influxdb-cloud' should not match 'influxdb-cloud-dedicated-*.yml')
const longerPrefixes = allStaticDirNames.filter((n) => n !== product.staticDirName && n.startsWith(product.staticDirName + '-'));
if (fs.existsSync(staticPath)) {
const staticFiles = fs.readdirSync(staticPath);
staticFiles
.filter((f) => {
if (!f.startsWith(product.staticDirName))
return false;
// Exclude files belonging to a longer-named product
for (const longer of longerPrefixes) {
if (f.startsWith(longer))
return false;
}
return f.endsWith('.yml') || f.endsWith('.json');
})
.forEach((f) => {
files.push(path.join(staticPath, f));
});
}
return { directories, files };
}
/** Clean output directories for a product before regeneration. */
function cleanProductOutputs(product, allStaticDirNames) {
const { directories, files } = getCleanupPaths(product, allStaticDirNames);
for (const dir of directories) {
console.log(`🧹 Removing directory: ${dir}`);
fs.rmSync(dir, { recursive: true, force: true });
}
for (const file of files) {
console.log(`🧹 Removing file: ${file}`);
fs.unlinkSync(file);
}
const total = directories.length + files.length;
if (total > 0) {
console.log(`✓ Cleaned ${directories.length} directories, ${files.length} files for ${product.staticDirName}`);
}
}
/** Display dry-run preview of what would be cleaned. */
function showDryRunPreview(product, allStaticDirNames) {
const { directories, files } = getCleanupPaths(product, allStaticDirNames);
console.log(`\nDRY RUN: Would clean the following for ${product.staticDirName}:\n`);
if (directories.length > 0) {
console.log('Directories to remove:');
directories.forEach((dir) => console.log(` - ${dir}`));
}
if (files.length > 0) {
console.log('\nFiles to remove:');
files.forEach((file) => console.log(` - ${file}`));
}
if (directories.length === 0 && files.length === 0) {
console.log(' (no files to clean)');
}
console.log(`\nSummary: ${directories.length} directories, ${files.length} files would be removed`);
}
// ---------------------------------------------------------------------------
// Link transformation
// ---------------------------------------------------------------------------
/** Fields that can contain markdown with links */
const MARKDOWN_FIELDS = new Set(['description', 'summary']);
exports.MARKDOWN_FIELDS = MARKDOWN_FIELDS;
/** Link placeholder pattern */
const LINK_PATTERN = /\/influxdb\/version\//g;
exports.LINK_PATTERN = LINK_PATTERN;
/**
* Transform documentation links in OpenAPI spec markdown fields.
* Replaces `/influxdb/version/` with the actual product path.
*/
function transformDocLinks(spec, productPath) {
function transformValue(value) {
if (typeof value === 'string') {
return value.replace(LINK_PATTERN, `${productPath}/`);
}
if (Array.isArray(value)) {
return value.map(transformValue);
}
if (value !== null && typeof value === 'object') {
return transformObject(value);
}
return value;
}
function transformObject(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (MARKDOWN_FIELDS.has(key) && typeof value === 'string') {
result[key] = value.replace(LINK_PATTERN, `${productPath}/`);
}
else if (value !== null && typeof value === 'object') {
result[key] = transformValue(value);
}
else {
result[key] = value;
}
}
return result;
}
return transformObject(spec);
}
/**
* Resolve a URL path to a content file path.
*
* @example '/influxdb3/core/api/auth/' 'content/influxdb3/core/api/auth/_index.md'
*/
function resolveContentPath(urlPath, contentDir) {
const normalized = urlPath.replace(/\/$/, '');
const indexPath = path.join(contentDir, normalized, '_index.md');
const directPath = path.join(contentDir, normalized + '.md');
if (fs.existsSync(indexPath))
return indexPath;
if (fs.existsSync(directPath))
return directPath;
return indexPath;
}
/**
* Validate that transformed links point to existing content.
*/
function validateDocLinks(spec, contentDir) {
const errors = [];
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
function extractLinks(value, jsonPath) {
if (typeof value === 'string') {
let match;
while ((match = linkPattern.exec(value)) !== null) {
const [, linkText, linkUrl] = match;
if (linkUrl.startsWith('/') && !linkUrl.startsWith('//')) {
const contentPath = resolveContentPath(linkUrl, contentDir);
if (!fs.existsSync(contentPath)) {
errors.push(`Broken link at ${jsonPath}: [${linkText}](${linkUrl})`);
}
}
}
linkPattern.lastIndex = 0;
}
else if (Array.isArray(value)) {
value.forEach((item, index) => extractLinks(item, `${jsonPath}[${index}]`));
}
else if (value !== null && typeof value === 'object') {
for (const [key, val] of Object.entries(value)) {
extractLinks(val, `${jsonPath}.${key}`);
}
}
}
extractLinks(spec, 'spec');
return errors;
}
/**
* Generate Hugo content pages from tag-based article data.
*
* Creates markdown files with frontmatter from article metadata.
* Each article becomes a page with type: api that renders via Hugo-native
* templates. Includes operation metadata for TOC generation.
*/
function generateTagPagesFromArticleData(options) {
const { articlesPath, contentPath, sectionSlug, menuKey, menuParent, productDescription, skipParentMenu, specDownloadPath, articleDataKey, articleSection, } = options;
const yaml = require('js-yaml');
const articlesFile = path.join(articlesPath, 'articles.yml');
if (!fs.existsSync(articlesFile)) {
console.warn(`⚠️ Articles file not found: ${articlesFile}`);
return;
}
const articlesContent = fs.readFileSync(articlesFile, 'utf8');
const data = yaml.load(articlesContent);
if (!data.articles || !Array.isArray(data.articles)) {
console.warn(`⚠️ No articles found in ${articlesFile}`);
return;
}
if (!fs.existsSync(contentPath)) {
fs.mkdirSync(contentPath, { recursive: true });
}
// Generate parent _index.md for the section
const sectionDir = path.join(contentPath, sectionSlug);
const parentIndexFile = path.join(sectionDir, '_index.md');
if (!fs.existsSync(sectionDir)) {
fs.mkdirSync(sectionDir, { recursive: true });
}
if (!fs.existsSync(parentIndexFile)) {
const apiDescription = productDescription ||
`Use the InfluxDB HTTP API to write data, query data, and manage databases, tables, and tokens.`;
const parentFrontmatter = {
title: menuParent || 'InfluxDB HTTP API',
description: apiDescription,
weight: 104,
type: 'api',
articleDataKey,
articleSection,
};
if (menuKey && !skipParentMenu) {
parentFrontmatter.menu = {
[menuKey]: {
name: menuParent || 'InfluxDB HTTP API',
identifier: `api-reference-${articleDataKey}-${sectionSlug}`,
parent: 'Reference',
},
};
}
if (apiProductsMap.size > 0) {
const altLinks = {};
apiProductsMap.forEach((apiPath, productName) => {
altLinks[productName] = apiPath;
});
parentFrontmatter.alt_links = altLinks;
}
const introText = apiDescription.replace('InfluxDB', '{{% product-name %}}');
const parentContent = `---
${yaml.dump(parentFrontmatter)}---
${introText}
{{< children >}}
`;
fs.writeFileSync(parentIndexFile, parentContent);
console.log(`✓ Generated parent index at ${parentIndexFile}`);
}
// Generate "All endpoints" page
const allEndpointsDir = path.join(sectionDir, 'all-endpoints');
const allEndpointsFile = path.join(allEndpointsDir, '_index.md');
if (!fs.existsSync(allEndpointsDir)) {
fs.mkdirSync(allEndpointsDir, { recursive: true });
}
const allEndpointsFrontmatter = {
title: 'All endpoints',
description: `View all API endpoints sorted by path.`,
type: 'api',
layout: 'all-endpoints',
weight: 999,
isAllEndpoints: true,
articleDataKey,
articleSection,
};
if (menuKey) {
allEndpointsFrontmatter.menu = {
[menuKey]: {
name: 'All endpoints',
identifier: `all-endpoints-${articleDataKey}-${sectionSlug}`,
parent: menuParent || 'InfluxDB HTTP API',
},
};
}
if (apiProductsMap.size > 0) {
const altLinks = {};
apiProductsMap.forEach((apiPath, productName) => {
altLinks[productName] = apiPath;
});
allEndpointsFrontmatter.alt_links = altLinks;
}
const allEndpointsContent = `---
${yaml.dump(allEndpointsFrontmatter)}---
All {{% product-name %}} API endpoints, sorted by path.
`;
fs.writeFileSync(allEndpointsFile, allEndpointsContent);
console.log(`✓ Generated all-endpoints page at ${allEndpointsFile}`);
// Generate a page for each article (tag)
for (const article of data.articles) {
const pagePath = path.join(contentPath, article.path);
const pageFile = path.join(pagePath, '_index.md');
if (!fs.existsSync(pagePath)) {
fs.mkdirSync(pagePath, { recursive: true });
}
const title = article.fields.title || article.fields.name || article.path;
const isConceptual = article.fields.isConceptual === true;
const weight = article.fields.weight ?? 100;
const frontmatter = {
title,
description: article.fields.description || `API reference for ${title}`,
type: 'api',
layout: isConceptual ? 'single' : 'list',
staticFilePath: article.fields.staticFilePath,
weight,
tag: article.fields.tag,
isConceptual,
menuGroup: article.fields.menuGroup,
specDownloadPath,
articleDataKey,
articleSection,
};
if (!isConceptual &&
article.fields.operations &&
article.fields.operations.length > 0) {
frontmatter.operations = article.fields.operations;
}
if (isConceptual && article.fields.tagDescription) {
frontmatter.tagDescription = article.fields.tagDescription;
}
if (article.fields.showSecuritySchemes) {
frontmatter.showSecuritySchemes = true;
}
// Add related links if present
if (article.fields.related &&
Array.isArray(article.fields.related) &&
article.fields.related.length > 0) {
frontmatter.related = article.fields.related;
}
// Add client library related link for InfluxDB 3 products
if (contentPath.includes('influxdb3/') && !isConceptual) {
const influxdb3Match = contentPath.match(/influxdb3\/([^/]+)/);
if (influxdb3Match) {
const productSegment = influxdb3Match[1];
const clientLibLink = {
title: 'InfluxDB 3 API client libraries',
href: `/influxdb3/${productSegment}/reference/client-libraries/v3/`,
};
const existing = frontmatter.related || [];
const alreadyHas = existing.some((r) => typeof r === 'object' && r.href === clientLibLink.href);
if (!alreadyHas) {
frontmatter.related = [...existing, clientLibLink];
}
}
}
if (apiProductsMap.size > 0) {
const altLinks = {};
apiProductsMap.forEach((apiPath, productName) => {
altLinks[productName] = apiPath;
});
frontmatter.alt_links = altLinks;
}
const pageContent = `---
${yaml.dump(frontmatter)}---
`;
fs.writeFileSync(pageFile, pageContent);
}
console.log(`✓ Generated ${data.articles.length} tag-based content pages in ${contentPath}`);
}
// ---------------------------------------------------------------------------
// Spec processing
// ---------------------------------------------------------------------------
/**
* Process a single API section: transform links, write static spec,
* generate tag data, and create Hugo content pages.
*/
function processApiSection(product, api, staticBasePath) {
const yaml = require('js-yaml');
const isDualApi = product.apis.length > 1;
console.log(`\n📄 Processing ${api.sectionSlug} section (${api.apiKey})`);
// --- 1. Determine paths ---
// Root spec download: single → {dir}.yml, dual → {dir}-{section}.yml
const specSuffix = isDualApi ? `-${api.sectionSlug}` : '';
const staticSpecPath = path.join(staticBasePath, `${product.staticDirName}${specSuffix}.yml`);
const staticJsonSpecPath = staticSpecPath.replace('.yml', '.json');
// Tag specs directory
const tagSpecsBase = isDualApi
? path.join(staticBasePath, product.staticDirName, api.sectionSlug)
: path.join(staticBasePath, product.staticDirName);
// Article data
const articlesPath = path.join(DOCS_ROOT, 'data/article_data/influxdb', product.staticDirName, api.sectionSlug);
// Download path for frontmatter
const specDownloadPath = `/openapi/${product.staticDirName}${specSuffix}.yml`;
// Path spec files for per-operation rendering
const pathSpecsDir = isDualApi
? path.join(staticBasePath, product.staticDirName, api.sectionSlug, 'paths')
: path.join(staticBasePath, product.staticDirName, 'paths');
// --- 2. Read and transform spec ---
if (!fs.existsSync(api.specFile)) {
console.warn(`⚠️ Spec file not found: ${api.specFile}`);
return;
}
const specContent = fs.readFileSync(api.specFile, 'utf8');
const specObject = yaml.load(specContent);
const productPath = `/${product.productDir}`;
const transformedSpec = transformDocLinks(specObject, productPath);
console.log(`✓ Transformed documentation links for ${api.apiKey} to ${productPath}`);
// Validate links if enabled
if (validateLinks) {
const contentDir = path.join(DOCS_ROOT, 'content');
const linkErrors = validateDocLinks(transformedSpec, contentDir);
if (linkErrors.length > 0) {
console.warn(`\n⚠️ Link validation warnings for ${api.specFile}:`);
linkErrors.forEach((err) => console.warn(` ${err}`));
}
}
// --- 3. Write transformed spec to static folder ---
if (!fs.existsSync(staticBasePath)) {
fs.mkdirSync(staticBasePath, { recursive: true });
}
fs.writeFileSync(staticSpecPath, yaml.dump(transformedSpec));
console.log(`✓ Wrote transformed spec to ${staticSpecPath}`);
fs.writeFileSync(staticJsonSpecPath, JSON.stringify(transformedSpec, null, 2));
console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`);
// --- 4. Generate tag-based data ---
console.log(`\n📋 Generating tag-based data for ${api.apiKey} in ${tagSpecsBase}...`);
openapiPathsToHugo.generateHugoDataByTag({
specFile: staticSpecPath,
dataOutPath: tagSpecsBase,
articleOutPath: articlesPath,
includePaths: true,
});
// Generate path-specific specs
openapiPathsToHugo.generatePathSpecificSpecs(staticSpecPath, pathSpecsDir);
// --- 5. Generate Hugo content pages ---
generateTagPagesFromArticleData({
articlesPath,
contentPath: product.pagesDir,
sectionSlug: api.sectionSlug,
menuKey: product.menuKey,
menuParent: 'InfluxDB HTTP API',
skipParentMenu: product.skipParentMenu,
specDownloadPath,
articleDataKey: product.staticDirName,
articleSection: api.sectionSlug,
});
}
/**
* Process a single product: clean outputs and process each API section.
*/
function processProduct(product, allStaticDirNames) {
console.log('\n' + '='.repeat(80));
console.log(`Processing ${product.productName}`);
console.log('='.repeat(80));
// Clean output directories before regeneration
if (!noClean && !dryRun) {
cleanProductOutputs(product, allStaticDirNames);
}
const staticBasePath = path.join(DOCS_ROOT, 'static/openapi');
// Fetch specs if needed
if (!skipFetch) {
const getswaggerScript = path.join(API_DOCS_ROOT, 'getswagger.sh');
if (fs.existsSync(getswaggerScript)) {
// The build function in generate-api-docs.sh handles per-product
// fetching. When called standalone, use product directory name.
execCommand(`cd ${API_DOCS_ROOT} && ./getswagger.sh ${product.productDir} -B`, `Fetching OpenAPI spec for ${product.productName}`);
}
else {
console.log(`⚠️ getswagger.sh not found, skipping fetch step`);
}
}
else {
console.log(`⏭️ Skipping getswagger.sh (--skip-fetch flag set)`);
}
// Process each API section independently
for (const api of product.apis) {
processApiSection(product, api, staticBasePath);
}
console.log(`\n✅ Successfully processed ${product.productName}\n`);
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
function main() {
const args = process.argv.slice(2).filter((arg) => !arg.startsWith('--'));
// Discover all products from .config.yml files
const allProducts = discoverProducts();
if (allProducts.length === 0) {
console.error('❌ No products discovered. Ensure .config.yml files exist under api-docs/.');
process.exit(1);
}
// Determine which products to process
let productsToProcess;
if (args.length === 0) {
productsToProcess = allProducts;
console.log(`\n📋 Discovered ${allProducts.length} products, processing all...\n`);
}
else {
// Match by staticDirName or productDir
productsToProcess = [];
const invalid = [];
for (const arg of args) {
const found = allProducts.find((p) => p.staticDirName === arg ||
p.productDir === arg ||
p.productDir.replace(/\//g, '-') === arg);
if (found) {
productsToProcess.push(found);
}
else {
invalid.push(arg);
}
}
if (invalid.length > 0) {
console.error(`\n❌ Unknown product identifier(s): ${invalid.join(', ')}`);
console.error('\nDiscovered products:');
allProducts.forEach((p) => {
console.error(` - ${p.staticDirName} (${p.productName}) [${p.productDir}]`);
});
process.exit(1);
}
console.log(`\n📋 Processing specified products: ${productsToProcess.map((p) => p.staticDirName).join(', ')}\n`);
}
// Collect all staticDirNames for prefix-safe cleanup
const allStaticDirNames = allProducts.map((p) => p.staticDirName);
// Handle dry-run mode
if (dryRun) {
console.log('\n📋 DRY RUN MODE - No files will be modified\n');
productsToProcess.forEach((p) => showDryRunPreview(p, allStaticDirNames));
console.log('\nDry run complete. No files were modified.');
return;
}
// Process each product
productsToProcess.forEach((product) => {
processProduct(product, allStaticDirNames);
});
console.log('\n' + '='.repeat(80));
console.log('✅ All products processed successfully!');
console.log('='.repeat(80) + '\n');
}
// Execute if run directly
if (require.main === module) {
main();
}
//# sourceMappingURL=generate-openapi-articles.js.map

View File

@ -0,0 +1,920 @@
"use strict";
/**
* OpenAPI to Hugo Data Converter
*
* Converts OpenAPI v3 specifications into Hugo-compatible data files.
* Generates both YAML and JSON versions of spec fragments grouped by path.
*
* @module openapi-paths-to-hugo-data
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.writePathSpecificSpecs = writePathSpecificSpecs;
exports.generateHugoDataByTag = generateHugoDataByTag;
exports.generateHugoData = generateHugoData;
exports.generatePathSpecificSpecs = generatePathSpecificSpecs;
const yaml = __importStar(require("js-yaml"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* Read a YAML file and parse it
*
* @param filepath - Path to the YAML file
* @param encoding - File encoding (default: 'utf8')
* @returns Parsed YAML content
*/
function readFile(filepath, encoding = 'utf8') {
const content = fs.readFileSync(filepath, encoding);
return yaml.load(content);
}
/**
* Write data to a YAML file
*
* @param data - Data to write
* @param outputTo - Output file path
*/
function writeDataFile(data, outputTo) {
fs.writeFileSync(outputTo, yaml.dump(data));
}
/**
* Write data to a JSON file
*
* @param data - Data to write
* @param outputTo - Output file path
*/
function writeJsonFile(data, outputTo) {
fs.writeFileSync(outputTo, JSON.stringify(data, null, 2));
}
/**
* OpenAPI utility functions
*/
const openapiUtils = {
/**
* Check if a path fragment is a placeholder (e.g., {id})
*
* @param str - Path fragment to check
* @returns True if the fragment is a placeholder
*/
isPlaceholderFragment(str) {
const placeholderRegex = /^\{.*\}$/;
return placeholderRegex.test(str);
},
};
/**
* Convert tag name to URL-friendly slug
*
* @param tagName - Tag name (e.g., "Write data", "Processing engine")
* @returns URL-friendly slug (e.g., "write-data", "processing-engine")
*/
function slugifyTag(tagName) {
return tagName
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
/**
* Menu group mappings for tag-based navigation
* Maps OpenAPI tags to sidebar groups
*/
const TAG_MENU_GROUPS = {
// Concepts group
'Quick start': 'Concepts',
Authentication: 'Concepts',
'Headers and parameters': 'Concepts',
'Response codes': 'Concepts',
// Data Operations group
'Write data': 'Data Operations',
'Query data': 'Data Operations',
'Cache data': 'Data Operations',
// Administration group
Database: 'Administration',
Table: 'Administration',
Token: 'Administration',
// Processing Engine group
'Processing engine': 'Processing Engine',
// Server group
'Server information': 'Server',
// Compatibility group
'Compatibility endpoints': 'Compatibility',
};
/**
* Get menu group for a tag
*
* @param tagName - Tag name
* @returns Menu group name or 'Other' if not mapped
*/
function getMenuGroupForTag(tagName) {
return TAG_MENU_GROUPS[tagName] || 'Other';
}
/**
* HTTP methods to check for operations
*/
const HTTP_METHODS = [
'get',
'post',
'put',
'patch',
'delete',
'options',
'head',
'trace',
];
/**
* Extract all operations from an OpenAPI document grouped by tag
*
* @param openapi - OpenAPI document
* @returns Map of tag name to operations with that tag
*/
function extractOperationsByTag(openapi) {
const tagOperations = new Map();
Object.entries(openapi.paths).forEach(([pathKey, pathItem]) => {
HTTP_METHODS.forEach((method) => {
const operation = pathItem[method];
if (operation) {
const opMeta = {
operationId: operation.operationId || `${method}-${pathKey}`,
method: method.toUpperCase(),
path: pathKey,
summary: operation.summary || '',
tags: operation.tags || [],
};
// Extract compatibility version if present
if (operation['x-compatibility-version']) {
opMeta.compatVersion = operation['x-compatibility-version'];
}
// Extract externalDocs if present
if (operation.externalDocs) {
opMeta.externalDocs = {
description: operation.externalDocs.description || '',
url: operation.externalDocs.url,
};
}
// Extract x-influxdatadocs-related if present
if (operation['x-influxdatadocs-related'] &&
Array.isArray(operation['x-influxdatadocs-related'])) {
opMeta.related = operation['x-influxdatadocs-related'];
}
// Extract x-related (title/href objects) if present
if (operation['x-related'] && Array.isArray(operation['x-related'])) {
opMeta.relatedLinks = operation['x-related'];
}
// Add operation to each of its tags
(operation.tags || []).forEach((tag) => {
if (!tagOperations.has(tag)) {
tagOperations.set(tag, []);
}
tagOperations.get(tag).push(opMeta);
});
}
});
});
return tagOperations;
}
/**
* Write OpenAPI specs grouped by tag to separate files
* Generates both YAML and JSON versions per tag
*
* @param openapi - OpenAPI document
* @param prefix - Filename prefix for output files
* @param outPath - Output directory path
*/
function writeTagOpenapis(openapi, prefix, outPath) {
const tagOperations = extractOperationsByTag(openapi);
// Process each tag
tagOperations.forEach((operations, tagName) => {
// Deep copy openapi
const doc = JSON.parse(JSON.stringify(openapi));
// Filter paths to only include those with operations for this tag
const filteredPaths = {};
Object.entries(openapi.paths).forEach(([pathKey, pathItem]) => {
const filteredPathItem = {};
let hasOperations = false;
HTTP_METHODS.forEach((method) => {
const operation = pathItem[method];
if (operation?.tags?.includes(tagName)) {
// Clone the operation and restrict tags to only this tag
// This prevents the operation from being rendered multiple times
// (once per tag) when an operation belongs to multiple tags
const filteredOperation = { ...operation, tags: [tagName] };
filteredPathItem[method] = filteredOperation;
hasOperations = true;
}
});
// Include path-level parameters if we have operations
if (hasOperations) {
if (pathItem.parameters) {
filteredPathItem.parameters = pathItem.parameters;
}
filteredPaths[pathKey] = filteredPathItem;
}
});
doc.paths = filteredPaths;
// Filter tags to only include this tag (and trait tags for context)
if (doc.tags) {
doc.tags = doc.tags.filter((tag) => tag.name === tagName || tag['x-traitTag']);
}
// Update info
const tagSlug = slugifyTag(tagName);
doc.info.title = tagName;
doc.info.description = `API reference for ${tagName}`;
doc['x-tagGroup'] = tagName;
try {
if (!fs.existsSync(outPath)) {
fs.mkdirSync(outPath, { recursive: true });
}
const baseFilename = `${prefix}${tagSlug}`;
const yamlPath = path.resolve(outPath, `${baseFilename}.yaml`);
const jsonPath = path.resolve(outPath, `${baseFilename}.json`);
writeDataFile(doc, yamlPath);
writeJsonFile(doc, jsonPath);
console.log(`Generated tag spec: ${baseFilename}.yaml (${Object.keys(filteredPaths).length} paths, ${operations.length} operations)`);
}
catch (err) {
console.error(`Error writing tag group ${tagName}:`, err);
}
});
// Also create specs for conceptual tags (x-traitTag) without operations
(openapi.tags || []).forEach((tag) => {
if (tag['x-traitTag'] && !tagOperations.has(tag.name)) {
const doc = JSON.parse(JSON.stringify(openapi));
doc.paths = {};
doc.tags = [tag];
doc.info.title = tag.name;
doc.info.description = tag.description || `API reference for ${tag.name}`;
doc['x-tagGroup'] = tag.name;
const tagSlug = slugifyTag(tag.name);
try {
const baseFilename = `${prefix}${tagSlug}`;
const yamlPath = path.resolve(outPath, `${baseFilename}.yaml`);
const jsonPath = path.resolve(outPath, `${baseFilename}.json`);
writeDataFile(doc, yamlPath);
writeJsonFile(doc, jsonPath);
console.log(`Generated conceptual tag spec: ${baseFilename}.yaml`);
}
catch (err) {
console.error(`Error writing conceptual tag ${tag.name}:`, err);
}
}
});
}
/**
* Convert API path to filename-safe slug
*
* @param apiPath - API path (e.g., "/api/v3/configure/token/admin")
* @returns Filename-safe slug (e.g., "api-v3-configure-token-admin")
*/
function pathToFileSlug(apiPath) {
return apiPath
.replace(/^\//, '') // Remove leading slash
.replace(/\//g, '-') // Replace slashes with dashes
.replace(/[{}]/g, '') // Remove curly braces from path params
.replace(/-+/g, '-') // Collapse multiple dashes
.replace(/-$/, ''); // Remove trailing dash
}
/**
* Write path-specific OpenAPI specs (one file per exact API path)
*
* Each file contains all HTTP methods for a single path, enabling
* operation pages to filter by method only (no path prefix conflicts).
*
* @param openapi - OpenAPI document
* @param outPath - Output directory path (e.g., "static/openapi/{product}/paths")
* @returns Map of API path to spec file path (for use in frontmatter)
*/
function writePathSpecificSpecs(openapi, outPath) {
const pathSpecFiles = new Map();
if (!fs.existsSync(outPath)) {
fs.mkdirSync(outPath, { recursive: true });
}
Object.entries(openapi.paths).forEach(([apiPath, pathItem]) => {
// Deep clone pathItem to avoid mutating original
const clonedPathItem = JSON.parse(JSON.stringify(pathItem));
// Limit each operation to a single tag to prevent duplicate rendering
// Operations with multiple tags would be rendered once per tag
const usedTags = new Set();
HTTP_METHODS.forEach((method) => {
const operation = clonedPathItem[method];
if (operation?.tags && operation.tags.length > 0) {
// Select the most specific tag to avoid duplicate rendering
// Prefer "Auth token" over "Authentication" for token-related operations
let primaryTag = operation.tags[0];
if (operation.tags.includes('Auth token')) {
primaryTag = 'Auth token';
}
operation.tags = [primaryTag];
usedTags.add(primaryTag);
}
});
// Create spec with just this path (all its methods)
// Include global security requirements so auth info displays correctly
const pathSpec = {
openapi: openapi.openapi,
info: {
...openapi.info,
title: apiPath,
description: `API reference for ${apiPath}`,
},
paths: { [apiPath]: clonedPathItem },
components: openapi.components, // Include for $ref resolution
servers: openapi.servers,
security: openapi.security, // Global security requirements
};
// Filter spec-level tags to only include those used by operations
if (openapi.tags) {
pathSpec.tags = openapi.tags.filter((tag) => usedTags.has(tag.name) && !tag['x-traitTag']);
}
// Write files
const slug = pathToFileSlug(apiPath);
const yamlPath = path.resolve(outPath, `${slug}.yaml`);
const jsonPath = path.resolve(outPath, `${slug}.json`);
writeDataFile(pathSpec, yamlPath);
writeJsonFile(pathSpec, jsonPath);
// Store the web-accessible path (without "static/" prefix)
// Hugo serves files from static/ at the root, so we extract the path after 'static/'
const staticMatch = yamlPath.match(/static\/(.+)$/);
const webPath = staticMatch ? `/${staticMatch[1]}` : yamlPath;
pathSpecFiles.set(apiPath, webPath);
});
console.log(`Generated ${pathSpecFiles.size} path-specific specs in ${outPath}`);
return pathSpecFiles;
}
/**
* Write OpenAPI specs grouped by path to separate files
* Generates both YAML and JSON versions
*
* @param openapi - OpenAPI document
* @param prefix - Filename prefix for output files
* @param outPath - Output directory path
*/
function writePathOpenapis(openapi, prefix, outPath) {
const pathGroups = {};
// Group paths by their base path (first 3-4 segments, excluding placeholders)
Object.keys(openapi.paths)
.sort()
.forEach((p) => {
const delimiter = '/';
let key = p.split(delimiter);
// Check if this is an item path (ends with a placeholder)
let isItemPath = openapiUtils.isPlaceholderFragment(key[key.length - 1]);
if (isItemPath) {
key = key.slice(0, -1);
}
// Take first 4 segments
key = key.slice(0, 4);
// Check if the last segment is still a placeholder
isItemPath = openapiUtils.isPlaceholderFragment(key[key.length - 1]);
if (isItemPath) {
key = key.slice(0, -1);
}
const groupKey = key.join('/');
pathGroups[groupKey] = pathGroups[groupKey] || {};
pathGroups[groupKey][p] = openapi.paths[p];
});
// Write each path group to separate YAML and JSON files
Object.keys(pathGroups).forEach((pg) => {
// Deep copy openapi
const doc = JSON.parse(JSON.stringify(openapi));
doc.paths = pathGroups[pg];
// Collect tags used by operations in this path group
const usedTags = new Set();
Object.values(doc.paths).forEach((pathItem) => {
const httpMethods = [
'get',
'post',
'put',
'patch',
'delete',
'options',
'head',
'trace',
];
httpMethods.forEach((method) => {
const operation = pathItem[method];
if (operation?.tags) {
operation.tags.forEach((tag) => usedTags.add(tag));
}
});
});
// Filter tags to only include those used by operations in this path group
// Exclude x-traitTag tags (supplementary documentation tags)
if (doc.tags) {
doc.tags = doc.tags.filter((tag) => usedTags.has(tag.name) && !tag['x-traitTag']);
}
// Simplify info for path-specific docs
doc.info.title = pg;
doc.info.description = `API reference for ${pg}`;
doc['x-pathGroup'] = pg;
try {
if (!fs.existsSync(outPath)) {
fs.mkdirSync(outPath, { recursive: true });
}
const baseFilename = `${prefix}${pg.replaceAll('/', '-').replace(/^-/, '')}`;
const yamlPath = path.resolve(outPath, `${baseFilename}.yaml`);
const jsonPath = path.resolve(outPath, `${baseFilename}.json`);
// Write both YAML and JSON versions
writeDataFile(doc, yamlPath);
writeJsonFile(doc, jsonPath);
console.log(`Generated: ${baseFilename}.yaml and ${baseFilename}.json`);
}
catch (err) {
console.error(`Error writing path group ${pg}:`, err);
}
});
}
/**
* Create article metadata for a path group
*
* @param openapi - OpenAPI document with x-pathGroup
* @returns Article metadata object
*/
function createArticleDataForPathGroup(openapi) {
const article = {
path: '',
fields: {
name: openapi['x-pathGroup'] || '',
describes: Object.keys(openapi.paths),
},
};
/**
* Convert OpenAPI path to Hugo-friendly article path
* Legacy endpoints (without /api/ prefix) go under api/ directly
* Versioned endpoints (with /api/vN/) keep their structure
*
* @param p - Path to convert (e.g., '/health', '/api/v3/query_sql')
* @returns Path suitable for Hugo content directory (e.g., 'api/health', 'api/v3/query_sql')
*/
const toHugoPath = (p) => {
if (!p) {
return '';
}
// If path doesn't start with /api/, it's a legacy endpoint
// Place it directly under api/ to avoid collision with /api/v1/* paths
if (!p.startsWith('/api/')) {
// /health -> api/health
// /write -> api/write
return `api${p}`;
}
// /api/v1/health -> api/v1/health
// /api/v2/write -> api/v2/write
// /api/v3/query_sql -> api/v3/query_sql
return p.replace(/^\//, '');
};
/**
* Convert path to tag-friendly format (dashes instead of slashes)
*
* @param p - Path to convert
* @returns Tag-friendly path
*/
const toTagPath = (p) => {
if (!p) {
return '';
}
return p.replace(/^\//, '').replaceAll('/', '-');
};
const pathGroup = openapi['x-pathGroup'] || '';
article.path = toHugoPath(pathGroup);
// Store original path for menu display (shows actual endpoint path)
article.fields.menuName = pathGroup;
article.fields.title = openapi.info?.title;
article.fields.description = openapi.description;
const pathGroupFrags = path.parse(openapi['x-pathGroup'] || '');
article.fields.tags = [pathGroupFrags?.dir, pathGroupFrags?.name]
.filter(Boolean)
.map((t) => toTagPath(t));
// Extract x-relatedLinks and OpenAPI tags from path items or operations
const relatedLinks = [];
const apiTags = [];
const httpMethods = [
'get',
'post',
'put',
'patch',
'delete',
'options',
'head',
'trace',
];
Object.values(openapi.paths).forEach((pathItem) => {
// Check path-level x-relatedLinks
if (pathItem['x-relatedLinks'] &&
Array.isArray(pathItem['x-relatedLinks'])) {
relatedLinks.push(...pathItem['x-relatedLinks'].filter((link) => !relatedLinks.includes(link)));
}
// Check operation-level x-relatedLinks and tags
httpMethods.forEach((method) => {
const operation = pathItem[method];
if (operation) {
// Extract x-relatedLinks
if (operation['x-relatedLinks'] &&
Array.isArray(operation['x-relatedLinks'])) {
relatedLinks.push(...operation['x-relatedLinks'].filter((link) => !relatedLinks.includes(link)));
}
// Extract OpenAPI tags from operation
if (operation.tags && Array.isArray(operation.tags)) {
operation.tags.forEach((tag) => {
if (!apiTags.includes(tag)) {
apiTags.push(tag);
}
});
}
}
});
});
// Only add related if there are links
if (relatedLinks.length > 0) {
article.fields.related = relatedLinks;
}
// Add OpenAPI tags from operations (for Hugo frontmatter)
if (apiTags.length > 0) {
article.fields.apiTags = apiTags;
}
return article;
}
/**
* Write OpenAPI article metadata to Hugo data files
* Generates articles.yml and articles.json
*
* @param sourcePath - Path to directory containing OpenAPI fragment files
* @param targetPath - Output path for article data
* @param opts - Options including file pattern filter
*/
function writeOpenapiArticleData(sourcePath, targetPath, opts) {
/**
* Check if path is a file
*/
const isFile = (filePath) => {
return fs.lstatSync(filePath).isFile();
};
/**
* Check if filename matches pattern
*/
const matchesPattern = (filePath) => {
return opts.filePattern
? path.parse(filePath).name.startsWith(opts.filePattern)
: true;
};
try {
const articles = fs
.readdirSync(sourcePath)
.map((fileName) => path.join(sourcePath, fileName))
.filter(matchesPattern)
.filter(isFile)
.filter((filePath) => filePath.endsWith('.yaml') || filePath.endsWith('.yml')) // Only process YAML files
.map((filePath) => {
const openapi = readFile(filePath);
const article = createArticleDataForPathGroup(openapi);
article.fields.source = filePath;
// Hugo omits "/static" from the URI when serving files stored in "./static"
article.fields.staticFilePath = filePath.replace(/^static\//, '/');
return article;
});
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true });
}
const articleCollection = { articles };
// Write both YAML and JSON versions
const yamlPath = path.resolve(targetPath, 'articles.yml');
const jsonPath = path.resolve(targetPath, 'articles.json');
writeDataFile(articleCollection, yamlPath);
writeJsonFile(articleCollection, jsonPath);
console.log(`Generated ${articles.length} articles in ${targetPath}`);
}
catch (e) {
console.error('Error writing article data:', e);
}
}
/**
* Sanitize markdown description by removing fragment links and ReDoc directives
*
* Handles three cases:
* 1. OpenAPI fragment links: [text](#section/...) -> text (removes the link entirely)
* 2. Relative links with fragments: [text](/path/#anchor) -> [text](/path/) (keeps link, removes fragment)
* 3. ReDoc injection directives: <!-- ReDoc-Inject: ... --> (removes entirely)
*
* This sanitization is necessary because fragment links don't work when article
* descriptions are rendered via the {{< children >}} shortcode on parent pages.
*
* @param description - Markdown description that may contain fragment links
* @returns Sanitized description suitable for children shortcode rendering
*/
function sanitizeDescription(description) {
if (!description) {
return '';
}
let sanitized = description;
// Remove ReDoc injection directives (e.g., <!-- ReDoc-Inject: <security-definitions> -->)
sanitized = sanitized.replace(/<!--\s*ReDoc-Inject:.*?-->/g, '');
// Handle markdown links:
// 1. OpenAPI fragment links (#section/..., #operation/..., #tag/...) -> replace with just the text
// 2. Relative links with fragments (/path/#anchor) -> keep link but remove fragment
sanitized = sanitized.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
// Case 1: OpenAPI fragment links (starts with #section/, #operation/, #tag/)
if (url.match(/^#(section|operation|tag)\//)) {
return text; // Just return the link text, no markdown link
}
// Case 2: Relative link with fragment (starts with /, contains #)
if (url.startsWith('/') && url.includes('#')) {
const urlWithoutFragment = url.split('#')[0];
if (urlWithoutFragment === '/' || urlWithoutFragment === '') {
return text;
}
return `[${text}](${urlWithoutFragment})`;
}
// Case 3: Keep other links as-is (external links, non-fragment links)
return match;
});
// Clean up extra whitespace left by directive removals
sanitized = sanitized.replace(/\n\n\n+/g, '\n\n').trim();
return sanitized;
}
/**
* Create article data for a tag-based grouping
*
* @param openapi - OpenAPI document with x-tagGroup
* @param operations - Operations for this tag
* @param tagMeta - Tag metadata from OpenAPI spec
* @returns Article metadata object
*/
function createArticleDataForTag(openapi, operations, tagMeta) {
const tagName = openapi['x-tagGroup'] || '';
const tagSlug = slugifyTag(tagName);
const isConceptual = tagMeta?.['x-traitTag'] === true;
const article = {
path: `api/${tagSlug}`,
fields: {
name: tagName,
describes: Object.keys(openapi.paths),
title: tagName,
description: sanitizeDescription(tagMeta?.description ||
openapi.info?.description ||
`API reference for ${tagName}`),
tag: tagName,
isConceptual,
menuGroup: getMenuGroupForTag(tagName),
operations: operations.map((op) => ({
operationId: op.operationId,
method: op.method,
path: op.path,
summary: op.summary,
tags: op.tags,
...(op.compatVersion && { compatVersion: op.compatVersion }),
...(op.externalDocs && { externalDocs: op.externalDocs }),
})),
},
};
// Add tag description for conceptual pages (sanitized for children shortcode)
if (tagMeta?.description) {
article.fields.tagDescription = sanitizeDescription(tagMeta.description);
}
// Show security schemes section on Authentication pages
if (tagName === 'Authentication') {
article.fields.showSecuritySchemes = true;
}
// Set custom weight for Quick start to appear first in nav
if (tagName === 'Quick start') {
article.fields.weight = 1;
}
// Set default weight for consistent sorting (articles without explicit weight)
if (article.fields.weight === undefined) {
article.fields.weight = 100;
}
// Aggregate related links from multiple sources into article-level related
// This populates Hugo frontmatter `related` field for "Related content" links
// Supports both plain URL strings and {title, href} objects
const relatedItems = [];
const seenHrefs = new Set();
// Helper to add a link, deduplicating by href
const addRelated = (item) => {
const href = typeof item === 'string' ? item : item.href;
if (!seenHrefs.has(href)) {
seenHrefs.add(href);
relatedItems.push(item);
}
};
// Tag-level x-related ({title, href} objects)
if (tagMeta?.['x-related']) {
tagMeta['x-related'].forEach(addRelated);
}
// Tag-level x-influxdatadocs-related (plain URLs)
if (tagMeta?.['x-influxdatadocs-related']) {
tagMeta['x-influxdatadocs-related'].forEach(addRelated);
}
// Tag-level externalDocs (legacy single link)
if (tagMeta?.externalDocs?.url) {
addRelated(tagMeta.externalDocs.url);
}
// Operation-level related links
operations.forEach((op) => {
if (op.relatedLinks) {
op.relatedLinks.forEach(addRelated);
}
if (op.related) {
op.related.forEach(addRelated);
}
if (op.externalDocs?.url) {
addRelated(op.externalDocs.url);
}
});
if (relatedItems.length > 0) {
article.fields.related = relatedItems;
}
return article;
}
/**
* Write tag-based OpenAPI article metadata to Hugo data files
* Generates articles.yml and articles.json
*
* @param sourcePath - Path to directory containing tag-based OpenAPI fragment files
* @param targetPath - Output path for article data
* @param openapi - Original OpenAPI document (for tag metadata)
* @param opts - Options including file pattern filter
*/
function writeOpenapiTagArticleData(sourcePath, targetPath, openapi, opts) {
const isFile = (filePath) => {
return fs.lstatSync(filePath).isFile();
};
const matchesPattern = (filePath) => {
return opts.filePattern
? path.parse(filePath).name.startsWith(opts.filePattern)
: true;
};
// Create tag metadata lookup
const tagMetaMap = new Map();
(openapi.tags || []).forEach((tag) => {
tagMetaMap.set(tag.name, tag);
});
try {
const articles = fs
.readdirSync(sourcePath)
.map((fileName) => path.join(sourcePath, fileName))
.filter(matchesPattern)
.filter(isFile)
.filter((filePath) => filePath.endsWith('.yaml') || filePath.endsWith('.yml'))
.map((filePath) => {
const tagOpenapi = readFile(filePath);
const tagName = tagOpenapi['x-tagGroup'] || tagOpenapi.info?.title || '';
const tagMeta = tagMetaMap.get(tagName);
// Extract operations from the tag-filtered spec
const operations = [];
Object.entries(tagOpenapi.paths).forEach(([pathKey, pathItem]) => {
HTTP_METHODS.forEach((method) => {
const operation = pathItem[method];
if (operation) {
const opMeta = {
operationId: operation.operationId || `${method}-${pathKey}`,
method: method.toUpperCase(),
path: pathKey,
summary: operation.summary || '',
tags: operation.tags || [],
};
// Extract compatibility version if present
if (operation['x-compatibility-version']) {
opMeta.compatVersion = operation['x-compatibility-version'];
}
// Extract externalDocs if present
if (operation.externalDocs) {
opMeta.externalDocs = {
description: operation.externalDocs.description || '',
url: operation.externalDocs.url,
};
}
// Extract x-influxdatadocs-related if present
if (operation['x-influxdatadocs-related'] &&
Array.isArray(operation['x-influxdatadocs-related'])) {
opMeta.related = operation['x-influxdatadocs-related'];
}
// Extract x-related (title/href objects)
if (operation['x-related'] &&
Array.isArray(operation['x-related'])) {
opMeta.relatedLinks = operation['x-related'];
}
operations.push(opMeta);
}
});
});
const article = createArticleDataForTag(tagOpenapi, operations, tagMeta);
article.fields.source = filePath;
article.fields.staticFilePath = filePath.replace(/^static\//, '/');
return article;
});
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true });
}
const articleCollection = { articles };
// Write both YAML and JSON versions
const yamlPath = path.resolve(targetPath, 'articles.yml');
const jsonPath = path.resolve(targetPath, 'articles.json');
writeDataFile(articleCollection, yamlPath);
writeJsonFile(articleCollection, jsonPath);
console.log(`Generated ${articles.length} tag-based articles in ${targetPath}`);
}
catch (e) {
console.error('Error writing tag article data:', e);
}
}
/**
* Generate Hugo data files from an OpenAPI specification grouped by tag
*
* This function:
* 1. Reads the OpenAPI spec file
* 2. Groups operations by their OpenAPI tags
* 3. Writes each tag group to separate YAML and JSON files
* 4. Generates tag-based article metadata for Hugo
*
* @param options - Generation options
*/
function generateHugoDataByTag(options) {
const filenamePrefix = `${path.parse(options.specFile).name}-`;
const sourceFile = readFile(options.specFile, 'utf8');
// Optionally generate path-based files for backwards compatibility
if (options.includePaths) {
console.log(`\nGenerating OpenAPI path files in ${options.dataOutPath}....`);
writePathOpenapis(sourceFile, filenamePrefix, options.dataOutPath);
}
// Generate tag-based files
const tagOutPath = options.includePaths
? path.join(options.dataOutPath, 'tags')
: options.dataOutPath;
console.log(`\nGenerating OpenAPI tag files in ${tagOutPath}....`);
writeTagOpenapis(sourceFile, filenamePrefix, tagOutPath);
console.log(`\nGenerating OpenAPI tag article data in ${options.articleOutPath}...`);
writeOpenapiTagArticleData(tagOutPath, options.articleOutPath, sourceFile, {
filePattern: filenamePrefix,
});
console.log('\nTag-based generation complete!\n');
}
/**
* Generate Hugo data files from an OpenAPI specification
*
* This function:
* 1. Reads the OpenAPI spec file
* 2. Groups paths by their base path
* 3. Writes each group to separate YAML and JSON files
* 4. Generates article metadata for Hugo
*
* @param options - Generation options
*/
function generateHugoData(options) {
const filenamePrefix = `${path.parse(options.specFile).name}-`;
const sourceFile = readFile(options.specFile, 'utf8');
console.log(`\nGenerating OpenAPI path files in ${options.dataOutPath}....`);
writePathOpenapis(sourceFile, filenamePrefix, options.dataOutPath);
console.log(`\nGenerating OpenAPI article data in ${options.articleOutPath}...`);
writeOpenapiArticleData(options.dataOutPath, options.articleOutPath, {
filePattern: filenamePrefix,
});
console.log('\nGeneration complete!\n');
}
/**
* Generate path-specific OpenAPI specs from a spec file
*
* Convenience wrapper that reads the spec file and generates path-specific specs.
*
* @param specFile - Path to OpenAPI spec file
* @param outPath - Output directory for path-specific specs
* @returns Map of API path to spec file web path (for use in frontmatter)
*/
function generatePathSpecificSpecs(specFile, outPath) {
const openapi = readFile(specFile, 'utf8');
return writePathSpecificSpecs(openapi, outPath);
}
// CommonJS export for backward compatibility
module.exports = {
generateHugoData,
generateHugoDataByTag,
generatePathSpecificSpecs,
writePathSpecificSpecs,
};
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1,334 @@
#!/usr/bin/env node
"use strict";
/**
* Post-Process Specs
*
* Applies content overlays and tag configuration to bundled OpenAPI specs.
* Runs after `getswagger.sh` bundles specs and before
* `generate-openapi-articles.ts` generates Hugo pages.
*
* Replaces Redocly decorators for:
* - info.yml overlays (title, description, version, license, contact, x-* fields)
* - servers.yml overlays (replaces spec.servers array)
* - tags.yml config (rename, describe, add x-related links to tags)
*
* Usage:
* node api-docs/scripts/dist/post-process-specs.js # All products
* node api-docs/scripts/dist/post-process-specs.js influxdb3/core # One product
*
* @module post-process-specs
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const yaml = __importStar(require("js-yaml"));
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const LOG_PREFIX = '[post-process]';
/** Build output directory for resolved specs. Source specs are never mutated. */
const BUILD_DIR = '_build';
/** Product directories that contain a .config.yml with `apis:` entries. */
const PRODUCT_DIRS = [
'influxdb3/core',
'influxdb3/enterprise',
'influxdb3/cloud-dedicated',
'influxdb3/cloud-serverless',
'influxdb3/clustered',
'influxdb/cloud',
'influxdb/v2',
'influxdb/v1',
'enterprise_influxdb/v1',
];
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Parse a YAML file and return the parsed object, or null if the file does
* not exist.
*/
function loadYaml(filePath) {
if (!fs.existsSync(filePath))
return null;
const raw = fs.readFileSync(filePath, 'utf8');
return yaml.load(raw);
}
/**
* Write an object to a YAML file.
*/
function writeYaml(filePath, data) {
fs.writeFileSync(filePath, yaml.dump(data, { lineWidth: -1 }), 'utf8');
}
function log(msg) {
process.stderr.write(`${LOG_PREFIX} ${msg}\n`);
}
// ---------------------------------------------------------------------------
// Content overlays
// ---------------------------------------------------------------------------
/**
* Resolve a content file path using the same convention as the Redocly
* docs-content.cjs helper: try API-specific directory first, fall back to
* product-level directory.
*
* @param filename - e.g. 'info.yml' or 'servers.yml'
* @param specDir - Absolute path to the directory containing the spec file.
* @param productAbsDir - Absolute path to the product directory.
* @returns Absolute path to the content file, or null if not found.
*/
function resolveContentFile(filename, specDir, productAbsDir) {
// API-specific: {specDir}/content/{filename}
const apiSpecific = path.join(specDir, 'content', filename);
if (fs.existsSync(apiSpecific))
return apiSpecific;
// Product-level fallback: {productAbsDir}/content/{filename}
const productLevel = path.join(productAbsDir, 'content', filename);
if (fs.existsSync(productLevel))
return productLevel;
return null;
}
/**
* Apply info.yml overlay to the spec. Merges each field present in the
* overlay into spec.info, preserving fields not mentioned in the overlay.
*
* @returns true if any fields were applied.
*/
function applyInfoOverlay(spec, specDir, productAbsDir, label) {
const infoPath = resolveContentFile('info.yml', specDir, productAbsDir);
if (!infoPath)
return false;
const overlay = loadYaml(infoPath);
if (!overlay)
return false;
if (!spec.info)
spec.info = {};
let applied = 0;
for (const [key, value] of Object.entries(overlay)) {
spec.info[key] = value;
applied++;
}
if (applied > 0) {
log(`${label}: applied ${applied} info field(s) from ${path.relative(productAbsDir, infoPath)}`);
}
return applied > 0;
}
/**
* Apply servers.yml overlay to the spec. Replaces spec.servers entirely.
*
* @returns true if servers were applied.
*/
function applyServersOverlay(spec, specDir, productAbsDir, label) {
const serversPath = resolveContentFile('servers.yml', specDir, productAbsDir);
if (!serversPath)
return false;
const servers = loadYaml(serversPath);
if (!servers || !Array.isArray(servers))
return false;
spec.servers = servers;
log(`${label}: applied ${servers.length} server(s) from ${path.relative(productAbsDir, serversPath)}`);
return true;
}
// ---------------------------------------------------------------------------
// Tag config
// ---------------------------------------------------------------------------
/**
* Collect every tag name referenced across all operations in the spec.
*/
function collectOperationTags(spec) {
const found = new Set();
for (const pathItem of Object.values(spec.paths ?? {})) {
for (const operation of Object.values(pathItem)) {
if (operation &&
typeof operation === 'object' &&
Array.isArray(operation.tags)) {
for (const t of operation.tags)
found.add(t);
}
}
}
return found;
}
/**
* Rename a tag throughout the spec: in `tags[]` and in every operation.
*/
function renameTag(spec, oldName, newName) {
for (const tag of spec.tags ?? []) {
if (tag.name === oldName)
tag.name = newName;
}
for (const pathItem of Object.values(spec.paths ?? {})) {
for (const operation of Object.values(pathItem)) {
if (operation &&
typeof operation === 'object' &&
Array.isArray(operation.tags)) {
operation.tags = operation.tags.map((t) => t === oldName ? newName : t);
}
}
}
}
/**
* Apply tag config from a `tags.yml` file to the spec.
*
* @returns true if any tags were patched.
*/
function applyTagConfig(spec, tagConfigPath, label) {
const tagsCfg = loadYaml(tagConfigPath);
if (!tagsCfg || !tagsCfg.tags) {
log(`${label}: tags.yml has no 'tags' key — skipping`);
return false;
}
if (!Array.isArray(spec.tags))
spec.tags = [];
const operationTags = collectOperationTags(spec);
const configKeys = Object.keys(tagsCfg.tags);
// Warn: config references a tag not in the spec
for (const cfgKey of configKeys) {
const effectiveName = tagsCfg.tags[cfgKey]?.rename ?? cfgKey;
if (!operationTags.has(cfgKey) && !operationTags.has(effectiveName)) {
log(`WARN ${label}: config tag '${cfgKey}' not found in spec operations`);
}
}
// Warn: spec has operation tags with no config entry
Array.from(operationTags).forEach((opTag) => {
const hasEntry = configKeys.some((k) => k === opTag || tagsCfg.tags[k]?.rename === opTag);
if (!hasEntry) {
log(`WARN ${label}: spec tag '${opTag}' has no config entry in tags.yml`);
}
});
// Apply transformations
for (const [tagKey, cfg] of Object.entries(tagsCfg.tags)) {
if (cfg.rename) {
log(`${label}: renaming tag '${tagKey}' → '${cfg.rename}'`);
renameTag(spec, tagKey, cfg.rename);
}
const resolvedName = cfg.rename ?? tagKey;
let tagObj = spec.tags.find((t) => t.name === resolvedName);
if (!tagObj) {
tagObj = { name: resolvedName };
spec.tags.push(tagObj);
}
if (cfg.description !== undefined)
tagObj.description = cfg.description.trim();
if (cfg['x-traitTag'] !== undefined)
tagObj['x-traitTag'] = cfg['x-traitTag'];
if (cfg['x-related'] !== undefined)
tagObj['x-related'] = cfg['x-related'];
}
log(`${label}: patched ${configKeys.length} tag(s)`);
return true;
}
// ---------------------------------------------------------------------------
// Core logic
// ---------------------------------------------------------------------------
/**
* Process a single product directory: read `.config.yml`, find spec files,
* apply content overlays and tag configs, write resolved specs to _build/.
*
* Source specs in api-docs/ are never mutated. Resolved output goes to
* api-docs/_build/{productDir}/{specFile} for downstream consumers
* (Redoc HTML, generate-openapi-articles.ts).
*/
function processProduct(apiDocsRoot, productDir) {
const productAbsDir = path.join(apiDocsRoot, productDir);
const configPath = path.join(productAbsDir, '.config.yml');
const config = loadYaml(configPath);
if (!config || !config.apis) {
log(`${productDir}: no .config.yml or no 'apis' key — skipping`);
return;
}
for (const [apiKey, apiEntry] of Object.entries(config.apis)) {
const specRelPath = apiEntry.root;
const specAbsPath = path.join(productAbsDir, specRelPath);
const specDir = path.join(productAbsDir, path.dirname(specRelPath));
const label = path.join(productDir, specRelPath);
if (!fs.existsSync(specAbsPath)) {
log(`${label}: spec not found at ${specAbsPath} — skipping`);
continue;
}
// Load spec once
const spec = loadYaml(specAbsPath);
if (!spec) {
log(`${label}: failed to parse spec — skipping`);
continue;
}
// Apply all transforms
applyInfoOverlay(spec, specDir, productAbsDir, label);
applyServersOverlay(spec, specDir, productAbsDir, label);
const tagConfigPath = path.join(specDir, 'tags.yml');
if (fs.existsSync(tagConfigPath)) {
applyTagConfig(spec, tagConfigPath, label);
}
// Write resolved spec to _build/, mirroring the source path structure
const outPath = path.join(apiDocsRoot, BUILD_DIR, productDir, specRelPath);
const outDir = path.dirname(outPath);
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
writeYaml(outPath, spec);
log(`${label}: wrote ${path.relative(apiDocsRoot, outPath)}`);
}
}
// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
function main() {
const args = process.argv.slice(2);
// Optional --root <path> flag for testing — overrides the default resolution.
let apiDocsRoot = path.resolve(__dirname, '../..'); // api-docs/scripts/dist -> api-docs/
let targetProduct;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--root' && args[i + 1]) {
apiDocsRoot = path.resolve(args[i + 1]);
i++;
}
else {
targetProduct = args[i];
}
}
const products = targetProduct ? [targetProduct] : PRODUCT_DIRS;
let hasError = false;
for (const productDir of products) {
try {
processProduct(apiDocsRoot, productDir);
}
catch (err) {
log(`ERROR ${productDir}: ${err.message}`);
hasError = true;
}
}
process.exit(hasError ? 1 : 0);
}
main();
//# sourceMappingURL=post-process-specs.js.map

View File

@ -0,0 +1,490 @@
#!/usr/bin/env node
"use strict";
/**
* Tests for post-process-specs.ts
*
* Standalone test script no test runner required.
*
* Usage:
* node api-docs/scripts/dist/test-post-process-specs.js
*
* Creates temporary fixtures in $TMPDIR, runs the compiled script against
* them via child_process, and reports pass/fail for each case.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const child_process_1 = require("child_process");
const yaml = __importStar(require("js-yaml"));
// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------
const SCRIPT = path.resolve(__dirname, 'post-process-specs.js');
function makeSpec(tags, operationTags, overrides) {
return {
openapi: '3.0.0',
info: { title: 'Test', version: '1.0.0' },
tags,
paths: {
'/test': {
get: {
operationId: 'testOp',
tags: operationTags,
responses: {},
},
},
},
...overrides,
};
}
function createTmpRoot() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'post-process-test-'));
const productDir = path.join(root, 'influxdb3', 'core');
const specDir = path.join(productDir, 'v3');
const specPath = path.join(specDir, 'openapi.yaml');
const buildSpecPath = path.join(root, '_build', 'influxdb3', 'core', 'v3', 'openapi.yaml');
fs.mkdirSync(specDir, { recursive: true });
const config = {
apis: {
data: {
root: 'v3/openapi.yaml',
},
},
};
fs.writeFileSync(path.join(productDir, '.config.yml'), yaml.dump(config));
return { root, productDir, specDir, specPath, buildSpecPath };
}
function writeYaml(filePath, data) {
fs.writeFileSync(filePath, yaml.dump(data, { lineWidth: -1 }), 'utf8');
}
function readYaml(filePath) {
return yaml.load(fs.readFileSync(filePath, 'utf8'));
}
function runScript(root, productFilter) {
const scriptArgs = ['--root', root];
if (productFilter)
scriptArgs.push(productFilter);
const result = (0, child_process_1.spawnSync)('node', [SCRIPT, ...scriptArgs], {
encoding: 'utf8',
timeout: 10_000,
});
return {
stdout: result.stdout ?? '',
stderr: result.stderr ?? '',
exitCode: result.status ?? 1,
};
}
function cleanup(root) {
fs.rmSync(root, { recursive: true, force: true });
}
// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------
let passed = 0;
let failed = 0;
const failures = [];
function pass(name) {
console.log(` PASS ${name}`);
passed++;
}
function fail(name, reason) {
console.log(` FAIL ${name}`);
console.log(` ${reason}`);
failed++;
failures.push(`${name}: ${reason}`);
}
function assert(name, condition, reason) {
if (condition) {
pass(name);
}
else {
fail(name, reason);
}
}
// ---------------------------------------------------------------------------
// Tag config tests
// ---------------------------------------------------------------------------
function testDescriptionSetting() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write line protocol data to InfluxDB.' },
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('1a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const tag = spec.tags?.find((t) => t.name === 'Write data');
assert('1b. description applied to tag', tag?.description === 'Write line protocol data to InfluxDB.', `description was: ${tag?.description}`);
}
finally {
cleanup(root);
}
}
function testTagRename() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Cache data' }], ['Cache data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Cache data': { rename: 'Cache distinct values' },
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('2a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const oldTag = spec.tags?.find((t) => t.name === 'Cache data');
assert('2b. old tag name gone from tags[]', !oldTag, 'old tag still present in tags[]');
const newTag = spec.tags?.find((t) => t.name === 'Cache distinct values');
assert('2c. new tag name in tags[]', !!newTag, 'renamed tag not found in tags[]');
const opTags = spec.paths?.['/test']?.['get']?.tags ?? [];
assert('2d. operation.tags[] updated', opTags.includes('Cache distinct values') &&
!opTags.includes('Cache data'), `operation tags: ${JSON.stringify(opTags)}`);
}
finally {
cleanup(root);
}
}
function testXRelated() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': {
description: 'Write data.',
'x-related': [
{ title: 'Write data guide', href: '/influxdb3/core/write-data/' },
],
},
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('3a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
const tag = spec.tags?.find((t) => t.name === 'Write data');
const related = tag?.['x-related'];
assert('3b. x-related present', Array.isArray(related) && related.length === 1, `x-related: ${JSON.stringify(related)}`);
assert('3c. x-related entry correct', related?.[0]?.title === 'Write data guide' &&
related?.[0]?.href === '/influxdb3/core/write-data/', `entry: ${JSON.stringify(related?.[0])}`);
}
finally {
cleanup(root);
}
}
function testStaleConfigWarning() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write data.' },
'Ghost tag': { description: 'This tag does not exist in the spec.' },
},
});
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('4a. exits 0 (warnings are not errors)', exitCode === 0, `exit code was ${exitCode}`);
assert('4b. stale config warning emitted', stderr.includes("config tag 'Ghost tag' not found in spec operations"), `stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testUncoveredTagWarning() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }, { name: 'Query data' }], ['Write data', 'Query data']));
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': { description: 'Write data.' },
},
});
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('5a. exits 0 (warnings are not errors)', exitCode === 0, `exit code was ${exitCode}`);
assert('5b. uncovered tag warning emitted', stderr.includes("spec tag 'Query data' has no config entry in tags.yml"), `stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testNoTagsYmlSilentSkip() {
const { root, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
const { stderr, exitCode } = runScript(root, 'influxdb3/core');
assert('6a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
assert('6b. no error output', !stderr.includes('ERROR'), `unexpected error in stderr: ${stderr}`);
}
finally {
cleanup(root);
}
}
function testMalformedYamlFails() {
const { root, specDir, specPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data']));
fs.writeFileSync(path.join(specDir, 'tags.yml'), 'tags:\n Write data:\n description: [\n bad yaml here', 'utf8');
const { exitCode } = runScript(root, 'influxdb3/core');
assert('7a. exits 1 on malformed YAML', exitCode === 1, `exit code was ${exitCode}`);
}
finally {
cleanup(root);
}
}
// ---------------------------------------------------------------------------
// Content overlay tests
// ---------------------------------------------------------------------------
// 8. Info overlay — API-specific content/info.yml
function testInfoOverlay() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: { title: 'Original Title', version: '0.0.0' },
}));
// Create API-specific content/info.yml
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'Overridden Title',
version: '2.0.0',
'x-influxdata-short-title': 'Short',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('8a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('8b. title overridden', spec.info.title === 'Overridden Title', `title: ${spec.info.title}`);
assert('8c. version overridden', spec.info.version === '2.0.0', `version: ${spec.info.version}`);
assert('8d. x-influxdata-short-title applied', spec.info['x-influxdata-short-title'] ===
'Short', `x-influxdata-short-title: ${spec.info['x-influxdata-short-title']}`);
}
finally {
cleanup(root);
}
}
// 9. Info overlay — product-level fallback
function testInfoOverlayProductFallback() {
const { root, productDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: { title: 'Original', version: '1.0.0' },
}));
// Create product-level content/info.yml (NOT in specDir/content/)
const contentDir = path.join(productDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'Product-Level Title',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('9a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('9b. title from product-level', spec.info.title === 'Product-Level Title', `title: ${spec.info.title}`);
assert('9c. version preserved', spec.info.version === '1.0.0', `version: ${spec.info.version}`);
}
finally {
cleanup(root);
}
}
// 10. Servers overlay
function testServersOverlay() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
servers: [{ url: 'https://old.example.com' }],
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'servers.yml'), [
{
url: 'https://{baseurl}',
description: 'InfluxDB API',
variables: {
baseurl: {
enum: ['localhost:8181'],
default: 'localhost:8181',
description: 'InfluxDB URL',
},
},
},
]);
const { exitCode } = runScript(root, 'influxdb3/core');
assert('10a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('10b. servers replaced', spec.servers?.length === 1, `server count: ${spec.servers?.length}`);
assert('10c. server URL correct', spec.servers?.[0]?.url === 'https://{baseurl}', `url: ${spec.servers?.[0]?.url}`);
assert('10d. server variables present', spec.servers?.[0]?.variables !== undefined, 'variables missing');
}
finally {
cleanup(root);
}
}
// 11. Info overlay preserves fields not in overlay
function testInfoOverlayPreservesFields() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([], [], {
info: {
title: 'Original Title',
version: '3.0.0',
description: 'Original description.',
license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' },
},
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
// Overlay only sets x-* fields, no title/version/description
writeYaml(path.join(contentDir, 'info.yml'), {
'x-influxdata-short-title': 'InfluxDB 3 API',
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('11a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('11b. title preserved', spec.info.title === 'Original Title', `title: ${spec.info.title}`);
assert('11c. version preserved', spec.info.version === '3.0.0', `version: ${spec.info.version}`);
assert('11d. description preserved', spec.info.description === 'Original description.', `desc: ${spec.info.description}`);
assert('11e. x-influxdata-short-title added', spec.info['x-influxdata-short-title'] ===
'InfluxDB 3 API', 'x-influxdata-short-title missing');
}
finally {
cleanup(root);
}
}
// 12. No content overlays — spec unchanged
function testNoOverlaysNoWrite() {
const { root, specPath, buildSpecPath } = createTmpRoot();
try {
const original = makeSpec([{ name: 'Write data' }], ['Write data']);
writeYaml(specPath, original);
const mtime = fs.statSync(specPath).mtimeMs;
// Small delay to detect mtime changes
const start = Date.now();
while (Date.now() - start < 50) {
/* busy wait */
}
const { exitCode } = runScript(root, 'influxdb3/core');
assert('12a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const built = readYaml(buildSpecPath);
assert('12b. build output matches input when no overlays/tags', JSON.stringify(built) === JSON.stringify(original), 'build output differed from source');
assert('12c. source file untouched', fs.statSync(specPath).mtimeMs === mtime, 'source spec modified');
}
finally {
cleanup(root);
}
}
// 13. Combined: info + servers + tags applied together
function testCombinedOverlaysAndTags() {
const { root, specDir, specPath, buildSpecPath } = createTmpRoot();
try {
writeYaml(specPath, makeSpec([{ name: 'Write data' }], ['Write data'], {
info: { title: 'Original', version: '1.0.0' },
servers: [{ url: 'https://old.example.com' }],
}));
const contentDir = path.join(specDir, 'content');
fs.mkdirSync(contentDir, { recursive: true });
writeYaml(path.join(contentDir, 'info.yml'), {
title: 'New Title',
'x-influxdata-short-title': 'Short',
});
writeYaml(path.join(contentDir, 'servers.yml'), [
{ url: 'https://new.example.com', description: 'New Server' },
]);
writeYaml(path.join(specDir, 'tags.yml'), {
tags: {
'Write data': {
description: 'Write line protocol data.',
'x-related': [{ title: 'Guide', href: '/guide/' }],
},
},
});
const { exitCode } = runScript(root, 'influxdb3/core');
assert('13a. exits 0', exitCode === 0, `exit code was ${exitCode}`);
const spec = readYaml(buildSpecPath);
assert('13b. info title updated', spec.info.title === 'New Title', `title: ${spec.info.title}`);
assert('13c. info version preserved', spec.info.version === '1.0.0', `version: ${spec.info.version}`);
assert('13d. x-influxdata-short-title set', spec.info['x-influxdata-short-title'] ===
'Short', 'missing');
assert('13e. servers replaced', spec.servers?.[0]?.url === 'https://new.example.com', `url: ${spec.servers?.[0]?.url}`);
const tag = spec.tags?.find((t) => t.name === 'Write data');
assert('13f. tag description set', tag?.description === 'Write line protocol data.', `desc: ${tag?.description}`);
assert('13g. tag x-related set', Array.isArray(tag?.['x-related']) && tag['x-related'].length === 1, `x-related: ${JSON.stringify(tag?.['x-related'])}`);
}
finally {
cleanup(root);
}
}
// ---------------------------------------------------------------------------
// Run all tests
// ---------------------------------------------------------------------------
const tests = [
// Tag config tests (carried forward)
['1. Tag description setting', testDescriptionSetting],
['2. Tag rename (tags[] and operation.tags[])', testTagRename],
['3. x-related links', testXRelated],
['4. Warning: stale config reference', testStaleConfigWarning],
['5. Warning: uncovered spec tag', testUncoveredTagWarning],
['6. No tags.yml — silent skip', testNoTagsYmlSilentSkip],
['7. Malformed YAML — exit 1', testMalformedYamlFails],
// Content overlay tests (new)
['8. Info overlay — API-specific', testInfoOverlay],
['9. Info overlay — product-level fallback', testInfoOverlayProductFallback],
['10. Servers overlay', testServersOverlay],
[
'11. Info overlay preserves fields not in overlay',
testInfoOverlayPreservesFields,
],
['12. No overlays or tags — build mirrors source', testNoOverlaysNoWrite],
['13. Combined: info + servers + tags', testCombinedOverlaysAndTags],
];
console.log('\npost-process-specs tests\n');
for (const [name, fn] of tests) {
console.log(name);
try {
fn();
}
catch (err) {
fail(name, `threw: ${err.message}`);
}
}
console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed`);
if (failures.length > 0) {
console.log('\nFailed:');
for (const f of failures)
console.log(` - ${f}`);
process.exit(1);
}
//# sourceMappingURL=test-post-process-specs.js.map

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,473 @@
/**
* API Table of Contents Component
*
* Generates "ON THIS PAGE" navigation from content headings or operations data.
* Features:
* - Builds TOC from h2 headings by default
* - Builds TOC from operations data passed via data-operations attribute (tag-based)
* - Highlights current section on scroll (intersection observer)
* - Smooth scroll to anchors
* - Updates when tab changes
*
* Usage:
* <aside class="api-toc" data-component="api-toc" data-operations='[...]'>
* <h4 class="api-toc-header">ON THIS PAGE</h4>
* <nav class="api-toc-nav"></nav>
* </aside>
*
* Attributes:
* - data-operations: JSON array of operation objects for server-rendered TOC
* - data-toc-depth: Max heading level to include (default: "2", use "3" for h2+h3)
*/
interface ComponentOptions {
component: HTMLElement;
}
interface TocEntry {
id: string;
text: string;
level: number;
}
/**
* Operation metadata from frontmatter (for tag-based pages)
*/
interface OperationMeta {
operationId: string;
method: string;
path: string;
summary: string;
tags: string[];
}
/**
* Get headings from the currently visible content
*
* @param maxLevel - Maximum heading level to include (default: 2)
*/
function getVisibleHeadings(maxLevel: number = 2): TocEntry[] {
// Find the active tab panel or main content area
const activePanel = document.querySelector(
'.tab-content:not([style*="display: none"]), [data-tab-panel]:not([style*="display: none"]), .article--content'
);
if (!activePanel) {
return [];
}
// Build selector based on maxLevel (e.g., 'h2' or 'h2, h3')
const selectors = [];
for (let level = 2; level <= maxLevel; level++) {
selectors.push(`h${level}`);
}
const headings = activePanel.querySelectorAll(selectors.join(', '));
const entries: TocEntry[] = [];
headings.forEach((heading) => {
// Skip headings without IDs
if (!heading.id) {
return;
}
// Skip hidden headings
const rect = heading.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
return;
}
const level = parseInt(heading.tagName.charAt(1), 10);
entries.push({
id: heading.id,
text: heading.textContent?.trim() || '',
level,
});
});
return entries;
}
/**
* Build TOC HTML from entries
*/
function buildTocHtml(entries: TocEntry[]): string {
if (entries.length === 0) {
// Return empty string - the TOC container can be hidden via CSS when empty
return '';
}
let html = '<ul class="api-toc-list">';
entries.forEach((entry) => {
const indent = entry.level === 3 ? ' api-toc-item--nested' : '';
html += `
<li class="api-toc-item${indent}">
<a href="#${entry.id}" class="api-toc-link">${entry.text}</a>
</li>
`;
});
html += '</ul>';
return html;
}
/**
* Get method badge class for HTTP method
*/
function getMethodClass(method: string): string {
const m = method.toLowerCase();
switch (m) {
case 'get':
return 'api-method--get';
case 'post':
return 'api-method--post';
case 'put':
return 'api-method--put';
case 'patch':
return 'api-method--patch';
case 'delete':
return 'api-method--delete';
default:
return '';
}
}
/**
* Build TOC HTML from operations data (for tag-based pages)
*/
function buildOperationsTocHtml(operations: OperationMeta[]): string {
if (operations.length === 0) {
return '<p class="api-toc-empty">No operations on this page.</p>';
}
let html = '<ul class="api-toc-list api-toc-list--operations">';
operations.forEach((op) => {
// Generate anchor ID matching Redocly operation/{operationId} format
const anchorId = `operation/${op.operationId}`;
const methodClass = getMethodClass(op.method);
html += `
<li class="api-toc-item api-toc-item--operation">
<a href="#${anchorId}" class="api-toc-link api-toc-link--operation">
<span class="api-method ${methodClass}">${op.method.toUpperCase()}</span>
<span class="api-path">${op.path}</span>
</a>
</li>
`;
});
html += '</ul>';
return html;
}
/**
* Parse operations from data attribute
*/
function parseOperationsData(component: HTMLElement): OperationMeta[] | null {
const dataAttr = component.getAttribute('data-operations');
if (!dataAttr) {
return null;
}
try {
const operations = JSON.parse(dataAttr) as OperationMeta[];
return Array.isArray(operations) ? operations : null;
} catch (e) {
console.warn('[API TOC] Failed to parse operations data:', e);
return null;
}
}
/**
* Set up intersection observer for scroll highlighting
*/
function setupScrollHighlighting(
container: HTMLElement,
entries: TocEntry[]
): IntersectionObserver | null {
if (entries.length === 0) {
return null;
}
const headingIds = entries.map((e) => e.id);
const links = container.querySelectorAll<HTMLAnchorElement>('.api-toc-link');
// Create a map of heading ID to link element
const linkMap = new Map<string, HTMLAnchorElement>();
links.forEach((link) => {
const href = link.getAttribute('href');
if (href?.startsWith('#')) {
linkMap.set(href.slice(1), link);
}
});
// Track which headings are visible
const visibleHeadings = new Set<string>();
const observer = new IntersectionObserver(
(observerEntries) => {
observerEntries.forEach((entry) => {
const id = entry.target.id;
if (entry.isIntersecting) {
visibleHeadings.add(id);
} else {
visibleHeadings.delete(id);
}
});
// Find the first visible heading (in document order)
let activeId: string | null = null;
for (const id of headingIds) {
if (visibleHeadings.has(id)) {
activeId = id;
break;
}
}
// If no heading is visible, use the last one that was scrolled past
if (!activeId && visibleHeadings.size === 0) {
const scrollY = window.scrollY;
for (let i = headingIds.length - 1; i >= 0; i--) {
const heading = document.getElementById(headingIds[i]);
if (heading && heading.offsetTop < scrollY + 100) {
activeId = headingIds[i];
break;
}
}
}
// Update active state on links
links.forEach((link) => {
link.classList.remove('is-active');
});
if (activeId) {
const activeLink = linkMap.get(activeId);
activeLink?.classList.add('is-active');
}
},
{
rootMargin: '-80px 0px -70% 0px',
threshold: 0,
}
);
// Observe all headings
headingIds.forEach((id) => {
const heading = document.getElementById(id);
if (heading) {
observer.observe(heading);
}
});
return observer;
}
/**
* Set up smooth scroll for TOC links
*/
function setupSmoothScroll(container: HTMLElement): void {
container.addEventListener('click', (event) => {
const target = event.target as HTMLElement;
const link = target.closest<HTMLAnchorElement>('.api-toc-link');
if (!link) {
return;
}
const href = link.getAttribute('href');
if (!href?.startsWith('#')) {
return;
}
const targetElement = document.getElementById(href.slice(1));
if (!targetElement) {
return;
}
event.preventDefault();
// Scroll with offset for fixed header
const headerOffset = 80;
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.scrollY - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
});
// Update URL hash without jumping
history.pushState(null, '', href);
});
}
/**
* Update TOC visibility based on active tab
*/
function updateTocVisibility(container: HTMLElement): void {
const operationsPanel = document.querySelector(
'[data-tab-panel="operations"]'
);
const isOperationsVisible =
operationsPanel &&
!operationsPanel.getAttribute('style')?.includes('display: none');
if (isOperationsVisible) {
container.classList.add('is-hidden');
} else {
container.classList.remove('is-hidden');
}
}
/**
* Watch for tab changes to rebuild TOC
*/
function watchTabChanges(
container: HTMLElement,
rebuild: () => void
): MutationObserver {
const tabPanels = document.querySelector('.api-tab-panels');
if (!tabPanels) {
return new MutationObserver(() => {});
}
const observer = new MutationObserver((mutations) => {
// Check if any tab panel visibility changed
const hasVisibilityChange = mutations.some((mutation) => {
return (
mutation.type === 'attributes' &&
(mutation.attributeName === 'style' ||
mutation.attributeName === 'class')
);
});
if (hasVisibilityChange) {
// Update visibility based on active tab
updateTocVisibility(container);
// Debounce rebuild
setTimeout(rebuild, 100);
}
});
observer.observe(tabPanels, {
attributes: true,
subtree: true,
attributeFilter: ['style', 'class'],
});
return observer;
}
/**
* Initialize API TOC component
*/
export default function ApiToc({ component }: ComponentOptions): void {
const nav = component.querySelector<HTMLElement>('.api-toc-nav');
if (!nav) {
console.warn('[API TOC] No .api-toc-nav element found');
return;
}
// Check if TOC was pre-rendered server-side (has existing links)
const hasServerRenderedToc = nav.querySelectorAll('.api-toc-link').length > 0;
if (hasServerRenderedToc) {
// Server-side TOC exists - show it, set up navigation and scroll highlighting
component.classList.remove('is-hidden');
setupSmoothScroll(component);
// Extract entries from pre-rendered links for scroll highlighting
const preRenderedLinks =
nav.querySelectorAll<HTMLAnchorElement>('.api-toc-link');
const preRenderedEntries: TocEntry[] = [];
preRenderedLinks.forEach((link) => {
const href = link.getAttribute('href');
if (href?.startsWith('#')) {
preRenderedEntries.push({
id: href.slice(1),
text: link.textContent?.trim() || '',
level: 2,
});
}
});
if (preRenderedEntries.length > 0) {
setupScrollHighlighting(component, preRenderedEntries);
}
return;
}
// Check for operations data (tag-based pages)
const operations = parseOperationsData(component);
let observer: IntersectionObserver | null = null;
// Get max heading level from data attribute (default: 2)
// Use data-toc-depth="3" to include h3 headings if needed
const maxHeadingLevel = parseInt(
component.getAttribute('data-toc-depth') || '2',
10
);
/**
* Rebuild the TOC
*/
function rebuild(): void {
// Clean up previous observer
if (observer) {
observer.disconnect();
observer = null;
}
// If operations data is present, build operations-based TOC
if (operations && operations.length > 0) {
if (nav) {
nav.innerHTML = buildOperationsTocHtml(operations);
}
// Don't hide TOC for tag-based pages - always show operations
component.classList.remove('is-hidden');
return;
}
// Otherwise, fall back to heading-based TOC
const entries = getVisibleHeadings(maxHeadingLevel);
if (nav) {
nav.innerHTML = buildTocHtml(entries);
}
// Hide TOC if no entries, show if entries exist
if (entries.length === 0) {
component.classList.add('is-hidden');
} else {
component.classList.remove('is-hidden');
// Set up scroll highlighting only when we have entries
observer = setupScrollHighlighting(component, entries);
}
}
// Check initial visibility (hide for Operations tab, only for non-operations pages)
if (!operations || operations.length === 0) {
updateTocVisibility(component);
}
// Initial build
rebuild();
// Set up smooth scroll
setupSmoothScroll(component);
// Watch for tab changes (only for non-operations pages)
if (!operations || operations.length === 0) {
watchTabChanges(component, rebuild);
}
// Also rebuild on window resize (headings may change visibility)
let resizeTimeout: number;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = window.setTimeout(rebuild, 250);
});
}

View File

@ -122,21 +122,29 @@ function expandAccordions() {
// Expand accordions on load based on URL anchor
function openAccordionByHash() {
var anchor = window.location.hash;
var hash = window.location.hash;
if (!hash || hash.length <= 1) return;
// Use native DOM method to handle special characters in IDs (like /)
var id = hash.substring(1); // Remove leading #
var anchorElement = document.getElementById(id);
if (!anchorElement) return;
var $anchor = $(anchorElement);
function expandElement() {
if ($(anchor).parents('.expand').length > 0) {
return $(anchor).closest('.expand').children('.expand-label');
} else if ($(anchor).hasClass('expand')) {
return $(anchor).children('.expand-label');
if ($anchor.parents('.expand').length > 0) {
return $anchor.closest('.expand').children('.expand-label');
} else if ($anchor.hasClass('expand')) {
return $anchor.children('.expand-label');
}
return null;
}
if (expandElement() != null) {
if (expandElement().children('.expand-toggle').hasClass('open')) {
// Do nothing?
} else {
expandElement().children('.expand-toggle').trigger('click');
var $expandLabel = expandElement();
if ($expandLabel != null) {
if (!$expandLabel.children('.expand-toggle').hasClass('open')) {
$expandLabel.children('.expand-toggle').trigger('click');
}
}
}

View File

@ -46,6 +46,7 @@ import SidebarSearch from './components/sidebar-search.js';
import { SidebarToggle } from './sidebar-toggle.js';
import Theme from './theme.js';
import ThemeSwitch from './theme-switch.js';
import ApiToc from './components/api-toc.ts';
/**
* Component Registry
@ -77,6 +78,7 @@ const componentRegistry = {
'sidebar-toggle': SidebarToggle,
theme: Theme,
'theme-switch': ThemeSwitch,
'api-toc': ApiToc,
};
/**

View File

@ -0,0 +1,67 @@
// API Code Samples
// Styles for inline curl examples and Ask AI links within API operations
.api-code-sample {
margin: $api-spacing-lg 0;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: $api-border-radius;
overflow: hidden;
.dark-theme & {
border-color: rgba(255, 255, 255, 0.1);
}
}
.api-code-sample-header {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
padding: $api-spacing-sm $api-spacing-md;
background: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.dark-theme & {
background: rgba(255, 255, 255, 0.03);
border-bottom-color: rgba(255, 255, 255, 0.1);
}
.api-code-sample-title {
font-size: 0.85rem;
font-weight: 600;
}
}
pre.api-code-block {
margin: 0;
padding: $api-spacing-md;
overflow-x: auto;
background: $article-code-bg;
color: $article-code;
font-size: 0.8rem;
line-height: 1.5;
border-radius: 0;
code {
background: none;
padding: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
white-space: pre;
}
}
.api-code-ask-ai {
font-size: 0.8rem;
font-weight: 400;
text-decoration: none;
color: $article-link;
opacity: 0.7;
transition: opacity 0.2s;
white-space: nowrap;
&:hover {
opacity: 1;
}
}

View File

@ -0,0 +1,785 @@
/////////////////////////////// API Reference Layout ///////////////////////////////
//
// 3-column layout for API reference documentation:
// - Left: Existing Hugo sidebar + API navigation section
// - Center: Content with page-level tabs (Operations | Server | Auth | Compatibility)
// - Right: "ON THIS PAGE" table of contents
//
////////////////////////////////////////////////////////////////////////////////
// Content wrapper becomes flex container when used with API content
// Override overflow:hidden from _content-wrapper.scss to enable sticky positioning
// Widen to compensate for the API TOC so article content matches regular pages
.content-wrapper.api-content {
display: flex;
flex-direction: row;
align-items: flex-start;
overflow: visible; // Required for sticky TOC to work
width: calc(75% + 200px);
max-width: calc(100% - 2rem);
}
// Main API content area (center column)
.api-main {
flex: 1;
min-width: 0; // Prevent flex item from overflowing
padding-right: 1rem;
}
// Right-side TOC (third column)
.api-toc {
width: 200px;
flex-shrink: 0;
position: sticky;
top: 80px; // Account for fixed header height
align-self: flex-start; // Critical for sticky to work in flexbox
max-height: calc(100vh - 100px);
overflow-y: auto;
padding: 1rem;
border-left: 1px solid $nav-border;
// Hidden state (used when a tab panel hides the TOC)
&.is-hidden {
display: none;
}
&-header {
font-size: 0.75rem;
font-weight: $bold;
text-transform: uppercase;
letter-spacing: 0.08rem;
color: rgba($article-heading, 0.5);
margin: 0 0 1rem;
}
&-nav {
// TOC list styles
.api-toc-list {
list-style: none;
margin: 0;
padding: 0;
}
.api-toc-item {
margin: 0;
&--nested {
padding-left: 0.75rem;
}
}
.api-toc-link {
display: block;
padding: 0.35rem 0;
font-size: 0.85rem;
color: $nav-item;
text-decoration: none;
transition: color 0.2s;
line-height: 1.4;
&:hover {
color: $nav-item-hover;
}
&.is-active {
color: $nav-active;
font-weight: $medium;
}
}
}
&-empty {
font-size: 0.85rem;
color: rgba($article-text, 0.5);
font-style: italic;
}
// Operations-based TOC (for tag-based pages)
&-nav .api-toc-list--operations {
.api-toc-item--operation {
margin: 0.35rem 0;
}
.api-toc-link--operation {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8rem;
padding: 0.3rem 0;
}
// HTTP method badges in TOC
.api-method {
display: inline-block;
font-size: 0.6rem;
font-weight: $bold;
text-transform: uppercase;
padding: 0.15rem 0.3rem;
border-radius: 3px;
min-width: 2.2rem;
text-align: center;
flex-shrink: 0;
&--get { background-color: $b-pool; color: #fff; } // #00A3FF - bright brand blue
&--post { background-color: $gr-rainforest; color: #fff; } // #34BB55 - bright brand green
&--put { background-color: $y-pineapple; color: #fff; } // #FFB94A - bright yellow (distinct from red)
&--patch { background-color: $br-new-purple; color: #fff; } // #9b2aff - distinctive brand purple
&--delete { background-color: $r-curacao; color: #fff; } // #F95F53 - bright brand red
}
.api-path {
font-family: $code;
font-size: 0.75rem;
word-break: break-all;
color: inherit;
}
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////// Operations List (Main Content) //////////////////////
////////////////////////////////////////////////////////////////////////////////
// Operations list section
.api-operations-list {
margin: 2rem 0;
h2 {
margin-bottom: 1rem;
}
}
// Grid container for operation cards
.api-operations-grid {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
// Individual operation card (clickable link)
.api-operation-card {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: rgba($article-bg, 0.5);
border: 1px solid $nav-border;
border-radius: $radius;
text-decoration: none;
color: $article-text;
transition: background-color 0.2s, border-color 0.2s;
&:hover {
background: rgba($article-bg, 0.8);
border-color: $nav-item-hover;
}
// HTTP method badge
.api-method {
display: inline-block;
font-size: 0.7rem;
font-weight: $bold;
text-transform: uppercase;
padding: 0.2rem 0.4rem;
border-radius: 3px;
min-width: 3.5rem;
text-align: center;
flex-shrink: 0;
margin-top: 0.15rem;
border: 2px solid;
background-color: transparent;
&--get { border-color: $b-pool; color: $b-pool; } // #00A3FF - bright brand blue
&--post { border-color: $gr-rainforest; color: $gr-rainforest; } // #34BB55 - bright brand green
&--put { border-color: $y-pineapple; color: $y-pineapple; } // #FFB94A - bright yellow (distinct from red)
&--patch { border-color: $br-new-purple; color: $br-new-purple; } // #9b2aff - distinctive brand purple
&--delete { border-color: $r-curacao; color: $r-curacao; } // #F95F53 - bright brand red
}
// API path in monospace
// Note: Uses <code> element but we override the default code background
// to prevent inconsistent "progress bar" appearance from varying text lengths
.api-path {
font-family: $code;
font-size: 0.9rem;
color: $article-heading;
word-break: break-all;
flex: 1;
min-width: 0; // Allow text to shrink and wrap
background: none; // Override default code background
padding: 0; // Remove default code padding
}
// Operation summary text
.api-operation-summary {
font-size: 0.875rem;
color: rgba($article-text, 0.8);
flex-shrink: 0;
}
}
// Responsive: Stack operation cards vertically on small screens
@include media(small) {
.api-operation-card {
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
.api-method {
align-self: flex-start;
margin-top: 0;
}
.api-path {
font-size: 0.85rem;
line-height: 1.4;
}
.api-operation-summary {
font-size: 0.8rem;
line-height: 1.5;
}
}
}
// Overview/Description section
.api-description {
margin: 2rem 0;
color: $article-text !important; // Override any inherited black color
h2 {
margin-bottom: 1rem;
}
// Ensure description text is visible and readable
p, ul, ol, pre, code {
color: $article-text !important;
opacity: 1;
}
// Also ensure direct text nodes use correct color
& > * {
color: $article-text !important;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////// API Navigation in Sidebar ///////////////////////////
////////////////////////////////////////////////////////////////////////////////
// API navigation wrapper - controls visibility
// Hidden by default, revealed via JS (localStorage) or on API pages
.api-nav-wrapper {
display: none; // Hidden by default
&.is-revealed {
display: block; // Revealed via JS
}
// Always show on API pages (server-rendered with .api-reference class)
.api-reference & {
display: block;
}
}
// API navigation section added to the existing Hugo sidebar
.api-nav {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid $nav-border;
&-header {
font-size: 0.85rem;
font-weight: $bold;
text-transform: uppercase;
letter-spacing: 0.06rem;
color: rgba($article-heading, 0.6);
margin: 0 0 1rem;
padding-left: 1.5rem;
}
// API nav groups (collapsible sections)
&-group {
margin-bottom: 0.5rem;
&-header {
display: flex;
align-items: center;
padding: 0.5rem 0 0.5rem 1.5rem;
font-weight: $medium;
color: $nav-category;
cursor: pointer;
transition: color 0.2s;
// Button reset for dark mode compatibility
background: none;
border: none;
width: 100%;
text-align: left;
font-size: 1.2rem; // Match sidebar .nav-category > a (19.2px)
font-family: inherit;
text-decoration: none; // For anchor version
&:hover {
color: $nav-category-hover;
}
&.is-active {
color: $nav-active;
}
// Collapse/expand indicator (for button headers)
&::before {
content: "";
display: inline-block;
width: 0;
height: 0;
margin-right: 0.5rem;
border-left: 5px solid $nav-border;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
transition: transform 0.2s;
flex-shrink: 0;
}
&.is-open::before {
transform: rotate(90deg);
}
}
// For anchor headers, keep the ::before arrow (same as button)
// No special handling needed - anchor headers look the same as button headers
a#{&}-header {
// Same styling as button, arrow works via ::before
}
&-items {
list-style: none;
padding-left: 2.5rem;
margin: 0;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
background: $body-bg; // Match sidebar background
&.is-open {
max-height: 2000px; // Large enough to show all operations
}
}
}
// Individual API nav items
&-item {
margin: 0.25rem 0;
position: relative;
a {
display: flex;
align-items: center;
padding: 0.35rem 0;
color: $nav-item;
text-decoration: none;
font-size: 0.95rem;
transition: color 0.2s;
&:hover {
color: $nav-item-hover;
}
}
&.is-active a {
color: $nav-active;
font-weight: $medium;
}
// HTTP method badge (legacy class)
.method-badge {
display: inline-block;
font-size: 0.65rem;
font-weight: $bold;
text-transform: uppercase;
padding: 0.15rem 0.35rem;
margin-right: 0.5rem;
border-radius: 3px;
min-width: 2.5rem;
text-align: center;
&.get { background-color: $gr-rainforest; color: #fff; }
&.post { background-color: $b-ocean; color: #fff; }
&.put { background-color: $br-galaxy; color: #fff; }
&.patch { background-color: $y-thunder; color: rgba($g5-pepper, 0.75); }
&.delete { background-color: $r-curacao; color: #fff; }
}
// Tag items that link to tag pages
&.api-nav-tag {
> a {
font-weight: $medium;
}
// Nested operations list under tag
.api-nav-operations {
list-style: none;
margin: 0.25rem 0 0.5rem;
padding-left: 0.75rem;
.api-nav-operation {
margin: 0.15rem 0;
a {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.85rem;
padding: 0.25rem 0;
}
}
}
}
// Operation items with method badges
&.api-nav-operation,
.api-nav-operation {
.api-method {
display: inline-block;
font-size: 0.55rem;
font-weight: $bold;
text-transform: uppercase;
padding: 0.1rem 0.25rem;
border-radius: 3px;
min-width: 2rem;
text-align: center;
flex-shrink: 0;
&--get { background-color: $b-pool; color: #fff; } // #00A3FF - bright brand blue
&--post { background-color: $gr-rainforest; color: #fff; } // #34BB55 - bright brand green
&--put { background-color: $y-pineapple; color: #fff; } // #FFB94A - bright yellow (distinct from red)
&--patch { background-color: $br-new-purple; color: #fff; } // #9b2aff - distinctive brand purple
&--delete { background-color: $r-curacao; color: #fff; } // #F95F53 - bright brand red
}
.api-path {
font-family: $code;
font-size: 0.85rem;
word-break: break-all;
color: inherit;
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// API Header with Actions ////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Header row with title and download button
.article--header-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
flex-wrap: wrap;
}
.article--header-text {
flex: 1 1 100%; // Take full width, allowing download button to wrap
min-width: 0;
}
// Summary paragraph in header - ensure full width
.article--summary {
max-width: none;
width: 100%;
}
// Download OpenAPI spec button
.api-spec-actions {
flex-shrink: 0;
}
.api-spec-download {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: $g20-white;
color: $article-text;
text-decoration: none;
border-radius: $radius;
font-size: 0.875rem;
font-weight: $medium;
transition: background-color 0.2s, color 0.2s;
border: 1px solid $nav-border;
white-space: nowrap;
&:hover {
background-color: $r-curacao;
color: $g20-white;
border-color: $r-curacao;
}
svg {
flex-shrink: 0;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////// API Tabs ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// API-specific tab wrapper (uses api-tabs-wrapper to avoid conflict with
// tabbed-content.js which handles .tabs-wrapper elements)
.api-tabs-wrapper {
margin: 1.5rem 0 1rem;
}
// API tab navigation bar
.api-tabs-nav {
display: flex;
flex-wrap: wrap;
gap: 2px;
a {
flex-grow: 1;
position: relative;
font-size: 1rem;
font-weight: $medium;
padding: 0.65rem 1.25rem;
display: inline-block;
white-space: nowrap;
text-align: center;
color: $article-tab-text !important;
border-radius: $radius;
background-color: $article-tab-bg;
text-decoration: none;
transition: background-color 0.2s, color 0.2s;
z-index: 1;
&::after {
content: '';
position: absolute;
display: block;
top: 0;
right: 0;
width: 100%;
height: 100%;
border-radius: $radius;
@include gradient($article-btn-gradient);
opacity: 0;
transition: opacity 0.2s;
z-index: -1;
}
&:hover {
color: $article-tab-active-text !important;
&::after {
opacity: 1;
}
}
&.is-active {
color: $article-tab-active-text !important;
&::after {
opacity: 1;
@include gradient($article-btn-gradient);
}
}
}
}
// Tab panels container
.api-tab-panels {
// Tab content visibility (follows existing pattern)
.tab-content:not(:first-of-type) {
display: none;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////// Authentication Tab Content //////////////////////////
////////////////////////////////////////////////////////////////////////////////
.api-auth-content {
max-width: 800px;
}
.api-auth-card {
background: $article-bg;
border: 1px solid $nav-border;
border-radius: $radius;
padding: 1.5rem;
margin-bottom: 1.5rem;
h3 {
margin-top: 0;
margin-bottom: 0.5rem;
}
h4 {
margin-top: 1rem;
margin-bottom: 0.5rem;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: rgba($article-text, 0.6);
}
pre {
margin: 0.5rem 0;
padding: 1rem;
background: $article-code-bg;
border-radius: $radius;
overflow-x: auto;
}
code {
font-family: $code;
font-size: 0.875rem;
}
}
.api-auth-badge .badge {
display: inline-block;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: $bold;
text-transform: uppercase;
border-radius: $radius;
&.recommended {
background: $gr-rainforest;
color: $g20-white;
}
}
////////////////////////////////////////////////////////////////////////////////
//////////////////////////// Server Tab Content ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
.api-server-panel {
max-width: 600px;
h2 {
margin-top: 0;
}
}
.server-url-config {
display: flex;
gap: 0.5rem;
align-items: flex-end;
margin: 1rem 0;
flex-wrap: wrap;
label {
width: 100%;
font-weight: $medium;
margin-bottom: 0.25rem;
}
input {
flex: 1;
min-width: 200px;
padding: 0.5rem;
border: 1px solid $nav-border;
border-radius: $radius;
font-family: $code;
background: $article-bg;
color: $article-text;
}
button {
padding: 0.5rem 1rem;
background: $r-curacao;
color: $g20-white;
border: none;
border-radius: $radius;
cursor: pointer;
font-weight: $medium;
&:hover {
background: darken($r-curacao, 10%);
}
}
}
.server-info {
margin-top: 1.5rem;
ul {
list-style: disc;
padding-left: 1.5rem;
}
li {
margin: 0.5rem 0;
}
code {
background: $article-code-bg;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: $code;
}
}
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////// MEDIA QUERIES ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Tablet: Hide TOC, keep sidebar
@include media(large) {
.content-wrapper.api-content {
flex-direction: column;
width: 75%; // Reset to default when TOC is hidden
}
.api-toc {
display: none;
}
.api-main {
padding-right: 0;
}
}
// Mobile: Standard Hugo sidebar behavior
@include media(medium) {
.content-wrapper.api-content {
flex-direction: column;
}
.api-toc {
display: none;
}
.api-main {
padding-right: 0;
}
// Collapse API nav in mobile view
.api-nav {
margin-top: 1rem;
padding-top: 0.5rem;
&-group-items {
max-height: none; // Show all items by default in mobile
}
}
}
// Large screens: Wider TOC
@include media(xlarge) {
.api-toc {
width: 240px;
}
}
// Compressed layout: narrower TOC, drop border to reduce visual clutter
// TOC is hidden at 1280px (large breakpoint), so this targets the
// narrow window where the TOC is visible but space is tight.
@media (min-width: 1281px) and (max-width: 1535px) {
.api-toc {
width: 180px;
border-left: none;
}
}

View File

@ -0,0 +1,540 @@
// API Operations Styles
// Renders OpenAPI operations, parameters, schemas, and responses
// Variables
$api-border-radius: 6px;
$api-spacing-sm: 0.5rem;
$api-spacing-md: 1rem;
$api-spacing-lg: 1.5rem;
$api-spacing-xl: 2rem;
// Method colors
$method-get: #00A3FF;
$method-post: #34BB55;
$method-put: #FFB94A;
$method-delete: #D63031;
$method-patch: #9b2aff;
// Status code colors intentionally distinct from method colors
$status-success: #34BB55;
$status-redirect: #FFB94A;
$status-client-error: #E17055;
$status-server-error: #9b2aff;
// ============================================
// Operation Block
// ============================================
.api-hugo-native {
width: 100%;
}
.api-operation {
margin-bottom: $api-spacing-xl;
padding-top: $api-spacing-xl;
border-top: 2px solid $nav-border;
// Keep inline code proportional to surrounding text
code {
font-size: inherit;
}
&:first-child {
border-top: none;
padding-top: 0;
}
&:target {
animation: highlight-operation 1.5s ease-out;
}
}
@keyframes highlight-operation {
0% {
outline: 2px solid rgba($method-get, 0.4);
outline-offset: 8px;
}
100% {
outline-color: transparent;
}
}
// Operation Header
.api-operation-header {
margin-bottom: $api-spacing-md;
}
.api-operation-endpoint {
display: flex;
align-items: center;
gap: $api-spacing-sm;
margin-bottom: $api-spacing-md;
padding-bottom: $api-spacing-md;
border-bottom: 1px solid $nav-border;
}
.api-method {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
border-radius: 4px;
color: #fff;
&--get { background-color: $method-get; }
&--post { background-color: $method-post; }
&--put { background-color: $method-put; }
&--delete { background-color: $method-delete; }
&--patch { background-color: $method-patch; }
}
.api-path {
font-family: $code;
font-size: 0.95rem;
color: $article-text;
background: $article-code-bg;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.api-operation-summary {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: $article-heading;
}
.api-operation-description {
margin: $api-spacing-md 0;
color: $article-text;
line-height: 1.6;
p:last-child {
margin-bottom: 0;
}
}
// ============================================
// Section Titles
// ============================================
.api-section-title {
display: flex;
align-items: center;
gap: $api-spacing-sm;
margin: $api-spacing-lg 0 $api-spacing-md;
padding-bottom: $api-spacing-sm;
font-size: 1rem;
font-weight: 600;
color: $article-heading;
border-bottom: 1px solid $nav-border;
}
// ============================================
// Parameters Section
// ============================================
.api-parameters {
margin: $api-spacing-lg 0;
}
.api-param-group {
margin-bottom: $api-spacing-md;
}
.api-param-group-title {
margin: $api-spacing-sm 0;
font-size: 0.85rem;
font-weight: 500;
color: rgba($article-text, 0.6);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.api-param-list {
// Flat list no outer border, rows separated by dividers
}
.api-param-row {
padding: $api-spacing-md 0;
border-bottom: 1px solid $nav-border;
&:last-child {
border-bottom: none;
}
}
.api-param-name-line {
display: flex;
align-items: center;
gap: $api-spacing-sm;
margin-bottom: 0.25rem;
}
.api-param-name {
font-family: $code;
font-size: 0.9rem;
font-weight: 600;
color: $article-heading;
}
.api-param-type {
font-size: 0.8rem;
color: rgba($article-text, 0.6);
}
.api-param-description {
margin-top: 0.25rem;
color: $article-text;
line-height: 1.5;
p {
margin: 0;
}
}
.api-param-enum,
.api-param-default {
margin-top: 0.5rem;
font-size: 0.85rem;
}
.api-param-enum-label,
.api-param-default-label {
color: rgba($article-text, 0.6);
}
.api-param-enum-value,
.api-param-default-value {
font-family: $code;
font-size: 0.8rem;
background: $article-code-bg;
padding: 0.125rem 0.375rem;
border-radius: 3px;
}
// ============================================
// Badges
// ============================================
.api-badge {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.375rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
border-radius: 3px;
&--required {
background: rgba($method-delete, 0.1);
color: $method-delete;
}
}
// ============================================
// Request Body Section
// ============================================
.api-request-body {
margin: $api-spacing-lg 0;
}
.api-request-body-description {
margin: $api-spacing-sm 0;
color: $article-text;
p:last-child {
margin-bottom: 0;
}
}
.api-content-type {
margin: $api-spacing-sm 0;
font-size: 0.85rem;
code {
font-family: $code;
background: $article-code-bg;
padding: 0.125rem 0.375rem;
border-radius: 3px;
}
}
.api-content-type-label {
color: rgba($article-text, 0.6);
}
// ============================================
// Schema Section
// ============================================
.api-schema {
margin: $api-spacing-md 0;
&--nested {
margin-left: $api-spacing-lg;
padding-left: $api-spacing-md;
border-left: 2px solid $nav-border;
}
}
.api-schema-properties {
// Flat list no outer border, rows separated by dividers
}
.api-schema-property {
padding: $api-spacing-md 0;
border-bottom: 1px solid $nav-border;
&:last-child {
border-bottom: none;
}
&--required {
.api-schema-property-name {
&::after {
content: '*';
color: $method-delete;
margin-left: 0.25rem;
}
}
}
}
.api-schema-property-header {
display: flex;
align-items: center;
gap: $api-spacing-sm;
margin-bottom: 0.25rem;
}
.api-schema-property-name {
font-family: $code;
font-size: 0.9rem;
font-weight: 600;
color: $article-heading;
}
.api-schema-property-type {
font-size: 0.8rem;
color: rgba($article-text, 0.6);
}
.api-schema-property-description {
margin-top: 0.25rem;
color: $article-text;
line-height: 1.5;
p {
margin: 0;
}
}
.api-schema-property-enum,
.api-schema-property-default,
.api-schema-property-example {
margin-top: 0.5rem;
font-size: 0.85rem;
}
.api-enum-label,
.api-default-label,
.api-example-label {
color: rgba($article-text, 0.6);
}
.api-enum-value,
.api-default-value,
.api-example-value {
font-family: $code;
font-size: 0.8rem;
background: $article-code-bg;
padding: 0.125rem 0.375rem;
border-radius: 3px;
}
// Schema Example Block
.api-schema-example {
margin-top: $api-spacing-md;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: $api-border-radius;
overflow: hidden;
.dark-theme & {
border-color: rgba(255, 255, 255, 0.1);
}
}
.api-schema-example-title {
display: block;
margin: 0;
padding: $api-spacing-sm $api-spacing-md;
font-size: 0.85rem;
font-weight: 600;
background: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.dark-theme & {
background: rgba(255, 255, 255, 0.03);
border-bottom-color: rgba(255, 255, 255, 0.1);
}
}
pre.api-schema-example-code {
margin: 0;
padding: $api-spacing-sm $api-spacing-md;
background: $article-code-bg;
overflow-x: auto;
code {
font-family: $code;
font-size: 0.85rem;
}
}
// ============================================
// Responses Section
// ============================================
.api-responses {
margin: $api-spacing-lg 0;
}
.api-response-list {
display: flex;
flex-direction: column;
gap: $api-spacing-sm;
}
.api-response {
padding: $api-spacing-sm 0;
}
.api-response-header {
display: flex;
align-items: center;
gap: $api-spacing-sm;
}
.api-response-status {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 3rem;
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
font-weight: 600;
border-radius: 4px;
color: #fff;
&--success { background-color: $status-success; }
&--redirect { background-color: $status-redirect; }
&--client-error { background-color: $status-client-error; }
&--server-error { background-color: $status-server-error; }
&--info { background-color: rgba($article-text, 0.6); }
}
.api-response-description {
color: $article-text;
}
.api-response-body {
margin-top: $api-spacing-sm;
margin-left: $api-spacing-lg;
padding: $api-spacing-sm 0 $api-spacing-sm $api-spacing-md;
border-left: 2px solid rgba($article-text, 0.1);
}
// ============================================
// Tag Overview
// ============================================
.api-tag-overview {
margin-bottom: $api-spacing-xl;
padding-bottom: $api-spacing-lg;
border-bottom: 1px solid $nav-border;
}
.api-tag-description {
color: $article-text;
line-height: 1.7;
h4, h5 {
margin-top: $api-spacing-lg;
margin-bottom: $api-spacing-sm;
color: $article-heading;
}
ul, ol {
padding-left: $api-spacing-lg;
}
a {
color: $article-link;
&:hover {
text-decoration: underline;
}
}
}
// ============================================
// Related Guides Section
// ============================================
.api-related-guides {
margin-top: $api-spacing-xl;
padding-top: $api-spacing-lg;
border-top: 1px solid $nav-border;
}
.api-related-title {
margin: 0 0 $api-spacing-md;
font-size: 1rem;
font-weight: 600;
color: $article-heading;
}
.api-related-list {
margin: 0;
padding: 0;
list-style: none;
li {
margin-bottom: $api-spacing-sm;
}
a {
color: $article-link;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
// ============================================
// Responsive Adjustments
// ============================================
@media (max-width: 768px) {
.api-operation-endpoint {
flex-wrap: wrap;
}
.api-path {
font-size: 0.85rem;
word-break: break-all;
}
.api-param-name-line,
.api-schema-property-header {
flex-wrap: wrap;
}
}

View File

@ -1,9 +1,16 @@
////////////////////////////////////////////////////////////////////////////////
// API Documentation Style Overrides
//
// Provides loading spinner and reusable HTTP method badge colors.
// Used by Hugo-native API templates for consistent styling.
////////////////////////////////////////////////////////////////////////////////
@import "tools/color-palette";
@import "tools/fonts";
// Fonts
$proxima: 'Proxima Nova', sans-serif;
$code: 'IBM Plex Mono', monospace;;
$code: 'IBM Plex Mono', monospace;
// Font weights
$medium: 500;
@ -22,7 +29,7 @@ $bold: 700;
}
@keyframes spinner {
to {transform: rotate(360deg);}
to { transform: rotate(360deg); }
}
.spinner:before {
@ -41,256 +48,15 @@ $bold: 700;
animation: spinner .6s linear infinite;
}
//////////////////////////////// InfluxDB Header ///////////////////////////////
#influx-header {
font-family: $proxima;
padding: 10px ;
display: flex;
align-items: center;
justify-content: space-between;
background-color: $g2-kevlar;
a {
text-decoration: none;
&.back {
color: $g20-white;
transition: color .2s;
&:hover {
color: $b-pool;
}
&:before {
content: "\e919";
font-family: 'icomoon-v2';
margin-right: .65rem;
}
}
&.btn {
padding: .5rem .75rem .5rem .65rem;
font-size: .85rem;
font-weight: 500;
color: $g15-platinum;
background: $g5-pepper;
border-radius: 4.5px;
transition: all .2s;
&:before {
content: "\e934";
display: inline-block;
font-size: .95rem;
margin-right: .5rem;
font-family: 'icomoon-v2';
}
&:hover {
color: $g20-white;
background: $b-pool;
}
}
}
}
// Header Media Queries
@media (max-width: 600px) {
#influx-header span.version {display: none;}
}
////////////////////////////////////////////////////////////////////////////////
/////////////////////////// HTTP Method Badge Colors ///////////////////////////
////////////////////////////////////////////////////////////////////////////////
.cjtbAK {
h1,h2,h3,h4,h5,h6,
p,li,th,td {
font-family: $proxima !important;
}
}
#redoc {
h1,h2,h3 {
font-weight: $medium !important;
}
}
// Section title padding
.dluJDj {
padding: 20px 0;
}
// Page h1
.dTJWQH {
color: $g7-graphite;
font-size: 2rem;
}
// Download button
.jIdpVJ {
background: $b-dodger;
color: $g20-white;
border: none;
border-radius: 3px;
font-family: $proxima;
font-size: .85rem;
font-weight: $medium;
transition: background-color .2s;
&:hover {
background-color: $b-pool;
}
}
// Tag h1s
.WxWXp {
color: $g7-graphite;
font-size: 1.75rem;
}
// Summaru h2s and table headers
.ioYTqA, .bxcHYI, .hoUoen {
color: $g7-graphite;
}
// h3s
.espozG {
color: $g8-storm;
}
// Links
.bnFPhO a { color: $b-dodger;
&:visited {color: $b-dodger;}
}
.redoc-json {
font-family: $code !important;
}
// Inline Code
.flfxUM code,
.gDsWLk code,
.kTVySD {
font-family: $code !important;
color: $cp-marguerite;
background: $cp-titan;
border-color: $cp-titan;
}
// Required tags
.jsTAxL {
color: $r-curacao;
}
///////////////////////////// RESPONSE COLOR BLOCKS ////////////////////////////
// Green
.hLVzSF, .fDvFMp {
background-color: rgba($gr-honeydew, .2);
color: $gr-emerald;
}
// Red
.byLrBg {
background-color: rgba($r-curacao, .1);
color: $r-curacao;
}
/////////////////////////////////// LEFT NAV ///////////////////////////////////
// Left nav background
.gZdDsM {
background-color: $g19-ghost;
}
.gpbcFk:hover, .sc-eTuwsz.active {
background-color: $g17-whisper;
}
// List item text
.SmuWE, .gcUzvG, .bbViyS, .sc-hrWEMg label {
font-family: $proxima !important;
}
.fyUykq {
font-weight: $medium;
}
// Request method tags
.cFwMcp {
&.post { background-color: $b-ocean; }
&.get { background-color: $gr-rainforest; }
&.put { background-color: $br-galaxy; }
&.patch { background-color: $y-thunder; color: rgba($g5-pepper, .75);}
&.delete { background-color: $r-curacao; }
}
// Active nav section
.gcUzvG, .iNzLCk:hover {
color: $br-magenta;
}
/////////////////////////////// RIGHT CODE COLUMN //////////////////////////////
// Right column backgrounds
.dtUibw, .fLUKgj {
background-color: $g2-kevlar;
h3,h4,h5,h6 {
font-family: $proxima !important;
font-weight: $medium !important;
}
}
// Code backgrounds
.irpqyy > .react-tabs__tab-panel {
background-color: $g0-obsidian;
}
.dHLKeu, .fVaxnA {
padding-left: 10px;
background-color: $g0-obsidian;
}
// Response code tabs
.irpqyy > ul > li {
background-color: $g0-obsidian;
border-radius: 3px;
&.react-tabs__tab--selected{ color: $br-pulsar;}
&.tab-error { color: $r-fire; }
&.tab-success { color: $gr-viridian; }
}
// Request methods
.bNYCAJ,
.jBjYbV,
.hOczRB,
.fRsrDc,
.hPskZd {
font-family: $proxima;
font-weight: $medium;
letter-spacing: .04em;
border-radius: 3px;
}
.bNYCAJ { background-color: $b-ocean; } /* Post */
.jBjYbV { background-color: $gr-viridian; } /* Get */
.hOczRB { background-color: $br-galaxy; } /* Put */
.fRsrDc { background-color: $y-thunder; color: $g5-pepper; } /* Patch */
.hPskZd { background-color: $r-curacao; } /* Delete */
// Content type block
.gzAoUb {
background-color: $g2-kevlar;
font-family: $proxima;
}
.iENVAs { font-family: $code; }
.dpMbau { font-family: $proxima; }
// Code controls
.fCJmC {
font-family: $proxima;
span { border-radius: 3px; }
}
// Code blocks
.kZHJcC { font-family: $code; }
.jCgylq {
.token.string {
color: $gr-honeydew;
& + a { color: $b-pool; }
}
.token.boolean { color: #f955b0; }
}
// Reusable method badge colors (used by _api-layout.scss .method-badge)
// These follow standard REST API color conventions
$method-get: $gr-rainforest;
$method-post: $b-ocean;
$method-put: $br-galaxy;
$method-patch: $y-thunder;
$method-delete: $r-curacao;

View File

@ -0,0 +1,92 @@
////////////////////////////////////////////////////////////////////////////////
// API Security Schemes Styling
//
// Styles for security schemes sections displayed on conceptual API pages
// (like Authentication). These sections are rendered from OpenAPI spec
// securitySchemes using Hugo templates.
////////////////////////////////////////////////////////////////////////////////
.api-security-schemes {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid $g5-pepper;
h2 {
margin-bottom: 1.5rem;
}
.security-scheme {
margin-bottom: 2rem;
h3 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: $article-heading;
}
}
.scheme-details {
margin-bottom: 1rem;
dl {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.5rem 1rem;
margin: 0;
}
dt {
font-weight: 600;
color: $g9-mountain;
}
dd {
margin: 0;
code {
background: $article-code-bg;
color: $article-code;
padding: 0.2em 0.5em;
border-radius: 3px;
font-size: 0.9em;
}
}
}
.scheme-description {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid $g5-pepper;
p:first-child {
margin-top: 0;
}
pre {
margin: 1rem 0;
}
}
}
// Dark theme overrides for security schemes
[data-theme="dark"],
html:has(link[title="dark-theme"]:not([disabled])) {
.api-security-schemes {
border-top-color: $grey25;
.security-scheme {
// Removed background and border - now using plain styling
}
.scheme-details {
dt {
color: $g15-platinum;
}
}
.scheme-description {
border-top-color: $grey25;
}
}
}

View File

@ -255,6 +255,66 @@
}
}
}
// API operation items within Hugo menu
.api-operation {
a {
display: flex;
align-items: center;
gap: 0.4rem;
}
// Path-based operation display (All endpoints list)
&--path .api-path {
font-family: $proxima;
font-size: 0.9rem;
color: inherit;
word-break: break-all;
}
}
.api-method {
font-size: 0.6rem;
font-weight: 700;
padding: 0.15rem 0.35rem;
border-radius: 3px;
text-transform: uppercase;
flex-shrink: 0;
line-height: 1;
border: 2px solid;
background-color: transparent;
// Using lighter InfluxData brand colors - bordered style for readability
&--get { border-color: $b-pool; color: $b-pool; } // #00A3FF - bright brand blue
&--post { border-color: $gr-rainforest; color: $gr-rainforest; } // #34BB55 - bright brand green
&--put { border-color: $y-pineapple; color: $y-pineapple; } // #FFB94A - bright yellow (distinct from red)
&--delete { border-color: $r-curacao; color: $r-curacao; } // #F95F53 - bright brand red
&--patch { border-color: $br-new-purple; color: $br-new-purple; } // #9b2aff - distinctive brand purple
}
// Compatibility version badge (v1 or v2)
.api-compat-badge {
font-size: 0.55rem;
font-weight: 600;
padding: 0.1rem 0.3rem;
border-radius: 3px;
text-transform: uppercase;
flex-shrink: 0;
line-height: 1;
margin-left: auto;
opacity: 0.8;
cursor: help;
&--v1 { background: #8b5cf6; color: white; } // Purple for v1
&--v2 { background: #06b6d4; color: white; } // Cyan for v2
}
// Non-link group labels (for multi-tag groups)
.nav-group-label {
color: $nav-item;
font-weight: $medium;
display: inline-block;
}
}
.feature-board-badge {

View File

@ -32,7 +32,11 @@
"layouts/v1-overrides",
"layouts/notifications",
"layouts/code-controls",
"layouts/v3-wayfinding";
"layouts/v3-wayfinding",
"layouts/api-layout",
"layouts/api-security-schemes",
"layouts/api-operations",
"layouts/api-code-samples";
// Import Components
@import "components/influxdb-version-detector",

View File

@ -1,17 +0,0 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API provides a programmatic interface for interactions with InfluxDB, such as writing and querying data,
and managing resources within an InfluxDB instance.
Access the InfluxDB API using the `/api/v2/` endpoint.
menu:
influxdb_cloud:
parent: Reference
name: InfluxDB HTTP API
weight: 3
influxdb/cloud/tags: [api]
source: /shared/influxdb-v2/reference/api/_index.md
---
<!-- The content of this file is at
// SOURCE content/shared/influxdb-v2/reference/api/_index.md-->

View File

@ -1,22 +0,0 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API provides a programmatic interface for interactions with InfluxDB, such as writing and querying data,
and managing resources within an InfluxDB instance.
Access the InfluxDB API using the `/api/v2/` or InfluxDB v1 endpoints.
menu:
influxdb_v2:
parent: Reference
name: InfluxDB HTTP API
weight: 3
influxdb/v2/tags: [api]
aliases:
- /influxdb/v2/concepts/api/
related:
- /influxdb/v2/api-guide/api_intro/
- /influxdb/v2/api-guide/influxdb-1x/
source: /shared/influxdb-v2/reference/api/_index.md
---
<!-- The content for this file is located at
// SOURCE content/shared/influxdb-v2/reference/api/_index.md -->

View File

@ -0,0 +1,27 @@
---
title: InfluxDB HTTP API
description: >-
Use the InfluxDB HTTP API to write data, query data, and manage databases,
tables, and tokens.
weight: 104
type: api
articleDataKey: influxdb3-cloud-dedicated
articleSection: management-api
menu:
influxdb3_cloud_dedicated:
name: InfluxDB HTTP API
identifier: api-reference-influxdb3-cloud-dedicated-management-api
parent: Reference
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---
Use the {{% product-name %}} HTTP API to write data, query data, and manage databases, tables, and tokens.
{{< children >}}

View File

@ -0,0 +1,25 @@
---
title: All endpoints
description: View all API endpoints sorted by path.
type: api
layout: all-endpoints
weight: 999
isAllEndpoints: true
articleDataKey: influxdb3-cloud-dedicated
articleSection: management-api
menu:
influxdb3_cloud_dedicated:
name: All endpoints
identifier: all-endpoints-influxdb3-cloud-dedicated-management-api
parent: InfluxDB HTTP API
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---
All {{% product-name %}} API endpoints, sorted by path.

View File

@ -1,41 +0,0 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API provides a programmatic interface for interactions with
InfluxDB, such as writing and querying data, and managing an InfluxDB cluster.
Access the InfluxDB API using the `/api/v2/write`, InfluxDB v1, or
Management API endpoints for InfluxDB Cloud Dedicated.
menu:
influxdb3_cloud_dedicated:
parent: Reference
name: InfluxDB HTTP API
weight: 105
influxdb3/cloud-dedicated/tags: [api]
---
The InfluxDB HTTP API provides a programmatic interface for interactions with
{{% product-name %}}, such as writing and querying data, and managing an InfluxDB cluster.
Access the InfluxDB HTTP API using the `/api/v2/` endpoint, InfluxDB v1 endpoints, or
Management API endpoints for {{% product-name %}}.
## InfluxDB v2 Compatibility API reference documentation
<a class="btn" href="/influxdb3/cloud-dedicated/api/v2/">InfluxDB v2 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v2-compatible
endpoints that work with {{% product-name %}} and with InfluxDB 2.x client
libraries and third-party integrations.
## InfluxDB v1 Compatibility API reference documentation
<a class="btn" href="/influxdb3/cloud-dedicated/api/v1/">InfluxDB v1 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v1-compatible `/write` and `/query` endpoints that work with {{% product-name %}} and with InfluxDB 1.x client libraries and third-party integrations.
## InfluxDB Management API reference documentation
<a class="btn" href="/influxdb3/cloud-dedicated/api/management/">InfluxDB Management API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB Management API endpoints.
The Management API lets cluster administrators manage resources such as databases, partitioning templates, and database tokens.

View File

@ -0,0 +1,27 @@
---
title: InfluxDB HTTP API
description: >-
Use the InfluxDB HTTP API to write data, query data, and manage databases,
tables, and tokens.
weight: 104
type: api
articleDataKey: influxdb3-cloud-serverless
articleSection: api
menu:
influxdb3_cloud_serverless:
name: InfluxDB HTTP API
identifier: api-reference-influxdb3-cloud-serverless-api
parent: Reference
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---
Use the {{% product-name %}} HTTP API to write data, query data, and manage databases, tables, and tokens.
{{< children >}}

View File

@ -0,0 +1,28 @@
---
title: API compatibility
description: >-
Overview of the InfluxDB v1 and v2 compatible write and query endpoints
available in InfluxDB 3 Cloud Serverless.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-api-compatibility.yaml
weight: 100
tag: API compatibility
isConceptual: true
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Overview of the InfluxDB v1 and v2 compatible write and query endpoints
available in InfluxDB 3 Cloud Serverless.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,39 @@
---
title: Authentication
description: >-
Use one of the following schemes to authenticate to the InfluxDB 3 Cloud
Serverless API:
- Token authentication
- Basic authentication
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-authentication.yaml
weight: 100
tag: Authentication
isConceptual: true
menuGroup: Concepts
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Use one of the following schemes to authenticate to the InfluxDB 3 Cloud
Serverless API:
- Token authentication
- Basic authentication
showSecuritySchemes: true
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,61 @@
---
title: Authorizations (API tokens)
description: >-
Create and manage API token authorizations that grant read and write
permissions to InfluxDB 3 Cloud Serverless organization resources.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-authorizations-api-tokens.yaml
weight: 100
tag: Authorizations (API tokens)
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetAuthorizations
method: GET
path: /api/v2/authorizations
summary: List authorizations
tags:
- Authorizations (API tokens)
- operationId: PostAuthorizations
method: POST
path: /api/v2/authorizations
summary: Create an authorization
tags:
- Authorizations (API tokens)
- operationId: GetAuthorizationsID
method: GET
path: /api/v2/authorizations/{authID}
summary: Retrieve an authorization
tags:
- Authorizations (API tokens)
- operationId: PatchAuthorizationsID
method: PATCH
path: /api/v2/authorizations/{authID}
summary: Update an API token to be active or inactive
tags:
- Authorizations (API tokens)
- operationId: DeleteAuthorizationsID
method: DELETE
path: /api/v2/authorizations/{authID}
summary: Delete an authorization
tags:
- Authorizations (API tokens)
related:
- title: Manage API tokens
href: /influxdb3/cloud-serverless/security/tokens/
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,53 @@
---
title: Bucket Schemas
description: >-
Manage explicit schemas for InfluxDB 3 Cloud Serverless buckets to enforce
column names and data types.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-bucket-schemas.yaml
weight: 100
tag: Bucket Schemas
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: getMeasurementSchemas
method: GET
path: /api/v2/buckets/{bucketID}/schema/measurements
summary: List measurement schemas of a bucket
tags:
- Bucket Schemas
- operationId: createMeasurementSchema
method: POST
path: /api/v2/buckets/{bucketID}/schema/measurements
summary: Create a measurement schema for a bucket
tags:
- Bucket Schemas
- operationId: getMeasurementSchema
method: GET
path: /api/v2/buckets/{bucketID}/schema/measurements/{measurementID}
summary: Retrieve a measurement schema
tags:
- Bucket Schemas
- operationId: updateMeasurementSchema
method: PATCH
path: /api/v2/buckets/{bucketID}/schema/measurements/{measurementID}
summary: Update a measurement schema
tags:
- Bucket Schemas
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,115 @@
---
title: Buckets
description: >-
Create and manage named storage locations (buckets) in InfluxDB 3 Cloud
Serverless, each with a configurable retention period.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-buckets.yaml
weight: 100
tag: Buckets
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetBuckets
method: GET
path: /api/v2/buckets
summary: List buckets
tags:
- Buckets
- operationId: PostBuckets
method: POST
path: /api/v2/buckets
summary: Create a bucket
tags:
- Buckets
- operationId: GetBucketsID
method: GET
path: /api/v2/buckets/{bucketID}
summary: Retrieve a bucket
tags:
- Buckets
- operationId: PatchBucketsID
method: PATCH
path: /api/v2/buckets/{bucketID}
summary: Update a bucket
tags:
- Buckets
- operationId: DeleteBucketsID
method: DELETE
path: /api/v2/buckets/{bucketID}
summary: Delete a bucket
tags:
- Buckets
- operationId: GetBucketsIDLabels
method: GET
path: /api/v2/buckets/{bucketID}/labels
summary: List all labels for a bucket
tags:
- Buckets
- operationId: PostBucketsIDLabels
method: POST
path: /api/v2/buckets/{bucketID}/labels
summary: Add a label to a bucket
tags:
- Buckets
- operationId: DeleteBucketsIDLabelsID
method: DELETE
path: /api/v2/buckets/{bucketID}/labels/{labelID}
summary: Delete a label from a bucket
tags:
- Buckets
- operationId: GetBucketsIDMembers
method: GET
path: /api/v2/buckets/{bucketID}/members
summary: List all users with member privileges for a bucket
tags:
- Buckets
- operationId: PostBucketsIDMembers
method: POST
path: /api/v2/buckets/{bucketID}/members
summary: Add a member to a bucket
tags:
- Buckets
- operationId: DeleteBucketsIDMembersID
method: DELETE
path: /api/v2/buckets/{bucketID}/members/{userID}
summary: Remove a member from a bucket
tags:
- Buckets
- operationId: GetBucketsIDOwners
method: GET
path: /api/v2/buckets/{bucketID}/owners
summary: List all owners of a bucket
tags:
- Buckets
- operationId: PostBucketsIDOwners
method: POST
path: /api/v2/buckets/{bucketID}/owners
summary: Add an owner to a bucket
tags:
- Buckets
- operationId: DeleteBucketsIDOwnersID
method: DELETE
path: /api/v2/buckets/{bucketID}/owners/{userID}
summary: Remove an owner from a bucket
tags:
- Buckets
related:
- title: Manage buckets
href: /influxdb3/cloud-serverless/admin/buckets/
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,28 @@
---
title: Common parameters
description: >-
Common query parameters used by InfluxDB 3 Cloud Serverless API endpoints,
including `bucket`, `bucketID`, `org`, and `orgID`.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-common-parameters.yaml
weight: 100
tag: Common parameters
isConceptual: true
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Common query parameters used by InfluxDB 3 Cloud Serverless API endpoints,
including `bucket`, `bucketID`, `org`, and `orgID`.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,60 @@
---
title: DBRPs
description: >-
Manage database and retention policy (DBRP) mappings that route InfluxDB
v1-compatible requests to InfluxDB 3 Cloud Serverless buckets.
type: api
layout: list
staticFilePath: /openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-dbrps.yaml
weight: 100
tag: DBRPs
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetDBRPs
method: GET
path: /api/v2/dbrps
summary: List database retention policy mappings
tags:
- DBRPs
- operationId: PostDBRP
method: POST
path: /api/v2/dbrps
summary: Add a database retention policy mapping
tags:
- DBRPs
- operationId: GetDBRPsID
method: GET
path: /api/v2/dbrps/{dbrpID}
summary: Retrieve a database retention policy mapping
tags:
- DBRPs
- operationId: PatchDBRPID
method: PATCH
path: /api/v2/dbrps/{dbrpID}
summary: Update a database retention policy mapping
tags:
- DBRPs
- operationId: DeleteDBRPID
method: DELETE
path: /api/v2/dbrps/{dbrpID}
summary: Delete a database retention policy
tags:
- DBRPs
related:
- title: Database and retention policy mapping
href: /influxdb3/cloud-serverless/reference/api/influxdb-1x/dbrp/
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,35 @@
---
title: Delete
description: >-
Delete time series data from an InfluxDB 3 Cloud Serverless bucket by
specifying a time range and optional predicate.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-delete.yaml
weight: 100
tag: Delete
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: PostDelete
method: POST
path: /api/v2/delete
summary: Delete data
tags:
- Delete
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,30 @@
---
title: Headers
description: >-
Standard HTTP request headers used by InfluxDB 3 Cloud Serverless API
endpoints, including `Accept`, `Authorization`, `Content-Length`, and
`Content-Type`.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-headers.yaml
weight: 100
tag: Headers
isConceptual: true
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Standard HTTP request headers used by InfluxDB 3 Cloud Serverless API
endpoints, including `Accept`, `Authorization`, `Content-Length`, and
`Content-Type`.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,71 @@
---
title: Invokable Scripts
description: >-
Store, manage, and execute custom Flux scripts in InfluxDB 3 Cloud Serverless.
Scripts accept runtime parameters and can be invoked via dedicated endpoints.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-invokable-scripts.yaml
weight: 100
tag: Invokable Scripts
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetScripts
method: GET
path: /api/v2/scripts
summary: List scripts
tags:
- Invokable Scripts
- operationId: PostScripts
method: POST
path: /api/v2/scripts
summary: Create a script
tags:
- Invokable Scripts
- operationId: GetScriptsID
method: GET
path: /api/v2/scripts/{scriptID}
summary: Retrieve a script
tags:
- Invokable Scripts
- operationId: PatchScriptsID
method: PATCH
path: /api/v2/scripts/{scriptID}
summary: Update a script
tags:
- Invokable Scripts
- operationId: DeleteScriptsID
method: DELETE
path: /api/v2/scripts/{scriptID}
summary: Delete a script
tags:
- Invokable Scripts
- operationId: PostScriptsIDInvoke
method: POST
path: /api/v2/scripts/{scriptID}/invoke
summary: Invoke a script
tags:
- Invokable Scripts
- operationId: GetScriptsIDParams
method: GET
path: /api/v2/scripts/{scriptID}/params
summary: Find script parameters.
tags:
- Invokable Scripts
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,35 @@
---
title: Limits
description: >-
Retrieve rate limits and usage quotas for an InfluxDB 3 Cloud Serverless
organization.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-limits.yaml
weight: 100
tag: Limits
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetOrgLimitsID
method: GET
path: /api/v2/orgs/{orgID}/limits
summary: Retrieve limits for an organization
tags:
- Limits
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,97 @@
---
title: Organizations
description: >-
View and manage InfluxDB 3 Cloud Serverless organizations, which are
workspaces that group users, buckets, and resources.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-organizations.yaml
weight: 100
tag: Organizations
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetOrgs
method: GET
path: /api/v2/orgs
summary: List organizations
tags:
- Organizations
- operationId: PostOrgs
method: POST
path: /api/v2/orgs
summary: Create an organization
tags:
- Organizations
- operationId: GetOrgsID
method: GET
path: /api/v2/orgs/{orgID}
summary: Retrieve an organization
tags:
- Organizations
- operationId: PatchOrgsID
method: PATCH
path: /api/v2/orgs/{orgID}
summary: Update an organization
tags:
- Organizations
- operationId: DeleteOrgsID
method: DELETE
path: /api/v2/orgs/{orgID}
summary: Delete an organization
tags:
- Organizations
- operationId: GetOrgsIDMembers
method: GET
path: /api/v2/orgs/{orgID}/members
summary: List all members of an organization
tags:
- Organizations
- operationId: PostOrgsIDMembers
method: POST
path: /api/v2/orgs/{orgID}/members
summary: Add a member to an organization
tags:
- Organizations
- operationId: DeleteOrgsIDMembersID
method: DELETE
path: /api/v2/orgs/{orgID}/members/{userID}
summary: Remove a member from an organization
tags:
- Organizations
- operationId: GetOrgsIDOwners
method: GET
path: /api/v2/orgs/{orgID}/owners
summary: List all owners of an organization
tags:
- Organizations
- operationId: PostOrgsIDOwners
method: POST
path: /api/v2/orgs/{orgID}/owners
summary: Add an owner to an organization
tags:
- Organizations
- operationId: DeleteOrgsIDOwnersID
method: DELETE
path: /api/v2/orgs/{orgID}/owners/{userID}
summary: Remove an owner from an organization
tags:
- Organizations
related:
- title: View and manage organizations
href: /influxdb3/cloud-serverless/admin/
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,28 @@
---
title: Pagination
description: >-
Query parameters for paginating results from list operations in the InfluxDB 3
Cloud Serverless API.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-pagination.yaml
weight: 100
tag: Pagination
isConceptual: true
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Query parameters for paginating results from list operations in the InfluxDB 3
Cloud Serverless API.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,28 @@
---
title: Quick start
description: >-
Get started writing and querying data with the InfluxDB 3 Cloud Serverless
API.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-quick-start.yaml
weight: 1
tag: Quick start
isConceptual: true
menuGroup: Concepts
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Get started writing and querying data with the InfluxDB 3 Cloud Serverless
API.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,35 @@
---
title: Resources
description: >-
Retrieve a list of top-level resource types available in the InfluxDB 3 Cloud
Serverless API.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-resources.yaml
weight: 100
tag: Resources
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetResources
method: GET
path: /api/v2/resources
summary: List all known resources
tags:
- Resources
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,28 @@
---
title: Response codes
description: >-
Standard HTTP status codes returned by InfluxDB 3 Cloud Serverless API
endpoints and their meanings.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-response-codes.yaml
weight: 100
tag: Response codes
isConceptual: true
menuGroup: Concepts
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Standard HTTP status codes returned by InfluxDB 3 Cloud Serverless API
endpoints and their meanings.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,33 @@
---
title: Routes
description: Retrieve top-level routes for the InfluxDB 3 Cloud Serverless API.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-routes.yaml
weight: 100
tag: Routes
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetRoutes
method: GET
path: /api/v2
summary: List all top level routes
tags:
- Routes
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,53 @@
---
title: Secrets
description: >-
Create and manage secrets (key-value pairs) for use in InfluxDB 3 Cloud
Serverless Flux scripts and tasks.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-secrets.yaml
weight: 100
tag: Secrets
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetOrgsIDSecrets
method: GET
path: /api/v2/orgs/{orgID}/secrets
summary: List all secret keys for an organization
tags:
- Secrets
- operationId: PatchOrgsIDSecrets
method: PATCH
path: /api/v2/orgs/{orgID}/secrets
summary: Update secrets in an organization
tags:
- Secrets
- operationId: DeleteOrgsIDSecretsID
method: DELETE
path: /api/v2/orgs/{orgID}/secrets/{secretID}
summary: Delete a secret from an organization
tags:
- Secrets
- operationId: PostOrgsIDSecrets
method: POST
path: /api/v2/orgs/{orgID}/secrets/delete
summary: Delete secrets from an organization
tags:
- Secrets
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,59 @@
---
title: Security and access endpoints
description: >-
Endpoints for managing authentication and access control in InfluxDB 3 Cloud
Serverless.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-security-and-access-endpoints.yaml
weight: 100
tag: Security and access endpoints
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetAuthorizations
method: GET
path: /api/v2/authorizations
summary: List authorizations
tags:
- Security and access endpoints
- operationId: PostAuthorizations
method: POST
path: /api/v2/authorizations
summary: Create an authorization
tags:
- Security and access endpoints
- operationId: GetAuthorizationsID
method: GET
path: /api/v2/authorizations/{authID}
summary: Retrieve an authorization
tags:
- Security and access endpoints
- operationId: PatchAuthorizationsID
method: PATCH
path: /api/v2/authorizations/{authID}
summary: Update an API token to be active or inactive
tags:
- Security and access endpoints
- operationId: DeleteAuthorizationsID
method: DELETE
path: /api/v2/authorizations/{authID}
summary: Delete an authorization
tags:
- Security and access endpoints
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,28 @@
---
title: Supported operations
description: >-
Overview of the common CRUD operations supported by the InfluxDB 3 Cloud
Serverless API.
type: api
layout: single
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-supported-operations.yaml
weight: 100
tag: Supported operations
isConceptual: true
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
tagDescription: >-
Overview of the common CRUD operations supported by the InfluxDB 3 Cloud
Serverless API.
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,41 @@
---
title: System information endpoints
description: >-
Endpoints for retrieving system-level information about the InfluxDB 3 Cloud
Serverless instance.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-system-information-endpoints.yaml
weight: 100
tag: System information endpoints
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetRoutes
method: GET
path: /api/v2
summary: List all top level routes
tags:
- System information endpoints
- operationId: GetResources
method: GET
path: /api/v2/resources
summary: List all known resources
tags:
- System information endpoints
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,154 @@
---
title: Tasks
description: >-
Schedule and manage Flux tasks that process and transform data on a recurring
basis in InfluxDB 3 Cloud Serverless.
type: api
layout: list
staticFilePath: /openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-tasks.yaml
weight: 100
tag: Tasks
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetTasks
method: GET
path: /api/v2/tasks
summary: List all tasks
tags:
- Tasks
- operationId: PostTasks
method: POST
path: /api/v2/tasks
summary: Create a task
tags:
- Tasks
- operationId: GetTasksID
method: GET
path: /api/v2/tasks/{taskID}
summary: Retrieve a task
tags:
- Tasks
- operationId: PatchTasksID
method: PATCH
path: /api/v2/tasks/{taskID}
summary: Update a task
tags:
- Tasks
- operationId: DeleteTasksID
method: DELETE
path: /api/v2/tasks/{taskID}
summary: Delete a task
tags:
- Tasks
- operationId: GetTasksIDLabels
method: GET
path: /api/v2/tasks/{taskID}/labels
summary: List labels for a task
tags:
- Tasks
- operationId: PostTasksIDLabels
method: POST
path: /api/v2/tasks/{taskID}/labels
summary: Add a label to a task
tags:
- Tasks
- operationId: DeleteTasksIDLabelsID
method: DELETE
path: /api/v2/tasks/{taskID}/labels/{labelID}
summary: Delete a label from a task
tags:
- Tasks
- operationId: GetTasksIDLogs
method: GET
path: /api/v2/tasks/{taskID}/logs
summary: Retrieve all logs for a task
tags:
- Tasks
- operationId: GetTasksIDMembers
method: GET
path: /api/v2/tasks/{taskID}/members
summary: List all task members
tags:
- Tasks
- operationId: PostTasksIDMembers
method: POST
path: /api/v2/tasks/{taskID}/members
summary: Add a member to a task
tags:
- Tasks
- operationId: DeleteTasksIDMembersID
method: DELETE
path: /api/v2/tasks/{taskID}/members/{userID}
summary: Remove a member from a task
tags:
- Tasks
- operationId: GetTasksIDOwners
method: GET
path: /api/v2/tasks/{taskID}/owners
summary: List all owners of a task
tags:
- Tasks
- operationId: PostTasksIDOwners
method: POST
path: /api/v2/tasks/{taskID}/owners
summary: Add an owner for a task
tags:
- Tasks
- operationId: DeleteTasksIDOwnersID
method: DELETE
path: /api/v2/tasks/{taskID}/owners/{userID}
summary: Remove an owner from a task
tags:
- Tasks
- operationId: GetTasksIDRuns
method: GET
path: /api/v2/tasks/{taskID}/runs
summary: List runs for a task
tags:
- Tasks
- operationId: PostTasksIDRuns
method: POST
path: /api/v2/tasks/{taskID}/runs
summary: Start a task run, overriding the schedule
tags:
- Tasks
- operationId: GetTasksIDRunsID
method: GET
path: /api/v2/tasks/{taskID}/runs/{runID}
summary: Retrieve a run for a task.
tags:
- Tasks
- operationId: DeleteTasksIDRunsID
method: DELETE
path: /api/v2/tasks/{taskID}/runs/{runID}
summary: Cancel a running task
tags:
- Tasks
- operationId: GetTasksIDRunsIDLogs
method: GET
path: /api/v2/tasks/{taskID}/runs/{runID}/logs
summary: Retrieve all logs for a run
tags:
- Tasks
- operationId: PostTasksIDRunsIDRetry
method: POST
path: /api/v2/tasks/{taskID}/runs/{runID}/retry
summary: Retry a task run
tags:
- Tasks
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,113 @@
---
title: Telegrafs
description: >-
Create and manage Telegraf agent configurations that collect and write data to
InfluxDB 3 Cloud Serverless.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-telegrafs.yaml
weight: 100
tag: Telegrafs
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetTelegrafs
method: GET
path: /api/v2/telegrafs
summary: List all Telegraf configurations
tags:
- Telegrafs
- operationId: PostTelegrafs
method: POST
path: /api/v2/telegrafs
summary: Create a Telegraf configuration
tags:
- Telegrafs
- operationId: GetTelegrafsID
method: GET
path: /api/v2/telegrafs/{telegrafID}
summary: Retrieve a Telegraf configuration
tags:
- Telegrafs
- operationId: PutTelegrafsID
method: PUT
path: /api/v2/telegrafs/{telegrafID}
summary: Update a Telegraf configuration
tags:
- Telegrafs
- operationId: DeleteTelegrafsID
method: DELETE
path: /api/v2/telegrafs/{telegrafID}
summary: Delete a Telegraf configuration
tags:
- Telegrafs
- operationId: GetTelegrafsIDLabels
method: GET
path: /api/v2/telegrafs/{telegrafID}/labels
summary: List all labels for a Telegraf config
tags:
- Telegrafs
- operationId: PostTelegrafsIDLabels
method: POST
path: /api/v2/telegrafs/{telegrafID}/labels
summary: Add a label to a Telegraf config
tags:
- Telegrafs
- operationId: DeleteTelegrafsIDLabelsID
method: DELETE
path: /api/v2/telegrafs/{telegrafID}/labels/{labelID}
summary: Delete a label from a Telegraf config
tags:
- Telegrafs
- operationId: GetTelegrafsIDMembers
method: GET
path: /api/v2/telegrafs/{telegrafID}/members
summary: List all users with member privileges for a Telegraf config
tags:
- Telegrafs
- operationId: PostTelegrafsIDMembers
method: POST
path: /api/v2/telegrafs/{telegrafID}/members
summary: Add a member to a Telegraf config
tags:
- Telegrafs
- operationId: DeleteTelegrafsIDMembersID
method: DELETE
path: /api/v2/telegrafs/{telegrafID}/members/{userID}
summary: Remove a member from a Telegraf config
tags:
- Telegrafs
- operationId: GetTelegrafsIDOwners
method: GET
path: /api/v2/telegrafs/{telegrafID}/owners
summary: List all owners of a Telegraf configuration
tags:
- Telegrafs
- operationId: PostTelegrafsIDOwners
method: POST
path: /api/v2/telegrafs/{telegrafID}/owners
summary: Add an owner to a Telegraf configuration
tags:
- Telegrafs
- operationId: DeleteTelegrafsIDOwnersID
method: DELETE
path: /api/v2/telegrafs/{telegrafID}/owners/{userID}
summary: Remove an owner from a Telegraf config
tags:
- Telegrafs
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,77 @@
---
title: Templates
description: >-
Export and apply InfluxDB templates, and manage template stacks for InfluxDB 3
Cloud Serverless.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-templates.yaml
weight: 100
tag: Templates
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: ListStacks
method: GET
path: /api/v2/stacks
summary: List installed stacks
tags:
- Templates
- operationId: CreateStack
method: POST
path: /api/v2/stacks
summary: Create a stack
tags:
- Templates
- operationId: ReadStack
method: GET
path: /api/v2/stacks/{stack_id}
summary: Retrieve a stack
tags:
- Templates
- operationId: UpdateStack
method: PATCH
path: /api/v2/stacks/{stack_id}
summary: Update a stack
tags:
- Templates
- operationId: DeleteStack
method: DELETE
path: /api/v2/stacks/{stack_id}
summary: Delete a stack and associated resources
tags:
- Templates
- operationId: UninstallStack
method: POST
path: /api/v2/stacks/{stack_id}/uninstall
summary: Uninstall a stack
tags:
- Templates
- operationId: ApplyTemplate
method: POST
path: /api/v2/templates/apply
summary: Apply or dry-run a template
tags:
- Templates
- operationId: ExportTemplate
method: POST
path: /api/v2/templates/export
summary: Export a new template
tags:
- Templates
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,34 @@
---
title: Usage
description: >-
Retrieve usage metrics and cardinality data for an InfluxDB 3 Cloud Serverless
organization.
type: api
layout: list
staticFilePath: /openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-usage.yaml
weight: 100
tag: Usage
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetOrgUsageID
method: GET
path: /api/v2/orgs/{orgID}/usage
summary: Retrieve usage for an organization
tags:
- Usage
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -0,0 +1,81 @@
---
title: Variables
description: Create and manage variables for use in InfluxDB 3 Cloud Serverless dashboards.
type: api
layout: list
staticFilePath: >-
/openapi/influxdb3-cloud-serverless/tags/influxdb3-cloud-serverless-variables.yaml
weight: 100
tag: Variables
isConceptual: false
menuGroup: Other
specDownloadPath: /openapi/influxdb3-cloud-serverless.yml
articleDataKey: influxdb3-cloud-serverless
articleSection: api
operations:
- operationId: GetVariables
method: GET
path: /api/v2/variables
summary: List all variables
tags:
- Variables
- operationId: PostVariables
method: POST
path: /api/v2/variables
summary: Create a variable
tags:
- Variables
- operationId: GetVariablesID
method: GET
path: /api/v2/variables/{variableID}
summary: Retrieve a variable
tags:
- Variables
- operationId: PutVariablesID
method: PUT
path: /api/v2/variables/{variableID}
summary: Replace a variable
tags:
- Variables
- operationId: PatchVariablesID
method: PATCH
path: /api/v2/variables/{variableID}
summary: Update a variable
tags:
- Variables
- operationId: DeleteVariablesID
method: DELETE
path: /api/v2/variables/{variableID}
summary: Delete a variable
tags:
- Variables
- operationId: GetVariablesIDLabels
method: GET
path: /api/v2/variables/{variableID}/labels
summary: List all labels for a variable
tags:
- Variables
- operationId: PostVariablesIDLabels
method: POST
path: /api/v2/variables/{variableID}/labels
summary: Add a label to a variable
tags:
- Variables
- operationId: DeleteVariablesIDLabelsID
method: DELETE
path: /api/v2/variables/{variableID}/labels/{labelID}
summary: Delete a label from a variable
tags:
- Variables
related:
- title: InfluxDB 3 API client libraries
href: /influxdb3/cloud-serverless/reference/client-libraries/v3/
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---

View File

@ -1,31 +0,0 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API provides a programmatic interface for interactions with InfluxDB.
Access the InfluxDB API using the `/api/v2/write` or InfluxDB v1 endpoints.
menu:
influxdb3_cloud_serverless:
parent: Reference
name: InfluxDB HTTP API
weight: 104
influxdb3/cloud-serverless/tags: [api]
---
The InfluxDB HTTP API provides a programmatic interface for interactions with
{{% product-name %}}, such as writing and querying data.
Access the InfluxDB HTTP API using the `/api/v2/` or InfluxDB v1 endpoints.
## InfluxDB v2 Compatibility API reference documentation
<a class="btn" href="/influxdb3/cloud-serverless/api/v2/">InfluxDB v2 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v2-compatible
endpoints that work with {{% product-name %}} and with InfluxDB 2.x client
libraries and third-party integrations.
## InfluxDB v1 Compatibility API reference documentation
<a class="btn" href="/influxdb3/cloud-serverless/api/v1/">InfluxDB v1 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v1-compatible `/write` and `/query` endpoints that work with {{% product-name %}} and with InfluxDB 1.x client libraries and third-party integrations.

View File

@ -0,0 +1,27 @@
---
title: InfluxDB HTTP API
description: >-
Use the InfluxDB HTTP API to write data, query data, and manage databases,
tables, and tokens.
weight: 104
type: api
articleDataKey: influxdb3-clustered
articleSection: management-api
menu:
influxdb3_clustered:
name: InfluxDB HTTP API
identifier: api-reference-influxdb3-clustered-management-api
parent: Reference
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---
Use the {{% product-name %}} HTTP API to write data, query data, and manage databases, tables, and tokens.
{{< children >}}

View File

@ -0,0 +1,25 @@
---
title: All endpoints
description: View all API endpoints sorted by path.
type: api
layout: all-endpoints
weight: 999
isAllEndpoints: true
articleDataKey: influxdb3-clustered
articleSection: management-api
menu:
influxdb3_clustered:
name: All endpoints
identifier: all-endpoints-influxdb3-clustered-management-api
parent: InfluxDB HTTP API
alt_links:
core: /influxdb3/core/api/
enterprise: /influxdb3/enterprise/api/
cloud-serverless: /influxdb3/cloud-serverless/api/
cloud-dedicated: /influxdb3/cloud-dedicated/api/
clustered: /influxdb3/clustered/api/
v2: /influxdb/v2/api/
cloud: /influxdb/cloud/api/
---
All {{% product-name %}} API endpoints, sorted by path.

View File

@ -1,32 +0,0 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API provides a programmatic interface for interactions with
InfluxDB, such as writing and querying data.
Access the InfluxDB API using the `/api/v2/write` or InfluxDB v1 endpoints.
menu:
influxdb3_clustered:
parent: Reference
name: InfluxDB HTTP API
weight: 127
influxdb3/clustered/tags: [api]
---
The InfluxDB HTTP API provides a programmatic interface for interactions with
{{% product-name %}}, such as writing and querying data.
Access the InfluxDB HTTP API using the `/api/v2/` or InfluxDB v1 endpoints.
## InfluxDB v2 Compatibility API reference documentation
<a class="btn" href="/influxdb3/clustered/api/v2/">InfluxDB v2 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v2-compatible
endpoints that work with {{% product-name %}} and with InfluxDB 2.x client
libraries and third-party integrations.
## InfluxDB v1 Compatibility API reference documentation
<a class="btn" href="/influxdb3/clustered/api/v1/">InfluxDB v1 API for {{% product-name %}}</a>
The API reference describes requests and responses for InfluxDB v1-compatible `/write` and `/query` endpoints that work with {{% product-name %}} and with InfluxDB 1.x client libraries and third-party integrations.

View File

@ -1,20 +1,12 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API for {{% product-name %}} provides a programmatic interface
for interactions with InfluxDB,
including writing, querying, and processing data, and managing an InfluxDB 3
instance.
menu:
influxdb3_core:
parent: Reference
name: InfluxDB HTTP API
weight: 104
influxdb3/core/tags: [api]
source: /shared/influxdb3-api-reference/_index.md
The InfluxDB HTTP API for InfluxDB 3 Core provides a programmatic interface
for interactions with InfluxDB.
# Redirect to the new location
aliases:
- /influxdb3/core/reference/api/
redirect: /influxdb3/core/api/
---
<!--
The content for this page is at
// SOURCE /content/shared/influxdb3-api-reference/_index.md
->
This page has moved to [InfluxDB HTTP API](/influxdb3/core/api/).

View File

@ -1,20 +1,10 @@
---
title: InfluxDB HTTP API
description: >
The InfluxDB HTTP API for {{% product-name %}} provides a programmatic interface
for interactions with InfluxDB,
including writing, querying, and processing data, and managing an InfluxDB 3
instance.
menu:
influxdb3_enterprise:
parent: Reference
name: InfluxDB HTTP API
weight: 104
influxdb3/enterprise/tags: [api]
source: /shared/influxdb3-api-reference/_index.md
The InfluxDB HTTP API for InfluxDB 3 Enterprise provides a programmatic interface
for interactions with InfluxDB.
# Redirect to the new location
redirect: /influxdb3/enterprise/api/
---
<!--
The content for this page is at
// SOURCE /content/shared/influxdb3-api-reference/_index.md
->
This page has moved to [InfluxDB HTTP API](/influxdb3/enterprise/api/).

View File

@ -1,78 +1,76 @@
/// <reference types="cypress" />
/**
* API Reference Documentation E2E Tests
*
* Tests:
* 1. API reference pages (link validation, content structure)
* 2. 3-column layout with TOC (for InfluxDB 3 Core/Enterprise)
* 3. Hugo-native tag page rendering
* 4. Related links from OpenAPI x-related frontmatter rendered HTML
*
* Run with:
* node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/api-reference.cy.js" content/influxdb3/core/reference/api/_index.md
*/
const fakeGoogleTagManager = {
trackingOptIn: () => {},
trackingOptOut: () => {}
}
trackingOptOut: () => {},
};
describe('API reference content', () => {
// API section index pages (generated from article data)
const subjects = [
'/influxdb/cloud/api/',
'/influxdb/cloud/api/v1/',
'/influxdb/cloud/api/v1-compatibility/',
'/influxdb/cloud/api/v2/',
'/influxdb/v2/api/',
'/influxdb/v2/api/v1/',
'/influxdb/v2/api/v1-compatibility/',
'/influxdb/v2/api/v2/',
'/influxdb3/cloud-dedicated/api/',
'/influxdb3/cloud-dedicated/api/management/',
'/influxdb3/cloud-dedicated/api/v1/',
'/influxdb3/cloud-dedicated/api/v1-compatibility/',
'/influxdb3/cloud-dedicated/api/v2/',
'/influxdb3/cloud-serverless/api/',
'/influxdb3/cloud-serverless/api/v1/',
'/influxdb3/cloud-serverless/api/v1-compatibility/',
'/influxdb3/cloud-serverless/api/v2/',
'/influxdb3/clustered/api/',
// TODO '/influxdb3/clustered/api/management/',
'/influxdb3/clustered/api/v1/',
'/influxdb3/clustered/api/v1-compatibility/',
'/influxdb3/clustered/api/v2/',
'/influxdb3/core/api/',
'/influxdb3/enterprise/api/',
'/influxdb3/cloud-dedicated/api/',
'/influxdb3/cloud-serverless/api/',
'/influxdb3/clustered/api/',
'/influxdb/cloud/api/',
'/influxdb/v2/api/',
];
subjects.forEach((subject) => {
describe(subject, () => {
beforeEach(() => {
// Intercept and modify the page HTML before it loads
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
// Modify the Kapa widget script attributes
// Avoid socket errors from fpjs in tests by disabling fingerprinting
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
// Intercept and modify the page HTML before it loads
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
// Modify the Kapa widget script attributes
// Avoid socket errors from fpjs in tests by disabling fingerprinting
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(subject);
window.fcdsc = fakeGoogleTagManager;
cy.stub(window.fcdsc, 'trackingOptIn').as('trackingOptIn');
cy.stub(window.fcdsc, 'trackingOptOut').as('trackingOptOut');
});
it(`has API info`, function () {
cy.get('script[data-user-analytics-fingerprint-enabled=false]').should('have.length', 1);
cy.get('script[data-user-analytics-fingerprint-enabled=false]').should(
'have.length',
1
);
cy.get('h1').first().should('have.length', 1);
cy.get('[data-role$=description]').should('have.length', 1);
// Check for description element (either article--description class or data-role attribute)
cy.get('.article--description, [data-role$=description]').should(
'have.length.at.least',
1
);
});
it('links back to the version home page', function () {
cy.get('a.back').contains('Docs')
.should('have.length', 1)
.click();
cy.get('a.back').contains('Docs').should('have.length', 1).click();
// Path should be the first two segments and trailing slash in $subject
cy.location('pathname')
.should('eq', subject.replace(/^(\/[^/]+\/[^/]+\/).*/, '$1'));
cy.location('pathname').should(
'eq',
subject.replace(/^(\/[^/]+\/[^/]+\/).*/, '$1')
);
cy.get('h1').should('have.length', 1);
});
it('contains valid internal links', function () {
@ -88,8 +86,7 @@ describe('API reference content', () => {
// cy.request doesn't show in your browser's Developer Tools
// because the request comes from Node, not from the browser.
cy.request($a.attr('href')).its('status').should('eq', 200);
});
});
});
});
it('contains valid external links', function () {
@ -109,3 +106,449 @@ describe('API reference content', () => {
});
});
});
/**
* API Reference Layout Tests
* Tests layout for InfluxDB 3 Core/Enterprise API documentation
*/
describe('API reference layout', () => {
const layoutSubjects = [
'/influxdb3/core/api/write-data/',
'/influxdb3/enterprise/api/write-data/',
];
layoutSubjects.forEach((subject) => {
describe(`${subject} layout`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(subject);
});
describe('Layout Structure', () => {
it('displays sidebar', () => {
cy.get('.sidebar').should('be.visible');
});
it('displays API content area', () => {
cy.get('.api-content, .content-wrapper, .article--content').should(
'exist'
);
});
it('displays TOC on page', () => {
cy.get('.api-toc').should('exist');
});
});
describe('Hugo-native renderer', () => {
it('renders API operations container', () => {
cy.get('.api-hugo-native, .api-operations-section').should('exist');
});
it('renders operation elements', () => {
cy.get('.api-operation').should('have.length.at.least', 1);
});
it('operation has method badge and path', () => {
cy.get('.api-operation')
.first()
.within(() => {
cy.get('.api-method').should('exist');
cy.get('.api-path').should('exist');
});
});
});
});
});
});
/**
* API Tag Page Tests
* Tests Hugo-native tag pages render operations correctly
*/
describe('API tag pages', () => {
const tagPages = [
'/influxdb3/core/api/write-data/',
'/influxdb3/core/api/query-data/',
'/influxdb3/enterprise/api/write-data/',
];
tagPages.forEach((page) => {
describe(`Tag page ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('displays page title', () => {
cy.get('h1').should('exist');
});
it('renders operations section', () => {
cy.get('.api-operations, .api-operations-section').should('exist');
});
it('operations have proper structure', () => {
cy.get('.api-operation')
.first()
.within(() => {
// Check for operation header with method and path
cy.get('.api-operation-header, .api-operation-endpoint').should(
'exist'
);
cy.get('.api-method').should('exist');
cy.get('.api-path').should('exist');
});
});
it('TOC contains operation links', () => {
cy.get('.api-toc-nav').should('exist');
cy.get('.api-toc-link').should('have.length.at.least', 1);
});
it('TOC links have method badges', () => {
cy.get('.api-toc-link .api-method').should('have.length.at.least', 1);
});
});
});
});
/**
* API Section Page Structure Tests
* Tests that API section pages show only tags (immediate children)
*/
describe('API section page structure', () => {
const sectionPages = ['/influxdb3/core/api/', '/influxdb3/enterprise/api/'];
sectionPages.forEach((page) => {
describe(`Section page ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('displays page title', () => {
cy.get('h1').should('contain', 'InfluxDB HTTP API');
});
it('shows tag pages as children', () => {
cy.get('.children-links h3 a').should('have.length.at.least', 5);
});
it('does not show individual operations in content area', () => {
// Operations cards should not appear in the main content
cy.get('.article--content .api-operation-card').should('not.exist');
});
it('has All endpoints link in navigation', () => {
cy.get('.sidebar a').contains('All endpoints').should('exist');
});
});
});
});
/**
* All Endpoints Page Tests
* Tests the "All endpoints" page shows all operations
*/
describe('All endpoints page', () => {
const allEndpointsPages = [
'/influxdb3/core/api/all-endpoints/',
'/influxdb3/enterprise/api/all-endpoints/',
];
allEndpointsPages.forEach((page) => {
describe(`All endpoints ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('displays page title "All endpoints"', () => {
cy.get('h1').should('contain', 'All endpoints');
});
it('shows v3 API section', () => {
cy.get('#v3-api').should('exist');
});
it('displays operation cards', () => {
cy.get('.api-operation-card').should('have.length.at.least', 10);
});
it('operation cards have method badges', () => {
cy.get('.api-operation-card .api-method').should(
'have.length.at.least',
10
);
});
it('operation cards have path codes', () => {
cy.get('.api-operation-card .api-path').should(
'have.length.at.least',
10
);
});
it('operation cards link to tag pages with operation anchors', () => {
cy.get('.api-operation-card')
.first()
.should('have.attr', 'href')
.and('match', /\/api\/.*\/#operation\//);
});
it('is accessible from navigation', () => {
// Navigate back to section page
cy.get('.sidebar a').contains('InfluxDB HTTP API').click();
// Then navigate to All endpoints
cy.get('.sidebar a').contains('All endpoints').click();
cy.url().should('include', '/all-endpoints/');
});
});
});
});
/**
* API Download Button Tests
* Tests that each tag page has a download button linking to the correct spec
*/
describe('API spec download buttons', () => {
const downloadTests = [
{
page: '/influxdb3/core/api/write-data/',
specPath: '/openapi/influxdb3-core.yml',
},
{
page: '/influxdb3/enterprise/api/write-data/',
specPath: '/openapi/influxdb3-enterprise.yml',
},
];
downloadTests.forEach(({ page, specPath }) => {
describe(`Download button on ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('has a download button', () => {
cy.get('.api-spec-download').should('exist');
});
it(`download button links to ${specPath}`, () => {
cy.get('.api-spec-download')
.should('have.attr', 'href', specPath)
.and('have.attr', 'download');
});
});
});
});
/**
* API Code Sample Tests
* Tests that inline curl examples render correctly on tag pages
*/
describe('API code samples', () => {
const tagPages = [
'/influxdb3/core/api/write-data/',
'/influxdb3/enterprise/api/write-data/',
];
tagPages.forEach((page) => {
describe(`Code samples on ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('each operation has a code sample', () => {
cy.get('.api-operation').each(($op) => {
cy.wrap($op).find('.api-code-sample').should('have.length', 1);
});
});
it('code samples have header and code block', () => {
cy.get('.api-code-sample')
.first()
.within(() => {
cy.get('.api-code-sample-header').should(
'contain',
'Example request'
);
cy.get('.api-code-block code').should('exist');
});
});
it('code block contains a curl command', () => {
cy.get('.api-code-block code')
.first()
.invoke('text')
.should('match', /curl --request (GET|POST|PUT|PATCH|DELETE)/);
});
it('curl command includes Authorization header', () => {
cy.get('.api-code-block code')
.first()
.invoke('text')
.should('include', 'Authorization: Bearer INFLUX_TOKEN');
});
it('POST operations include request body in curl', () => {
cy.get('.api-operation[data-method="post"]')
.first()
.find('.api-code-block code')
.invoke('text')
.should('include', '--data-raw');
});
it('code samples have Ask AI links', () => {
cy.get('.api-code-sample .api-code-ask-ai')
.first()
.should('have.attr', 'data-query')
.and('not.be.empty');
});
});
});
});
/**
* API Client Library Related Link Tests
* Tests that InfluxDB 3 tag pages include client library related links
*/
describe('API client library related links', () => {
const influxdb3Pages = [
'/influxdb3/core/api/write-data/',
'/influxdb3/enterprise/api/write-data/',
];
influxdb3Pages.forEach((page) => {
describe(`Client library link on ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('includes InfluxDB 3 API client libraries in related links', () => {
cy.get('.related ul li a')
.filter(':contains("InfluxDB 3 API client libraries")')
.should('have.length', 1)
.and('have.attr', 'href')
.and('match', /\/influxdb3\/\w+\/reference\/client-libraries\/v3\//);
});
});
});
});
/**
* API Related Links Tests
* Tests that x-related from OpenAPI specs renders as related links on tag pages
*/
describe('API related links', () => {
const pagesWithRelated = ['/influxdb3/core/api/write-data/'];
pagesWithRelated.forEach((page) => {
describe(`Related links on ${page}`, () => {
beforeEach(() => {
cy.intercept('GET', '**', (req) => {
req.continue((res) => {
if (res.headers['content-type']?.includes('text/html')) {
res.body = res.body.replace(
/data-user-analytics-fingerprint-enabled="true"/,
'data-user-analytics-fingerprint-enabled="false"'
);
}
});
});
cy.visit(page);
});
it('displays a related section', () => {
cy.get('.related').should('exist');
cy.get('.related h4#related').should('contain', 'Related');
});
it('renders related links from x-related as anchor elements', () => {
cy.get('.related ul li a').should('have.length.at.least', 2);
});
it('related links have title text and valid href', () => {
cy.get('.related ul li a').each(($a) => {
// Each link has non-empty text
cy.wrap($a).invoke('text').should('not.be.empty');
// Each link has an href starting with /
cy.wrap($a).should('have.attr', 'href').and('match', /^\//);
});
});
it('related links resolve to valid pages', () => {
cy.get('.related ul li a').each(($a) => {
const href = $a.attr('href');
cy.request(href).its('status').should('eq', 200);
});
});
});
});
});

View File

@ -2,8 +2,8 @@ influxdb3_core:
name: InfluxDB 3 Core
altname: InfluxDB 3 Core
namespace: influxdb3
content_path: influxdb3/core
label_group: v3-monolith
api_path: /influxdb3/core/api/
alt_link_key: core
menu_category: self-managed
versions: [core]
list_order: 2
@ -40,13 +40,13 @@ influxdb3_enterprise:
name: InfluxDB 3 Enterprise
altname: InfluxDB 3 Enterprise
namespace: influxdb3
content_path: influxdb3/enterprise
label_group: v3-monolith
api_path: /influxdb3/enterprise/api/
alt_link_key: enterprise
menu_category: self-managed
versions: [enterprise]
list_order: 2
latest: enterprise
latest_patch: 3.8.4
latest_patch: 3.8.3
placeholder_host: localhost:8181
limits:
database: 100
@ -78,8 +78,6 @@ influxdb3_explorer:
name: InfluxDB 3 Explorer
altname: Explorer
namespace: influxdb3_explorer
content_path: influxdb3/explorer
label_group: explorer
menu_category: tools
list_order: 1
latest: explorer
@ -96,8 +94,8 @@ influxdb3_cloud_serverless:
name: InfluxDB Cloud Serverless
altname: InfluxDB Cloud
namespace: influxdb
content_path: influxdb3/cloud-serverless
label_group: v3-distributed
api_path: /influxdb3/cloud-serverless/api/
alt_link_key: cloud-serverless
menu_category: managed
versions: [cloud-serverless]
list_order: 2
@ -131,8 +129,8 @@ influxdb3_cloud_dedicated:
name: InfluxDB Cloud Dedicated
altname: InfluxDB Cloud
namespace: influxdb
content_path: influxdb3/cloud-dedicated
label_group: v3-distributed
api_path: /influxdb3/cloud-dedicated/api/
alt_link_key: cloud-dedicated
menu_category: managed
versions: [cloud-dedicated]
list_order: 3
@ -164,8 +162,8 @@ influxdb3_clustered:
name: InfluxDB Clustered
altname: InfluxDB Clustered
namespace: influxdb
content_path: influxdb3/clustered
label_group: v3-distributed
api_path: /influxdb3/clustered/api/
alt_link_key: clustered
menu_category: self-managed
versions: [clustered]
list_order: 3
@ -200,12 +198,8 @@ influxdb:
name__v1: InfluxDB OSS v1
altname: InfluxDB OSS
namespace: influxdb
content_path:
v2: influxdb/v2
v1: influxdb/v1
label_group:
v2: v2
v1: v1
api_path: /influxdb/v2/api/
alt_link_key: v2
succeeded_by: influxdb3_core
menu_category: self-managed
list_order: 1
@ -216,7 +210,7 @@ influxdb:
latest: v2.8
latest_patches:
v2: 2.8.0
v1: 1.12.3
v1: 1.12.2
latest_cli:
v2: 2.7.5
detector_config:
@ -251,8 +245,8 @@ influxdb_cloud:
name__vcloud: InfluxDB Cloud (TSM)
altname: InfluxDB Cloud
namespace: influxdb
content_path: influxdb/cloud
label_group: v2-cloud
api_path: /influxdb/cloud/api/
alt_link_key: cloud
menu_category: managed
versions: [cloud]
list_order: 1
@ -282,14 +276,12 @@ influxdb_cloud:
telegraf:
name: Telegraf
namespace: telegraf
content_path: telegraf
label_group: telegraf
menu_category: other
list_order: 6
versions: [v1]
latest: v1.38
latest: v1.37
latest_patches:
v1: 1.38.0
v1: 1.37.3
ai_sample_questions:
- How do I configure Telegraf for InfluxDB 3?
- How do I write a custom Telegraf plugin?
@ -308,8 +300,6 @@ telegraf_controller:
chronograf:
name: Chronograf
namespace: chronograf
content_path: chronograf
label_group: chronograf
menu_category: other
list_order: 7
versions: [v1]
@ -325,8 +315,6 @@ chronograf:
kapacitor:
name: Kapacitor
namespace: kapacitor
content_path: kapacitor
label_group: kapacitor
menu_category: other
list_order: 7
versions: [v1]
@ -342,8 +330,6 @@ kapacitor:
enterprise_influxdb:
name: 'InfluxDB Enterprise v1'
namespace: enterprise_influxdb
content_path: enterprise_influxdb
label_group: v1-enterprise
menu_category: self-managed
list_order: 5
versions: [v1]
@ -398,8 +384,6 @@ influxdb_cloud1:
flux:
name: Flux
namespace: flux
content_path: flux
label_group: flux
menu_category: languages
list_order: 8
versions: [v0]

View File

@ -0,0 +1,154 @@
# Clustered & Cloud Dedicated API Documentation Structure Design
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Unified API documentation for Clustered and Cloud Dedicated with separate downloadable specs for Data API and Management API.
**Architecture:** Single nav menu combining tags from both API specs, with dual download buttons on landing page and context-aware buttons on tag pages.
**Tech Stack:** Hugo templates, OpenAPI specs, existing API doc generator
***
## Context
Clustered and Cloud Dedicated have two distinct API planes:
- **Data API** - Write and Query endpoints using database tokens
- **Management API** - Databases, Tables, Tokens endpoints using management tokens
## Design Decisions
### Navigation Structure
Single `/api/` section with combined nav:
```
InfluxDB HTTP API
├── Quick start (conceptual)
├── Authentication (conceptual, covers both token types)
├── API compatibility (conceptual)
├── Common parameters (conceptual)
├── Headers (conceptual)
├── Response codes (conceptual)
├── Database tokens (Management API)
├── Databases (Management API)
├── Tables (Management API)
├── Ping (Data API)
├── Query data (Data API)
├── Write data (Data API)
└── All endpoints (combined)
```
### Download Buttons
**Landing page:** Two buttons side-by-side
- "Download Data API Spec" → `/openapi/influxdb-{product}-v2-data-api.yml`
- "Download Management API Spec" → `/openapi/influxdb-{product}-management-api.yml`
**Tag pages:** Context-aware single button based on `staticFilePath`
### Authentication Page
Single unified page covering:
- Token types table (Management vs Database)
- Authentication schemes table (Bearer, Token, Basic, Query string)
- Which endpoints use which token type
- Security schemes from OpenAPI spec
### OpenAPI Spec Organization
**`v2/ref.yml` (Data API):**
- Contains all conceptual tags (Quick start, Authentication, etc.)
- Contains Data API operation tags (Ping, Query data, Write data)
**`management/openapi.yml`:**
- NO CHANGES to source file
- Contains only operation tags (Database tokens, Databases, Tables)
Generator combines both specs into unified `articles.yml`.
### Cleanup Required
Remove old pages:
- `/content/influxdb3/clustered/api/v2/_index.html`
- `/content/influxdb3/cloud-dedicated/api/v2/_index.html`
- `/content/influxdb3/*/api/admin-authentication-management-operations/`
- `/content/influxdb3/*/api/management-authentication-admin-operations/`
***
## Implementation Tasks
### Task 1: Update Authentication tag in Data API specs
Update `api-docs/influxdb3/clustered/v2/ref.yml` and `api-docs/influxdb3/cloud-dedicated/v2/ref.yml`:
- Revise Authentication tag description to cover both token types
- Include table of token types and which endpoints use them
- Keep `showSecuritySchemes: true` for security scheme rendering
### Task 2: Update Quick start tag in Data API specs
Update Quick start in both v2/ref.yml files:
- Cover both Data and Management API getting started flow
- Show management token creation, then database/token setup, then write/query
### Task 3: Add dual download buttons to API landing page
Modify `layouts/api/list.html` or create partial:
- Detect Clustered/Cloud Dedicated products
- Show two download buttons on section index pages
- Style buttons side-by-side
### Task 4: Update tag page download button logic
Modify `layouts/api/single.html` and/or `layouts/api/list.html`:
- Detect API type from `staticFilePath` (contains `management-api` or `v2-data-api`)
- Show appropriate download button for the API
### Task 5: Remove old v2 HTML pages
Delete:
- `content/influxdb3/clustered/api/v2/`
- `content/influxdb3/cloud-dedicated/api/v2/`
### Task 6: Remove old authentication directories
Delete leftover directories:
- `content/influxdb3/clustered/api/admin-authentication-management-operations/`
- `content/influxdb3/clustered/api/management-authentication-admin-operations/`
- `content/influxdb3/cloud-dedicated/api/admin-authentication-management-operations/`
- `content/influxdb3/cloud-dedicated/api/management-authentication-admin-operations/`
### Task 7: Regenerate API docs and verify
Run `yarn build:api-docs` and verify:
- Nav shows combined tags from both APIs
- Authentication page has unified content
- Download buttons work correctly
- Old pages are gone
***
## Success Criteria
- [ ] Single Authentication page covers both token types clearly
- [ ] Landing page shows two download buttons for Clustered/Cloud Dedicated
- [ ] Tag pages show context-appropriate download button
- [ ] Nav combines tags from both API specs
- [ ] Old v2 HTML pages removed
- [ ] Old duplicate authentication directories removed
- [ ] `yarn build:api-docs` succeeds
- [ ] Hugo builds without errors

View File

@ -0,0 +1,184 @@
# API Reference RapiDoc Migration Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Migrate all InfluxDB products from Redoc-based API reference to the new RapiDoc-based UI that's already working for influxdb3-core and influxdb3-enterprise.
**Architecture:** The new API reference uses RapiDoc Mini to render OpenAPI specs. The generation script (`api-docs/scripts/generate-openapi-articles.ts`) processes OpenAPI specs and creates Hugo content pages with frontmatter that references spec files. Hugo layouts (`layouts/api/` and `layouts/api-operation/`) render these pages using the RapiDoc component.
**Tech Stack:** TypeScript (generation scripts), Hugo templates, RapiDoc web component, OpenAPI 3.0 specs
***
## Overview
### Products to Migrate
| Product | Status | Spec Location | Target Content Path |
| ----------------------- | ------- | ----------------------------------------------------------- | ----------------------------------------- |
| influxdb3-core | ✅ Done | `api-docs/influxdb3/core/v3/ref.yml` | `content/influxdb3/core/api/` |
| influxdb3-enterprise | ✅ Done | `api-docs/influxdb3/enterprise/v3/ref.yml` | `content/influxdb3/enterprise/api/` |
| cloud-dedicated | Partial | `api-docs/influxdb3/cloud-dedicated/management/openapi.yml` | `content/influxdb3/cloud-dedicated/api/` |
| cloud-serverless | Partial | `api-docs/influxdb3/cloud-serverless/v2/ref.yml` | `content/influxdb3/cloud-serverless/api/` |
| clustered | Partial | `api-docs/influxdb3/clustered/management/openapi.yml` | `content/influxdb3/clustered/api/` |
| cloud-v2 | Partial | `api-docs/influxdb/cloud/v2/ref.yml` | `content/influxdb/cloud/api/` |
| oss-v2 | Partial | `api-docs/influxdb/v2/v2/ref.yml` | `content/influxdb/v2/api/` |
| oss-v1 | Partial | `api-docs/influxdb/v1/v1/ref.yml` | `content/influxdb/v1/api/` |
| enterprise\_influxdb-v1 | Partial | `api-docs/enterprise_influxdb/v1/v1/ref.yml` | `content/enterprise_influxdb/v1/api/` |
### Key Files
- **Generation script:** `api-docs/scripts/generate-openapi-articles.ts`
- **Core conversion:** `api-docs/scripts/openapi-paths-to-hugo-data/index.ts`
- **Hugo layouts:** `layouts/api/`, `layouts/api-operation/`
- **RapiDoc component:** `assets/js/components/rapidoc-mini.ts`
- **Styles:** `assets/styles/layouts/_api-layout.scss`
***
**API Tag Page Consolidation Complete**
The API documentation structure has been refactored from individual operation pages to consolidated tag pages:
1. **Before**: Each API operation had its own page (e.g., `/api/v3/configure/distinct_cache/`)
2. **After**: All operations for a tag are rendered inline on the tag page (e.g., `/api/cache-data/#post-/api/v3/configure/distinct_cache`)
**Key implementation details:**
- Server-side TOC generated from frontmatter `operations` array using Hugo templates
- `safeURL` filter prevents Hugo from URL-encoding anchor slashes
- JavaScript `api-toc.ts` detects pre-rendered TOC and preserves it
- RapiDoc's `scrollToPath()` method handles TOC click navigation to shadow DOM elements
- `goto-path` attribute initializes RapiDoc to scroll to operation from URL hash on page load
- `update-route="true"` enables RapiDoc to update URL hash as user navigates
See [API tag pages design](2026-01-21-api-tag-pages-design.md) for link anchor patterns and route information.
## Fix all InfluxDB products
These products already have generated content but may need spec adjustments and testing.
### Task 1.1: Verify API Generation
**Files:**
- Check: `content/influxdb3/cloud-dedicated/api/_index.md`
- Check: `api-docs/influxdb3/cloud-dedicated/management/openapi.yml`
- Verify: `static/openapi/influxdb3-cloud-dedicated/`
**Step 1: Check existing generated content**
```bash
ls -la content/influxdb3/cloud-dedicated/api/
cat content/influxdb3/cloud-dedicated/api/_index.md
```
Expected: Should see `_index.md` and subdirectories for each tag.
**Step 2: Verify OpenAPI spec exists and is valid**
```bash
head -50 api-docs/influxdb3/cloud-dedicated/management/openapi.yml
```
Expected: Valid OpenAPI 3.x spec with `openapi:`, `info:`, `paths:` sections.
**Step 3: Run generation**
```bash
yarn build:api-docs
```
Or for just this product:
```bash
node api-docs/scripts/dist/generate-openapi-articles.js cloud-dedicated
```
**Step 4: Start Hugo and verify pages render**
```bash
npx hugo server --port 1315
```
Visit the product URL--for example: <http://localhost:1315/influxdb3/cloud-dedicated/api/>
Expected: API reference pages render with RapiDoc component showing operations.
**Step 5: Check for console errors**
Open browser DevTools, verify no JavaScript errors related to RapiDoc.
**Step 6: Commit if working**
```bash
git add content/influxdb3/cloud-dedicated/api/
git add static/openapi/influxdb3-cloud-dedicated/
git add data/article-data/influxdb3/cloud-dedicated/
git commit -m "feat(api): generate cloud-dedicated API reference with RapiDoc"
```
### How to generate API reference articles
**Step 1: Rebuild TypeScript**
```bash
cd api-docs/scripts && yarn build
```
Or from root:
```bash
tsc --project api-docs/scripts/tsconfig.json
```
**Step 2: Test compilation succeeded**
```bash
node api-docs/scripts/dist/generate-openapi-articles.js --help
```
**Step 3: Commit the config change**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): enable cloud-v2 product config for RapiDoc migration"
```
***
## Verification Checklist
Before considering migration complete:
- [ ] All product API pages render without errors
- [ ] RapiDoc "Try It Out" works for each product
- [ ] Mobile responsive layout works correctly
- [ ] Navigation menus updated
- [ ] Old URLs redirect to new locations
- [ ] E2E tests pass
- [ ] No console errors in browser DevTools
- [ ] Links validation passes
***
## Rollback Plan
If issues are found:
1. Revert the product config changes in `generate-openapi-articles.ts`
2. Remove generated content directories
3. Restore original navigation files from git history
```bash
git checkout HEAD~N -- content/influxdb/cloud/reference/api/
git checkout HEAD~N -- api-docs/scripts/generate-openapi-articles.ts
```
***
## Notes
- The `useTagBasedGeneration` option creates pages organized by OpenAPI tags (used for influxdb3 products)
- The path-based generation creates pages organized by API paths (used for v2 products)
- The `skipParentMenu` option prevents duplicate menu entries when existing reference pages have menus

View File

@ -0,0 +1,145 @@
# API Tag Pages Design
## Overview
Consolidate API documentation onto tag pages, where each tag page displays all operations for that tag using RapiDoc. This replaces the previous path-based page structure.
## Goals
1. Keep tag-based navigation in the left sidebar
2. Remove operations as children of tags in the left sidebar
3. Each tag page displays all RapiDoc renderings for operations in that tag
4. "On this page" TOC links to Overview and each operation
5. No frame/internal scrolling - page scrolls naturally as one document
6. Consistent styling with existing implementation
7. Clear visual separation between operations
## URL Structure
- **Tag page:** `/influxdb3/core/api/cache-data/`
- **Operation anchor:** `/influxdb3/core/api/cache-data/#post-/api/v3/configure/distinct_cache`
## RapiDoc Anchor Reference
| Feature | Format/Value |
| ------------------------------ | ---------------------------------------------------------------------------------- |
| Anchor format | `#{method}-{path}` (e.g., `#post-/api/v3/configure/distinct_cache`) |
| `goto-path` attribute | Navigate to operation on load: `goto-path="post-/api/v3/configure/distinct_cache"` |
| `scrollToPath(path)` method | Programmatic navigation |
| `update-route` (default: true) | Updates URL hash as user scrolls |
| `route-prefix` (default: #) | Hash prefix for routes |
| Built-in anchors | `#overview`, `#servers`, `#auth`, `#operations-top` |
## Page Layout
```
┌─────────────────────────────────────────────────────────────┐
│ Header / Top Nav │
├──────────┬─────────────────────────────────────┬────────────┤
│ │ │ │
│ Left │ Main Content │ On This │
│ Sidebar │ │ Page TOC │
│ (nav) │ ┌─────────────────────────────┐ │ │
│ │ │ <h1>Cache data</h1> │ │ Overview │
│ │ │ <p>Tag description...</p> │ │ POST ... │
│ │ └─────────────────────────────┘ │ DELETE .. │
│ │ │ POST ... │
│ │ ┌─────────────────────────────┐ │ DELETE .. │
│ │ │ RapiDoc (full height, │ │ │
│ │ │ no internal scroll) │ │ │
│ │ │ │ │ │
│ │ │ - POST distinct_cache │ │ │
│ │ │ - DELETE distinct_cache │ │ │
│ │ │ - POST last_cache │ │ │
│ │ │ - DELETE last_cache │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
├──────────┴─────────────────────────────────────┴────────────┤
│ Footer │
└─────────────────────────────────────────────────────────────┘
```
## RapiDoc Configuration
- `render-style="read"` - Linear document flow (no internal scrolling)
- `spec-url` - Tag-specific spec (e.g., `/openapi/influxdb3-core/tags/tags/influxdb3-core-cache-data.yaml`)
- `update-route="true"` - URL updates as user navigates (default)
- No fixed height on container - expands to fit content
## "On This Page" TOC
Generated server-side from frontmatter `operations` array:
```yaml
operations:
- operationId: PostConfigureDistinctCache
method: POST
path: /api/v3/configure/distinct_cache
summary: Create distinct cache
- operationId: DeleteConfigureDistinctCache
method: DELETE
path: /api/v3/configure/distinct_cache
summary: Delete distinct cache
```
TOC output:
```
ON THIS PAGE
- Overview
- POST /api/v3/configure/distinct_cache
- DELETE /api/v3/configure/distinct_cache
- POST /api/v3/configure/last_cache
- DELETE /api/v3/configure/last_cache
```
Links use `#{method}-{path}` format matching RapiDoc anchors.
## Hash Navigation
1. On page load, JS reads `window.location.hash`
2. If hash present, set RapiDoc's `goto-path` attribute (without the `#`)
3. RapiDoc's default `update-route=true` updates URL as user scrolls
4. Native URL sharing works
## Files to Modify
### Layouts
- `layouts/api/list.html` - Embed RapiDoc instead of operation cards grid
- `layouts/partials/api/rapidoc-tag.html` - New partial for tag-level RapiDoc
### JavaScript
- `assets/js/components/rapidoc-mini.ts` - Add hash-based `goto-path` initialization
### Remove/Deprecate
- `layouts/api-path/path.html` - Path page layout
- `layouts/partials/api/rapidoc-path.html` - Path partial
- Generated path pages in `content/influxdb3/*/api/v*/`
### Keep
- Tag-specific spec files (`static/openapi/influxdb3-core/tags/tags/`)
- Generation script for tag pages and article data
- All endpoints page
### Generation Script
- Remove `generatePathPages()` function
- Keep tag page generation
- Ensure frontmatter `operations` array is complete for TOC
## Development Scope
Focus on `influxdb3/core` first, then migrate other products.
## Testing
- Verify tag pages load with all operations rendered
- Test hash navigation (direct URL, TOC clicks, browser back/forward)
- Verify no internal scrolling - page flows naturally
- Check visual separation between operations
- Test "On this page" TOC links

View File

@ -0,0 +1,174 @@
# API Link Migration: Redoc to RapiDoc Anchors
## Overview
Migrate all internal API operation links from Redoc's `#operation/{operationId}` format to RapiDoc's native `#{method}-{path}` format.
## Background
The RapiDoc migration changes how anchor links work for API operations:
| Source | Pattern | Example |
|--------|---------|---------|
| **Redoc (old)** | `#operation/{operationId}` | `#operation/PostTasks` |
| **RapiDoc (new)** | `#{method}-{path}` | `#post-/api/v2/tasks` |
**Scope:** 237 links across 111 content files use the Redoc pattern.
**Constraint:** Don't modify source OpenAPI specs—transformation happens at the link level only.
## Goals
1. **Prevent 404s** for external links to API pages (base URL stability)
2. **Clean migration** of all internal links to RapiDoc's native format
3. **Validation** via link-checker after migration
## Non-Goals
- Backward compatibility for fragment identifiers (URL fragments are client-side only; server redirects can't translate them)
- External links with old `#operation/` fragments will land on the correct page but won't auto-scroll
## URL Structure
**API page URLs remain stable:**
- `/influxdb/cloud/api/` — All endpoints page
- `/influxdb3/core/api/` — All endpoints page
- `/influxdb3/core/api/{tag-name}/` — Tag page
**Anchor format changes:**
- Old: `/influxdb/cloud/api/#operation/PostTasks`
- New: `/influxdb/cloud/api/#post-/api/v2/tasks`
## RapiDoc Anchor Format
RapiDoc uses `#{method}-{path}` with these conventions:
- Method is lowercase: `post`, `get`, `delete`, `put`, `patch`
- Path parameters `{param}` become `-param-`: `/tasks/{taskID}``/tasks/-taskID-`
- Slashes in fragments are valid per RFC 3986
**Examples:**
```
#get-/api/v2/tasks
#post-/api/v2/write
#delete-/api/v2/tasks/-taskID-
#get-/api/v2/tasks/-taskID-/runs/-runID-
```
## Migration Script Design
### Location
`helper-scripts/migrate-api-links.js` (one-time migration tool, plain JS)
### Algorithm
**Step 1: Build lookup table from OpenAPI specs**
Key by product to handle duplicate operationIds across specs:
```json
{
"influxdb3/cloud-dedicated": {
"PostWrite": "post-/api/v2/write",
"GetDatabaseTokens": "get-/api/v0/accounts/-accountId-/clusters/-clusterId-/tokens"
},
"influxdb3/core": {
"PostWrite": "post-/api/v3/write"
},
"influxdb/cloud": {
"PostTasks": "post-/api/v2/tasks",
"GetTasksID": "get-/api/v2/tasks/-taskID-"
}
}
```
**Step 2: Scan and transform content files**
```
For each .md file in content/:
Find all patterns: #operation/(\w+)
Extract product from link URL path
Look up operationId in product's mapping
Replace with RapiDoc anchor format
Flag unmapped operationIds for manual review
```
**Step 3: Report**
- Files modified
- Links updated (count)
- Unmapped operationIds (manual review needed)
- Dry-run mode available
### Edge Cases
| Case | Example | Handling |
|------|---------|----------|
| Path parameters | `{taskID}` | Replace with `-taskID-` in anchor |
| Multiple params | `/tasks/{taskID}/runs/{runID}` | Replace all params |
| Missing operationId | Path exists but no operationId in spec | Flag for manual review |
| Deprecated operations | Link to removed endpoint | Flag as potentially broken |
### Usage
```bash
# Dry-run (report only, no changes)
node helper-scripts/migrate-api-links.js --dry-run
# Execute migration
node helper-scripts/migrate-api-links.js
# Review changes
git diff content/
```
## Validation
### Pre-migration
Verify API page URLs are stable:
- Check `_index.md` files have `aliases:` if paths changed
- Confirm no 404s for existing API base paths
### Post-migration
```bash
# Build site
npx hugo --quiet
# Run link-checker on full site
link-checker check public/
```
## Rollback
Git provides easy rollback:
```bash
git checkout -- content/
```
## Files to Create/Modify
### New Files
- `helper-scripts/migrate-api-links.js` — Migration script
### Modified Files
- ~111 content files containing API operation links
## Testing Checklist
- [ ] Dry-run reports expected changes
- [ ] All operationIds map successfully (or flagged for review)
- [ ] Links transform to correct RapiDoc format
- [ ] Hugo build succeeds after migration
- [ ] Link-checker passes on full site
- [ ] Spot-check: anchors navigate to correct operations in browser
## Related Documents
- [API Tag Pages Design](2026-01-21-api-tag-pages-design.md)
- [API Reference RapiDoc Migration Plan](2026-01-07-api-reference-rapidoc-migration.md)

View File

@ -0,0 +1,605 @@
# API Link Migration Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Migrate all 237 internal API links from Redoc `#operation/{operationId}` format to RapiDoc `#{method}-{path}` format.
**Architecture:** One-time Node.js script that (1) parses OpenAPI specs to build operationId→anchor mapping, (2) scans content files for `#operation/` links, (3) replaces with RapiDoc anchors using the mapping.
**Tech Stack:** Node.js, js-yaml (already in dependencies), glob (already in dependencies)
---
## Spec Files → Product URL Mapping
| Spec File | Product URL Prefix |
|-----------|-------------------|
| `api-docs/influxdb/cloud/v2/ref.yml` | `/influxdb/cloud/api/` |
| `api-docs/influxdb/v2/v2/ref.yml` | `/influxdb/v2/api/` |
| `api-docs/influxdb/v1/v1/ref.yml` | `/influxdb/v1/api/` |
| `api-docs/enterprise_influxdb/v1/v1/ref.yml` | `/enterprise_influxdb/v1/api/` |
| `api-docs/influxdb3/core/v3/ref.yml` | `/influxdb3/core/api/` |
| `api-docs/influxdb3/enterprise/v3/ref.yml` | `/influxdb3/enterprise/api/` |
| `api-docs/influxdb3/cloud-dedicated/v2/ref.yml` | `/influxdb3/cloud-dedicated/api/` |
| `api-docs/influxdb3/cloud-dedicated/management/openapi.yml` | `/influxdb3/cloud-dedicated/api/management/` |
| `api-docs/influxdb3/cloud-serverless/v2/ref.yml` | `/influxdb3/cloud-serverless/api/` |
| `api-docs/influxdb3/clustered/v2/ref.yml` | `/influxdb3/clustered/api/` |
| `api-docs/influxdb3/clustered/management/openapi.yml` | `/influxdb3/clustered/api/management/` |
---
## Task 1: Create Migration Script Skeleton
**Files:**
- Create: `helper-scripts/migrate-api-links.js`
**Step 1: Create the script with CLI setup**
```javascript
#!/usr/bin/env node
/**
* migrate-api-links.js
*
* One-time migration script to convert Redoc API links to RapiDoc format.
*
* Usage:
* node helper-scripts/migrate-api-links.js --dry-run # Preview changes
* node helper-scripts/migrate-api-links.js # Execute migration
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const { glob } = require('glob');
// CLI arguments
const args = process.argv.slice(2);
const DRY_RUN = args.includes('--dry-run');
const VERBOSE = args.includes('--verbose');
// Paths
const ROOT_DIR = path.resolve(__dirname, '..');
const CONTENT_DIR = path.join(ROOT_DIR, 'content');
const API_DOCS_DIR = path.join(ROOT_DIR, 'api-docs');
// Spec file → product URL mapping
const SPEC_MAPPINGS = [
{ spec: 'influxdb/cloud/v2/ref.yml', urlPrefix: '/influxdb/cloud/api/' },
{ spec: 'influxdb/v2/v2/ref.yml', urlPrefix: '/influxdb/v2/api/' },
{ spec: 'influxdb/v1/v1/ref.yml', urlPrefix: '/influxdb/v1/api/' },
{ spec: 'enterprise_influxdb/v1/v1/ref.yml', urlPrefix: '/enterprise_influxdb/v1/api/' },
{ spec: 'influxdb3/core/v3/ref.yml', urlPrefix: '/influxdb3/core/api/' },
{ spec: 'influxdb3/enterprise/v3/ref.yml', urlPrefix: '/influxdb3/enterprise/api/' },
{ spec: 'influxdb3/cloud-dedicated/v2/ref.yml', urlPrefix: '/influxdb3/cloud-dedicated/api/' },
{ spec: 'influxdb3/cloud-dedicated/management/openapi.yml', urlPrefix: '/influxdb3/cloud-dedicated/api/management/' },
{ spec: 'influxdb3/cloud-serverless/v2/ref.yml', urlPrefix: '/influxdb3/cloud-serverless/api/' },
{ spec: 'influxdb3/clustered/v2/ref.yml', urlPrefix: '/influxdb3/clustered/api/' },
{ spec: 'influxdb3/clustered/management/openapi.yml', urlPrefix: '/influxdb3/clustered/api/management/' },
];
console.log(`API Link Migration Script`);
console.log(`Mode: ${DRY_RUN ? 'DRY RUN (no changes)' : 'EXECUTE'}\n`);
```
**Step 2: Make it executable and test**
Run:
```bash
chmod +x helper-scripts/migrate-api-links.js
node helper-scripts/migrate-api-links.js --dry-run
```
Expected: Script runs and prints header without errors.
**Step 3: Commit**
```bash
git add helper-scripts/migrate-api-links.js
git commit -m "feat(api): add migration script skeleton"
```
---
## Task 2: Build OperationId Lookup Table
**Files:**
- Modify: `helper-scripts/migrate-api-links.js`
**Step 1: Add function to parse spec and extract operationIds**
Add after the SPEC_MAPPINGS constant:
```javascript
/**
* Convert path parameters from {param} to -param- (RapiDoc format)
*/
function convertPathParams(path) {
return path.replace(/\{([^}]+)\}/g, '-$1-');
}
/**
* Build RapiDoc anchor from method and path
* Format: {method}-{path} with {param} → -param-
*/
function buildAnchor(method, pathStr) {
const convertedPath = convertPathParams(pathStr);
return `${method.toLowerCase()}-${convertedPath}`;
}
/**
* Parse OpenAPI spec and extract operationId → anchor mapping
*/
function parseSpec(specPath) {
const mapping = {};
try {
const content = fs.readFileSync(specPath, 'utf8');
const spec = yaml.load(content);
if (!spec.paths) {
console.warn(` Warning: No paths in ${specPath}`);
return mapping;
}
for (const [pathStr, pathItem] of Object.entries(spec.paths)) {
const methods = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
for (const method of methods) {
const operation = pathItem[method];
if (operation && operation.operationId) {
const anchor = buildAnchor(method, pathStr);
mapping[operation.operationId] = anchor;
if (VERBOSE) {
console.log(` ${operation.operationId} → #${anchor}`);
}
}
}
}
} catch (error) {
console.error(` Error parsing ${specPath}: ${error.message}`);
}
return mapping;
}
/**
* Build complete lookup table from all specs
* Returns: { urlPrefix: { operationId: anchor } }
*/
function buildLookupTable() {
const lookup = {};
console.log('Building operationId lookup table...\n');
for (const { spec, urlPrefix } of SPEC_MAPPINGS) {
const specPath = path.join(API_DOCS_DIR, spec);
if (!fs.existsSync(specPath)) {
console.warn(` Skipping missing spec: ${spec}`);
continue;
}
console.log(` Processing: ${spec}`);
const mapping = parseSpec(specPath);
lookup[urlPrefix] = mapping;
console.log(` Found ${Object.keys(mapping).length} operations`);
}
console.log('');
return lookup;
}
// Test: Build and display lookup table
const lookupTable = buildLookupTable();
console.log('Lookup table built successfully.\n');
```
**Step 2: Test lookup table generation**
Run:
```bash
node helper-scripts/migrate-api-links.js --dry-run --verbose 2>&1 | head -50
```
Expected: See operationId mappings printed for each spec.
**Step 3: Commit**
```bash
git add helper-scripts/migrate-api-links.js
git commit -m "feat(api): add operationId lookup table generation"
```
---
## Task 3: Add Content File Scanner
**Files:**
- Modify: `helper-scripts/migrate-api-links.js`
**Step 1: Add function to find and parse links**
Add after buildLookupTable function:
```javascript
/**
* Find all #operation/ links in a file
* Returns array of { match, operationId, urlPath, fullUrl }
*/
function findOperationLinks(content) {
const links = [];
// Match patterns like: /influxdb/cloud/api/#operation/PostTasks
// or /influxdb3/cloud-dedicated/api/management/#operation/CreateDatabaseToken
const regex = /(\/[a-z0-9_/-]+\/api(?:\/management)?(?:\/[a-z0-9-]*)?\/)#operation\/(\w+)/g;
let match;
while ((match = regex.exec(content)) !== null) {
links.push({
match: match[0],
urlPath: match[1],
operationId: match[2],
});
}
return links;
}
/**
* Find the best matching URL prefix for a given URL path
*/
function findUrlPrefix(urlPath, lookup) {
// Sort by length descending to match most specific first
const prefixes = Object.keys(lookup).sort((a, b) => b.length - a.length);
for (const prefix of prefixes) {
if (urlPath.startsWith(prefix) || urlPath === prefix.slice(0, -1)) {
return prefix;
}
}
return null;
}
/**
* Scan content directory for files with #operation/ links
*/
async function scanContentFiles(lookup) {
console.log('Scanning content files for #operation/ links...\n');
const files = await glob('**/*.md', { cwd: CONTENT_DIR });
const results = {
filesWithLinks: [],
totalLinks: 0,
unmapped: [],
};
for (const file of files) {
const filePath = path.join(CONTENT_DIR, file);
const content = fs.readFileSync(filePath, 'utf8');
const links = findOperationLinks(content);
if (links.length > 0) {
const fileResult = {
file,
links: [],
};
for (const link of links) {
const urlPrefix = findUrlPrefix(link.urlPath, lookup);
if (!urlPrefix) {
results.unmapped.push({ file, ...link, reason: 'No matching URL prefix' });
continue;
}
const productLookup = lookup[urlPrefix];
const anchor = productLookup[link.operationId];
if (!anchor) {
results.unmapped.push({ file, ...link, reason: 'OperationId not found in spec' });
continue;
}
fileResult.links.push({
...link,
urlPrefix,
newAnchor: anchor,
oldLink: `${link.urlPath}#operation/${link.operationId}`,
newLink: `${link.urlPath}#${anchor}`,
});
}
if (fileResult.links.length > 0) {
results.filesWithLinks.push(fileResult);
results.totalLinks += fileResult.links.length;
}
}
}
return results;
}
```
**Step 2: Add main execution and reporting**
Replace the test code at the bottom with:
```javascript
async function main() {
// Build lookup table
const lookupTable = buildLookupTable();
// Scan content files
const results = await scanContentFiles(lookupTable);
// Report findings
console.log('=== SCAN RESULTS ===\n');
console.log(`Files with links: ${results.filesWithLinks.length}`);
console.log(`Total links to migrate: ${results.totalLinks}`);
console.log(`Unmapped links: ${results.unmapped.length}\n`);
if (VERBOSE && results.filesWithLinks.length > 0) {
console.log('Links to migrate:');
for (const { file, links } of results.filesWithLinks) {
console.log(`\n ${file}:`);
for (const link of links) {
console.log(` ${link.oldLink}`);
console.log(` → ${link.newLink}`);
}
}
}
if (results.unmapped.length > 0) {
console.log('\n=== UNMAPPED LINKS (require manual review) ===\n');
for (const item of results.unmapped) {
console.log(` ${item.file}:`);
console.log(` ${item.match}`);
console.log(` Reason: ${item.reason}\n`);
}
}
if (DRY_RUN) {
console.log('\n[DRY RUN] No files modified. Run without --dry-run to apply changes.');
}
}
main().catch(console.error);
```
**Step 3: Test scanner**
Run:
```bash
node helper-scripts/migrate-api-links.js --dry-run
```
Expected: See count of files and links found, plus any unmapped links.
**Step 4: Commit**
```bash
git add helper-scripts/migrate-api-links.js
git commit -m "feat(api): add content file scanner for operation links"
```
---
## Task 4: Add Link Replacement Logic
**Files:**
- Modify: `helper-scripts/migrate-api-links.js`
**Step 1: Add replacement function**
Add before the main function:
```javascript
/**
* Replace operation links in a file
* Returns the modified content
*/
function replaceLinks(content, links) {
let modified = content;
for (const link of links) {
// Replace all occurrences of this specific link
modified = modified.split(link.oldLink).join(link.newLink);
}
return modified;
}
/**
* Apply migrations to files
*/
async function applyMigrations(results) {
console.log('\n=== APPLYING MIGRATIONS ===\n');
let filesModified = 0;
let linksReplaced = 0;
for (const { file, links } of results.filesWithLinks) {
const filePath = path.join(CONTENT_DIR, file);
const originalContent = fs.readFileSync(filePath, 'utf8');
const modifiedContent = replaceLinks(originalContent, links);
if (originalContent !== modifiedContent) {
fs.writeFileSync(filePath, modifiedContent, 'utf8');
filesModified++;
linksReplaced += links.length;
console.log(` ✓ ${file} (${links.length} links)`);
}
}
console.log(`\nMigration complete: ${filesModified} files modified, ${linksReplaced} links replaced.`);
}
```
**Step 2: Update main function to apply changes**
Update the main function to call applyMigrations when not in dry-run mode:
```javascript
async function main() {
// Build lookup table
const lookupTable = buildLookupTable();
// Scan content files
const results = await scanContentFiles(lookupTable);
// Report findings
console.log('=== SCAN RESULTS ===\n');
console.log(`Files with links: ${results.filesWithLinks.length}`);
console.log(`Total links to migrate: ${results.totalLinks}`);
console.log(`Unmapped links: ${results.unmapped.length}\n`);
if (VERBOSE && results.filesWithLinks.length > 0) {
console.log('Links to migrate:');
for (const { file, links } of results.filesWithLinks) {
console.log(`\n ${file}:`);
for (const link of links) {
console.log(` ${link.oldLink}`);
console.log(` → ${link.newLink}`);
}
}
}
if (results.unmapped.length > 0) {
console.log('\n=== UNMAPPED LINKS (require manual review) ===\n');
for (const item of results.unmapped) {
console.log(` ${item.file}:`);
console.log(` ${item.match}`);
console.log(` Reason: ${item.reason}\n`);
}
}
// Apply migrations if not dry-run
if (DRY_RUN) {
console.log('\n[DRY RUN] No files modified. Run without --dry-run to apply changes.');
} else if (results.filesWithLinks.length > 0) {
await applyMigrations(results);
} else {
console.log('\nNo links to migrate.');
}
}
main().catch(console.error);
```
**Step 3: Test dry-run shows expected changes**
Run:
```bash
node helper-scripts/migrate-api-links.js --dry-run --verbose 2>&1 | head -100
```
Expected: See specific link transformations listed.
**Step 4: Commit script completion**
```bash
git add helper-scripts/migrate-api-links.js
git commit -m "feat(api): complete migration script with replacement logic"
```
---
## Task 5: Execute Migration
**Step 1: Final dry-run review**
Run:
```bash
node helper-scripts/migrate-api-links.js --dry-run
```
Review the output. Verify:
- Link count matches expectations (~237 links)
- No critical unmapped links
- Transformations look correct
**Step 2: Execute migration**
Run:
```bash
node helper-scripts/migrate-api-links.js
```
Expected: Files modified, links replaced.
**Step 3: Review changes**
Run:
```bash
git diff content/ | head -200
```
Verify transformations look correct (spot check a few).
**Step 4: Commit migrated content**
```bash
git add content/
git commit -m "refactor(api): migrate operation links to RapiDoc anchor format
Migrated ~237 links from #operation/{operationId} to #{method}-{path} format
for RapiDoc compatibility."
```
---
## Task 6: Validate with Link-Checker
**Step 1: Build Hugo site**
Run:
```bash
npx hugo --quiet
```
Expected: Build succeeds without errors.
**Step 2: Run link-checker**
Run:
```bash
link-checker check public/
```
Or if link-checker isn't installed globally:
```bash
# Map changed content files to HTML and check
git diff --name-only HEAD~1 HEAD | grep '\.md$' | head -20 | \
xargs -I {} link-checker map {} | \
xargs link-checker check
```
Expected: No broken links related to API anchors.
**Step 3: Manual spot-check in browser**
1. Start Hugo server: `npx hugo server`
2. Visit a page with migrated links
3. Click API links and verify they navigate to correct operations
**Step 4: Final commit if any fixes needed**
If link-checker found issues, fix and commit:
```bash
git add content/
git commit -m "fix(api): correct link migration issues found by link-checker"
```
---
## Summary
| Task | Description | Output |
|------|-------------|--------|
| 1 | Script skeleton | `helper-scripts/migrate-api-links.js` |
| 2 | Lookup table generation | operationId → anchor mapping |
| 3 | Content file scanner | Find all `#operation/` links |
| 4 | Replacement logic | Transform links in place |
| 5 | Execute migration | ~237 links migrated |
| 6 | Validate | Link-checker passes |

View File

@ -0,0 +1,93 @@
# InfluxDB v1 API Consistency Design
**Date:** 2026-02-04
**Goal:** Make InfluxDB v1 API specs consistent with other products by using the same Redocly-based overlay approach.
## Current State
- `api-docs/influxdb/v1/v1/ref.yml` - Complete standalone spec (OSS)
- `api-docs/enterprise_influxdb/v1/v1/ref.yml` - Complete standalone spec (Enterprise)
- Not using Redocly decorators or content overlays
- Not integrated with `getswagger.sh`
## Target State
- Both v1 products use `.config.yml` and `content/` overlays like other products
- Integrated with `getswagger.sh` and Redocly decorator pipeline
- Remove unused tag-groups decorator (not used by RapiDoc)
## Design Decisions
1. **Keep both specs as complete, standalone files** - Accept duplication for simplicity
2. **Use overlays for info and servers only** - Paths stay in each `ref.yml`
3. **Remove tag-groups entirely** - Not used by RapiDoc UI
## Implementation
### 1. Directory Structure
```
api-docs/
influxdb/
v1/
.config.yml # Redocly config
v1/
content/
info.yml # OSS info overlay
servers.yml # OSS servers overlay
ref.yml # Complete OSS spec (exists)
enterprise_influxdb/
v1/
.config.yml # Redocly config
v1/
content/
info.yml # Enterprise info overlay
servers.yml # Enterprise servers overlay
ref.yml # Complete Enterprise spec (exists)
```
### 2. Redocly Decorator Changes
**Remove (unused with RapiDoc):**
- `openapi/plugins/decorators/tags/set-tag-groups.cjs`
- `tag-groups.yml` loading from `docs-content.cjs`
- `set-tag-groups` references in `docs-plugin.cjs`
- All `content/tag-groups.yml` files across products
**Keep:**
- `set-info.cjs` - merges info.yml overlay
- `set-servers.cjs` - merges servers.yml overlay
- `replace-shortcodes.cjs` - handles doc URL placeholders
### 3. getswagger.sh Changes
Add functions:
```bash
function updateOSSV1 {
postProcess influxdb/v1/v1/ref.yml 'influxdb/v1/.config.yml' 'v1@1'
}
function updateEnterpriseV1 {
postProcess enterprise_influxdb/v1/v1/ref.yml 'enterprise_influxdb/v1/.config.yml' 'v1@1'
}
```
## Tasks
1. [x] Create `influxdb/v1/.config.yml`
2. [x] Create `influxdb/v1/v1/content/info.yml`
3. [x] Create `influxdb/v1/v1/content/servers.yml`
4. [x] Create `enterprise_influxdb/v1/.config.yml`
5. [x] Create `enterprise_influxdb/v1/v1/content/info.yml`
6. [x] Create `enterprise_influxdb/v1/v1/content/servers.yml`
7. [x] Remove tag-groups decorator and all tag-groups.yml files
8. [x] Add `updateOSSV1()` and `updateEnterpriseV1()` to getswagger.sh
9. [x] Test: Run getswagger.sh for both v1 products
10. [x] Test: Verify API pages render correctly
## Completed: 2026-02-04
All tasks completed successfully. The v1 products now use the same Redocly overlay pattern as other products.

View File

@ -0,0 +1,344 @@
# Hugo-Native API Reference Migration Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Complete migration to Hugo-native API reference rendering for all InfluxDB products, removing RapiDoc and simplifying the codebase.
**Architecture:** The Hugo-native approach renders OpenAPI specs using Hugo templates instead of RapiDoc web components. This provides faster page loads, better SEO, consistent styling, and easier customization. Users access operations only through tag pages (no individual operation URLs).
**Tech Stack:** TypeScript (generation scripts), Hugo templates, SCSS, OpenAPI 3.0 specs
***
## Overview
### Design Principles
- **Consistency:** Unified look and feel across all API reference pages
- **Performance:** Fast page loads, full SEO indexability (no shadow DOM)
- **Simplicity:** No web components, no client-side rendering
- **Tag-based navigation:** Operations grouped by tag, accessed via tag pages only
### URL Structure
- **API index:** `/influxdb3/core/api/`
- **Tag page:** `/influxdb3/core/api/cache-distinct-values/`
- **All endpoints:** `/influxdb3/core/api/all-endpoints/`
**Note:** Individual operation pages (e.g., `/influxdb3/core/api/v1/write/`) are being removed. Operations are accessed only through tag pages.
***
## Migration Tasks
### Task 1: Promote Hugo-native templates to default ✅ COMPLETED
**Priority:** High | **Status:** Completed 2026-02-13
Move Hugo-native templates from POC location to production location.
**Files moved:**
- `layouts/partials/api/hugo-native/tag-renderer.html``layouts/partials/api/tag-renderer.html`
- `layouts/partials/api/hugo-native/operation.html``layouts/partials/api/operation.html`
- `layouts/partials/api/hugo-native/parameters.html``layouts/partials/api/parameters.html`
- `layouts/partials/api/hugo-native/parameter-row.html``layouts/partials/api/parameter-row.html`
- `layouts/partials/api/hugo-native/request-body.html``layouts/partials/api/request-body.html`
- `layouts/partials/api/hugo-native/schema.html``layouts/partials/api/schema.html`
- `layouts/partials/api/hugo-native/responses.html``layouts/partials/api/responses.html`
**Completed steps:**
1. ✅ Moved 7 files from `hugo-native/` subdirectory to parent directory
2. ✅ Updated `layouts/api/list.html` to use new locations (removed `hugo-native/` prefix)
3. ✅ Removed `$useHugoNative` conditional logic from `layouts/api/list.html`
4. ✅ Deleted `layouts/partials/api/hugo-native/` directory
**Verification:** Hugo build passes, pages render correctly at `/influxdb3/core/api/`
***
### Task 2: Remove RapiDoc templates and partials ✅ COMPLETED
**Priority:** High | **Status:** Completed 2026-02-13
Delete RapiDoc-specific templates now that Hugo-native is the default.
**Files deleted:**
- `layouts/partials/api/rapidoc.html`
- `layouts/partials/api/rapidoc-tag.html`
- `layouts/partials/api/rapidoc-mini.html`
**Verification:** `grep -r "rapidoc" layouts/` returns no results
***
### Task 3: Remove RapiDoc JavaScript components ✅ COMPLETED
**Priority:** High | **Status:** Completed 2026-02-13
Delete RapiDoc-specific TypeScript components.
**Files deleted:**
- `assets/js/components/api-rapidoc.ts`
- `assets/js/components/rapidoc-mini.ts`
**Files updated:**
- `assets/js/main.js` - Removed RapiDoc component imports and registrations
**Verification:** `yarn build:ts` completes without errors
***
### Task 4: Remove operation page generation ✅ COMPLETED
**Priority:** High | **Status:** Completed 2026-02-13
Update generation scripts to remove dead code and RapiDoc references.
**Files modified:**
- `api-docs/scripts/generate-openapi-articles.ts` - Removed \~200 lines of dead `generatePathPages` function
- `api-docs/scripts/openapi-paths-to-hugo-data/index.ts` - Updated comments to remove RapiDoc references
**Changes:**
1. ✅ Removed dead `generatePathPages` function (operation page generation was already disabled)
2. ✅ Updated comments from "RapiDoc" to "Hugo-native templates"
3. ✅ Updated "RapiDoc fragment links" to "OpenAPI fragment links"
**Note:** The `useHugoNative` flag was not found in the codebase - operation page generation was already disabled with a comment noting operations are rendered inline on tag pages.
***
### Task 5: Update Cypress tests for Hugo-native ✅ COMPLETED
**Priority:** High | **Status:** Completed 2026-02-13
Simplified Cypress tests now that we use standard HTML instead of shadow DOM.
**Files modified:**
- `cypress/e2e/content/api-reference.cy.js` - Rewrote test file
**Changes:**
1. ✅ Removed entire "RapiDoc Mini component" describe block (\~160 lines of shadow DOM tests)
2. ✅ Added "API tag pages" tests with Hugo-native selectors (`.api-operation`, `.api-method`, `.api-path`)
3. ✅ Added "API section page structure" tests
4. ✅ Added "All endpoints page" tests
5. ✅ Updated "API reference layout" tests to use Hugo-native selectors
**New test structure implemented:**
- `API reference content` - Tests API index pages load with valid links
- `API reference layout` - Tests 3-column layout (sidebar, content, TOC)
- `API tag pages` - Tests operation rendering, method badges, TOC links
- `API section page structure` - Tests tag listing on section pages
- `All endpoints page` - Tests operation cards with links to tag pages
***
### Task 6: Clean up styles ✅ COMPLETED
**Priority:** Medium | **Status:** Completed 2026-02-13
Remove RapiDoc-specific styles, JavaScript, and references from the codebase.
**Files modified:**
- `assets/styles/layouts/_api-layout.scss` - Removed \~40 lines of `rapi-doc::part()` CSS selectors
- `assets/styles/layouts/_api-overrides.scss` - Updated comment header
- `assets/styles/layouts/_api-security-schemes.scss` - Removed \~290 lines of dead auth modal styles
- `assets/js/main.js` - Removed dead `api-auth-input` import and registration
- `assets/js/components/api-toc.ts` - Removed RapiDoc-specific code and updated comments
**Files deleted:**
- `static/css/rapidoc-custom.css` - Unused static CSS file
**Changes:**
1. ✅ Removed `rapi-doc` container styling and `::part()` selectors from `_api-layout.scss`
2. ✅ Removed dead auth modal section from `_api-security-schemes.scss` (was for RapiDoc "Try it" integration)
3. ✅ Removed `api-auth-input` dead import from `main.js` (component file was already deleted)
4. ✅ Removed `setupRapiDocNavigation()` dead function and references from `api-toc.ts`
5. ✅ Updated comments throughout to remove RapiDoc mentions
6. ✅ Rebuilt `api-docs/scripts/dist/` to update compiled JavaScript
**Architecture decision:** Kept operation styles separate from layout styles for cleaner separation of concerns:
- `_api-layout.scss` handles page structure and navigation
- `_api-operations.scss` handles operation/schema component rendering (renamed from `_api-hugo-native.scss`)
***
### Task 7: Fix Generation Script for Clean Regeneration ✅ COMPLETED
**Priority:** Medium | **Status:** Completed 2026-02-17
Added clean regeneration to prevent stale files from accumulating when tags are renamed or removed.
**Files modified:**
- `api-docs/scripts/generate-openapi-articles.ts` - Added cleanup functions and CLI flags
**Implementation:**
1. ✅ Added `--no-clean` flag to skip cleanup (default is to clean)
2. ✅ Added `--dry-run` flag to preview what would be deleted
3. ✅ Added `getCleanupPaths()` function to identify directories/files to clean
4. ✅ Added `cleanProductOutputs()` function to delete directories and files
5. ✅ Added `showDryRunPreview()` function for dry-run output
6. ✅ Integrated cleanup into `processProduct()` (runs before generation)
7. ✅ Updated script header documentation with new usage examples
**Cleaned directories per product:**
- `static/openapi/{staticDirName}/` - Tag specs
- `static/openapi/{staticDirName}-*.yml` and `.json` - Root specs
- `data/article_data/influxdb/{productKey}/` - Article data
- `content/{pagesDir}/api/` - Content pages
**Design:** See `plans/2026-02-17-api-clean-regeneration-design.md`
***
### Task 8: Apply Cache Data tag split to InfluxDB 3 Enterprise
**Priority:** Medium
Apply the same tag split done for Core.
**Files to modify:**
- `api-docs/influxdb3/enterprise/v3/ref.yml`
**Changes:**
1. Replace "Cache data" tag with "Cache distinct values" and "Cache last value" tags
2. Update operation tag references
3. Update x-tagGroups references
4. Regenerate: `sh api-docs/generate-api-docs.sh`
***
### Task 9: Migrate remaining products to Hugo-native
**Priority:** Medium
After the infrastructure is in place, migrate remaining products.
**Products:**
- [ ] cloud-dedicated (management API)
- [ ] cloud-serverless
- [ ] clustered (management API)
- [ ] cloud-v2
- [ ] oss-v2
- [ ] oss-v1
**For each product:**
1. Review tag structure in OpenAPI spec
2. Add `x-influxdata-related` links where appropriate
3. Clean and regenerate
4. Verify all tag pages render correctly
***
## Key Files Reference
**Hugo-Native Templates (after migration):**
- `layouts/partials/api/tag-renderer.html` - Main tag page renderer
- `layouts/partials/api/operation.html` - Individual operation renderer
- `layouts/partials/api/parameters.html` - Parameters section
- `layouts/partials/api/parameter-row.html` - Single parameter row
- `layouts/partials/api/request-body.html` - Request body section
- `layouts/partials/api/schema.html` - JSON schema renderer
- `layouts/partials/api/responses.html` - Response section
**Layouts:**
- `layouts/api/list.html` - Tag page layout (Hugo-native only)
- `layouts/api/section.html` - API section page layout
- `layouts/api/all-endpoints.html` - All endpoints page layout
**Styles:**
- `assets/styles/layouts/_api-layout.scss` - Consolidated API styles
**Generation:**
- `api-docs/scripts/generate-openapi-articles.ts` - Main generation script
- `api-docs/scripts/openapi-paths-to-hugo-data/index.ts` - OpenAPI processing
***
## Verification Checklist
Before considering migration complete for each product:
- [ ] All tag pages render without errors
- [ ] Operation details (parameters, request body, responses) display correctly
- [ ] Schema references resolve and render
- [ ] `x-influxdata-related` links appear at page bottom
- [ ] Navigation shows correct tag structure
- [ ] Mobile responsive layout works
- [ ] No console errors in browser DevTools
- [ ] "On this page" TOC links work correctly
- [ ] Cypress tests pass
- [ ] No RapiDoc references remain in codebase
## Files to Delete (Summary)
**Already deleted (Tasks 1-3):**
- ✅ `layouts/partials/api/rapidoc.html`
- ✅ `layouts/partials/api/rapidoc-tag.html`
- ✅ `layouts/partials/api/rapidoc-mini.html`
- ✅ `layouts/partials/api/hugo-native/` (entire directory - 7 files moved to parent)
- ✅ `assets/js/components/api-rapidoc.ts`
- ✅ `assets/js/components/rapidoc-mini.ts`
**Still to review (Task 6):**
- `assets/styles/layouts/_api-overrides.scss` (if RapiDoc-only)
***
## Migration Findings
### Completed Work Summary (Tasks 1-5)
**Infrastructure changes:**
- Hugo-native templates are now the default (no feature flag required)
- All RapiDoc code removed from layouts and JavaScript
- Generation scripts cleaned up (\~200 lines of dead code removed)
- Cypress tests simplified (no more shadow DOM piercing)
**Key discoveries:**
1. The `useHugoNative` flag did not exist in the codebase - operation page generation was already disabled
2. The `generatePathPages` function was dead code that could be safely removed
3. RapiDoc Mini tests were \~160 lines that are no longer needed
4. Hugo build and TypeScript compilation both pass after all changes
**Verification status:**
- ✅ Hugo build: `npx hugo --quiet` passes
- ✅ TypeScript: `yarn build:ts` passes
- ⏳ Cypress tests: Need to run `yarn test:e2e` to verify new tests pass
- ⏳ Visual review: Need to check pages render correctly in browser
### Remaining Work (Tasks 6-9)
1. **Task 6 (styles)**: Review and consolidate SCSS files
2. **Task 7 (clean regeneration)**: Add `--clean` flag to generation scripts
3. **Task 8 (Enterprise tags)**: Split Cache Data tag in Enterprise spec
4. **Task 9 (product migration)**: Apply to remaining 6 products

View File

@ -0,0 +1,160 @@
# API Clean Regeneration Design
**Goal:** Add clean regeneration to `generate-openapi-articles.ts` to prevent stale files from accumulating when tags are renamed or removed.
**Problem:** When OpenAPI tags are renamed (e.g., "Cache data" → "Cache distinct values" + "Cache last value"), old generated files persist alongside new ones, causing navigation confusion and stale content.
***
## CLI Interface
**New flags:**
| Flag | Description |
| ------------ | ---------------------------------------------------- |
| `--no-clean` | Skip directory cleanup (preserve existing files) |
| `--dry-run` | Show what would be deleted without actually deleting |
**Behavior:**
- Default is to clean before generating (no flag needed)
- `--dry-run` implies `--no-clean` (shows deletions but doesn't execute or generate)
- Existing flags (`--validate-links`, `--skip-fetch`) continue to work
**Usage examples:**
```bash
# Default: clean and regenerate all products
node generate-openapi-articles.js
# Clean and regenerate specific product
node generate-openapi-articles.js influxdb3_core
# Preview what would be deleted
node generate-openapi-articles.js --dry-run
# Preserve existing files (legacy behavior)
node generate-openapi-articles.js --no-clean
```
***
## Directories Cleaned Per Product
For each product (e.g., `influxdb3_core`), the following are cleaned:
| Location | Pattern | Example |
| ------------- | -------------------------------------------------- | -------------------------------------------- |
| Tag specs | `static/openapi/{staticDirName}/` | `static/openapi/influxdb3-core/` |
| Root specs | `static/openapi/{staticDirName}-*.yml` and `.json` | `static/openapi/influxdb3-core-ref.yml` |
| Article data | `data/article_data/influxdb/{productKey}/` | `data/article_data/influxdb/influxdb3_core/` |
| Content pages | `content/{pagesDir}/api/` | `content/influxdb3/core/api/` |
**Boundaries:**
- Only cleans the `api/` subdirectory within content, not the entire product
- Only cleans files matching the product's `staticDirName` pattern
- Never touches other products' files
- Multi-spec products (cloud-dedicated, clustered) clean all spec variants
***
## Dry-Run Output Format
```
$ node generate-openapi-articles.js influxdb3_core --dry-run
DRY RUN: Would clean the following for influxdb3_core:
Directories to remove:
- static/openapi/influxdb3-core/
- data/article_data/influxdb/influxdb3_core/
- content/influxdb3/core/api/
Files to remove:
- static/openapi/influxdb3-core-ref.yml
- static/openapi/influxdb3-core-ref.json
Summary: 3 directories, 2 files would be removed
Skipping generation (dry-run mode).
```
***
## Code Structure
**File modified:** `api-docs/scripts/generate-openapi-articles.ts`
**New CLI flag parsing:**
```typescript
const noClean = process.argv.includes('--no-clean');
const dryRun = process.argv.includes('--dry-run');
```
**New functions:**
```typescript
/**
* Get all paths that would be cleaned for a product
*/
function getCleanupPaths(productKey: string, config: ProductConfig): {
directories: string[];
files: string[];
}
/**
* Clean output directories for a product before regeneration
*/
function cleanProductOutputs(productKey: string, config: ProductConfig): void
/**
* Display dry-run preview of what would be cleaned
*/
function showDryRunPreview(productKey: string, config: ProductConfig): void
```
**Changes to `processProduct()`:**
```typescript
function processProduct(productKey: string, config: ProductConfig): void {
// Clean before generating (unless --no-clean or --dry-run)
if (!noClean && !dryRun) {
cleanProductOutputs(productKey, config);
}
// Existing generation logic...
}
```
**Changes to `main()`:**
```typescript
function main(): void {
// Handle dry-run mode
if (dryRun) {
productsToProcess.forEach((productKey) => {
showDryRunPreview(productKey, productConfigs[productKey]);
});
console.log('\nDry run complete. No files were modified.');
return; // Exit without generating
}
// Existing processing logic...
}
```
**No changes to:** `openapi-paths-to-hugo-data/index.ts`
***
## Verification
After implementation:
1. Run `--dry-run` and verify output matches expected format
2. Run without flags and verify old files are removed
3. Run with `--no-clean` and verify files are preserved
4. Verify Hugo build passes after clean regeneration
5. Verify no stale tag pages appear in navigation

View File

@ -0,0 +1,519 @@
# API Clean Regeneration Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add `--no-clean` and `--dry-run` flags to `generate-openapi-articles.ts` so stale files are automatically removed before regeneration.
**Architecture:** Delete-and-regenerate approach. Before processing each product, remove its output directories (static specs, article data, content pages), then generate fresh. Default behavior is to clean; `--no-clean` preserves existing files.
**Tech Stack:** TypeScript, Node.js fs module, existing generation script
**Design:** See `plans/2026-02-17-api-clean-regeneration-design.md`
***
## Task 1: Add CLI Flag Parsing
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts:88-89`
**Step 1: Add flag constants after existing flags**
Find the existing CLI flags section (around line 88):
```typescript
// CLI flags
const validateLinks = process.argv.includes('--validate-links');
const skipFetch = process.argv.includes('--skip-fetch');
```
Add new flags below:
```typescript
// CLI flags
const validateLinks = process.argv.includes('--validate-links');
const skipFetch = process.argv.includes('--skip-fetch');
const noClean = process.argv.includes('--no-clean');
const dryRun = process.argv.includes('--dry-run');
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): add --no-clean and --dry-run CLI flags"
```
***
## Task 2: Add getCleanupPaths Function
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts`
**Step 1: Add getCleanupPaths function after getStaticDirName function (around line 170)**
```typescript
/**
* Get all paths that would be cleaned for a product
*
* @param productKey - Product identifier (e.g., 'influxdb3_core')
* @param config - Product configuration
* @returns Object with directories and files arrays
*/
function getCleanupPaths(
productKey: string,
config: ProductConfig
): { directories: string[]; files: string[] } {
const staticDirName = getStaticDirName(productKey);
const staticPath = path.join(DOCS_ROOT, 'static/openapi');
const directories: string[] = [];
const files: string[] = [];
// Tag specs directory: static/openapi/{staticDirName}/
const tagSpecsDir = path.join(staticPath, staticDirName);
if (fs.existsSync(tagSpecsDir)) {
directories.push(tagSpecsDir);
}
// Article data directory: data/article_data/influxdb/{productKey}/
const articleDataDir = path.join(
DOCS_ROOT,
`data/article_data/influxdb/${productKey}`
);
if (fs.existsSync(articleDataDir)) {
directories.push(articleDataDir);
}
// Content pages directory: content/{pagesDir}/api/
const contentApiDir = path.join(config.pagesDir, 'api');
if (fs.existsSync(contentApiDir)) {
directories.push(contentApiDir);
}
// Root spec files: static/openapi/{staticDirName}-*.yml and .json
if (fs.existsSync(staticPath)) {
const staticFiles = fs.readdirSync(staticPath);
const pattern = new RegExp(`^${staticDirName}-.*\\.(yml|json)$`);
staticFiles
.filter((f) => pattern.test(f))
.forEach((f) => {
files.push(path.join(staticPath, f));
});
}
return { directories, files };
}
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): add getCleanupPaths function"
```
***
## Task 3: Add cleanProductOutputs Function
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts`
**Step 1: Add cleanProductOutputs function after getCleanupPaths**
```typescript
/**
* Clean output directories for a product before regeneration
*
* @param productKey - Product identifier
* @param config - Product configuration
*/
function cleanProductOutputs(productKey: string, config: ProductConfig): void {
const { directories, files } = getCleanupPaths(productKey, config);
// Remove directories recursively
for (const dir of directories) {
console.log(`🧹 Removing directory: ${dir}`);
fs.rmSync(dir, { recursive: true, force: true });
}
// Remove individual files
for (const file of files) {
console.log(`🧹 Removing file: ${file}`);
fs.unlinkSync(file);
}
const total = directories.length + files.length;
if (total > 0) {
console.log(
`✓ Cleaned ${directories.length} directories, ${files.length} files for ${productKey}`
);
}
}
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): add cleanProductOutputs function"
```
***
## Task 4: Add showDryRunPreview Function
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts`
**Step 1: Add showDryRunPreview function after cleanProductOutputs**
```typescript
/**
* Display dry-run preview of what would be cleaned
*
* @param productKey - Product identifier
* @param config - Product configuration
*/
function showDryRunPreview(productKey: string, config: ProductConfig): void {
const { directories, files } = getCleanupPaths(productKey, config);
console.log(`\nDRY RUN: Would clean the following for ${productKey}:\n`);
if (directories.length > 0) {
console.log('Directories to remove:');
directories.forEach((dir) => console.log(` - ${dir}`));
}
if (files.length > 0) {
console.log('\nFiles to remove:');
files.forEach((file) => console.log(` - ${file}`));
}
if (directories.length === 0 && files.length === 0) {
console.log(' (no files to clean)');
}
console.log(
`\nSummary: ${directories.length} directories, ${files.length} files would be removed`
);
}
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): add showDryRunPreview function"
```
***
## Task 5: Integrate Cleanup into processProduct
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts:1129-1135`
**Step 1: Add cleanup call at the start of processProduct function**
Find the beginning of `processProduct` function (around line 1129):
```typescript
function processProduct(productKey: string, config: ProductConfig): void {
console.log('\n' + '='.repeat(80));
console.log(`Processing ${config.description || productKey}`);
console.log('='.repeat(80));
```
Add cleanup after the header output:
```typescript
function processProduct(productKey: string, config: ProductConfig): void {
console.log('\n' + '='.repeat(80));
console.log(`Processing ${config.description || productKey}`);
console.log('='.repeat(80));
// Clean output directories before regeneration (unless --no-clean)
if (!noClean && !dryRun) {
cleanProductOutputs(productKey, config);
}
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): integrate cleanup into processProduct"
```
***
## Task 6: Add Dry-Run Mode to main Function
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts:1307-1346`
**Step 1: Add dry-run handling after product validation in main()**
Find the section after product validation (around line 1340):
```typescript
// Validate product keys
const invalidProducts = productsToProcess.filter(
(key) => !productConfigs[key]
);
if (invalidProducts.length > 0) {
// ... error handling ...
}
// Process each product
productsToProcess.forEach((productKey) => {
```
Add dry-run handling before the forEach:
```typescript
// Validate product keys
const invalidProducts = productsToProcess.filter(
(key) => !productConfigs[key]
);
if (invalidProducts.length > 0) {
// ... error handling ...
}
// Handle dry-run mode
if (dryRun) {
console.log('\n📋 DRY RUN MODE - No files will be modified\n');
productsToProcess.forEach((productKey) => {
showDryRunPreview(productKey, productConfigs[productKey]);
});
console.log('\nDry run complete. No files were modified.');
return;
}
// Process each product
productsToProcess.forEach((productKey) => {
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "feat(api): add dry-run mode to main function"
```
***
## Task 7: Update Script Header Documentation
**Files:**
- Modify: `api-docs/scripts/generate-openapi-articles.ts:1-21`
**Step 1: Update the usage documentation in the file header**
Find the header comment block and update:
```typescript
#!/usr/bin/env node
/**
* Generate OpenAPI Articles Script
*
* Generates Hugo data files and content pages from OpenAPI specifications
* for all InfluxDB products.
*
* This script:
* 1. Cleans output directories (unless --no-clean)
* 2. Runs getswagger.sh to fetch/bundle OpenAPI specs
* 3. Copies specs to static directory for download
* 4. Generates path group fragments (YAML and JSON)
* 5. Creates article metadata (YAML and JSON)
* 6. Generates Hugo content pages from article data
*
* Usage:
* node generate-openapi-articles.js # Clean and generate all products
* node generate-openapi-articles.js cloud-v2 # Clean and generate single product
* node generate-openapi-articles.js --no-clean # Generate without cleaning
* node generate-openapi-articles.js --dry-run # Preview what would be cleaned
* node generate-openapi-articles.js --skip-fetch # Skip getswagger.sh fetch step
* node generate-openapi-articles.js --validate-links # Validate documentation links
*
* @module generate-openapi-articles
*/
```
**Step 2: Verify TypeScript compiles**
Run: `yarn build:ts`
Expected: Compiles without errors
**Step 3: Commit**
```bash
git add api-docs/scripts/generate-openapi-articles.ts
git commit -m "docs(api): update script header with new flags"
```
***
## Task 8: Rebuild Compiled JavaScript
**Files:**
- Modify: `api-docs/scripts/dist/generate-openapi-articles.js` (generated)
**Step 1: Rebuild TypeScript**
Run: `yarn build:ts`
Expected: Compiles without errors, updates dist/ files
**Step 2: Verify the compiled output includes new functions**
Run: `grep -n "getCleanupPaths\|cleanProductOutputs\|showDryRunPreview\|noClean\|dryRun" api-docs/scripts/dist/generate-openapi-articles.js | head -10`
Expected: Shows line numbers where new code appears
**Step 3: Commit compiled output**
```bash
git add api-docs/scripts/dist/
git commit -m "build(api): rebuild compiled generation scripts"
```
***
## Task 9: Manual Testing
**Step 1: Test dry-run mode**
Run: `node api-docs/scripts/dist/generate-openapi-articles.js influxdb3_core --dry-run`
Expected output format:
```
📋 DRY RUN MODE - No files will be modified
DRY RUN: Would clean the following for influxdb3_core:
Directories to remove:
- static/openapi/influxdb3-core
- data/article_data/influxdb/influxdb3_core
- content/influxdb3/core/api
Files to remove:
- static/openapi/influxdb3-core-ref.yml
- static/openapi/influxdb3-core-ref.json
Summary: 3 directories, 2 files would be removed
Dry run complete. No files were modified.
```
**Step 2: Verify files were NOT deleted after dry-run**
Run: `ls content/influxdb3/core/api/`
Expected: Directory still exists with content
**Step 3: Test actual clean regeneration**
Run: `node api-docs/scripts/dist/generate-openapi-articles.js influxdb3_core --skip-fetch`
Expected: Shows cleanup messages, then regenerates successfully
**Step 4: Verify Hugo build passes**
Run: `npx hugo --quiet`
Expected: Builds without errors
**Step 5: Test --no-clean flag preserves files**
First, create a marker file:
```bash
touch content/influxdb3/core/api/MARKER_FILE.md
```
Run: `node api-docs/scripts/dist/generate-openapi-articles.js influxdb3_core --skip-fetch --no-clean`
Verify marker still exists:
```bash
ls content/influxdb3/core/api/MARKER_FILE.md
```
Expected: File exists
Clean up marker:
```bash
rm content/influxdb3/core/api/MARKER_FILE.md
```
***
## Task 10: Update Migration Plan
**Files:**
- Modify: `plans/2026-02-13-hugo-native-api-migration.md`
**Step 1: Mark Task 7 as completed**
Update the task status from planned to completed.
**Step 2: Commit**
```bash
git add plans/2026-02-13-hugo-native-api-migration.md
git commit -m "docs(plan): mark Task 7 (clean regeneration) as completed"
```
***
## Verification Checklist
Before considering complete:
- [ ] `yarn build:ts` compiles without errors
- [ ] `--dry-run` shows expected output format
- [ ] `--dry-run` does NOT delete any files
- [ ] Default mode (no flags) cleans before regenerating
- [ ] `--no-clean` preserves existing files
- [ ] `npx hugo --quiet` builds successfully after regeneration
- [ ] All new code is committed

View File

@ -0,0 +1,125 @@
# API Code Samples & Ask AI Integration Plan
## Scope
This plan covers:
1. **Inline curl examples** for each API operation, generated at Hugo template time from the OpenAPI spec
2. **"Ask AI about this example"** link on each curl example, using the existing Kapa integration
3. **Client library related link** on all InfluxDB 3 API tag pages
**Out of scope** (separate plans):
- Site-wide Ask AI on all code blocks (render-codeblock hook)
- Client library tabbed code samples with language tabs
- Duplicate response schema rendering (already shown in Responses section)
***
## Architecture
**No build script changes for curl generation.** The curl example is constructed entirely in a Hugo partial (`api/code-sample.html`) using data already loaded by `tag-renderer.html` — the full parsed OpenAPI spec with server URLs, parameters, request body schemas, and examples.
The existing `influxdb-url.js` automatically replaces the default placeholder host in `<pre>` elements with the user's custom URL. No new JavaScript is needed for URL personalization.
### Operation layout order (revised)
1. Header (method + path + summary)
2. Description
3. Parameters
4. Request Body
5. **Example (curl + Ask AI)** — new
6. Responses
***
## curl Example Generation
### Partial: `layouts/partials/api/code-sample.html`
Receives the operation definition (`$opDef`), spec (`$spec`), and operation metadata from `operation.html`. Constructs a curl command:
1. **Server URL**: `spec.servers[0].url` — falls back to the product's `placeholder_host`. The existing `influxdb-url.js` replaces this in the DOM if the user has a custom URL.
2. **Method**: Always explicit `--request METHOD`
3. **Path**: Appended to server URL. `{param}` placeholders left as-is in the URL.
4. **Query parameters**: Only required ones. Uses `example` value if available, otherwise an `UPPER_SNAKE_CASE` placeholder derived from the parameter name.
5. **Headers**:
- Always: `--header "Authorization: Bearer INFLUX_TOKEN"`
- When request body exists: `--header "Content-Type: ..."` derived from the first key in `requestBody.content`
6. **Request body**:
- `application/json`: Uses `schema.example` if present. If no example, body is omitted entirely — no synthesized fake data.
- `text/plain` (line protocol): Hardcoded sample: `--data-raw "measurement,tag=value field=1.0"`
- No example and no special content type: body omitted, shows only URL + headers.
### Ask AI link
Each code sample block includes an "Ask AI about this example" link using the existing `ask-ai-open` CSS class and `data-query` attribute. The existing `ask-ai-trigger.js` handles click events and opens the Kapa widget — no new JavaScript needed.
```html
<a href="#" class="ask-ai-open api-code-ask-ai"
data-query="Explain this API request: POST /api/v2/write - Write data">
Ask AI about this example
</a>
```
***
## Client Library Related Link
The generation script adds a related link to `/influxdb3/{product}/reference/client-libraries/v3/` for all InfluxDB 3 product tag pages.
**InfluxDB 3 products** (identified by `pagesDir` containing `influxdb3/`):
- `influxdb3_core`
- `influxdb3_enterprise`
- `cloud-dedicated`
- `cloud-serverless`
- `clustered`
**Excluded** (future plan with v2 client library links):
- `cloud-v2`, `oss-v2`, `oss-v1`, `enterprise-v1`
The `{product}` segment is derived from the `pagesDir` (e.g., `content/influxdb3/core` yields `core`).
***
## File Changes
### New files
| File | Purpose |
| ---------------------------------------------- | ---------------------------- |
| `layouts/partials/api/code-sample.html` | curl example + Ask AI link |
| `assets/styles/layouts/_api-code-samples.scss` | Styles for code sample block |
### Modified files
| File | Change |
| ----------------------------------------------- | ------------------------------------------------------------------ |
| `layouts/partials/api/operation.html` | Insert `code-sample.html` between request body and responses |
| `assets/styles/styles-default.scss` | Import `_api-code-samples.scss` |
| `api-docs/scripts/generate-openapi-articles.ts` | Add client library reference related link for InfluxDB 3 tag pages |
### Not modified
| File | Reason |
| ------------------------------------------------------ | ------------------------ |
| `layouts/api/list.html` | No layout changes needed |
| `assets/js/main.js` | No new JS components |
| `assets/js/components/api-toc.ts` | TOC unchanged |
| `assets/styles/layouts/_api-layout.scss` | Layout unchanged |
| `api-docs/scripts/openapi-paths-to-hugo-data/index.ts` | No data model changes |
***
## Verification
1. **Build**: `npx hugo --quiet` — no template errors
2. **Visual**: Dev server — navigate to API tag page (e.g., `/influxdb3/core/api/write-data/`) — each operation has a curl example between Request Body and Responses
3. **URL replacement**: Set a custom URL in the URL selector — verify it replaces the host in curl examples
4. **Ask AI**: Click "Ask AI about this example" — Kapa opens with pre-populated query
5. **Related link**: Client library reference link appears at bottom of all InfluxDB 3 API tag pages
6. **Cypress**: Add test verifying `.api-code-sample` elements render on tag pages
7. **Dark/light mode**: Code block renders correctly in both themes
8. **Responsive**: Code sample block handles narrow viewports (horizontal scroll for long curl commands)

77
docs/plans/TESTING.md Normal file
View File

@ -0,0 +1,77 @@
# API Reference Testing Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Validate Hugo-native API reference pages render correctly and all tests pass.
**Architecture:** Hugo-native rendering uses standard HTML without shadow DOM, making tests simpler. No RapiDoc web components - operations are rendered server-side by Hugo templates.
**Tech Stack:** Hugo templates, SCSS, Cypress
***
## Test Structure
The API reference tests validate:
1. **API index pages** - Main API landing pages load correctly
2. **API tag pages** - Tag pages render operations with parameters/responses
3. **Section structure** - Section pages list tag children correctly
4. **All endpoints** - All endpoints page shows all operations
5. **Layout** - 3-column layout with sidebar, content, and TOC
## Running Tests
### Quick validation
```bash
# Build site
yarn hugo --quiet
# Start server
yarn hugo server &
# Test pages load
curl -s -o /dev/null -w "%{http_code}" http://localhost:1313/influxdb3/core/api/
# Expected: 200
# Run Cypress tests (for example, for InfluxDB 3 Core)
node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/api-reference.cy.js" content/influxdb3/core/api/_index.md
# Stop server
pkill -f "hugo server"
```
### Full test suite
```bash
node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/api-reference.cy.js"
```
## Test Selectors (Hugo-Native)
Since Hugo-native uses standard HTML, tests use simple CSS selectors:
| Element | Selector |
| ---------------- | ------------------- |
| Page title | `h1` |
| Operation | `.api-operation` |
| Method badge | `.api-method` |
| Path | `.api-path` |
| Parameters table | `.api-parameters` |
| Request body | `.api-request-body` |
| Responses | `.api-responses` |
| TOC | `.api-toc` |
| Related links | `.article--related` |
## Expected Test Coverage
- [ ] API index pages (Core, Enterprise, Cloud Dedicated, Clustered, Cloud Serverless)
- [ ] Tag pages render operations
- [ ] Parameters display correctly
- [ ] Request body sections display
- [ ] Response sections display
- [ ] TOC links work
- [ ] All endpoints page lists operations
- [ ] Section pages list tags
- [ ] Links are valid

View File

@ -0,0 +1,331 @@
#!/usr/bin/env node
/**
* migrate-api-links.js
*
* One-time migration script to convert Redoc API links to RapiDoc format.
*
* Usage:
* node helper-scripts/migrate-api-links.js --dry-run # Preview changes
* node helper-scripts/migrate-api-links.js # Execute migration
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const { glob } = require('glob');
// CLI arguments
const args = process.argv.slice(2);
const DRY_RUN = args.includes('--dry-run');
const VERBOSE = args.includes('--verbose');
// Paths
const ROOT_DIR = path.resolve(__dirname, '..');
const CONTENT_DIR = path.join(ROOT_DIR, 'content');
const API_DOCS_DIR = path.join(ROOT_DIR, 'api-docs');
// Spec file → product URL mapping
const SPEC_MAPPINGS = [
{ spec: 'influxdb/cloud/v2/ref.yml', urlPrefix: '/influxdb/cloud/api/' },
{ spec: 'influxdb/v2/v2/ref.yml', urlPrefix: '/influxdb/v2/api/' },
{ spec: 'influxdb/v1/v1/ref.yml', urlPrefix: '/influxdb/v1/api/' },
{ spec: 'enterprise_influxdb/v1/v1/ref.yml', urlPrefix: '/enterprise_influxdb/v1/api/' },
{ spec: 'influxdb3/core/v3/ref.yml', urlPrefix: '/influxdb3/core/api/' },
{ spec: 'influxdb3/enterprise/v3/ref.yml', urlPrefix: '/influxdb3/enterprise/api/' },
{ spec: 'influxdb3/cloud-dedicated/v2/ref.yml', urlPrefix: '/influxdb3/cloud-dedicated/api/' },
{ spec: 'influxdb3/cloud-dedicated/management/openapi.yml', urlPrefix: '/influxdb3/cloud-dedicated/api/management/' },
{ spec: 'influxdb3/cloud-serverless/v2/ref.yml', urlPrefix: '/influxdb3/cloud-serverless/api/' },
{ spec: 'influxdb3/clustered/v2/ref.yml', urlPrefix: '/influxdb3/clustered/api/' },
{ spec: 'influxdb3/clustered/management/openapi.yml', urlPrefix: '/influxdb3/clustered/api/management/' },
];
// Version placeholder mappings for shared content
// Maps /version/ placeholder URLs to representative specs for operationId lookup
const VERSION_PLACEHOLDER_MAPPINGS = [
// InfluxDB 3 v3 API (core/enterprise share same operationIds)
{ pattern: /^\/influxdb3\/version\/api\/v3\//, lookupPrefix: '/influxdb3/core/api/' },
// InfluxDB 3 reference path variant
{ pattern: /^\/influxdb3\/[^/]+\/reference\/api\/v3\//, lookupPrefix: '/influxdb3/core/api/' },
// InfluxDB v2 API - use v2 (OSS) as it has more operations than cloud (replication, etc.)
{ pattern: /^\/influxdb\/version\/api\/v2\//, lookupPrefix: '/influxdb/v2/api/' },
{ pattern: /^\/influxdb\/version\/api\/v1\//, lookupPrefix: '/influxdb/v2/api/' }, // v1 compat is in v2 spec
{ pattern: /^\/influxdb\/version\/api\//, lookupPrefix: '/influxdb/v2/api/' },
// InfluxDB 3 version placeholder (generic)
{ pattern: /^\/influxdb3\/version\/api\//, lookupPrefix: '/influxdb3/cloud-serverless/api/' },
];
/**
* Convert path parameters from {param} to -param- (RapiDoc format)
*/
function convertPathParams(path) {
return path.replace(/\{([^}]+)\}/g, '-$1-');
}
/**
* Build RapiDoc anchor from method and path
* Format: {method}-{path} with {param} -param-
*/
function buildAnchor(method, pathStr) {
const convertedPath = convertPathParams(pathStr);
return `${method.toLowerCase()}-${convertedPath}`;
}
/**
* Parse OpenAPI spec and extract operationId anchor mapping
*/
function parseSpec(specPath) {
const mapping = {};
try {
const content = fs.readFileSync(specPath, 'utf8');
const spec = yaml.load(content);
if (!spec.paths) {
console.warn(` Warning: No paths in ${specPath}`);
return mapping;
}
for (const [pathStr, pathItem] of Object.entries(spec.paths)) {
const methods = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head'];
for (const method of methods) {
const operation = pathItem[method];
if (operation && operation.operationId) {
const anchor = buildAnchor(method, pathStr);
mapping[operation.operationId] = anchor;
if (VERBOSE) {
console.log(` ${operation.operationId} → #${anchor}`);
}
}
}
}
} catch (error) {
console.error(` Error parsing ${specPath}: ${error.message}`);
}
return mapping;
}
/**
* Build complete lookup table from all specs
* Returns: { urlPrefix: { operationId: anchor } }
*/
function buildLookupTable() {
const lookup = {};
console.log('Building operationId lookup table...\n');
for (const { spec, urlPrefix } of SPEC_MAPPINGS) {
const specPath = path.join(API_DOCS_DIR, spec);
if (!fs.existsSync(specPath)) {
console.warn(` Skipping missing spec: ${spec}`);
continue;
}
console.log(` Processing: ${spec}`);
const mapping = parseSpec(specPath);
lookup[urlPrefix] = mapping;
console.log(` Found ${Object.keys(mapping).length} operations`);
}
console.log('');
return lookup;
}
/**
* Find all #operation/ links in a file
* Returns array of { match, operationId, urlPath, fullUrl }
*/
function findOperationLinks(content) {
const links = [];
// Match patterns like: /influxdb/cloud/api/#operation/PostTasks
// or /influxdb3/cloud-dedicated/api/management/#operation/CreateDatabaseToken
const regex = /(\/[a-z0-9_/-]+\/api(?:\/management)?(?:\/[a-z0-9-]*)?\/)#operation\/(\w+)/g;
let match;
while ((match = regex.exec(content)) !== null) {
links.push({
match: match[0],
urlPath: match[1],
operationId: match[2],
});
}
return links;
}
/**
* Find the best matching URL prefix for a given URL path
* Also handles /version/ placeholders in shared content
*/
function findUrlPrefix(urlPath, lookup) {
// Sort by length descending to match most specific first
const prefixes = Object.keys(lookup).sort((a, b) => b.length - a.length);
for (const prefix of prefixes) {
if (urlPath.startsWith(prefix) || urlPath === prefix.slice(0, -1)) {
return prefix;
}
}
// Check version placeholder mappings for shared content
for (const { pattern, lookupPrefix } of VERSION_PLACEHOLDER_MAPPINGS) {
if (pattern.test(urlPath)) {
if (VERBOSE) {
console.log(` Mapped ${urlPath}${lookupPrefix} (version placeholder)`);
}
return lookupPrefix;
}
}
return null;
}
/**
* Scan content directory for files with #operation/ links
*/
async function scanContentFiles(lookup) {
console.log('Scanning content files for #operation/ links...\n');
const files = await glob('**/*.md', { cwd: CONTENT_DIR });
const results = {
filesWithLinks: [],
totalLinks: 0,
unmapped: [],
};
for (const file of files) {
const filePath = path.join(CONTENT_DIR, file);
const content = fs.readFileSync(filePath, 'utf8');
const links = findOperationLinks(content);
if (links.length > 0) {
const fileResult = {
file,
links: [],
};
for (const link of links) {
const urlPrefix = findUrlPrefix(link.urlPath, lookup);
if (!urlPrefix) {
results.unmapped.push({ file, ...link, reason: 'No matching URL prefix' });
continue;
}
const productLookup = lookup[urlPrefix];
const anchor = productLookup[link.operationId];
if (!anchor) {
results.unmapped.push({ file, ...link, reason: 'OperationId not found in spec' });
continue;
}
fileResult.links.push({
...link,
urlPrefix,
newAnchor: anchor,
oldLink: `${link.urlPath}#operation/${link.operationId}`,
newLink: `${link.urlPath}#${anchor}`,
});
}
if (fileResult.links.length > 0) {
results.filesWithLinks.push(fileResult);
results.totalLinks += fileResult.links.length;
}
}
}
return results;
}
/**
* Replace operation links in a file
* Returns the modified content
*/
function replaceLinks(content, links) {
let modified = content;
for (const link of links) {
// Replace all occurrences of this specific link
modified = modified.split(link.oldLink).join(link.newLink);
}
return modified;
}
/**
* Apply migrations to files
*/
async function applyMigrations(results) {
console.log('\n=== APPLYING MIGRATIONS ===\n');
let filesModified = 0;
let linksReplaced = 0;
for (const { file, links } of results.filesWithLinks) {
const filePath = path.join(CONTENT_DIR, file);
const originalContent = fs.readFileSync(filePath, 'utf8');
const modifiedContent = replaceLinks(originalContent, links);
if (originalContent !== modifiedContent) {
fs.writeFileSync(filePath, modifiedContent, 'utf8');
filesModified++;
linksReplaced += links.length;
console.log(`${file} (${links.length} links)`);
}
}
console.log(`\nMigration complete: ${filesModified} files modified, ${linksReplaced} links replaced.`);
}
async function main() {
console.log(`API Link Migration Script`);
console.log(`Mode: ${DRY_RUN ? 'DRY RUN (no changes)' : 'EXECUTE'}\n`);
// Build lookup table
const lookupTable = buildLookupTable();
// Scan content files
const results = await scanContentFiles(lookupTable);
// Report findings
console.log('=== SCAN RESULTS ===\n');
console.log(`Files with links: ${results.filesWithLinks.length}`);
console.log(`Total links to migrate: ${results.totalLinks}`);
console.log(`Unmapped links: ${results.unmapped.length}\n`);
if (VERBOSE && results.filesWithLinks.length > 0) {
console.log('Links to migrate:');
for (const { file, links } of results.filesWithLinks) {
console.log(`\n ${file}:`);
for (const link of links) {
console.log(` ${link.oldLink}`);
console.log(`${link.newLink}`);
}
}
}
if (results.unmapped.length > 0) {
console.log('\n=== UNMAPPED LINKS (require manual review) ===\n');
for (const item of results.unmapped) {
console.log(` ${item.file}:`);
console.log(` ${item.match}`);
console.log(` Reason: ${item.reason}\n`);
}
}
// Apply migrations if not dry-run
if (DRY_RUN) {
console.log('\n[DRY RUN] No files modified. Run without --dry-run to apply changes.');
} else if (results.filesWithLinks.length > 0) {
await applyMigrations(results);
} else {
console.log('\nNo links to migrate.');
}
}
main().catch(console.error);

View File

@ -8,26 +8,39 @@ The llms.txt format helps LLMs discover and understand documentation structure.
## Template Files
### `index.llms.txt`
- **Location**: `/layouts/index.llms.txt`
### `index.llmstxt.txt`
- **Location**: `/layouts/index.llmstxt.txt`
- **Output**: `/llms.txt` (site-level)
- **Type**: Hugo template
- **Purpose**: Primary entry point for LLM discovery
- **Content**: Dynamically generated from `data/products.yml` with:
- Product descriptions from data files
- Organized by product category
- Conditional rendering for optional products
- **Content**: Hardcoded curated list of major product sections with:
- Direct links to product documentation
- Product descriptions
- Organized by product category (InfluxDB 3, InfluxDB 2, InfluxDB 1, Tools)
### `section.llms.txt`
- **Location**: `/layouts/_default/section.llms.txt`
- **Output**: Product/section-level llms.txt files (e.g., `/influxdb3/core/llms.txt`)
- **Type**: Hugo template
- **Purpose**: Provide curated navigation for specific products/sections
### `landing-influxdb.llms.txt`
- **Location**: `/layouts/section/landing-influxdb.llms.txt`
- **Output**: Section-level llms.txt files (e.g., `/influxdb3/core/llms.txt`)
- **Type**: Hugo template (for `landing-influxdb` layout type)
- **Purpose**: Provide curated navigation for specific products/sections with landing-influxdb layout
- **Content**: Dynamically generated from:
- Product metadata from `data/products.yml`
- Section content and child pages
- Page descriptions
### `landing-influxdb.llmstxt.txt`
- **Location**: `/layouts/_default/landing-influxdb.llmstxt.txt`
- **Output**: Landing page llms.txt files
- **Type**: Hugo template (for `landing-influxdb` layout type in \_default)
- **Purpose**: Generate llms.txt for landing pages
- **Content**: Dynamically generated from:
- Product metadata from `data/products.yml`
- Page title and description
- Child pages list
## Hugo Configuration
In `config/_default/hugo.yml`:
@ -58,39 +71,47 @@ After building with `hugo`:
```
public/
├── llms.txt # Site-level discovery file
├── llms.txt # Site-level discovery file (from index.llmstxt.txt)
├── influxdb3/
│ ├── core/
│ │ ├── llms.txt # InfluxDB 3 Core product index
│ │ ├── get-started/
│ │ │ └── llms.txt # Section-level index
│ │ └── query-data/
│ │ └── llms.txt # Section-level index
│ │ └── llms.txt # InfluxDB 3 Core product index (landing-influxdb layout)
│ ├── cloud-dedicated/
│ │ └── llms.txt # Cloud Dedicated product index
│ └── cloud-serverless/
│ └── llms.txt # Cloud Serverless product index
│ │ └── llms.txt # Cloud Dedicated product index (landing-influxdb layout)
│ ├── cloud-serverless/
│ │ └── llms.txt # Cloud Serverless product index (landing-influxdb layout)
│ └── clustered/
│ └── llms.txt # Clustered product index (landing-influxdb layout)
├── influxdb/
│ ├── v2/
│ │ └── llms.txt # InfluxDB v2 product index (landing-influxdb layout)
│ └── cloud/
│ └── llms.txt # InfluxDB Cloud TSM index (landing-influxdb layout)
├── telegraf/
│ └── v1/
│ └── llms.txt # Telegraf product index
│ └── llms.txt # Telegraf product index (landing-influxdb layout)
└── flux/
└── v0/
└── llms.txt # Flux product index
└── llms.txt # Flux product index (landing-influxdb layout)
```
Note: llms.txt files are only generated for pages with the `landing-influxdb` layout type and for the site root.
## llmstxt.org Specification Compliance
### Required Elements
- ✅ **H1 header**: Product or section name
- ✅ **Curated links**: Not exhaustive - intentionally selective
### Optional Elements
- ✅ **Blockquote summary**: Brief product/section description
- ✅ **Content paragraphs**: Additional context (NO headings allowed)
- ✅ **H2-delimited sections**: Organize links by category
- ✅ **Link format**: `[Title](url): Description`
### Key Rules
1. **H1 is required** - Only the product/section name
2. **Content sections cannot have headings** - Use paragraphs only
3. **Curate, don't list everything** - Be selective with links
@ -101,24 +122,25 @@ public/
### For Site-Level (/llms.txt)
Edit `/layouts/index.llms.txt` directly. This file is hardcoded for precise curation of top-level products.
Edit `/layouts/index.llmstxt.txt` directly. This file is hardcoded for precise curation of top-level products.
### For Product/Section-Level
### For Product/Section-Level (landing-influxdb layout)
The `/layouts/_default/section.llms.txt` template automatically generates llms.txt files for all sections.
The `/layouts/section/landing-influxdb.llms.txt` template automatically generates llms.txt files for pages with the `landing-influxdb` layout type.
**To customize a specific product's llms.txt:**
1. Create a product-specific template following Hugo's lookup order:
```
layouts/influxdb3/core/section.llms.txt # Specific to Core
layouts/influxdb3/section.llms.txt # All InfluxDB 3 products
layouts/_default/section.llms.txt # Default for all
layouts/influxdb3/core/landing-influxdb.llms.txt # Specific to Core
layouts/influxdb3/landing-influxdb.llms.txt # All InfluxDB 3 products
layouts/section/landing-influxdb.llms.txt # Default for all sections
```
2. **Example: Custom template for InfluxDB 3 Core**
Create `/layouts/influxdb3/core/section.llms.txt`:
Create `/layouts/influxdb3/core/landing-influxdb.llms.txt`:
```
# InfluxDB 3 Core
@ -137,6 +159,10 @@ The `/layouts/_default/section.llms.txt` template automatically generates llms.t
- [Query with SQL](/influxdb3/core/query-data/sql/): SQL query guide
```
### For Landing Pages in \_default
The `/layouts/_default/landing-influxdb.llmstxt.txt` template generates llms.txt for landing pages that use the default layout lookup.
### Using Product Metadata from data/products.yml
The template accesses product metadata:
@ -206,7 +232,7 @@ llms.txt files are automatically generated during:
### Updating Site-Level llms.txt
Edit `/layouts/index.llms.txt` to add/remove product links.
Edit `/layouts/index.llmstxt.txt` to add/remove product links.
### Troubleshooting
@ -223,4 +249,10 @@ Edit `/layouts/index.llms.txt` to add/remove product links.
- [llmstxt.org specification](https://llmstxt.org/)
- [Hugo output formats](https://gohugo.io/templates/output-formats/)
- [InfluxData products.yml](../../data/products.yml)
- [InfluxData products.yml](../data/products.yml)
## Current Template Files
- `/layouts/index.llmstxt.txt` - Root site llms.txt generator
- `/layouts/section/landing-influxdb.llms.txt` - Section-level llms.txt for landing-influxdb layout
- `/layouts/_default/landing-influxdb.llmstxt.txt` - Default landing page llms.txt generator

View File

@ -1 +1,111 @@
{{ .Content }}
{{/* API Documentation Default Layout Fallback layout for API documentation
pages. Delegates to appropriate templates based on page type: - Section pages:
Use section.html logic (children listing) - Pages with staticFilePath: Use
Hugo-native renderer Note: This template exists as a catch-all but specific
templates (api/section.html, api/list.html, api/single.html) should be
preferred. */}}
{{/* Extract product and version from URL path for download buttons */}}
{{/* Example: /influxdb3/clustered/api/ → ["", "influxdb3", "clustered", "api", ""] */}}
{{ $pathParts := split .RelPermalink "/" }}
{{ $version := "" }}
{{ if ge (len $pathParts) 3 }}
{{ $version = index $pathParts 2 }}
{{ end }}
{{/* Section pages without staticFilePath render content directly */}} {{ if and .IsSection (not .Params.staticFilePath)
}} {{ partial "header.html" . }} {{ partial "topnav.html" . }}
<div class="page-wrapper">
{{ partial "sidebar.html" . }}
<div class="content-wrapper api-content">
<div class="api-main">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<h1 class="article--title">{{ .Title }}</h1>
{{ with .Description }}
<p class="article--description">{{ . }}</p>
{{ end }}
</header>
{{/* Dual download buttons for Clustered and Cloud Dedicated */}}
{{ if or (eq $version "clustered") (eq $version "cloud-dedicated") }}
<div class="api-spec-actions api-spec-actions--dual">
<a href="/openapi/influxdb-{{ $version }}-v2-data-api.yml" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Data API Spec
</a>
<a href="/openapi/influxdb-{{ $version }}-management-api.yml" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Management API Spec
</a>
</div>
{{ end }}
{{/* SECTION INDEX - Show page content then children listing */}} {{
with .Content }}
<section class="api-section-content">{{ . }}</section>
{{ end }} {{/* Always show tag pages from article data */}} {{ partial
"api/section-children.html" . }} {{ partial "article/related.html" . }}
</article>
</div>
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav"></nav>
</aside>
</div>
</div>
{{ partial "footer.html" . }} {{ else }} {{/* Pages with staticFilePath
(operation pages) use Hugo-native renderer */}} {{ partial "header.html" . }} {{
partial "topnav.html" . }}
<div class="page-wrapper">
{{ partial "sidebar.html" . }}
<div class="content-wrapper">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<h1 class="article--title">{{ .Title }}</h1>
{{ with .Description }}
<p class="article--description">{{ . }}</p>
{{ end }}
</header>
{{/* Render API documentation using the configured renderer */}} {{
partial "api/renderer.html" . }}
</article>
<div class="copyright">&copy; {{ now.Year }} InfluxData, Inc.</div>
</div>
</div>
{{ partial "footer.html" . }} {{ end }}
<style>
/* Dual download buttons container for Clustered/Cloud Dedicated */
.api-spec-actions--dual {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 1rem 0;
}
/* Responsive - stack buttons on smaller screens */
@media (max-width: 600px) {
.api-spec-actions--dual {
flex-direction: column;
}
.api-spec-actions--dual .api-spec-download {
width: 100%;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,48 @@
{{/*
All Endpoints Layout
Shows all API operations on a single page, grouped by version and sorted by path.
Used for /api/all-endpoints/ pages.
Uses data from:
- data/article_data/influxdb/{product}/articles.yml
*/}}
{{ partial "header.html" . }}
{{ partial "topnav.html" . }}
<div class="page-wrapper">
{{ partial "sidebar.html" . }}
<div class="content-wrapper api-content">
<div class="api-main">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<h1 class="article--title">{{ .Title }}</h1>
{{ with .Description }}
<p class="article--description">{{ . }}</p>
{{ end }}
</header>
{{ with .Content }}
<section class="api-section-content">
{{ . }}
</section>
{{ end }}
{{/* Get all operations from article data */}}
{{ partial "api/all-endpoints-list.html" . }}
{{ partial "article/related.html" . }}
</article>
</div>
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav"></nav>
</aside>
</div>
</div>
{{ partial "footer.html" . }}

200
layouts/api/list.html Normal file
View File

@ -0,0 +1,200 @@
{{/* API Documentation Layout
Two modes:
1. Section index (no 'tag' param) - lists tag pages from article data
2. Tag page (has 'tag' param) - shows operations via Hugo-native templates
Conceptual pages (isConceptual: true) show content without operations.
*/}}
<!-- USING_API_LIST_HTML_TEMPLATE -->
{{ partial "header.html" . }} {{ partial "topnav.html" . }}
<div class="page-wrapper">
{{ partial "sidebar.html" . }}
<div class="content-wrapper api-content">
<div class="api-main">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<h1 class="article--title">{{ .Title }}</h1>
{{/* Only show description in header for section index pages */}}
{{ if not (isset .Params "tag") }}
{{ with .Description }}
<p class="article--description">{{ . }}</p>
{{ end }}
{{ end }}
</header>
{{ $hasTag := isset .Params "tag" }}
{{ if not $hasTag }}
{{/* SECTION INDEX - Show intro content then tag-based children */}}
{{ with .Content }}
<section class="api-section-content">{{ . }}</section>
{{ end }}
{{ partial "api/section-children.html" . }}
{{ else }}
{{/* TAG PAGE - Show operations or conceptual content */}}
{{ $isConceptual := .Params.isConceptual | default false }}
{{ if $isConceptual }}
<section class="api-conceptual-content">
{{ with .Content }} {{ . }} {{ else }}
{{ with .Params.tagDescription }}{{ . | markdownify }}{{ end }}
{{ end }}
</section>
{{ else }}
{{/* Operational Page - Show all operations */}}
{{/* Download OpenAPI spec button — uses specDownloadPath from frontmatter */}}
{{ with .Params.specDownloadPath }}
<div class="api-spec-actions">
<a href="{{ . }}" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download OpenAPI Spec
</a>
</div>
{{ end }}
{{/* Hugo page content if any (for custom intro content) */}}
{{ with .Content }}
<section class="api-content-body">{{ . }}</section>
{{ end }}
{{/* Render operations using Hugo-native templates */}}
{{ with .Params.staticFilePath }}
<section class="api-operations-section">
{{ partial "api/tag-renderer.html" $ }}
</section>
{{ end }} {{ end }} {{ end }} {{ partial "article/related.html" . }}
</article>
</div>
{{/* ON THIS PAGE TOC - Generated from operations array */}} {{ $operations
:= .Params.operations }} {{ $hasTag := isset .Params "tag" }} {{
$isConceptual := .Params.isConceptual | default false }} {{ if and $hasTag
(not $isConceptual) }}
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav">
{{/* Operation links - use Redocly anchor format: operation/{operationId} */}}
{{ range $operations }} {{
$anchor := printf "#operation/%s" .operationId }}
<a
href="{{ $anchor | safeURL }}"
class="api-toc-link api-toc-link--operation"
>
<span
class="api-method api-method--{{ lower .method }} api-method--small"
>{{ upper .method }}</span
>
<span class="api-toc-summary"
>{{ with .summary }}{{ . }}{{ else }}{{ .path }}{{ end }}</span
>
</a>
{{ end }}
</nav>
</aside>
{{ else }}
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav"></nav>
</aside>
{{ end }}
</div>
</div>
{{ partial "footer.html" . }}
<style>
/* API TOC styling */
.api-toc {
position: sticky;
top: 80px;
max-height: calc(100vh - 100px);
overflow-y: auto;
padding: 1rem;
width: 280px;
flex-shrink: 0;
}
.api-toc-header {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #545667;
margin: 0 0 0.75rem 0;
}
.api-toc-nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.api-toc-link {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.5rem;
font-size: 0.85rem;
color: #545667;
text-decoration: none;
border-radius: 4px;
transition:
background-color 0.15s,
color 0.15s;
}
.api-toc-link:hover {
background-color: rgba(0, 163, 255, 0.08);
color: #00a3ff;
}
.api-toc-link--operation {
/* Use default font for human-readable summaries */
}
.api-toc-summary {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.8rem;
}
/* Small method badges for TOC */
.api-method--small {
font-size: 0.65rem;
padding: 0.125rem 0.375rem;
flex-shrink: 0;
}
/* Dark mode */
[data-theme='dark'] .api-toc-header,
html:has(link[title='dark-theme']:not([disabled])) .api-toc-header {
color: #9ea0a6;
}
[data-theme='dark'] .api-toc-link,
html:has(link[title='dark-theme']:not([disabled])) .api-toc-link {
color: #b4b6bc;
}
[data-theme='dark'] .api-toc-link:hover,
html:has(link[title='dark-theme']:not([disabled])) .api-toc-link:hover {
background-color: rgba(0, 163, 255, 0.15);
color: #00a3ff;
}
/* Responsive - hide TOC on smaller screens */
@media (max-width: 1200px) {
.api-toc {
display: none;
}
}
</style>

86
layouts/api/section.html Normal file
View File

@ -0,0 +1,86 @@
{{/* API Documentation Section Layout Used for API section index pages (e.g.,
/influxdb3/core/api/). Shows page content with children listing.
For tag pages (with 'tag' param), Hugo uses list.html instead.
*/}}
{{/* Extract product and version from URL path for download buttons */}}
{{/* Example: /influxdb3/clustered/api/ → ["", "influxdb3", "clustered", "api", ""] */}}
{{ $pathParts := split .RelPermalink "/" }}
{{ $version := "" }}
{{ if ge (len $pathParts) 3 }}
{{ $version = index $pathParts 2 }}
{{ end }}
{{ partial "header.html" . }} {{ partial "topnav.html" . }}
<div class="page-wrapper">
{{ partial "sidebar.html" . }}
<div class="content-wrapper api-content">
<div class="api-main">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<h1 class="article--title">{{ .Title }}</h1>
{{ with .Description }}
<p class="article--description">{{ . }}</p>
{{ end }}
</header>
{{/* Dual download buttons for Clustered and Cloud Dedicated */}}
{{ if or (eq $version "clustered") (eq $version "cloud-dedicated") }}
<div class="api-spec-actions api-spec-actions--dual">
<a href="/openapi/influxdb-{{ $version }}-v2-data-api.yml" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Data API Spec
</a>
<a href="/openapi/influxdb-{{ $version }}-management-api.yml" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Management API Spec
</a>
</div>
{{ end }}
{{/* SECTION INDEX - Show intro content then tag-based children */}} {{
with .Content }}
<section class="api-section-content">{{ . }}</section>
{{ end }} {{/* Always show tag pages from article data */}} {{ partial
"api/section-children.html" . }} {{ partial "article/related.html" . }}
</article>
</div>
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav"></nav>
</aside>
</div>
</div>
{{ partial "footer.html" . }}
<style>
/* Dual download buttons container for Clustered/Cloud Dedicated */
.api-spec-actions--dual {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 1rem 0;
}
/* Responsive - stack buttons on smaller screens */
@media (max-width: 600px) {
.api-spec-actions--dual {
flex-direction: column;
}
.api-spec-actions--dual .api-spec-download {
width: 100%;
justify-content: center;
}
}
</style>

152
layouts/api/single.html Normal file
View File

@ -0,0 +1,152 @@
{{/*
API Documentation Single Page Layout
Used for:
- Conceptual pages (isConceptual: true) like Authentication, Quick start
- Individual operation pages (legacy - being phased out)
For conceptual pages:
- Shows Hugo content or tagDescription markdown
Required frontmatter:
- title: Page title
- isConceptual: true (for conceptual pages)
*/}}
{{ partial "header.html" . }}
{{ partial "topnav.html" . }}
<div class="page-wrapper">
{{/* Left: Existing Hugo sidebar (includes API nav via sidebar.html) */}}
{{ partial "sidebar.html" . }}
{{/* Center + Right: Content and TOC */}}
<div class="content-wrapper api-content">
<div class="api-main">
<article class="article article--content api-reference" role="main">
<header class="article--header">
<div class="article--header-row">
<div class="article--header-text">
{{/* For operation pages, show method badge with title */}}
{{ with .Params.method }}
<div class="api-operation-header">
<span class="api-method api-method--{{ lower . }}">{{ upper . }}</span>
<h1 class="article--title">{{ $.Title }}</h1>
</div>
{{ with $.Params.path }}
<code class="api-operation-path">{{ . }}</code>
{{ end }}
{{ else }}
<h1 class="article--title">{{ .Title }}</h1>
{{ end }}
{{/* Summary/Description - skip for conceptual pages (shown in content section) */}}
{{ if not (.Params.isConceptual | default false) }}
{{ with .Params.summary }}
<p class="article--summary">{{ . | markdownify }}</p>
{{ else }}
{{ with .Description }}
<p class="article--summary">{{ . | markdownify }}</p>
{{ end }}
{{ end }}
{{ end }}
</div>
{{/* Download OpenAPI spec button - context-aware for Clustered/Cloud Dedicated */}}
{{ with .Params.staticFilePath }}
{{/* Extract product name from path like /openapi/influxdb-oss-v2/tags/... */}}
{{ $productName := replaceRE `^/openapi/([^/]+)/.*$` "$1" . }}
{{/* Check if this is a dual-API product (Clustered or Cloud Dedicated) */}}
{{ $isDualApi := or (strings.Contains $productName "clustered") (strings.Contains $productName "cloud-dedicated") }}
{{ if $isDualApi }}
{{/* Determine API type from path */}}
{{ $isManagementApi := strings.Contains . "management-api" }}
{{ if $isManagementApi }}
{{ $specPath := printf "/openapi/%s-management-api.yml" $productName }}
<div class="api-spec-actions">
<a href="{{ $specPath }}" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Management API Spec
</a>
</div>
{{ else }}
{{ $specPath := printf "/openapi/%s-v2-data-api.yml" $productName }}
<div class="api-spec-actions">
<a href="{{ $specPath }}" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download Data API Spec
</a>
</div>
{{ end }}
{{ else }}
{{/* Single-spec products - existing behavior */}}
{{ $completeSpecPath := printf "/openapi/%s.yml" $productName }}
<div class="api-spec-actions">
<a href="{{ $completeSpecPath }}" class="btn api-spec-download" download>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 12L3 7h3V2h4v5h3L8 12z"/>
<path d="M1 14h14v2H1v-2z"/>
</svg>
Download OpenAPI Spec
</a>
</div>
{{ end }}
{{ end }}
</div>
</header>
{{ $isConceptual := .Params.isConceptual | default false }}
{{ if $isConceptual }}
{{/* Conceptual Page - Show content directly */}}
<section class="api-conceptual-content">
{{ with .Content }}
{{ . }}
{{ else }}
{{ with .Params.tagDescription }}
{{ . | markdownify }}
{{ end }}
{{ end }}
</section>
{{/* Security Schemes from OpenAPI spec (only show if showSecuritySchemes: true) */}}
{{ if .Params.showSecuritySchemes }}
{{ partial "api/security-schemes.html" . }}
{{ end }}
{{ else }}
{{/* Operation Page - Hugo-native rendering */}}
{{/* Note: Individual operation pages are being phased out. */}}
{{/* Operations are now accessed via tag pages only. */}}
{{/* Hugo page content shown as overview */}}
{{ with .Content }}
<section class="api-content-overview">
{{ . }}
</section>
{{ end }}
{{ end }}
{{/* Related documentation links */}}
{{ partial "article/related.html" . }}
</article>
</div>
{{/* Right: Page TOC - "ON THIS PAGE" */}}
<aside class="api-toc" data-component="api-toc">
<h4 class="api-toc-header">ON THIS PAGE</h4>
<nav class="api-toc-nav"></nav>
</aside>
</div>
</div>
{{ partial "footer.html" . }}

View File

@ -0,0 +1,221 @@
{{/*
All Endpoints List
Renders all API operations grouped by version (v3, v2, v1) and sorted by path.
Links point to tag pages with hash anchors (e.g., /api/cache-data/#operation/PostConfigureDistinctCache)
Excludes conceptual/trait tag operations.
Uses frontmatter params:
- articleDataKey: product data key (e.g., 'influxdb3-core')
- articleSection: section slug (e.g., 'api' or 'management-api')
*/}}
{{ $currentPage := . }}
{{/* Read data key and section from frontmatter */}}
{{ $dataKey := .Params.articleDataKey | default "" }}
{{ $section := .Params.articleSection | default "" }}
{{/* Get article data using frontmatter-driven lookup */}}
{{ $articles := slice }}
{{ if and $dataKey $section }}
{{ with site.Data.article_data }}
{{ with index . "influxdb" }}
{{ with index . $dataKey }}
{{ with index . $section }}
{{ with index . "articles" }}
{{ with .articles }}
{{ $articles = . }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* Build a map of tag name -> article path for URL lookups */}}
{{ $tagToPath := dict }}
{{ range $articles }}
{{ if and (reflect.IsMap .) (isset . "fields") }}
{{ $fields := index . "fields" }}
{{ if reflect.IsMap $fields }}
{{ if isset $fields "tag" }}
{{ $tag := index $fields "tag" }}
{{ $path := index . "path" }}
{{ $tagToPath = merge $tagToPath (dict $tag $path) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* Collect all operations from non-conceptual articles */}}
{{ $allOperations := slice }}
{{ range $articles }}
{{ if and (reflect.IsMap .) (isset . "fields") }}
{{ $fields := index . "fields" }}
{{ if reflect.IsMap $fields }}
{{ $isConceptual := false }}
{{ if isset $fields "isConceptual" }}
{{ $isConceptual = index $fields "isConceptual" }}
{{ end }}
{{ if not $isConceptual }}
{{ if isset $fields "operations" }}
{{ $tag := index $fields "tag" }}
{{ $articlePath := index . "path" }}
{{ range index $fields "operations" }}
{{ $allOperations = $allOperations | append (dict "op" . "tag" $tag "articlePath" $articlePath) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ if gt (len $allOperations) 0 }}
{{/* Group operations by API version prefix */}}
{{ $v3Ops := slice }}
{{ $v2Ops := slice }}
{{ $v1Ops := slice }}
{{ $otherOps := slice }}
{{ range $allOperations }}
{{ $path := .op.path }}
{{ if hasPrefix $path "/api/v3" }}
{{ $v3Ops = $v3Ops | append . }}
{{ else if hasPrefix $path "/api/v2" }}
{{ $v2Ops = $v2Ops | append . }}
{{ else if or (hasPrefix $path "/api/v1") (hasPrefix $path "/health") (hasPrefix $path "/ping") (hasPrefix $path "/metrics") (hasPrefix $path "/query") (hasPrefix $path "/write") }}
{{ $v1Ops = $v1Ops | append . }}
{{ else }}
{{ $otherOps = $otherOps | append . }}
{{ end }}
{{ end }}
{{/* Sort each group by path then method */}}
{{ $sortV3 := slice }}
{{ range $v3Ops }}
{{ $sortKey := printf "%s %s" .op.path (upper .op.method) }}
{{ $sortV3 = $sortV3 | append (dict "sortKey" $sortKey "data" .) }}
{{ end }}
{{ $sortV3 = sort $sortV3 "sortKey" }}
{{ $sortV2 := slice }}
{{ range $v2Ops }}
{{ $sortKey := printf "%s %s" .op.path (upper .op.method) }}
{{ $sortV2 = $sortV2 | append (dict "sortKey" $sortKey "data" .) }}
{{ end }}
{{ $sortV2 = sort $sortV2 "sortKey" }}
{{ $sortV1 := slice }}
{{ range $v1Ops }}
{{ $sortKey := printf "%s %s" .op.path (upper .op.method) }}
{{ $sortV1 = $sortV1 | append (dict "sortKey" $sortKey "data" .) }}
{{ end }}
{{ $sortV1 = sort $sortV1 "sortKey" }}
{{/* Render v3 API endpoints */}}
{{ if gt (len $sortV3) 0 }}
<section class="api-operations-list api-version-group">
<h2 id="v3-api">v3 API</h2>
<div class="api-operations-grid">
{{ range $sortV3 }}
{{ $op := .data.op }}
{{ $articlePath := .data.articlePath }}
{{/* Build tag page URL with hash anchor: operation/{operationId} */}}
{{/* Build tag page URL relative to the section (parent of all-endpoints) */}}
{{ $sectionUrl := $currentPage.Parent.RelPermalink }}
{{ $tagSlug := path.Base $articlePath }}
{{ $tagPageUrl := printf "%s%s/" $sectionUrl $tagSlug }}
{{ $hashAnchor := printf "#operation/%s" $op.operationId }}
<a href="{{ $tagPageUrl }}{{ $hashAnchor | safeURL }}" class="api-operation-card">
<span class="api-method api-method--{{ lower $op.method }}">{{ upper $op.method }}</span>
<code class="api-path">{{ $op.path }}</code>
<span class="api-operation-summary">{{ $op.summary }}</span>
</a>
{{ end }}
</div>
</section>
{{ end }}
{{/* Render v2-compatible endpoints */}}
{{ if gt (len $sortV2) 0 }}
<section class="api-operations-list api-version-group">
<h2 id="v2-compatible">v2-compatible API</h2>
<div class="api-operations-grid">
{{ range $sortV2 }}
{{ $op := .data.op }}
{{ $articlePath := .data.articlePath }}
{{/* Build tag page URL with hash anchor: operation/{operationId} */}}
{{/* Build tag page URL relative to the section (parent of all-endpoints) */}}
{{ $sectionUrl := $currentPage.Parent.RelPermalink }}
{{ $tagSlug := path.Base $articlePath }}
{{ $tagPageUrl := printf "%s%s/" $sectionUrl $tagSlug }}
{{ $hashAnchor := printf "#operation/%s" $op.operationId }}
<a href="{{ $tagPageUrl }}{{ $hashAnchor | safeURL }}" class="api-operation-card">
<span class="api-method api-method--{{ lower $op.method }}">{{ upper $op.method }}</span>
<code class="api-path">{{ $op.path }}</code>
<span class="api-operation-summary">{{ $op.summary }}</span>
{{ with $op.compatVersion }}<span class="api-compat-badge api-compat-badge--{{ . }}">{{ . }}</span>{{ end }}
</a>
{{ end }}
</div>
</section>
{{ end }}
{{/* Render v1-compatible endpoints */}}
{{ if gt (len $sortV1) 0 }}
<section class="api-operations-list api-version-group">
<h2 id="v1-compatible">v1-compatible API</h2>
<div class="api-operations-grid">
{{ range $sortV1 }}
{{ $op := .data.op }}
{{ $articlePath := .data.articlePath }}
{{/* Build tag page URL with hash anchor: operation/{operationId} */}}
{{/* Build tag page URL relative to the section (parent of all-endpoints) */}}
{{ $sectionUrl := $currentPage.Parent.RelPermalink }}
{{ $tagSlug := path.Base $articlePath }}
{{ $tagPageUrl := printf "%s%s/" $sectionUrl $tagSlug }}
{{ $hashAnchor := printf "#operation/%s" $op.operationId }}
<a href="{{ $tagPageUrl }}{{ $hashAnchor | safeURL }}" class="api-operation-card">
<span class="api-method api-method--{{ lower $op.method }}">{{ upper $op.method }}</span>
<code class="api-path">{{ $op.path }}</code>
<span class="api-operation-summary">{{ $op.summary }}</span>
{{ with $op.compatVersion }}<span class="api-compat-badge api-compat-badge--{{ . }}">{{ . }}</span>{{ end }}
</a>
{{ end }}
</div>
</section>
{{ end }}
{{/* Render Management API endpoints (paths not matching v1/v2/v3 patterns) */}}
{{ if gt (len $otherOps) 0 }}
{{ $sortOther := slice }}
{{ range $otherOps }}
{{ $sortKey := printf "%s %s" .op.path (upper .op.method) }}
{{ $sortOther = $sortOther | append (dict "sortKey" $sortKey "data" .) }}
{{ end }}
{{ $sortOther = sort $sortOther "sortKey" }}
<section class="api-operations-list api-version-group">
<h2 id="management-api">Management API</h2>
<div class="api-operations-grid">
{{ range $sortOther }}
{{ $op := .data.op }}
{{ $articlePath := .data.articlePath }}
{{/* Build tag page URL with hash anchor: operation/{operationId} */}}
{{/* Build tag page URL relative to the section (parent of all-endpoints) */}}
{{ $sectionUrl := $currentPage.Parent.RelPermalink }}
{{ $tagSlug := path.Base $articlePath }}
{{ $tagPageUrl := printf "%s%s/" $sectionUrl $tagSlug }}
{{ $hashAnchor := printf "#operation/%s" $op.operationId }}
<a href="{{ $tagPageUrl }}{{ $hashAnchor | safeURL }}" class="api-operation-card">
<span class="api-method api-method--{{ lower $op.method }}">{{ upper $op.method }}</span>
<code class="api-path">{{ $op.path }}</code>
<span class="api-operation-summary">{{ $op.summary }}</span>
</a>
{{ end }}
</div>
</section>
{{ end }}
{{ else }}
<p>No endpoints available.</p>
{{ end }}

View File

@ -0,0 +1,235 @@
{{/*
API Code Sample
Renders an inline curl example for an API operation, constructed from the
OpenAPI spec at Hugo build time. The existing influxdb-url.js replaces
the default host in <pre> elements if the user has a custom URL set.
Params:
- opDef: The operation definition from the parsed spec
- operation: Operation metadata from frontmatter (method, path, summary, operationId)
- spec: The full OpenAPI spec object for resolving $ref
- context: The page context
*/}}
{{ $opDef := .opDef }}
{{ $operation := .operation }}
{{ $spec := .spec }}
{{ $method := upper $operation.method }}
{{ $path := $operation.path }}
{{/* --- Resolve server URL --- */}}
{{ $serverUrl := "" }}
{{ with index ($spec.servers | default slice) 0 }}
{{ $serverUrl = .url | default "" }}
{{/* Resolve {variable} placeholders using variable defaults */}}
{{ range $varName, $varDef := .variables | default dict }}
{{ $placeholder := printf "{%s}" $varName }}
{{ $serverUrl = replace $serverUrl $placeholder ($varDef.default | default "") }}
{{ end }}
{{ end }}
{{ if not $serverUrl }}
{{ $serverUrl = "http://localhost:8086" }}
{{ end }}
{{/* --- Resolve parameters (handle $ref) --- */}}
{{ $params := $opDef.parameters | default slice }}
{{ $resolvedParams := slice }}
{{ range $params }}
{{ $param := . }}
{{ if isset . "$ref" }}
{{ $refPath := index . "$ref" }}
{{ $refParts := split $refPath "/" }}
{{ if ge (len $refParts) 4 }}
{{ $paramName := index $refParts 3 }}
{{ with index $spec.components.parameters $paramName }}
{{ $param = . }}
{{ end }}
{{ end }}
{{ end }}
{{ $resolvedParams = $resolvedParams | append $param }}
{{ end }}
{{/* --- Build query string from required query parameters --- */}}
{{ $queryParts := slice }}
{{ range $resolvedParams }}
{{ if and (eq .in "query") .required }}
{{ $value := "" }}
{{ with .schema }}
{{ if .example }}
{{ $value = .example | string }}
{{ else if .default }}
{{ $value = .default | string }}
{{ end }}
{{ end }}
{{ if not $value }}
{{ $value = .name | upper | replaceRE "[^A-Z0-9]" "_" }}
{{ end }}
{{ $queryParts = $queryParts | append (printf "%s=%s" .name $value) }}
{{ end }}
{{ end }}
{{ $fullUrl := printf "%s%s" $serverUrl $path }}
{{ if gt (len $queryParts) 0 }}
{{ $fullUrl = printf "%s?%s" $fullUrl (delimit $queryParts "&") }}
{{ end }}
{{/* --- Resolve request body (handle $ref) --- */}}
{{ $requestBody := $opDef.requestBody | default dict }}
{{ if isset $requestBody "$ref" }}
{{ $refPath := index $requestBody "$ref" }}
{{ $refParts := split $refPath "/" }}
{{ if ge (len $refParts) 4 }}
{{ $rbName := index $refParts 3 }}
{{ with index $spec.components.requestBodies $rbName }}
{{ $requestBody = . }}
{{ end }}
{{ end }}
{{ end }}
{{/* --- Determine content type and body --- */}}
{{ $contentType := "" }}
{{ $bodyFlag := "" }}
{{ $rbContent := $requestBody.content | default dict }}
{{ if gt (len $rbContent) 0 }}
{{/* Get first content type key */}}
{{ range $ct, $_ := $rbContent }}
{{ if not $contentType }}
{{ $contentType = $ct }}
{{ end }}
{{ end }}
{{ $mediaType := index $rbContent $contentType | default dict }}
{{ if hasPrefix $contentType "text/plain" }}
{{/* Line protocol — use first example value or a default sample */}}
{{ $lpSample := "measurement,tag=value field=1.0" }}
{{ with $mediaType.examples }}
{{ range $_, $ex := . }}
{{ if not $bodyFlag }}
{{ $lpSample = $ex.value | string }}
{{/* Take only the first line for single-line display */}}
{{ $lines := split $lpSample "\n" }}
{{ $lpSample = index $lines 0 }}
{{ end }}
{{ end }}
{{ end }}
{{ $bodyFlag = printf "--data-raw '%s'" $lpSample }}
{{ else if hasPrefix $contentType "application/json" }}
{{/* JSON — use schema.example, or build from properties */}}
{{ with $mediaType.schema }}
{{/* Resolve schema $ref */}}
{{ $schema := . }}
{{ if isset . "$ref" }}
{{ $refPath := index . "$ref" }}
{{ $refParts := split $refPath "/" }}
{{ if ge (len $refParts) 4 }}
{{ $schemaName := index $refParts 3 }}
{{ with index $spec.components.schemas $schemaName }}
{{ $schema = . }}
{{ end }}
{{ end }}
{{ end }}
{{ if $schema.example }}
{{ $bodyFlag = printf "--data-raw '%s'" (jsonify $schema.example) }}
{{ else if $schema.properties }}
{{/* Build example JSON from schema properties */}}
{{ $bodyObj := dict }}
{{ $requiredList := $schema.required | default slice }}
{{ range $propName, $propDef := $schema.properties }}
{{/* Resolve property $ref */}}
{{ $prop := $propDef }}
{{ if isset $propDef "$ref" }}
{{ $pRefPath := index $propDef "$ref" }}
{{ $pRefParts := split $pRefPath "/" }}
{{ if ge (len $pRefParts) 4 }}
{{ $pSchemaName := index $pRefParts 3 }}
{{ with index $spec.components.schemas $pSchemaName }}
{{ $prop = . }}
{{ end }}
{{ end }}
{{ end }}
{{/* Use example → default → enum[0] → type placeholder */}}
{{ $val := "" }}
{{ if ne $prop.example nil }}
{{ $val = $prop.example }}
{{ else if ne $prop.default nil }}
{{ $val = $prop.default }}
{{ else if $prop.enum }}
{{ $val = index $prop.enum 0 }}
{{ else if eq $prop.type "string" }}
{{ $val = printf "%s" ($propName | upper) }}
{{ else if eq $prop.type "integer" }}
{{ $val = 0 }}
{{ else if eq $prop.type "number" }}
{{ $val = 0 }}
{{ else if eq $prop.type "boolean" }}
{{ $val = false }}
{{ else if eq $prop.type "array" }}
{{ if $prop.items }}
{{ if eq $prop.items.type "string" }}
{{ $val = slice "example" }}
{{ else }}
{{ $val = slice }}
{{ end }}
{{ else }}
{{ $val = slice }}
{{ end }}
{{ else if eq $prop.type "object" }}
{{ $val = dict }}
{{ else }}
{{ $val = printf "%s" ($propName | upper) }}
{{ end }}
{{ $bodyObj = merge $bodyObj (dict $propName $val) }}
{{ end }}
{{ $bodyFlag = printf "--data-raw '%s'" (jsonify (dict "indent" " ") $bodyObj) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{/* --- Assemble curl command --- */}}
{{ $lines := slice }}
{{ $lines = $lines | append (printf "curl --request %s \\" $method) }}
{{ $lines = $lines | append (printf " \"%s\" \\" $fullUrl) }}
{{ $lines = $lines | append " --header \"Authorization: Bearer INFLUX_TOKEN\" \\" }}
{{ if $contentType }}
{{ $lines = $lines | append (printf " --header \"Content-Type: %s\" \\" $contentType) }}
{{ end }}
{{ if $bodyFlag }}
{{/* Last line — no trailing backslash */}}
{{ $lines = $lines | append (printf " %s" $bodyFlag) }}
{{ else }}
{{/* Remove trailing backslash from last header line */}}
{{ $lastIdx := sub (len $lines) 1 }}
{{ $lastLine := index $lines $lastIdx }}
{{ $lastLine = strings.TrimSuffix " \\" $lastLine }}
{{ $newLines := slice }}
{{ range $i, $line := $lines }}
{{ if eq $i $lastIdx }}
{{ $newLines = $newLines | append $lastLine }}
{{ else }}
{{ $newLines = $newLines | append $line }}
{{ end }}
{{ end }}
{{ $lines = $newLines }}
{{ end }}
{{ $curlCommand := delimit $lines "\n" }}
{{/* --- Build Ask AI query --- */}}
{{ $aiQuery := printf "Explain this %s %s API request and its response: %s" $method $path ($operation.summary | default "") }}
{{/* --- Render --- */}}
<div class="api-code-sample">
<div class="api-code-sample-header">
<span class="api-code-sample-title">Example request</span>
<a href="#" class="ask-ai-open api-code-ask-ai"
data-query="{{ $aiQuery }}">
Ask AI about this
</a>
</div>
<div class="api-code-sample-body">
<pre class="api-code-block"><code class="language-sh">{{ $curlCommand }}</code></pre>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More