From 94c449371464f07321957d47950415cdeab4c991 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 2 Dec 2025 15:37:30 -0600 Subject: [PATCH 01/33] feat(api): Uplift API reference docs with Scalar renderer Replace legacy API documentation approach with modern Scalar-based rendering: ## Architecture Changes - Add renderer abstraction (`layouts/partials/api/`) supporting Scalar and RapiDoc - Create `api` layout type for API reference pages (single.html, list.html) - Configure renderer via `site.Params.apiRenderer` (default: scalar) ## OpenAPI Processing Pipeline (TypeScript) - `api-docs/scripts/generate-openapi-articles.ts` - Main generation script - `api-docs/scripts/openapi-paths-to-hugo-data/` - OpenAPI to Hugo data converter - Generates per-endpoint path fragments for AI agent access - Creates Hugo content pages with `type: api` frontmatter ## AI Agent Accessibility - Full specs at `/openapi/influxdb-{product}.yml` and `.json` - Per-endpoint fragments at `/openapi/influxdb-{product}/paths/` - `` tags in HTML for machine discovery ## Scalar Features - Dark/light theme support synchronized with site theme - InfluxData brand colors - Responsive layout - Download link for OpenAPI spec ## Products Supported - cloud-v2, oss-v2 - influxdb3-core, influxdb3-enterprise - cloud-dedicated, cloud-serverless, clustered Usage: node api-docs/scripts/dist/generate-openapi-articles.js [product] --- .gitignore | 9 + api-docs/scripts/README.md | 390 ++++++++++++ .../scripts/dist/generate-openapi-articles.js | 343 ++++++++++ .../dist/openapi-paths-to-hugo-data/index.js | 275 ++++++++ api-docs/scripts/generate-openapi-articles.ts | 360 +++++++++++ .../openapi-paths-to-hugo-data/index.ts | 528 ++++++++++++++++ .../openapi-paths-to-hugo-data/package.json | 14 + .../openapi-paths-to-hugo-data/yarn.lock | 32 + api-docs/scripts/tsconfig.json | 25 + config/_default/hugo.yml | 2 + .../influxdb/cloud-v2/articles.json | 597 ++++++++++++++++++ layouts/_default/api.html | 34 +- layouts/api/list.html | 33 + layouts/api/single.html | 33 + layouts/partials/api/rapidoc.html | 179 ++++++ layouts/partials/api/renderer.html | 25 + layouts/partials/api/scalar.html | 186 ++++++ 17 files changed, 3064 insertions(+), 1 deletion(-) create mode 100644 api-docs/scripts/README.md create mode 100644 api-docs/scripts/dist/generate-openapi-articles.js create mode 100644 api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js create mode 100644 api-docs/scripts/generate-openapi-articles.ts create mode 100644 api-docs/scripts/openapi-paths-to-hugo-data/index.ts create mode 100644 api-docs/scripts/openapi-paths-to-hugo-data/package.json create mode 100644 api-docs/scripts/openapi-paths-to-hugo-data/yarn.lock create mode 100644 api-docs/scripts/tsconfig.json create mode 100644 data/article-data/influxdb/cloud-v2/articles.json create mode 100644 layouts/api/list.html create mode 100644 layouts/api/single.html create mode 100644 layouts/partials/api/rapidoc.html create mode 100644 layouts/partials/api/renderer.html create mode 100644 layouts/partials/api/scalar.html diff --git a/.gitignore b/.gitignore index 5b5487a3d5..caeee60bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,13 @@ package-lock.json /content/influxdb*/**/api/**/*.html !api-docs/**/.config.yml /api-docs/redoc-static.html* + +# API documentation generation (generated by api-docs/scripts/) +/content/influxdb/*/api/** +/content/influxdb3/*/api/** +/content/influxdb3/*/reference/api/** +/static/openapi + /helper-scripts/output/* /telegraf-build !telegraf-build/templates @@ -38,6 +45,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 diff --git a/api-docs/scripts/README.md b/api-docs/scripts/README.md new file mode 100644 index 0000000000..2ea5f440d1 --- /dev/null +++ b/api-docs/scripts/README.md @@ -0,0 +1,390 @@ +# API Documentation Generation Scripts + +TypeScript-based scripts for generating Hugo data files and content pages from OpenAPI specifications. + +## Overview + +These scripts convert OpenAPI v3 specifications into Hugo-compatible data files and content pages for all InfluxDB products. + +### What Gets Generated + +For each product, the scripts generate: + +1. **OpenAPI Spec Copies** (static directory): + - `influxdb-{product}.yml` - YAML version of the spec + - `influxdb-{product}.json` - JSON version of the spec + +2. **Path Group Fragments** (static/openapi/{product}/paths/): + - Separate YAML and JSON files for each API path group + - Example: `ref-api-v2-buckets.yaml` and `ref-api-v2-buckets.json` + +3. **Article Metadata** (data/article-data/influxdb/{product}/): + - `articles.yml` - Hugo data file with article metadata + - `articles.json` - JSON version for programmatic access + +4. **Hugo Content Pages** (content/{product}/api/): + - Markdown files generated from article data + - One page per API path group + +## Quick Start + +### Build Scripts + +Compile TypeScript to JavaScript (required before running): + +```bash +yarn build:api-scripts +``` + +### Generate API Pages + +**Generate all products:** + +```bash +yarn build:api-pages +``` + +**Generate specific product(s):** + +```bash +yarn build:api-pages:product cloud-v2 +yarn build:api-pages:product cloud-v2 oss-v2 +``` + +## Supported Products + +| Product ID | Description | Spec File | Content Path | +| ---------------------- | ------------------------- | ------------------------------------------------ | -------------------------------------------- | +| `cloud-v2` | InfluxDB Cloud (v2 API) | `api-docs/cloud/v2/ref.yml` | `content/influxdb/cloud/api/v2` | +| `oss-v2` | InfluxDB OSS v2 | `api-docs/v2/ref.yml` | `content/influxdb/v2/api/v2` | +| `influxdb3-core` | InfluxDB 3 Core | `api-docs/influxdb3/core/v3/ref.yml` | `content/influxdb3/core/reference/api` | +| `influxdb3-enterprise` | InfluxDB 3 Enterprise | `api-docs/influxdb3/enterprise/v3/ref.yml` | `content/influxdb3/enterprise/reference/api` | +| `cloud-dedicated` | InfluxDB Cloud Dedicated | `api-docs/influxdb3/cloud-dedicated/v2/ref.yml` | `content/influxdb/cloud-dedicated/api` | +| `cloud-serverless` | InfluxDB Cloud Serverless | `api-docs/influxdb3/cloud-serverless/v2/ref.yml` | `content/influxdb/cloud-serverless/api` | +| `clustered` | InfluxDB Clustered | `api-docs/influxdb3/clustered/v2/ref.yml` | `content/influxdb/clustered/api` | + +## Architecture + +### TypeScript Files + +``` +api-docs/scripts/ +├── tsconfig.json # TypeScript configuration +├── generate-openapi-articles.ts # Main orchestration script +└── openapi-paths-to-hugo-data/ + ├── index.ts # Core conversion logic + └── package.json # Module dependencies +``` + +### Compiled JavaScript + +After running `yarn build:api-scripts`, compiled files are in: + +``` +api-docs/scripts/dist/ +├── generate-openapi-articles.js +├── generate-openapi-articles.d.ts +└── openapi-paths-to-hugo-data/ + ├── index.js + └── index.d.ts +``` + +## Script Details + +### generate-openapi-articles.ts + +Main orchestration script that processes products. + +**For each product, it:** + +1. Runs `getswagger.sh` to fetch/bundle the OpenAPI spec +2. Copies spec to `static/openapi/influxdb-{product}.yml` +3. Generates JSON version at `static/openapi/influxdb-{product}.json` +4. Generates path group fragments (YAML and JSON) +5. Creates article metadata (YAML and JSON) +6. Generates Hugo content pages + +**Usage:** + +```bash +node api-docs/scripts/dist/generate-openapi-articles.js [product-ids...] + +# Examples: +node api-docs/scripts/dist/generate-openapi-articles.js # All products +node api-docs/scripts/dist/generate-openapi-articles.js cloud-v2 # Single product +node api-docs/scripts/dist/generate-openapi-articles.js cloud-v2 oss-v2 # Multiple products +``` + +**Output:** + +``` +📋 Processing all products... + +================================================================================ +Processing InfluxDB Cloud (v2 API) +================================================================================ + +Fetching OpenAPI spec for cloud-v2... +✓ Copied spec to static/openapi/influxdb-cloud-v2.yml +✓ Generated JSON spec at static/openapi/influxdb-cloud-v2.json + +Generating OpenAPI path files in static/openapi/influxdb-cloud-v2/paths.... +Generated: ref-api-v2-buckets.yaml and ref-api-v2-buckets.json +... + +Generating OpenAPI article data in data/article-data/influxdb/cloud-v2... +Generated 32 articles in data/article-data/influxdb/cloud-v2 + +✅ Successfully processed InfluxDB Cloud (v2 API) +``` + +### openapi-paths-to-hugo-data/index.ts + +Core conversion library that processes OpenAPI specs. + +**Key Functions:** + +- `generateHugoData(options)` - Main entry point +- `writePathOpenapis()` - Groups paths and writes fragments +- `createArticleDataForPathGroup()` - Generates article metadata + +**Path Grouping Logic:** + +Paths are grouped by their base path (first 3-4 segments, excluding placeholders): + +``` +/api/v2/buckets → api-v2-buckets +/api/v2/buckets/{id} → api-v2-buckets (same group) +/api/v2/authorizations → api-v2-authorizations +``` + +**Output Formats:** + +- **YAML**: Hugo-compatible data files +- **JSON**: Programmatic access and tooling + +## Development + +### Prerequisites + +- Node.js >= 16.0.0 +- Yarn package manager +- TypeScript installed (via root package.json) + +### Setup + +```bash +# Install dependencies (from repo root) +yarn install + +# Or install in the openapi-paths-to-hugo-data module +cd api-docs/scripts/openapi-paths-to-hugo-data +yarn install +``` + +### TypeScript Configuration + +The scripts use a dedicated `tsconfig.json` with CommonJS output: + +```json +{ + "compilerOptions": { + "target": "ES2021", + "module": "CommonJS", + "outDir": "./dist", + "strict": true, + ... + } +} +``` + +### Making Changes + +1. Edit TypeScript files in `api-docs/scripts/` +2. Compile: `yarn build:api-scripts` +3. Test: `yarn build:api-pages:product cloud-v2` + +### Watch Mode + +For active development: + +```bash +cd api-docs/scripts/openapi-paths-to-hugo-data +yarn build:watch +``` + +## Testing + +### Unit Test Example + +```javascript +const converter = require('./api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js'); + +converter.generateHugoData({ + specFile: 'api-docs/influxdb/cloud/v2/ref.yml', + dataOutPath: './test-output/paths', + articleOutPath: './test-output/articles' +}); +``` + +### Verify Output + +After generation, check: + +1. **Path fragments exist:** + ```bash + ls -l static/openapi/influxdb-cloud-v2/paths/ + ``` + +2. **Both formats generated:** + ```bash + ls -l static/openapi/influxdb-cloud-v2/paths/*.{yaml,json} + ``` + +3. **Article data created:** + ```bash + cat data/article-data/influxdb/cloud-v2/articles.yml + cat data/article-data/influxdb/cloud-v2/articles.json + ``` + +4. **Hugo pages generated:** + ```bash + ls -l content/influxdb/cloud/api/v2/ + ``` + +## Troubleshooting + +### TypeScript Compilation Errors + +```bash +# Clean and rebuild +rm -rf api-docs/scripts/dist +yarn build:api-scripts +``` + +### Missing Type Definitions + +```bash +cd api-docs/scripts/openapi-paths-to-hugo-data +yarn add --dev @types/js-yaml @types/node +``` + +### Spec File Not Found + +Make sure to run `getswagger.sh` first: + +```bash +cd api-docs +./getswagger.sh cloud-v2 -B +``` + +### Path Grouping Issues + +The script groups paths by their first 3-4 segments. If you need different grouping: + +1. Edit `writePathOpenapis()` in `openapi-paths-to-hugo-data/index.ts` +2. Modify the `key.slice(0, 4)` logic +3. Rebuild: `yarn build:api-scripts` + +## Migration from JavaScript + +The original JavaScript files are preserved for reference: + +- `api-docs/scripts/generate-openapi-articles.js` (original) +- `api-docs/scripts/openapi-paths-to-hugo-data/index.js` (original) + +### Key Improvements + +1. **TypeScript**: Full type safety and IDE support +2. **Dual Formats**: Generates both YAML and JSON +3. **All Products**: Includes all 7 InfluxDB products +4. **Better Errors**: Clear error messages with product validation +5. **CLI Arguments**: Support for processing specific products +6. **Comprehensive Logging**: Progress indicators and status messages + +## Related Documentation + +- **API Docs README**: `api-docs/README.md` - Complete API documentation workflow +- **OpenAPI Plugins**: `api-docs/openapi/plugins/` - Custom processing plugins +- **Hugo Data to Pages**: `hugo-data-to-pages/` - Page generation from data files + +## Examples + +### Generate Only Cloud Products + +```bash +yarn build:api-pages:product cloud-v2 cloud-dedicated cloud-serverless +``` + +### Generate Only InfluxDB 3 Products + +```bash +yarn build:api-pages:product influxdb3-core influxdb3-enterprise +``` + +### Process Single Product Manually + +```bash +# Compile first +yarn build:api-scripts + +# Run for specific product +node api-docs/scripts/dist/generate-openapi-articles.js oss-v2 +``` + +## API Reference + +### generateHugoData(options) + +Generate Hugo data files from an OpenAPI specification. + +**Parameters:** + +- `options.specFile` (string) - Path to the OpenAPI spec file +- `options.dataOutPath` (string) - Output path for OpenAPI path fragments +- `options.articleOutPath` (string) - Output path for article metadata + +**Example:** + +```javascript +const { generateHugoData } = require('./api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js'); + +generateHugoData({ + specFile: 'api-docs/influxdb/cloud/v2/ref.yml', + dataOutPath: 'static/openapi/influxdb-cloud-v2/paths', + articleOutPath: 'data/article-data/influxdb/cloud-v2' +}); +``` + +### productConfigs + +Map of product configurations exported from `generate-openapi-articles.ts`. + +**Type:** + +```typescript +type ProductConfig = { + specFile: string; // Path to OpenAPI spec + pagesDir: string; // Hugo content directory + description?: string; // Product description +}; + +const productConfigs: Record; +``` + +**Usage:** + +```javascript +const { productConfigs } = require('./api-docs/scripts/dist/generate-openapi-articles.js'); + +console.log(productConfigs['cloud-v2']); +// { +// specFile: 'api-docs/cloud/v2/ref.yml', +// pagesDir: 'content/influxdb/cloud/api/v2', +// description: 'InfluxDB Cloud (v2 API)' +// } +``` + +## License + +Same as parent docs-v2 repository (MIT). diff --git a/api-docs/scripts/dist/generate-openapi-articles.js b/api-docs/scripts/dist/generate-openapi-articles.js new file mode 100644 index 0000000000..92dcc39115 --- /dev/null +++ b/api-docs/scripts/dist/generate-openapi-articles.js @@ -0,0 +1,343 @@ +#!/usr/bin/env node +'use strict'; +/** + * Generate OpenAPI Articles Script + * + * Generates Hugo data files and content pages from OpenAPI specifications + * for all InfluxDB products. + * + * This script: + * 1. Runs getswagger.sh to fetch/bundle OpenAPI specs + * 2. Copies specs to static directory for download + * 3. Generates path group fragments (YAML and JSON) + * 4. Creates article metadata (YAML and JSON) + * 5. Generates Hugo content pages from article data + * + * Usage: + * node generate-openapi-articles.js # Generate all products + * node generate-openapi-articles.js cloud-v2 # Generate single product + * node generate-openapi-articles.js cloud-v2 oss-v2 # Generate multiple products + * + * @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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.productConfigs = void 0; +exports.processProduct = processProduct; +exports.generateDataFromOpenAPI = generateDataFromOpenAPI; +exports.generatePagesFromArticleData = generatePagesFromArticleData; +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'); +// Calculate the relative paths +const DOCS_ROOT = '.'; +const API_DOCS_ROOT = 'api-docs'; +/** + * Execute a shell command and handle errors + * + * @param command - Command to execute + * @param description - Human-readable description of the command + * @throws Exits process with code 1 on error + */ +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); + } +} +/** + * Generate Hugo data files from OpenAPI specification + * + * @param specFile - Path to the OpenAPI spec file + * @param dataOutPath - Output path for OpenAPI path fragments + * @param articleOutPath - Output path for article metadata + */ +function generateDataFromOpenAPI(specFile, dataOutPath, articleOutPath) { + if (!fs.existsSync(dataOutPath)) { + fs.mkdirSync(dataOutPath, { recursive: true }); + } + openapiPathsToHugo.generateHugoData({ + dataOutPath, + articleOutPath, + specFile, + }); +} +/** + * Generate Hugo content pages from article data + * + * Creates markdown files with frontmatter from article metadata. + * Each article becomes a page with type: api that renders via Scalar. + * + * @param articlesPath - Path to the articles data directory + * @param contentPath - Output path for generated content pages + */ +function generatePagesFromArticleData(articlesPath, contentPath) { + const yaml = require('js-yaml'); + const articlesFile = path.join(articlesPath, 'articles.yml'); + if (!fs.existsSync(articlesFile)) { + console.warn(`⚠️ Articles file not found: ${articlesFile}`); + return; + } + // Read articles data + 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; + } + // Ensure content directory exists + if (!fs.existsSync(contentPath)) { + fs.mkdirSync(contentPath, { recursive: true }); + } + // Generate a page for each article + for (const article of data.articles) { + const pagePath = path.join(contentPath, article.path); + const pageFile = path.join(pagePath, '_index.md'); + // Create directory if needed + if (!fs.existsSync(pagePath)) { + fs.mkdirSync(pagePath, { recursive: true }); + } + // Generate frontmatter + const frontmatter = { + title: article.fields.name || article.path, + description: `API reference for ${article.fields.name || article.path}`, + type: 'api', + staticFilePath: article.fields.staticFilePath, + weight: 100, + }; + const pageContent = `--- +${yaml.dump(frontmatter)}--- +`; + fs.writeFileSync(pageFile, pageContent); + } + console.log( + `✓ Generated ${data.articles.length} content pages in ${contentPath}` + ); +} +/** + * Product configurations for all InfluxDB editions + * + * Maps product identifiers to their OpenAPI specs and content directories + */ +const productConfigs = { + 'cloud-v2': { + specFile: path.join(API_DOCS_ROOT, 'influxdb/cloud/v2/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb/cloud/api/v2'), + description: 'InfluxDB Cloud (v2 API)', + }, + 'oss-v2': { + specFile: path.join(API_DOCS_ROOT, 'influxdb/v2/v2/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb/v2/api/v2'), + description: 'InfluxDB OSS v2', + }, + 'influxdb3-core': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/core/v3/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/core/reference/api'), + description: 'InfluxDB 3 Core', + }, + 'influxdb3-enterprise': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/enterprise/v3/ref.yml'), + pagesDir: path.join( + DOCS_ROOT, + 'content/influxdb3/enterprise/reference/api' + ), + description: 'InfluxDB 3 Enterprise', + }, + 'cloud-dedicated': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-dedicated/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-dedicated/api'), + description: 'InfluxDB Cloud Dedicated', + }, + 'cloud-serverless': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-serverless/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-serverless/api'), + description: 'InfluxDB Cloud Serverless', + }, + clustered: { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/clustered/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/clustered/api'), + description: 'InfluxDB Clustered', + }, +}; +exports.productConfigs = productConfigs; +/** + * Process a single product: fetch spec, generate data, and create pages + * + * @param productKey - Product identifier (e.g., 'cloud-v2') + * @param config - Product configuration + */ +function processProduct(productKey, config) { + console.log('\n' + '='.repeat(80)); + console.log(`Processing ${config.description || productKey}`); + console.log('='.repeat(80)); + const staticPath = path.join(DOCS_ROOT, 'static/openapi'); + const staticSpecPath = path.join(staticPath, `influxdb-${productKey}.yml`); + const staticJsonSpecPath = path.join( + staticPath, + `influxdb-${productKey}.json` + ); + const staticPathsPath = path.join(staticPath, `influxdb-${productKey}/paths`); + const articlesPath = path.join( + DOCS_ROOT, + `data/article-data/influxdb/${productKey}` + ); + // Check if spec file exists + if (!fs.existsSync(config.specFile)) { + console.warn(`⚠️ Spec file not found: ${config.specFile}`); + console.log('Skipping this product. Run getswagger.sh first if needed.\n'); + return; + } + try { + // Step 1: Execute the getswagger.sh script to fetch/bundle the spec + const getswaggerScript = path.join(API_DOCS_ROOT, 'getswagger.sh'); + if (fs.existsSync(getswaggerScript)) { + execCommand( + `${getswaggerScript} ${productKey} -B`, + `Fetching OpenAPI spec for ${productKey}` + ); + } else { + console.log(`⚠️ getswagger.sh not found, skipping fetch step`); + } + // Step 2: Ensure static directory exists + if (!fs.existsSync(staticPath)) { + fs.mkdirSync(staticPath, { recursive: true }); + } + // Step 3: Copy the generated OpenAPI spec to static folder (YAML) + if (fs.existsSync(config.specFile)) { + fs.copyFileSync(config.specFile, staticSpecPath); + console.log(`✓ Copied spec to ${staticSpecPath}`); + // Step 4: Generate JSON version of the spec + try { + const yaml = require('js-yaml'); + const specContent = fs.readFileSync(config.specFile, 'utf8'); + const specObject = yaml.load(specContent); + fs.writeFileSync( + staticJsonSpecPath, + JSON.stringify(specObject, null, 2) + ); + console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`); + } catch (jsonError) { + console.warn(`⚠️ Could not generate JSON spec: ${jsonError}`); + } + } + // Step 5: Generate Hugo data from OpenAPI spec (path fragments for AI agents) + generateDataFromOpenAPI(config.specFile, staticPathsPath, articlesPath); + // Step 6: Generate Hugo content pages from article data + generatePagesFromArticleData(articlesPath, config.pagesDir); + console.log( + `\n✅ Successfully processed ${config.description || productKey}\n` + ); + } catch (error) { + console.error(`\n❌ Error processing ${productKey}:`, error); + process.exit(1); + } +} +/** + * Main execution function + */ +function main() { + const args = process.argv.slice(2); + // Determine which products to process + let productsToProcess; + if (args.length === 0) { + // No arguments: process all products + productsToProcess = Object.keys(productConfigs); + console.log('\n📋 Processing all products...\n'); + } else { + // Arguments provided: process only specified products + productsToProcess = args; + console.log( + `\n📋 Processing specified products: ${productsToProcess.join(', ')}\n` + ); + } + // Validate product keys + const invalidProducts = productsToProcess.filter( + (key) => !productConfigs[key] + ); + if (invalidProducts.length > 0) { + console.error( + `\n❌ Invalid product identifier(s): ${invalidProducts.join(', ')}` + ); + console.error('\nValid products:'); + Object.keys(productConfigs).forEach((key) => { + console.error(` - ${key}: ${productConfigs[key].description}`); + }); + process.exit(1); + } + // Process each product + productsToProcess.forEach((productKey) => { + const config = productConfigs[productKey]; + processProduct(productKey, config); + }); + 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 diff --git a/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js b/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js new file mode 100644 index 0000000000..363643bc6f --- /dev/null +++ b/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js @@ -0,0 +1,275 @@ +'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 (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) + for (var k in mod) + if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.generateHugoData = generateHugoData; +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); + }, +}; +/** + * 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]; + doc.info.title = `${pg}\n${doc.info.title}`; + 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 path to snake case for article path + * + * @param p - Path to convert + * @returns Snake-cased path + */ + const snakifyPath = (p) => { + if (!p) { + return ''; + } + return p.replace(/^\//, '').replaceAll('/', '-'); + }; + article.path = snakifyPath(openapi['x-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) => snakifyPath(t)); + 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); + } +} +/** + * 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'); +} +// CommonJS export for backward compatibility +module.exports = { + generateHugoData, +}; +//# sourceMappingURL=index.js.map diff --git a/api-docs/scripts/generate-openapi-articles.ts b/api-docs/scripts/generate-openapi-articles.ts new file mode 100644 index 0000000000..c48c9e9a38 --- /dev/null +++ b/api-docs/scripts/generate-openapi-articles.ts @@ -0,0 +1,360 @@ +#!/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. Runs getswagger.sh to fetch/bundle OpenAPI specs + * 2. Copies specs to static directory for download + * 3. Generates path group fragments (YAML and JSON) + * 4. Creates article metadata (YAML and JSON) + * 5. Generates Hugo content pages from article data + * + * Usage: + * node generate-openapi-articles.js # Generate all products + * node generate-openapi-articles.js cloud-v2 # Generate single product + * node generate-openapi-articles.js cloud-v2 oss-v2 # Generate multiple products + * + * @module generate-openapi-articles + */ + +import { execSync } from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; + +// Import the OpenAPI to Hugo converter +const openapiPathsToHugo = require('./openapi-paths-to-hugo-data/index.js'); + +/** + * Product configuration for API generation + */ +interface ProductConfig { + /** Path to the OpenAPI spec file */ + specFile: string; + /** Path to the Hugo content directory for generated pages */ + pagesDir: string; + /** Optional description of the product */ + description?: string; +} + +/** + * Map of product identifiers to their configuration + */ +type ProductConfigMap = Record; + +// Calculate the relative paths +const DOCS_ROOT = '.'; +const API_DOCS_ROOT = 'api-docs'; + +/** + * Execute a shell command and handle errors + * + * @param command - Command to execute + * @param description - Human-readable description of the command + * @throws Exits process with code 1 on error + */ +function execCommand(command: string, description?: string): void { + try { + if (description) { + console.log(`\n${description}...`); + } + console.log(`Executing: ${command}\n`); + execSync(command, { stdio: 'inherit' }); + } catch (error) { + console.error(`\n❌ Error executing command: ${command}`); + if (error instanceof Error) { + console.error(error.message); + } + process.exit(1); + } +} + +/** + * Generate Hugo data files from OpenAPI specification + * + * @param specFile - Path to the OpenAPI spec file + * @param dataOutPath - Output path for OpenAPI path fragments + * @param articleOutPath - Output path for article metadata + */ +function generateDataFromOpenAPI( + specFile: string, + dataOutPath: string, + articleOutPath: string +): void { + if (!fs.existsSync(dataOutPath)) { + fs.mkdirSync(dataOutPath, { recursive: true }); + } + + openapiPathsToHugo.generateHugoData({ + dataOutPath, + articleOutPath, + specFile, + }); +} + +/** + * Generate Hugo content pages from article data + * + * Creates markdown files with frontmatter from article metadata. + * Each article becomes a page with type: api that renders via Scalar. + * + * @param articlesPath - Path to the articles data directory + * @param contentPath - Output path for generated content pages + */ +function generatePagesFromArticleData( + articlesPath: string, + contentPath: string +): void { + const yaml = require('js-yaml'); + const articlesFile = path.join(articlesPath, 'articles.yml'); + + if (!fs.existsSync(articlesFile)) { + console.warn(`⚠️ Articles file not found: ${articlesFile}`); + return; + } + + // Read articles data + const articlesContent = fs.readFileSync(articlesFile, 'utf8'); + const data = yaml.load(articlesContent) as { + articles: Array<{ path: string; fields: Record }>; + }; + + if (!data.articles || !Array.isArray(data.articles)) { + console.warn(`⚠️ No articles found in ${articlesFile}`); + return; + } + + // Ensure content directory exists + if (!fs.existsSync(contentPath)) { + fs.mkdirSync(contentPath, { recursive: true }); + } + + // Generate a page for each article + for (const article of data.articles) { + const pagePath = path.join(contentPath, article.path); + const pageFile = path.join(pagePath, '_index.md'); + + // Create directory if needed + if (!fs.existsSync(pagePath)) { + fs.mkdirSync(pagePath, { recursive: true }); + } + + // Generate frontmatter + const frontmatter = { + title: article.fields.name || article.path, + description: `API reference for ${article.fields.name || article.path}`, + type: 'api', + staticFilePath: article.fields.staticFilePath, + weight: 100, + }; + + const pageContent = `--- +${yaml.dump(frontmatter)}--- +`; + + fs.writeFileSync(pageFile, pageContent); + } + + console.log( + `✓ Generated ${data.articles.length} content pages in ${contentPath}` + ); +} + +/** + * Product configurations for all InfluxDB editions + * + * Maps product identifiers to their OpenAPI specs and content directories + */ +const productConfigs: ProductConfigMap = { + 'cloud-v2': { + specFile: path.join(API_DOCS_ROOT, 'influxdb/cloud/v2/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb/cloud/api/v2'), + description: 'InfluxDB Cloud (v2 API)', + }, + 'oss-v2': { + specFile: path.join(API_DOCS_ROOT, 'influxdb/v2/v2/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb/v2/api/v2'), + description: 'InfluxDB OSS v2', + }, + 'influxdb3-core': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/core/v3/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/core/reference/api'), + description: 'InfluxDB 3 Core', + }, + 'influxdb3-enterprise': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/enterprise/v3/ref.yml'), + pagesDir: path.join( + DOCS_ROOT, + 'content/influxdb3/enterprise/reference/api' + ), + description: 'InfluxDB 3 Enterprise', + }, + 'cloud-dedicated': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-dedicated/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-dedicated/api'), + description: 'InfluxDB Cloud Dedicated', + }, + 'cloud-serverless': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-serverless/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-serverless/api'), + description: 'InfluxDB Cloud Serverless', + }, + clustered: { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/clustered/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/clustered/api'), + description: 'InfluxDB Clustered', + }, +}; + +/** + * Process a single product: fetch spec, generate data, and create pages + * + * @param productKey - Product identifier (e.g., 'cloud-v2') + * @param config - Product configuration + */ +function processProduct(productKey: string, config: ProductConfig): void { + console.log('\n' + '='.repeat(80)); + console.log(`Processing ${config.description || productKey}`); + console.log('='.repeat(80)); + + const staticPath = path.join(DOCS_ROOT, 'static/openapi'); + const staticSpecPath = path.join(staticPath, `influxdb-${productKey}.yml`); + const staticJsonSpecPath = path.join( + staticPath, + `influxdb-${productKey}.json` + ); + const staticPathsPath = path.join(staticPath, `influxdb-${productKey}/paths`); + const articlesPath = path.join( + DOCS_ROOT, + `data/article-data/influxdb/${productKey}` + ); + + // Check if spec file exists + if (!fs.existsSync(config.specFile)) { + console.warn(`⚠️ Spec file not found: ${config.specFile}`); + console.log('Skipping this product. Run getswagger.sh first if needed.\n'); + return; + } + + try { + // Step 1: Execute the getswagger.sh script to fetch/bundle the spec + const getswaggerScript = path.join(API_DOCS_ROOT, 'getswagger.sh'); + if (fs.existsSync(getswaggerScript)) { + execCommand( + `${getswaggerScript} ${productKey} -B`, + `Fetching OpenAPI spec for ${productKey}` + ); + } else { + console.log(`⚠️ getswagger.sh not found, skipping fetch step`); + } + + // Step 2: Ensure static directory exists + if (!fs.existsSync(staticPath)) { + fs.mkdirSync(staticPath, { recursive: true }); + } + + // Step 3: Copy the generated OpenAPI spec to static folder (YAML) + if (fs.existsSync(config.specFile)) { + fs.copyFileSync(config.specFile, staticSpecPath); + console.log(`✓ Copied spec to ${staticSpecPath}`); + + // Step 4: Generate JSON version of the spec + try { + const yaml = require('js-yaml'); + const specContent = fs.readFileSync(config.specFile, 'utf8'); + const specObject = yaml.load(specContent); + fs.writeFileSync( + staticJsonSpecPath, + JSON.stringify(specObject, null, 2) + ); + console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`); + } catch (jsonError) { + console.warn(`⚠️ Could not generate JSON spec: ${jsonError}`); + } + } + + // Step 5: Generate Hugo data from OpenAPI spec (path fragments for AI agents) + generateDataFromOpenAPI(config.specFile, staticPathsPath, articlesPath); + + // Step 6: Generate Hugo content pages from article data + generatePagesFromArticleData(articlesPath, config.pagesDir); + + console.log( + `\n✅ Successfully processed ${config.description || productKey}\n` + ); + } catch (error) { + console.error(`\n❌ Error processing ${productKey}:`, error); + process.exit(1); + } +} + +/** + * Main execution function + */ +function main(): void { + const args = process.argv.slice(2); + + // Determine which products to process + let productsToProcess: string[]; + + if (args.length === 0) { + // No arguments: process all products + productsToProcess = Object.keys(productConfigs); + console.log('\n📋 Processing all products...\n'); + } else { + // Arguments provided: process only specified products + productsToProcess = args; + console.log( + `\n📋 Processing specified products: ${productsToProcess.join(', ')}\n` + ); + } + + // Validate product keys + const invalidProducts = productsToProcess.filter( + (key) => !productConfigs[key] + ); + if (invalidProducts.length > 0) { + console.error( + `\n❌ Invalid product identifier(s): ${invalidProducts.join(', ')}` + ); + console.error('\nValid products:'); + Object.keys(productConfigs).forEach((key) => { + console.error(` - ${key}: ${productConfigs[key].description}`); + }); + process.exit(1); + } + + // Process each product + productsToProcess.forEach((productKey) => { + const config = productConfigs[productKey]; + processProduct(productKey, config); + }); + + 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(); +} + +// Export for use as a module +export { + productConfigs, + processProduct, + generateDataFromOpenAPI, + generatePagesFromArticleData, +}; diff --git a/api-docs/scripts/openapi-paths-to-hugo-data/index.ts b/api-docs/scripts/openapi-paths-to-hugo-data/index.ts new file mode 100644 index 0000000000..c634101bef --- /dev/null +++ b/api-docs/scripts/openapi-paths-to-hugo-data/index.ts @@ -0,0 +1,528 @@ +/** + * 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 + */ + +import * as yaml from 'js-yaml'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * OpenAPI path item object + */ +interface PathItem { + get?: Operation; + post?: Operation; + put?: Operation; + patch?: Operation; + delete?: Operation; + options?: Operation; + head?: Operation; + trace?: Operation; + parameters?: Parameter[]; + [key: string]: unknown; +} + +/** + * OpenAPI operation object + */ +interface Operation { + operationId?: string; + summary?: string; + description?: string; + tags?: string[]; + parameters?: Parameter[]; + requestBody?: RequestBody; + responses?: Record; + [key: string]: unknown; +} + +/** + * OpenAPI parameter object + */ +interface Parameter { + name: string; + in: 'query' | 'header' | 'path' | 'cookie'; + description?: string; + required?: boolean; + schema?: Schema; + [key: string]: unknown; +} + +/** + * OpenAPI request body object + */ +interface RequestBody { + description?: string; + content?: Record; + required?: boolean; + [key: string]: unknown; +} + +/** + * OpenAPI response object + */ +interface Response { + description: string; + content?: Record; + headers?: Record; + [key: string]: unknown; +} + +/** + * OpenAPI media type object + */ +interface MediaType { + schema?: Schema; + example?: unknown; + examples?: Record; + [key: string]: unknown; +} + +/** + * OpenAPI schema object + */ +interface Schema { + type?: string; + format?: string; + description?: string; + properties?: Record; + items?: Schema; + required?: string[]; + [key: string]: unknown; +} + +/** + * OpenAPI header object + */ +interface Header { + description?: string; + schema?: Schema; + [key: string]: unknown; +} + +/** + * OpenAPI example object + */ +interface Example { + summary?: string; + description?: string; + value?: unknown; + [key: string]: unknown; +} + +/** + * OpenAPI document structure + */ +interface OpenAPIDocument { + openapi: string; + info: Info; + paths: Record; + components?: Components; + servers?: Server[]; + tags?: Tag[]; + description?: string; + 'x-pathGroup'?: string; + [key: string]: unknown; +} + +/** + * OpenAPI info object + */ +interface Info { + title: string; + version: string; + description?: string; + termsOfService?: string; + contact?: Contact; + license?: License; + [key: string]: unknown; +} + +/** + * OpenAPI contact object + */ +interface Contact { + name?: string; + url?: string; + email?: string; + [key: string]: unknown; +} + +/** + * OpenAPI license object + */ +interface License { + name: string; + url?: string; + [key: string]: unknown; +} + +/** + * OpenAPI components object + */ +interface Components { + schemas?: Record; + responses?: Record; + parameters?: Record; + requestBodies?: Record; + headers?: Record; + securitySchemes?: Record; + [key: string]: unknown; +} + +/** + * OpenAPI security scheme object + */ +interface SecurityScheme { + type: string; + description?: string; + [key: string]: unknown; +} + +/** + * OpenAPI server object + */ +interface Server { + url: string; + description?: string; + variables?: Record; + [key: string]: unknown; +} + +/** + * OpenAPI server variable object + */ +interface ServerVariable { + default: string; + enum?: string[]; + description?: string; + [key: string]: unknown; +} + +/** + * OpenAPI tag object + */ +interface Tag { + name: string; + description?: string; + externalDocs?: ExternalDocs; + [key: string]: unknown; +} + +/** + * OpenAPI external docs object + */ +interface ExternalDocs { + url: string; + description?: string; + [key: string]: unknown; +} + +/** + * Article metadata for Hugo + */ +interface Article { + path: string; + fields: { + name: string; + describes: string[]; + title?: string; + description?: string; + tags?: string[]; + source?: string; + staticFilePath?: string; + }; +} + +/** + * Article collection for Hugo data files + */ +interface ArticleCollection { + articles: Article[]; +} + +/** + * Options for generating Hugo data + */ +export interface GenerateHugoDataOptions { + /** Path to the OpenAPI spec file */ + specFile: string; + /** Output path for generated OpenAPI path fragments */ + dataOutPath: string; + /** Output path for article metadata */ + articleOutPath: string; +} + +/** + * Options for writing OpenAPI article data + */ +interface WriteOpenapiArticleDataOptions { + /** File pattern to match when filtering files */ + filePattern?: string; +} + +/** + * 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: string, + encoding: BufferEncoding = 'utf8' +): OpenAPIDocument { + const content = fs.readFileSync(filepath, encoding); + return yaml.load(content) as OpenAPIDocument; +} + +/** + * Write data to a YAML file + * + * @param data - Data to write + * @param outputTo - Output file path + */ +function writeDataFile(data: unknown, outputTo: string): void { + 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: unknown, outputTo: string): void { + 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: string): boolean { + const placeholderRegex = /^\{.*\}$/; + return placeholderRegex.test(str); + }, +}; + +/** + * 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: OpenAPIDocument, + prefix: string, + outPath: string +): void { + const pathGroups: Record> = {}; + + // 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: OpenAPIDocument = JSON.parse(JSON.stringify(openapi)); + doc.paths = pathGroups[pg]; + doc.info.title = `${pg}\n${doc.info.title}`; + 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: OpenAPIDocument): Article { + const article: Article = { + path: '', + fields: { + name: openapi['x-pathGroup'] || '', + describes: Object.keys(openapi.paths), + }, + }; + + /** + * Convert path to snake case for article path + * + * @param p - Path to convert + * @returns Snake-cased path + */ + const snakifyPath = (p: string): string => { + if (!p) { + return ''; + } + return p.replace(/^\//, '').replaceAll('/', '-'); + }; + + article.path = snakifyPath(openapi['x-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) => snakifyPath(t)); + + 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: string, + targetPath: string, + opts: WriteOpenapiArticleDataOptions +): void { + /** + * Check if path is a file + */ + const isFile = (filePath: string): boolean => { + return fs.lstatSync(filePath).isFile(); + }; + + /** + * Check if filename matches pattern + */ + const matchesPattern = (filePath: string): boolean => { + 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: 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); + } +} + +/** + * 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 + */ +export function generateHugoData(options: GenerateHugoDataOptions): void { + 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'); +} + +// CommonJS export for backward compatibility +module.exports = { + generateHugoData, +}; diff --git a/api-docs/scripts/openapi-paths-to-hugo-data/package.json b/api-docs/scripts/openapi-paths-to-hugo-data/package.json new file mode 100644 index 0000000000..78bd5bc114 --- /dev/null +++ b/api-docs/scripts/openapi-paths-to-hugo-data/package.json @@ -0,0 +1,14 @@ +{ + "name": "openapi-paths-to-hugo-data", + "version": "1.0.0", + "description": "Convert OpenAPI specifications to Hugo data files for API documentation", + "main": "index.js", + "type": "commonjs", + "dependencies": { + "js-yaml": "^4.1.1" + }, + "devDependencies": {}, + "scripts": {}, + "author": "InfluxData", + "license": "MIT" +} diff --git a/api-docs/scripts/openapi-paths-to-hugo-data/yarn.lock b/api-docs/scripts/openapi-paths-to-hugo-data/yarn.lock new file mode 100644 index 0000000000..96bb86828b --- /dev/null +++ b/api-docs/scripts/openapi-paths-to-hugo-data/yarn.lock @@ -0,0 +1,32 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/js-yaml@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" + integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== + +"@types/node@^24.10.1": + version "24.10.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" + integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== + dependencies: + undici-types "~7.16.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +js-yaml@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== diff --git a/api-docs/scripts/tsconfig.json b/api-docs/scripts/tsconfig.json new file mode 100644 index 0000000000..e36776534b --- /dev/null +++ b/api-docs/scripts/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2021", + "lib": ["ES2021"], + "module": "CommonJS", + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "sourceMap": true, + "types": ["node"] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/config/_default/hugo.yml b/config/_default/hugo.yml index c576413cd4..08859dea60 100644 --- a/config/_default/hugo.yml +++ b/config/_default/hugo.yml @@ -98,6 +98,8 @@ module: params: env: development environment: development + # API documentation renderer: "scalar" (default) or "rapidoc" + apiRenderer: scalar # Configure the server for development server: diff --git a/data/article-data/influxdb/cloud-v2/articles.json b/data/article-data/influxdb/cloud-v2/articles.json new file mode 100644 index 0000000000..2267c4303d --- /dev/null +++ b/data/article-data/influxdb/cloud-v2/articles.json @@ -0,0 +1,597 @@ +{ + "articles": [ + { + "path": "api-v2-authorizations", + "fields": { + "name": "/api/v2/authorizations", + "describes": [ + "/api/v2/authorizations", + "/api/v2/authorizations/{authID}" + ], + "title": "/api/v2/authorizations\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "authorizations" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml" + } + }, + { + "path": "api-v2-buckets", + "fields": { + "name": "/api/v2/buckets", + "describes": [ + "/api/v2/buckets", + "/api/v2/buckets/{bucketID}", + "/api/v2/buckets/{bucketID}/labels", + "/api/v2/buckets/{bucketID}/labels/{labelID}", + "/api/v2/buckets/{bucketID}/members", + "/api/v2/buckets/{bucketID}/members/{userID}", + "/api/v2/buckets/{bucketID}/owners", + "/api/v2/buckets/{bucketID}/owners/{userID}", + "/api/v2/buckets/{bucketID}/schema/measurements", + "/api/v2/buckets/{bucketID}/schema/measurements/{measurementID}" + ], + "title": "/api/v2/buckets\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "buckets" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml" + } + }, + { + "path": "api-v2-checks", + "fields": { + "name": "/api/v2/checks", + "describes": [ + "/api/v2/checks", + "/api/v2/checks/{checkID}", + "/api/v2/checks/{checkID}/labels", + "/api/v2/checks/{checkID}/labels/{labelID}", + "/api/v2/checks/{checkID}/query" + ], + "title": "/api/v2/checks\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "checks" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml" + } + }, + { + "path": "api-v2-dashboards", + "fields": { + "name": "/api/v2/dashboards", + "describes": [ + "/api/v2/dashboards", + "/api/v2/dashboards/{dashboardID}", + "/api/v2/dashboards/{dashboardID}/cells", + "/api/v2/dashboards/{dashboardID}/cells/{cellID}", + "/api/v2/dashboards/{dashboardID}/cells/{cellID}/view", + "/api/v2/dashboards/{dashboardID}/labels", + "/api/v2/dashboards/{dashboardID}/labels/{labelID}", + "/api/v2/dashboards/{dashboardID}/members", + "/api/v2/dashboards/{dashboardID}/members/{userID}", + "/api/v2/dashboards/{dashboardID}/owners", + "/api/v2/dashboards/{dashboardID}/owners/{userID}" + ], + "title": "/api/v2/dashboards\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "dashboards" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml" + } + }, + { + "path": "api-v2-dbrps", + "fields": { + "name": "/api/v2/dbrps", + "describes": [ + "/api/v2/dbrps", + "/api/v2/dbrps/{dbrpID}" + ], + "title": "/api/v2/dbrps\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "dbrps" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml" + } + }, + { + "path": "api-v2-delete", + "fields": { + "name": "/api/v2/delete", + "describes": [ + "/api/v2/delete" + ], + "title": "/api/v2/delete\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "delete" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml" + } + }, + { + "path": "api-v2-flags", + "fields": { + "name": "/api/v2/flags", + "describes": [ + "/api/v2/flags" + ], + "title": "/api/v2/flags\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "flags" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml" + } + }, + { + "path": "api-v2-labels", + "fields": { + "name": "/api/v2/labels", + "describes": [ + "/api/v2/labels", + "/api/v2/labels/{labelID}" + ], + "title": "/api/v2/labels\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "labels" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml" + } + }, + { + "path": "api-v2-maps", + "fields": { + "name": "/api/v2/maps", + "describes": [ + "/api/v2/maps/mapToken" + ], + "title": "/api/v2/maps\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "maps" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml" + } + }, + { + "path": "api-v2-me", + "fields": { + "name": "/api/v2/me", + "describes": [ + "/api/v2/me", + "/api/v2/me/password" + ], + "title": "/api/v2/me\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "me" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml" + } + }, + { + "path": "api-v2-notificationEndpoints", + "fields": { + "name": "/api/v2/notificationEndpoints", + "describes": [ + "/api/v2/notificationEndpoints", + "/api/v2/notificationEndpoints/{endpointID}", + "/api/v2/notificationEndpoints/{endpointID}/labels", + "/api/v2/notificationEndpoints/{endpointID}/labels/{labelID}" + ], + "title": "/api/v2/notificationEndpoints\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "notificationEndpoints" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml" + } + }, + { + "path": "api-v2-notificationRules", + "fields": { + "name": "/api/v2/notificationRules", + "describes": [ + "/api/v2/notificationRules", + "/api/v2/notificationRules/{ruleID}", + "/api/v2/notificationRules/{ruleID}/labels", + "/api/v2/notificationRules/{ruleID}/labels/{labelID}", + "/api/v2/notificationRules/{ruleID}/query" + ], + "title": "/api/v2/notificationRules\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "notificationRules" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml" + } + }, + { + "path": "api-v2-orgs", + "fields": { + "name": "/api/v2/orgs", + "describes": [ + "/api/v2/orgs", + "/api/v2/orgs/{orgID}", + "/api/v2/orgs/{orgID}/limits", + "/api/v2/orgs/{orgID}/members", + "/api/v2/orgs/{orgID}/members/{userID}", + "/api/v2/orgs/{orgID}/owners", + "/api/v2/orgs/{orgID}/owners/{userID}", + "/api/v2/orgs/{orgID}/secrets", + "/api/v2/orgs/{orgID}/secrets/delete", + "/api/v2/orgs/{orgID}/secrets/{secretID}", + "/api/v2/orgs/{orgID}/usage" + ], + "title": "/api/v2/orgs\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "orgs" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml" + } + }, + { + "path": "api-v2-query", + "fields": { + "name": "/api/v2/query", + "describes": [ + "/api/v2/query", + "/api/v2/query/analyze", + "/api/v2/query/ast", + "/api/v2/query/suggestions", + "/api/v2/query/suggestions/{name}" + ], + "title": "/api/v2/query\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "query" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml" + } + }, + { + "path": "api-v2-resources", + "fields": { + "name": "/api/v2/resources", + "describes": [ + "/api/v2/resources" + ], + "title": "/api/v2/resources\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "resources" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml" + } + }, + { + "path": "api-v2-scripts", + "fields": { + "name": "/api/v2/scripts", + "describes": [ + "/api/v2/scripts", + "/api/v2/scripts/{scriptID}", + "/api/v2/scripts/{scriptID}/invoke", + "/api/v2/scripts/{scriptID}/params" + ], + "title": "/api/v2/scripts\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "scripts" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml" + } + }, + { + "path": "api-v2-setup", + "fields": { + "name": "/api/v2/setup", + "describes": [ + "/api/v2/setup", + "/api/v2/setup/user" + ], + "title": "/api/v2/setup\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "setup" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml" + } + }, + { + "path": "api-v2-signin", + "fields": { + "name": "/api/v2/signin", + "describes": [ + "/api/v2/signin" + ], + "title": "/api/v2/signin\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "signin" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml" + } + }, + { + "path": "api-v2-signout", + "fields": { + "name": "/api/v2/signout", + "describes": [ + "/api/v2/signout" + ], + "title": "/api/v2/signout\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "signout" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml" + } + }, + { + "path": "api-v2-stacks", + "fields": { + "name": "/api/v2/stacks", + "describes": [ + "/api/v2/stacks", + "/api/v2/stacks/{stack_id}", + "/api/v2/stacks/{stack_id}/uninstall" + ], + "title": "/api/v2/stacks\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "stacks" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml" + } + }, + { + "path": "api-v2-tasks", + "fields": { + "name": "/api/v2/tasks", + "describes": [ + "/api/v2/tasks", + "/api/v2/tasks/{taskID}", + "/api/v2/tasks/{taskID}/labels", + "/api/v2/tasks/{taskID}/labels/{labelID}", + "/api/v2/tasks/{taskID}/logs", + "/api/v2/tasks/{taskID}/members", + "/api/v2/tasks/{taskID}/members/{userID}", + "/api/v2/tasks/{taskID}/owners", + "/api/v2/tasks/{taskID}/owners/{userID}", + "/api/v2/tasks/{taskID}/runs", + "/api/v2/tasks/{taskID}/runs/{runID}", + "/api/v2/tasks/{taskID}/runs/{runID}/logs", + "/api/v2/tasks/{taskID}/runs/{runID}/retry" + ], + "title": "/api/v2/tasks\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "tasks" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml" + } + }, + { + "path": "api-v2-telegraf", + "fields": { + "name": "/api/v2/telegraf", + "describes": [ + "/api/v2/telegraf/plugins" + ], + "title": "/api/v2/telegraf\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "telegraf" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml" + } + }, + { + "path": "api-v2-telegrafs", + "fields": { + "name": "/api/v2/telegrafs", + "describes": [ + "/api/v2/telegrafs", + "/api/v2/telegrafs/{telegrafID}", + "/api/v2/telegrafs/{telegrafID}/labels", + "/api/v2/telegrafs/{telegrafID}/labels/{labelID}", + "/api/v2/telegrafs/{telegrafID}/members", + "/api/v2/telegrafs/{telegrafID}/members/{userID}", + "/api/v2/telegrafs/{telegrafID}/owners", + "/api/v2/telegrafs/{telegrafID}/owners/{userID}" + ], + "title": "/api/v2/telegrafs\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "telegrafs" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml" + } + }, + { + "path": "api-v2-templates", + "fields": { + "name": "/api/v2/templates", + "describes": [ + "/api/v2/templates/apply", + "/api/v2/templates/export" + ], + "title": "/api/v2/templates\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "templates" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml" + } + }, + { + "path": "api-v2-users", + "fields": { + "name": "/api/v2/users", + "describes": [ + "/api/v2/users", + "/api/v2/users/{userID}", + "/api/v2/users/{userID}/password" + ], + "title": "/api/v2/users\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "users" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml" + } + }, + { + "path": "api-v2-variables", + "fields": { + "name": "/api/v2/variables", + "describes": [ + "/api/v2/variables", + "/api/v2/variables/{variableID}", + "/api/v2/variables/{variableID}/labels", + "/api/v2/variables/{variableID}/labels/{labelID}" + ], + "title": "/api/v2/variables\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "variables" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml" + } + }, + { + "path": "api-v2-write", + "fields": { + "name": "/api/v2/write", + "describes": [ + "/api/v2/write" + ], + "title": "/api/v2/write\nInfluxDB Cloud API Service", + "tags": [ + "api-v2", + "write" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml" + } + }, + { + "path": "api-v2", + "fields": { + "name": "/api/v2", + "describes": [ + "/api/v2" + ], + "title": "/api/v2\nInfluxDB Cloud API Service", + "tags": [ + "api", + "v2" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml" + } + }, + { + "path": "legacy-authorizations", + "fields": { + "name": "/legacy/authorizations", + "describes": [ + "/legacy/authorizations", + "/legacy/authorizations/{authID}", + "/legacy/authorizations/{authID}/password" + ], + "title": "/legacy/authorizations\nInfluxDB Cloud API Service", + "tags": [ + "legacy", + "authorizations" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml" + } + }, + { + "path": "ping", + "fields": { + "name": "/ping", + "describes": [ + "/ping" + ], + "title": "/ping\nInfluxDB Cloud API Service", + "tags": [ + "", + "ping" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-ping.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-ping.yaml" + } + }, + { + "path": "query", + "fields": { + "name": "/query", + "describes": [ + "/query" + ], + "title": "/query\nInfluxDB Cloud API Service", + "tags": [ + "", + "query" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-query.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-query.yaml" + } + }, + { + "path": "write", + "fields": { + "name": "/write", + "describes": [ + "/write" + ], + "title": "/write\nInfluxDB Cloud API Service", + "tags": [ + "", + "write" + ], + "source": "static/openapi/influxdb-cloud-v2/paths/ref-write.yaml", + "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-write.yaml" + } + } + ] +} \ No newline at end of file diff --git a/layouts/_default/api.html b/layouts/_default/api.html index 68a09ae661..bee90952e4 100644 --- a/layouts/_default/api.html +++ b/layouts/_default/api.html @@ -1 +1,33 @@ -{{ .Content }} +{{/* + API Documentation Default Layout + + Full page layout for API documentation using the renderer abstraction. + The renderer (Scalar or RapiDoc) is selected via site.Params.apiRenderer. + + Required frontmatter: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ partial "header.html" . }} +{{ partial "topnav.html" . }} + +
+ {{ partial "sidebar.html" . }} +
+
+
+

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ + {{/* Render API documentation using the configured renderer */}} + {{ partial "api/renderer.html" . }} + +
+ +
+
+ +{{ partial "footer.html" . }} diff --git a/layouts/api/list.html b/layouts/api/list.html new file mode 100644 index 0000000000..6c865e70ff --- /dev/null +++ b/layouts/api/list.html @@ -0,0 +1,33 @@ +{{/* + API Documentation List/Section Layout + + Uses the renderer abstraction to display API documentation. + The renderer (Scalar or RapiDoc) is selected via site.Params.apiRenderer. + + Required frontmatter: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ partial "header.html" . }} +{{ partial "topnav.html" . }} + +
+ {{ partial "sidebar.html" . }} +
+
+
+

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ + {{/* Render API documentation using the configured renderer */}} + {{ partial "api/renderer.html" . }} + +
+ +
+
+ +{{ partial "footer.html" . }} diff --git a/layouts/api/single.html b/layouts/api/single.html new file mode 100644 index 0000000000..3bf97deb1a --- /dev/null +++ b/layouts/api/single.html @@ -0,0 +1,33 @@ +{{/* + API Documentation Single Page Layout + + Uses the renderer abstraction to display API documentation. + The renderer (Scalar or RapiDoc) is selected via site.Params.apiRenderer. + + Required frontmatter: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ partial "header.html" . }} +{{ partial "topnav.html" . }} + +
+ {{ partial "sidebar.html" . }} +
+
+
+

{{ .Title }}

+ {{ with .Description }} +

{{ . }}

+ {{ end }} +
+ + {{/* Render API documentation using the configured renderer */}} + {{ partial "api/renderer.html" . }} + +
+ +
+
+ +{{ partial "footer.html" . }} diff --git a/layouts/partials/api/rapidoc.html b/layouts/partials/api/rapidoc.html new file mode 100644 index 0000000000..01b491deb8 --- /dev/null +++ b/layouts/partials/api/rapidoc.html @@ -0,0 +1,179 @@ +{{/* + RapiDoc API Documentation Renderer (Legacy) + + Legacy API documentation renderer using RapiDoc. + Maintained for backward compatibility. + + Required page params: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ $specPath := .Params.staticFilePath }} +{{ $specPathJSON := replace $specPath ".yaml" ".json" | replace ".yml" ".json" }} + +{{/* Machine-readable links for AI agent discovery */}} +{{ if $specPath }} + + +{{ end }} + +
+ {{/* Download link for the spec */}} + {{ if $specPath }} + + {{ end }} + + {{/* RapiDoc component */}} + +
+ +{{/* Load RapiDoc from CDN */}} + + + + + diff --git a/layouts/partials/api/renderer.html b/layouts/partials/api/renderer.html new file mode 100644 index 0000000000..c6b795f37f --- /dev/null +++ b/layouts/partials/api/renderer.html @@ -0,0 +1,25 @@ +{{/* + API Renderer Abstraction + + Selects and loads the appropriate API documentation renderer based on site + configuration. Supports: + - scalar (default): Modern, accessible API documentation + - rapidoc: Legacy renderer for backward compatibility + + Required page params: + - staticFilePath: Path to the OpenAPI specification file + + Site params: + - apiRenderer: "scalar" or "rapidoc" (defaults to "scalar") +*/}} + +{{ $renderer := site.Params.apiRenderer | default "scalar" }} + +{{ if eq $renderer "scalar" }} + {{ partial "api/scalar.html" . }} +{{ else if eq $renderer "rapidoc" }} + {{ partial "api/rapidoc.html" . }} +{{ else }} + {{/* Fallback to scalar if unknown renderer specified */}} + {{ partial "api/scalar.html" . }} +{{ end }} diff --git a/layouts/partials/api/scalar.html b/layouts/partials/api/scalar.html new file mode 100644 index 0000000000..885e286ed8 --- /dev/null +++ b/layouts/partials/api/scalar.html @@ -0,0 +1,186 @@ +{{/* + Scalar API Documentation Renderer + + Modern, accessible API documentation powered by Scalar. + Features: + - Dark/light theme support synchronized with site theme + - InfluxData brand colors + - Responsive layout + - AI agent spec discovery via link[rel=alternate] + - Download link for OpenAPI spec + + Required page params: + - staticFilePath: Path to the OpenAPI specification file +*/}} + +{{ $specPath := .Params.staticFilePath }} +{{ $specPathJSON := replace $specPath ".yaml" ".json" | replace ".yml" ".json" }} + +{{/* Machine-readable links for AI agent discovery */}} +{{ if $specPath }} + + +{{ end }} + +
+ {{/* Download link for the spec */}} + {{ if $specPath }} + + {{ end }} + + {{/* Scalar API Reference container */}} +
+
+ +{{/* Load Scalar from CDN */}} + + + + + From 64d056ebea7a3ff20bc4f364c7efe8774d9a0124 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 2 Dec 2025 15:53:58 -0600 Subject: [PATCH 02/33] fix(shortcodes): Fix Hugo 0.134 compatibility issues - latest-patch.html: Replace deprecated .Store with local variable assignment. The .Store method was removed from shortcode context in newer Hugo versions. - api-endpoint.html: Add nil check for productRef lookup to prevent index errors when productKey is not in productAliases dictionary. Falls back to "influxdb" as default product reference. --- layouts/shortcodes/api-endpoint.html | 7 +++++-- layouts/shortcodes/latest-patch.html | 13 +++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/layouts/shortcodes/api-endpoint.html b/layouts/shortcodes/api-endpoint.html index 1203a87a4a..bd75b59455 100644 --- a/layouts/shortcodes/api-endpoint.html +++ b/layouts/shortcodes/api-endpoint.html @@ -5,9 +5,12 @@ {{- $parsedProductKey := cond $isOSS "oss" $currentVersion -}} {{- $productKey := .Get "influxdb_host" | default $parsedProductKey -}} {{- $productAliases := dict "oss" "influxdb" "cloud" "influxdb_cloud" "cloud-tsm" "influxdb_cloud" "core" "influxdb3_core" "enterprise" "influxdb3_enterprise" "cloud-serverless" "influxdb3_cloud_serverless" "serverless" "influxdb3_cloud_serverless" "cloud-dedicated" "influxdb3_cloud_dedicated" "dedicated" "influxdb3_cloud_dedicated" "clustered" "influxdb3_clustered" -}} -{{- $productRef := index $productAliases $productKey -}} +{{- $productRef := index $productAliases $productKey | default "influxdb" -}} {{- $productData := index .Site.Data.products $productRef -}} -{{- $placeholderHost := $productData.placeholder_host }} +{{- $placeholderHost := "" -}} +{{- with $productData -}} + {{- $placeholderHost = .placeholder_host -}} +{{- end -}} {{- $method := .Get "method" | upper -}} {{- $methodStyle := .Get "method" | lower -}} {{- $apiRef := .Get "api-ref" | default "" -}} diff --git a/layouts/shortcodes/latest-patch.html b/layouts/shortcodes/latest-patch.html index df03397a7b..484d606ca3 100644 --- a/layouts/shortcodes/latest-patch.html +++ b/layouts/shortcodes/latest-patch.html @@ -10,19 +10,20 @@ {{- $patchVersions := index (index .Site.Data.products $product) "latest_patches" -}} {{- $cliVersions := index .Site.Data.products.influxdb "latest_cli" -}} {{- $isInfluxDB3 := eq $product "influxdb3" -}} +{{- $patchVersion := "" -}} {{- if $cli }} {{- if eq $version "cloud" -}} - {{- .Store.Set "patchVersion" (index $cliVersions $latestVersion) -}} + {{- $patchVersion = index $cliVersions $latestVersion -}} {{- else -}} - {{- .Store.Set "patchVersion" (index $cliVersions $version) -}} + {{- $patchVersion = index $cliVersions $version -}} {{- end -}} {{- else -}} {{- if eq $version "cloud" -}} - {{- .Store.Set "patchVersion" (index $patchVersions $latestVersion) -}} + {{- $patchVersion = index $patchVersions $latestVersion -}} {{- else if $isInfluxDB3 -}} - {{- .Store.Set "patchVersion" (index .Site.Data.products (print $product "_" $version)).latest_patch -}} + {{- $patchVersion = (index .Site.Data.products (print $product "_" $version)).latest_patch -}} {{- else -}} - {{- .Store.Set "patchVersion" (index $patchVersions $version) -}} + {{- $patchVersion = index $patchVersions $version -}} {{- end -}} {{- end -}} -{{- .Store.Get "patchVersion" -}} \ No newline at end of file +{{- $patchVersion -}} \ No newline at end of file From ea8349cc1eb3b255ad72d15b63c3a743f246e6f8 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 2 Dec 2025 16:11:49 -0600 Subject: [PATCH 03/33] chore: Update build:api-docs script to use new generator - yarn build:api-docs: Generate API docs for all products - yarn build:api-docs cloud-v2: Generate for specific product - yarn build:api-docs:compile: Recompile TypeScript if modified --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 22dc9e6864..5ee36aa39f 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "docs:create": "node scripts/docs-create.js", "docs:edit": "node scripts/docs-edit.js", "docs:add-placeholders": "node scripts/add-placeholders.js", - "build:api-docs": "cd api-docs && sh generate-api-docs.sh", + "build:api-docs": "node api-docs/scripts/dist/generate-openapi-articles.js", + "build:api-docs:compile": "tsc --project api-docs/scripts/tsconfig.json", "build:pytest:image": "docker build -t influxdata/docs-pytest:latest -f Dockerfile.pytest .", "build:agent:instructions": "node ./helper-scripts/build-agent-instructions.js", "build:ts": "tsc --project tsconfig.json --outDir dist", From 981e0c2566ece20e5781374f08d9d76bc537caa8 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Fri, 5 Dec 2025 10:27:02 -0600 Subject: [PATCH 04/33] chore(claude): Add claude skill for Hugo development/testing workflow. Split ui-dev subagent into hugo-, ts-, and testing-specific agents. --- .claude/agents/hugo-ui-dev.md | 269 ++++++++++++ .claude/agents/ts-component-dev.md | 403 +++++++++++++++++ .claude/agents/ui-dev.md | 253 ----------- .claude/agents/ui-testing.md | 442 +++++++++++++++++++ .claude/skills/hugo-template-dev/SKILL.md | 508 ++++++++++++++++++++++ 5 files changed, 1622 insertions(+), 253 deletions(-) create mode 100644 .claude/agents/hugo-ui-dev.md create mode 100644 .claude/agents/ts-component-dev.md delete mode 100644 .claude/agents/ui-dev.md create mode 100644 .claude/agents/ui-testing.md create mode 100644 .claude/skills/hugo-template-dev/SKILL.md diff --git a/.claude/agents/hugo-ui-dev.md b/.claude/agents/hugo-ui-dev.md new file mode 100644 index 0000000000..e1e8a9de59 --- /dev/null +++ b/.claude/agents/hugo-ui-dev.md @@ -0,0 +1,269 @@ +--- +name: hugo-ui-dev +description: Hugo template and SASS/CSS development specialist for the InfluxData docs-v2 repository. Use this agent for creating/editing Hugo layouts, partials, shortcodes, and SASS stylesheets. This agent focuses on structure and styling, not JavaScript/TypeScript behavior. +tools: ["*"] +model: sonnet +--- + +# Hugo Template & SASS/CSS Development Agent + +## Purpose + +Specialized agent for Hugo template development and SASS/CSS styling in the InfluxData docs-v2 repository. Handles the **structure and styling** layer of the documentation site UI. + +## Scope and Responsibilities + +### Primary Capabilities + +1. **Hugo Template Development** + - Create and modify layouts in `layouts/`, `layouts/partials/`, `layouts/shortcodes/` + - Implement safe data access patterns for Hugo templates + - Handle Hugo's template inheritance and partial inclusion + - Configure page types and content organization + +2. **SASS/CSS Styling** + - Develop styles in `assets/styles/` + - Implement responsive layouts and component styling + - Follow BEM or project-specific naming conventions + - Optimize CSS for production builds + +3. **Hugo Data Integration** + - Access data from `data/` directory safely + - Pass data to components via `data-*` attributes + - Handle YAML/JSON data files for dynamic content + +### Out of Scope (Use ts-component-dev agent instead) + +- TypeScript/JavaScript component implementation +- Event handlers and user interaction logic +- State management and DOM manipulation +- Component registry and initialization + +## Critical Testing Requirement + +**Hugo's `npx hugo --quiet` only validates template syntax, not runtime execution.** + +Template errors like accessing undefined fields, nil values, or incorrect type assertions only appear when Hugo actually renders pages. + +### Mandatory Testing Protocol + +After modifying any file in `layouts/`: + +```bash +# Step 1: Start Hugo server and check for errors +npx hugo server --port 1314 2>&1 | head -50 +``` + +**Success criteria:** + +- No `error calling partial` messages +- No `can't evaluate field` errors +- No `template: ... failed` messages +- Server shows "Web Server is available at " + +```bash +# Step 2: Verify the page renders +curl -s -o /dev/null -w "%{http_code}" http://localhost:1314/PATH/TO/PAGE/ +``` + +```bash +# Step 3: Stop the test server +pkill -f "hugo server --port 1314" +``` + +### Quick Test Command + +```bash +timeout 15 npx hugo server --port 1314 2>&1 | grep -E "(error|Error|ERROR|fail|FAIL)" | head -20; pkill -f "hugo server --port 1314" 2>/dev/null +``` + +If output is empty, no errors were detected. + +## Common Hugo Template Patterns + +### Safe Data Access + +**Wrong - direct hyphenated key access:** + +```go +{{ .Site.Data.article-data.influxdb }} +``` + +**Correct - use index function:** + +```go +{{ index .Site.Data "article-data" "influxdb" }} +``` + +### Safe Nested Access + +```go +{{ $articleDataRoot := index .Site.Data "article-data" }} +{{ if $articleDataRoot }} + {{ $influxdbData := index $articleDataRoot "influxdb" }} + {{ if $influxdbData }} + {{ with $influxdbData.articles }} + {{/* Safe to use . here */}} + {{ end }} + {{ end }} +{{ end }} +``` + +### Safe Field Access with isset + +```go +{{ if and $data (isset $data "field") }} + {{ index $data "field" }} +{{ end }} +``` + +### Iterating Safely + +```go +{{ range $idx, $item := $articles }} + {{ $path := "" }} + {{ if isset $item "path" }} + {{ $path = index $item "path" }} + {{ end }} + {{ if $path }} + {{/* Now safe to use $path */}} + {{ end }} +{{ end }} +``` + +## Template-to-TypeScript Communication + +Pass data via `data-*` attributes - **never use inline JavaScript**: + +**Template:** + +```html +
+ {{/* HTML structure only - no onclick handlers */}} +
+``` + +The TypeScript component (handled by ts-component-dev agent) will read these attributes. + +## File Organization + +``` +layouts/ +├── _default/ # Default templates +├── partials/ # Reusable template fragments +│ └── api/ # API-specific partials +├── shortcodes/ # Content shortcodes +└── TYPE/ # Type-specific templates + └── single.html # Single page template + +assets/styles/ +├── styles-default.scss # Main stylesheet +└── layouts/ + └── _api-layout.scss # Layout-specific styles +``` + +### Partial Naming + +- Use descriptive names: `api/sidebar-nav.html`, not `nav.html` +- Group related partials in subdirectories +- Include comments at the top describing purpose and required context + +## Debugging Templates + +### Print Variables for Debugging + +```go +{{/* Temporary debugging - REMOVE before committing */}} +
{{ printf "%#v" $myVariable }}
+``` + +### Enable Verbose Mode + +```bash +npx hugo server --port 1314 --verbose 2>&1 | head -100 +``` + +### Check Data File Loading + +```bash +cat data/article-data/influxdb/influxdb3-core/articles.yml | head -20 +``` + +## SASS/CSS Guidelines + +### File Organization + +- Component styles in `assets/styles/layouts/` +- Use SASS variables from existing theme +- Follow mobile-first responsive design + +### Naming Conventions + +- Use BEM or project conventions +- Prefix component styles (e.g., `.api-nav`, `.api-toc`) +- Use state classes: `.is-active`, `.is-open`, `.is-hidden` + +### Common Patterns + +```scss +// Component container +.api-nav { + // Base styles + + &-group-header { + // Child element + } + + &.is-open { + // State modifier + } +} +``` + +## Workflow + +1. **Understand Requirements** + - What page type or layout is being modified? + - What data does the template need? + - Does this require styling changes? + +2. **Implement Template** + - Use safe data access patterns + - Add `data-component` attributes for interactive elements + - Do not add inline JavaScript + +3. **Add Styling** + - Create/modify SCSS files as needed + - Follow existing patterns and variables + +4. **Test Runtime** + - Run Hugo server (not just build) + - Verify page renders without errors + - Check styling in browser + +5. **Clean Up** + - Remove debug statements + - Stop test server + +## Quality Checklist + +Before considering template work complete: + +- [ ] No inline ` +``` + +**Correct - Clean separation:** + +Template (`layouts/partials/api/sidebar-nav.html`): + +```html + +``` + +TypeScript (`assets/js/components/api-nav.ts`): + +```typescript +interface ApiNavOptions { + component: HTMLElement; +} + +export default function initApiNav({ component }: ApiNavOptions): void { + const headers = component.querySelectorAll('.api-nav-group-header'); + + headers.forEach((header) => { + header.addEventListener('click', () => { + const isOpen = header.classList.toggle('is-open'); + header.setAttribute('aria-expanded', String(isOpen)); + header.nextElementSibling?.classList.toggle('is-open', isOpen); + }); + }); +} +``` + +Register in `main.js`: + +```javascript +import initApiNav from './components/api-nav.js'; + +const componentRegistry = { + 'api-nav': initApiNav, + // ... other components +}; +``` + +### Data Passing Pattern + +Pass Hugo data to TypeScript via `data-*` attributes: + +Template: + +```html +
+
+``` + +TypeScript: + +```typescript +interface TocOptions { + component: HTMLElement; +} + +interface TocData { + headings: string[]; + scrollOffset: number; +} + +function parseData(component: HTMLElement): TocData { + const headingsRaw = component.dataset.headings; + const headings = headingsRaw ? JSON.parse(headingsRaw) : []; + const scrollOffset = parseInt(component.dataset.scrollOffset || '0', 10); + + return { headings, scrollOffset }; +} + +export default function initApiToc({ component }: TocOptions): void { + const data = parseData(component); + // Use data.headings and data.scrollOffset +} +``` + +### Minimal Inline Scripts (Exception) + +The **only** acceptable inline scripts are minimal initialization that MUST run before component registration: + +```html +{{/* Acceptable: Critical path, no logic, runs immediately */}} + +``` + +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-scalar.ts # Scalar/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: + +1. [ ] Create TypeScript file in `assets/js/components/` +2. [ ] Define interface for component options +3. [ ] Export default initializer function +4. [ ] Register in `main.js` componentRegistry +5. [ ] Add `data-component` attribute to HTML element +6. [ ] Pass data via `data-*` attributes (not inline JS) +7. [ ] Write Cypress tests for the component +8. [ ] **NO inline ` - - - diff --git a/layouts/partials/api/tab-panels.html b/layouts/partials/api/tab-panels.html new file mode 100644 index 0000000000..68d28973c0 --- /dev/null +++ b/layouts/partials/api/tab-panels.html @@ -0,0 +1,31 @@ +{{/* + API Reference Tab Panels (DEPRECATED) + + This partial is kept for backward compatibility. + The new architecture renders content directly in layouts: + - layouts/api/list.html: Tag pages with operations list + - layouts/api/single.html: Individual operation pages with RapiDoc + + For conceptual pages (isConceptual: true), renders tag description content. + For operational pages, renders the API documentation via RapiDoc. +*/}} + +{{ $isConceptual := .Params.isConceptual | default false }} + +{{ if $isConceptual }} +{{/* Conceptual Page - Display tag description content only */}} +
+ {{ with .Content }} + {{ . }} + {{ else }} + {{ with .Params.tagDescription }} + {{ . | markdownify }} + {{ end }} + {{ end }} +
+{{ else }} +{{/* Operations Page - RapiDoc renderer */}} +
+ {{ partial "api/rapidoc.html" . }} +
+{{ end }} diff --git a/layouts/partials/api/tabs.html b/layouts/partials/api/tabs.html new file mode 100644 index 0000000000..a33099cc0f --- /dev/null +++ b/layouts/partials/api/tabs.html @@ -0,0 +1,10 @@ +{{/* + API Reference Page Tabs (DEPRECATED) + + This partial is kept for backward compatibility but renders nothing. + The new architecture uses: + - Tag pages (list.html): Display operations list directly + - Operation pages (single.html): Display RapiDoc for single operation +*/}} + +{{/* No tabs rendered - using simplified layout */}} diff --git a/layouts/partials/sidebar.html b/layouts/partials/sidebar.html index 0dd150396b..2d89d773c8 100644 --- a/layouts/partials/sidebar.html +++ b/layouts/partials/sidebar.html @@ -108,4 +108,9 @@

Additional resources

{{ end }} + + {{/* API Reference Navigation - shown only for API pages */}} + {{ if eq .Type "api" }} + {{ partial "api/sidebar-nav.html" . }} + {{ end }} From 195b7a0fd6b7be5a66be8f4435693b74f2933d3e Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:06:43 -0600 Subject: [PATCH 09/33] feat(api): Add TypeScript components for API UI interactions - Add api-nav.ts for sidebar navigation collapse/expand - Add api-scalar.ts for Scalar API renderer integration - Add api-tabs.ts for tab switching functionality - Add api-toc.ts for table of contents generation - Register components in main.js --- assets/js/components/api-nav.ts | 76 +++++ assets/js/components/api-scalar.ts | 326 ++++++++++++++++++++++ assets/js/components/api-tabs.ts | 144 ++++++++++ assets/js/components/api-toc.ts | 434 +++++++++++++++++++++++++++++ assets/js/main.js | 8 + 5 files changed, 988 insertions(+) create mode 100644 assets/js/components/api-nav.ts create mode 100644 assets/js/components/api-scalar.ts create mode 100644 assets/js/components/api-tabs.ts create mode 100644 assets/js/components/api-toc.ts diff --git a/assets/js/components/api-nav.ts b/assets/js/components/api-nav.ts new file mode 100644 index 0000000000..876ca80926 --- /dev/null +++ b/assets/js/components/api-nav.ts @@ -0,0 +1,76 @@ +/** + * API Navigation Component + * + * Handles collapsible navigation groups in the API sidebar. + * Features: + * - Toggle expand/collapse on group headers + * - ARIA accessibility support + * - Keyboard navigation + * + * Usage: + * + */ + +interface ComponentOptions { + component: HTMLElement; +} + +/** + * Initialize API Navigation component + */ +export default function ApiNav({ component }: ComponentOptions): void { + const headers = component.querySelectorAll( + '.api-nav-group-header' + ); + + headers.forEach((header) => { + header.addEventListener('click', () => { + const isOpen = header.classList.toggle('is-open'); + header.setAttribute('aria-expanded', String(isOpen)); + + const items = header.nextElementSibling; + if (items) { + items.classList.toggle('is-open', isOpen); + } + }); + + // Keyboard support - Enter and Space already work for buttons + // but add support for arrow keys to navigate between groups + header.addEventListener('keydown', (event: KeyboardEvent) => { + const allHeaders = Array.from(headers); + const currentIndex = allHeaders.indexOf(header); + + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + if (currentIndex < allHeaders.length - 1) { + allHeaders[currentIndex + 1].focus(); + } + break; + case 'ArrowUp': + event.preventDefault(); + if (currentIndex > 0) { + allHeaders[currentIndex - 1].focus(); + } + break; + case 'Home': + event.preventDefault(); + allHeaders[0].focus(); + break; + case 'End': + event.preventDefault(); + allHeaders[allHeaders.length - 1].focus(); + break; + } + }); + }); +} diff --git a/assets/js/components/api-scalar.ts b/assets/js/components/api-scalar.ts new file mode 100644 index 0000000000..62161a2140 --- /dev/null +++ b/assets/js/components/api-scalar.ts @@ -0,0 +1,326 @@ +/** + * Scalar API Documentation Component + * + * Initializes the Scalar API reference viewer for OpenAPI documentation. + * Features: + * - Dynamic CDN loading of Scalar library + * - Theme synchronization with site theme + * - InfluxData brand colors + * - Error handling and fallback UI + * + * Usage: + *
+ */ + +import { getPreference } from '../services/local-storage.js'; + +interface ComponentOptions { + component: HTMLElement; +} + +interface ScalarConfig { + url: string; + forceDarkModeState?: 'dark' | 'light'; + layout?: 'classic' | 'modern'; + showSidebar?: boolean; + hideDarkModeToggle?: boolean; + hideSearch?: boolean; + documentDownloadType?: 'none' | 'yaml' | 'json'; + hideModels?: boolean; + hideTestRequestButton?: boolean; + withDefaultFonts?: boolean; + customCss?: string; +} + +type ScalarCreateFn = ( + selector: string | HTMLElement, + config: ScalarConfig +) => void; + +declare global { + interface Window { + Scalar?: { + createApiReference: ScalarCreateFn; + }; + } +} + +const SCALAR_CDN = 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@latest'; + +/** + * Load script dynamically + */ +function loadScript(src: string, timeout = 8000): Promise { + return new Promise((resolve, reject) => { + // Check if script already exists + const existing = Array.from(document.scripts).find( + (s) => s.src && s.src.includes(src) + ); + if (existing && window.Scalar?.createApiReference) { + return resolve(); + } + + const script = document.createElement('script'); + script.src = src; + script.defer = true; + script.onload = () => resolve(); + script.onerror = () => reject(new Error(`Failed to load script: ${src}`)); + + document.head.appendChild(script); + + // Fallback timeout + setTimeout(() => { + if (window.Scalar?.createApiReference) { + resolve(); + } else { + reject(new Error(`Timeout loading script: ${src}`)); + } + }, timeout); + }); +} + +/** + * Get current theme from localStorage (source of truth for Hugo theme system) + */ +function getTheme(): 'dark' | 'light' { + const theme = getPreference('theme'); + return theme === 'dark' ? 'dark' : 'light'; +} + +/** + * Poll for Scalar availability + */ +function waitForScalar(maxAttempts = 50, interval = 100): Promise { + return new Promise((resolve, reject) => { + let attempts = 0; + + const checkInterval = setInterval(() => { + attempts++; + + if (window.Scalar?.createApiReference) { + clearInterval(checkInterval); + resolve(); + } else if (attempts >= maxAttempts) { + clearInterval(checkInterval); + reject( + new Error(`Scalar not available after ${maxAttempts * interval}ms`) + ); + } + }, interval); + }); +} + +/** + * Initialize Scalar API reference + */ +async function initScalar( + container: HTMLElement, + specUrl: string +): Promise { + if (!window.Scalar?.createApiReference) { + throw new Error('Scalar is not available'); + } + + // Clean up previous Scalar instance (important for theme switching) + // Remove any Scalar-injected content and classes + container.innerHTML = ''; + // Remove Scalar's dark-mode class from body if it exists + document.body.classList.remove('dark-mode'); + + const isDark = getTheme() === 'dark'; + + window.Scalar.createApiReference(container, { + url: specUrl, + forceDarkModeState: getTheme(), + layout: 'classic', + showSidebar: false, + hideDarkModeToggle: true, + hideSearch: true, + documentDownloadType: 'none', + hideModels: false, + hideTestRequestButton: false, + withDefaultFonts: false, + customCss: ` + :root { + /* Typography - match Hugo docs site */ + --scalar-font: 'Proxima Nova', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --scalar-font-code: 'IBM Plex Mono', Monaco, Consolas, monospace; + --scalar-font-size-base: 16px; + --scalar-line-height: 1.65; + + /* InfluxData brand colors */ + --scalar-color-1: #F63C41; + --scalar-color-2: #d32f34; + --scalar-color-accent: #F63C41; + + /* Border radius */ + --scalar-radius: 4px; + --scalar-radius-lg: 8px; + + /* Background and text colors - theme-aware */ + --scalar-background-1: ${isDark ? '#1a1a2e' : '#ffffff'}; + --scalar-background-2: ${isDark ? '#232338' : '#f7f8fa'}; + --scalar-background-3: ${isDark ? '#2d2d44' : '#f0f2f5'}; + --scalar-text-1: ${isDark ? '#e0e0e0' : '#2b2b2b'}; + --scalar-text-2: ${isDark ? '#a0a0a0' : '#545454'}; + --scalar-text-3: ${isDark ? '#888888' : '#757575'}; + --scalar-border-color: ${isDark ? '#3a3a50' : '#e0e0e0'}; + + /* Heading colors */ + --scalar-heading-color: ${isDark ? '#ffffff' : '#2b2b2b'}; + } + + /* Match Hugo heading styles */ + h1, h2, h3, h4, h5, h6 { + font-family: var(--scalar-font); + font-weight: 600; + color: var(--scalar-heading-color); + line-height: 1.25; + } + + h1 { font-size: 2rem; } + h2 { font-size: 1.5rem; margin-top: 2rem; } + h3 { font-size: 1.25rem; margin-top: 1.5rem; } + h4 { font-size: 1rem; margin-top: 1rem; } + + /* Body text size */ + p, li, td, th { + font-size: 1rem; + line-height: var(--scalar-line-height); + } + + /* Code block styling */ + pre, code { + font-family: var(--scalar-font-code); + font-size: 0.875rem; + } + + /* Hide section-content div */ + div.section-content { + display: none !important; + } + `, + }); + + console.log( + '[API Docs] Scalar initialized with spec:', + specUrl, + 'theme:', + getTheme() + ); +} + +/** + * Show error message in container + */ +function showError(container: HTMLElement, message: string): void { + container.innerHTML = `

${message}

`; +} + +/** + * Watch for Hugo theme changes via stylesheet manipulation + * Hugo theme.js enables/disables link[title*="theme"] elements + */ +function watchThemeChanges(container: HTMLElement, specUrl: string): void { + // Watch for stylesheet changes in the document + const observer = new MutationObserver(() => { + const currentTheme = getTheme(); + console.log('[API Docs] Theme changed to:', currentTheme); + // Re-initialize Scalar with new theme + initScalar(container, specUrl).catch((error) => { + console.error( + '[API Docs] Failed to re-initialize Scalar on theme change:', + error + ); + }); + }); + + // Watch for changes to stylesheet link elements + const head = document.querySelector('head'); + if (head) { + observer.observe(head, { + attributes: true, + attributeFilter: ['disabled'], + subtree: true, + }); + } + + // Also watch for localStorage changes from other tabs + window.addEventListener('storage', (event) => { + if (event.key === 'influxdata_docs_preferences' && event.newValue) { + try { + const prefs = JSON.parse(event.newValue); + if (prefs.theme) { + const currentTheme = getTheme(); + console.log( + '[API Docs] Theme changed via storage event to:', + currentTheme + ); + initScalar(container, specUrl).catch((error) => { + console.error( + '[API Docs] Failed to re-initialize Scalar on storage change:', + error + ); + }); + } + } catch (error) { + console.error( + '[API Docs] Failed to parse localStorage preferences:', + error + ); + } + } + }); +} + +/** + * Initialize API Scalar component + */ +export default async function ApiScalar({ + component, +}: ComponentOptions): Promise { + try { + // Get spec path from data attribute + const specPath = component.dataset.specPath; + const cdn = component.dataset.cdn || SCALAR_CDN; + + if (!specPath) { + console.error('[API Docs] No OpenAPI specification path provided'); + showError( + component, + 'Error: No API specification configured for this page.' + ); + return; + } + + // Build full URL for spec (Scalar needs absolute URL) + const specUrl = window.location.origin + specPath; + + // Load Scalar from CDN if not already loaded + if (!window.Scalar?.createApiReference) { + try { + await loadScript(cdn); + } catch (err) { + console.error('[API Docs] Failed to load Scalar from CDN', err); + } + } + + // Wait for Scalar to be ready + try { + await waitForScalar(); + } catch (err) { + console.error('[API Docs] Scalar failed to initialize', err); + showError(component, 'Error: API viewer failed to load.'); + return; + } + + // Initialize Scalar + await initScalar(component, specUrl); + + // Watch for theme changes and re-initialize Scalar when theme changes + watchThemeChanges(component, specUrl); + } catch (err) { + console.error('[API Docs] ApiScalar component error', err); + showError(component, 'Error: API viewer failed to initialize.'); + } +} diff --git a/assets/js/components/api-tabs.ts b/assets/js/components/api-tabs.ts new file mode 100644 index 0000000000..25b41ec16f --- /dev/null +++ b/assets/js/components/api-tabs.ts @@ -0,0 +1,144 @@ +/** + * API Tabs Component + * + * Handles tab switching for API reference documentation. + * Uses data-tab and data-tab-panel attributes for explicit panel targeting, + * unlike the generic tabs which use positional indexing. + * + * Features: + * - Explicit panel targeting via data-tab-panel + * - Deep linking via URL hash + * - Browser back/forward navigation support + * - Custom event dispatch for TOC updates + * + * Usage: + *
+ * + *
+ *
+ *
...
+ *
...
+ *
+ */ + +interface ComponentOptions { + component: HTMLElement; +} + +/** + * Find the panels container (sibling element after tabs) + */ +function findPanelsContainer(tabsWrapper: HTMLElement): HTMLElement | null { + let sibling = tabsWrapper.nextElementSibling; + while (sibling) { + if (sibling.classList.contains('api-tab-panels')) { + return sibling as HTMLElement; + } + sibling = sibling.nextElementSibling; + } + return null; +} + +/** + * Switch to a specific tab + */ +function switchTab( + tabsWrapper: HTMLElement, + panelsContainer: HTMLElement, + tabId: string, + updateHash = true +): void { + // Update active tab + const tabs = tabsWrapper.querySelectorAll('[data-tab]'); + tabs.forEach((tab) => { + if (tab.dataset.tab === tabId) { + tab.classList.add('is-active'); + } else { + tab.classList.remove('is-active'); + } + }); + + // Update visible panel + const panels = + panelsContainer.querySelectorAll('[data-tab-panel]'); + panels.forEach((panel) => { + if (panel.dataset.tabPanel === tabId) { + panel.style.display = 'block'; + } else { + panel.style.display = 'none'; + } + }); + + // Update URL hash without scrolling + if (updateHash) { + history.replaceState(null, '', '#' + tabId); + } + + // Dispatch custom event for TOC update + document.dispatchEvent( + new CustomEvent('api-tab-change', { detail: { tab: tabId } }) + ); +} + +/** + * Get tab ID from URL hash + */ +function getTabFromHash(): string | null { + const hash = window.location.hash.substring(1); + return hash || null; +} + +/** + * Initialize API Tabs component + */ +export default function ApiTabs({ component }: ComponentOptions): void { + const panelsContainer = findPanelsContainer(component); + + if (!panelsContainer) { + console.warn('[API Tabs] No .api-tab-panels container found'); + return; + } + + const tabs = component.querySelectorAll('[data-tab]'); + + if (tabs.length === 0) { + console.warn('[API Tabs] No tabs found with data-tab attribute'); + return; + } + + // Handle tab clicks + tabs.forEach((tab) => { + tab.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); // Prevent other tab handlers from firing + + const tabId = tab.dataset.tab; + if (tabId) { + switchTab(component, panelsContainer, tabId); + } + }); + }); + + // Handle deep linking via URL hash on load + const hashTab = getTabFromHash(); + if (hashTab) { + const matchingTab = component.querySelector(`[data-tab="${hashTab}"]`); + if (matchingTab) { + switchTab(component, panelsContainer, hashTab, false); + } + } + + // Handle browser back/forward navigation + window.addEventListener('hashchange', () => { + const newTabId = getTabFromHash(); + if (newTabId) { + const matchingTab = component.querySelector(`[data-tab="${newTabId}"]`); + if (matchingTab) { + switchTab(component, panelsContainer, newTabId, false); + } + } + }); +} diff --git a/assets/js/components/api-toc.ts b/assets/js/components/api-toc.ts new file mode 100644 index 0000000000..06b0d2f589 --- /dev/null +++ b/assets/js/components/api-toc.ts @@ -0,0 +1,434 @@ +/** + * API Table of Contents Component + * + * Generates "ON THIS PAGE" navigation from content headings or operations data. + * Features: + * - Builds TOC from h2/h3 headings in the active tab panel (legacy) + * - 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: + * + */ + +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[]; +} + +/** + * Check if the active panel contains a RapiDoc component + */ +function isRapiDocActive(): boolean { + const activePanel = document.querySelector( + '.tab-content:not([style*="display: none"]), [data-tab-panel]:not([style*="display: none"])' + ); + return activePanel?.querySelector('rapi-doc') !== null; +} + +/** + * Get headings from the currently visible content + */ +function getVisibleHeadings(): 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 []; + } + + const headings = activePanel.querySelectorAll('h2, h3'); + 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; + } + + entries.push({ + id: heading.id, + text: heading.textContent?.trim() || '', + level: heading.tagName === 'H2' ? 2 : 3, + }); + }); + + return entries; +} + +/** + * Build TOC HTML from entries + */ +function buildTocHtml(entries: TocEntry[]): string { + if (entries.length === 0) { + // Check if RapiDoc is active - show helpful message + if (isRapiDocActive()) { + return '

Use RapiDoc\'s navigation below to explore this endpoint.

'; + } + return '

No sections on this page.

'; + } + + let html = '
    '; + + entries.forEach((entry) => { + const indent = entry.level === 3 ? ' api-toc-item--nested' : ''; + html += ` +
  • + ${entry.text} +
  • + `; + }); + + html += '
'; + 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 '

No operations on this page.

'; + } + + let html = '
    '; + + operations.forEach((op) => { + // Generate anchor ID from operationId (Scalar uses operationId for anchors) + const anchorId = op.operationId; + const methodClass = getMethodClass(op.method); + + html += ` +
  • + + ${op.method.toUpperCase()} + ${op.path} + +
  • + `; + }); + + html += '
'; + 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('.api-toc-link'); + + // Create a map of heading ID to link element + const linkMap = new Map(); + 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(); + + 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('.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 + * Hide TOC for Operations tab (RapiDoc has built-in navigation) + */ +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('.api-toc-nav'); + + if (!nav) { + console.warn('[API TOC] No .api-toc-nav element found'); + return; + } + + // Check for operations data (tag-based pages) + const operations = parseOperationsData(component); + let observer: IntersectionObserver | null = null; + + /** + * 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(); + if (nav) { + nav.innerHTML = buildTocHtml(entries); + } + + // Set up scroll highlighting + 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); + }); +} diff --git a/assets/js/main.js b/assets/js/main.js index 826ad9a116..a3cfbaddc5 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -46,6 +46,10 @@ 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 ApiNav from './components/api-nav.ts'; +import ApiScalar from './components/api-scalar.ts'; +import ApiTabs from './components/api-tabs.ts'; +import ApiToc from './components/api-toc.ts'; /** * Component Registry @@ -77,6 +81,10 @@ const componentRegistry = { 'sidebar-toggle': SidebarToggle, theme: Theme, 'theme-switch': ThemeSwitch, + 'api-nav': ApiNav, + 'api-scalar': ApiScalar, + 'api-tabs': ApiTabs, + 'api-toc': ApiToc, }; /** From 27b79ca43a15c9b752405856ca68cf7ae3d1d5f2 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:07:01 -0600 Subject: [PATCH 10/33] feat(api): Add article data files and sidebar navigation partial - Add YAML article data files for all InfluxDB products - Add sidebar-nav.html partial for API navigation rendering - Rename data directory from article-data to article_data for Hugo compatibility - Remove obsolete JSON articles file --- .../influxdb/cloud-v2/articles.json | 597 --------- .../influxdb/cloud-dedicated/articles.yml | 24 + .../influxdb/cloud-v2/articles.yml | 499 ++++++++ .../influxdb/clustered/articles.yml | 47 + .../influxdb/influxdb3_core/articles.yml | 1094 +++++++++++++++++ .../influxdb3_enterprise/articles.yml | 246 ++++ .../article_data/influxdb/oss-v2/articles.yml | 757 ++++++++++++ layouts/partials/api/sidebar-nav.html | 244 ++++ 8 files changed, 2911 insertions(+), 597 deletions(-) delete mode 100644 data/article-data/influxdb/cloud-v2/articles.json create mode 100644 data/article_data/influxdb/cloud-dedicated/articles.yml create mode 100644 data/article_data/influxdb/cloud-v2/articles.yml create mode 100644 data/article_data/influxdb/clustered/articles.yml create mode 100644 data/article_data/influxdb/influxdb3_core/articles.yml create mode 100644 data/article_data/influxdb/influxdb3_enterprise/articles.yml create mode 100644 data/article_data/influxdb/oss-v2/articles.yml create mode 100644 layouts/partials/api/sidebar-nav.html diff --git a/data/article-data/influxdb/cloud-v2/articles.json b/data/article-data/influxdb/cloud-v2/articles.json deleted file mode 100644 index 2267c4303d..0000000000 --- a/data/article-data/influxdb/cloud-v2/articles.json +++ /dev/null @@ -1,597 +0,0 @@ -{ - "articles": [ - { - "path": "api-v2-authorizations", - "fields": { - "name": "/api/v2/authorizations", - "describes": [ - "/api/v2/authorizations", - "/api/v2/authorizations/{authID}" - ], - "title": "/api/v2/authorizations\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "authorizations" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml" - } - }, - { - "path": "api-v2-buckets", - "fields": { - "name": "/api/v2/buckets", - "describes": [ - "/api/v2/buckets", - "/api/v2/buckets/{bucketID}", - "/api/v2/buckets/{bucketID}/labels", - "/api/v2/buckets/{bucketID}/labels/{labelID}", - "/api/v2/buckets/{bucketID}/members", - "/api/v2/buckets/{bucketID}/members/{userID}", - "/api/v2/buckets/{bucketID}/owners", - "/api/v2/buckets/{bucketID}/owners/{userID}", - "/api/v2/buckets/{bucketID}/schema/measurements", - "/api/v2/buckets/{bucketID}/schema/measurements/{measurementID}" - ], - "title": "/api/v2/buckets\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "buckets" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml" - } - }, - { - "path": "api-v2-checks", - "fields": { - "name": "/api/v2/checks", - "describes": [ - "/api/v2/checks", - "/api/v2/checks/{checkID}", - "/api/v2/checks/{checkID}/labels", - "/api/v2/checks/{checkID}/labels/{labelID}", - "/api/v2/checks/{checkID}/query" - ], - "title": "/api/v2/checks\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "checks" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml" - } - }, - { - "path": "api-v2-dashboards", - "fields": { - "name": "/api/v2/dashboards", - "describes": [ - "/api/v2/dashboards", - "/api/v2/dashboards/{dashboardID}", - "/api/v2/dashboards/{dashboardID}/cells", - "/api/v2/dashboards/{dashboardID}/cells/{cellID}", - "/api/v2/dashboards/{dashboardID}/cells/{cellID}/view", - "/api/v2/dashboards/{dashboardID}/labels", - "/api/v2/dashboards/{dashboardID}/labels/{labelID}", - "/api/v2/dashboards/{dashboardID}/members", - "/api/v2/dashboards/{dashboardID}/members/{userID}", - "/api/v2/dashboards/{dashboardID}/owners", - "/api/v2/dashboards/{dashboardID}/owners/{userID}" - ], - "title": "/api/v2/dashboards\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "dashboards" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml" - } - }, - { - "path": "api-v2-dbrps", - "fields": { - "name": "/api/v2/dbrps", - "describes": [ - "/api/v2/dbrps", - "/api/v2/dbrps/{dbrpID}" - ], - "title": "/api/v2/dbrps\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "dbrps" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml" - } - }, - { - "path": "api-v2-delete", - "fields": { - "name": "/api/v2/delete", - "describes": [ - "/api/v2/delete" - ], - "title": "/api/v2/delete\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "delete" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml" - } - }, - { - "path": "api-v2-flags", - "fields": { - "name": "/api/v2/flags", - "describes": [ - "/api/v2/flags" - ], - "title": "/api/v2/flags\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "flags" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml" - } - }, - { - "path": "api-v2-labels", - "fields": { - "name": "/api/v2/labels", - "describes": [ - "/api/v2/labels", - "/api/v2/labels/{labelID}" - ], - "title": "/api/v2/labels\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "labels" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml" - } - }, - { - "path": "api-v2-maps", - "fields": { - "name": "/api/v2/maps", - "describes": [ - "/api/v2/maps/mapToken" - ], - "title": "/api/v2/maps\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "maps" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml" - } - }, - { - "path": "api-v2-me", - "fields": { - "name": "/api/v2/me", - "describes": [ - "/api/v2/me", - "/api/v2/me/password" - ], - "title": "/api/v2/me\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "me" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml" - } - }, - { - "path": "api-v2-notificationEndpoints", - "fields": { - "name": "/api/v2/notificationEndpoints", - "describes": [ - "/api/v2/notificationEndpoints", - "/api/v2/notificationEndpoints/{endpointID}", - "/api/v2/notificationEndpoints/{endpointID}/labels", - "/api/v2/notificationEndpoints/{endpointID}/labels/{labelID}" - ], - "title": "/api/v2/notificationEndpoints\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "notificationEndpoints" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml" - } - }, - { - "path": "api-v2-notificationRules", - "fields": { - "name": "/api/v2/notificationRules", - "describes": [ - "/api/v2/notificationRules", - "/api/v2/notificationRules/{ruleID}", - "/api/v2/notificationRules/{ruleID}/labels", - "/api/v2/notificationRules/{ruleID}/labels/{labelID}", - "/api/v2/notificationRules/{ruleID}/query" - ], - "title": "/api/v2/notificationRules\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "notificationRules" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml" - } - }, - { - "path": "api-v2-orgs", - "fields": { - "name": "/api/v2/orgs", - "describes": [ - "/api/v2/orgs", - "/api/v2/orgs/{orgID}", - "/api/v2/orgs/{orgID}/limits", - "/api/v2/orgs/{orgID}/members", - "/api/v2/orgs/{orgID}/members/{userID}", - "/api/v2/orgs/{orgID}/owners", - "/api/v2/orgs/{orgID}/owners/{userID}", - "/api/v2/orgs/{orgID}/secrets", - "/api/v2/orgs/{orgID}/secrets/delete", - "/api/v2/orgs/{orgID}/secrets/{secretID}", - "/api/v2/orgs/{orgID}/usage" - ], - "title": "/api/v2/orgs\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "orgs" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml" - } - }, - { - "path": "api-v2-query", - "fields": { - "name": "/api/v2/query", - "describes": [ - "/api/v2/query", - "/api/v2/query/analyze", - "/api/v2/query/ast", - "/api/v2/query/suggestions", - "/api/v2/query/suggestions/{name}" - ], - "title": "/api/v2/query\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "query" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml" - } - }, - { - "path": "api-v2-resources", - "fields": { - "name": "/api/v2/resources", - "describes": [ - "/api/v2/resources" - ], - "title": "/api/v2/resources\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "resources" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml" - } - }, - { - "path": "api-v2-scripts", - "fields": { - "name": "/api/v2/scripts", - "describes": [ - "/api/v2/scripts", - "/api/v2/scripts/{scriptID}", - "/api/v2/scripts/{scriptID}/invoke", - "/api/v2/scripts/{scriptID}/params" - ], - "title": "/api/v2/scripts\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "scripts" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml" - } - }, - { - "path": "api-v2-setup", - "fields": { - "name": "/api/v2/setup", - "describes": [ - "/api/v2/setup", - "/api/v2/setup/user" - ], - "title": "/api/v2/setup\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "setup" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml" - } - }, - { - "path": "api-v2-signin", - "fields": { - "name": "/api/v2/signin", - "describes": [ - "/api/v2/signin" - ], - "title": "/api/v2/signin\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "signin" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml" - } - }, - { - "path": "api-v2-signout", - "fields": { - "name": "/api/v2/signout", - "describes": [ - "/api/v2/signout" - ], - "title": "/api/v2/signout\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "signout" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml" - } - }, - { - "path": "api-v2-stacks", - "fields": { - "name": "/api/v2/stacks", - "describes": [ - "/api/v2/stacks", - "/api/v2/stacks/{stack_id}", - "/api/v2/stacks/{stack_id}/uninstall" - ], - "title": "/api/v2/stacks\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "stacks" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml" - } - }, - { - "path": "api-v2-tasks", - "fields": { - "name": "/api/v2/tasks", - "describes": [ - "/api/v2/tasks", - "/api/v2/tasks/{taskID}", - "/api/v2/tasks/{taskID}/labels", - "/api/v2/tasks/{taskID}/labels/{labelID}", - "/api/v2/tasks/{taskID}/logs", - "/api/v2/tasks/{taskID}/members", - "/api/v2/tasks/{taskID}/members/{userID}", - "/api/v2/tasks/{taskID}/owners", - "/api/v2/tasks/{taskID}/owners/{userID}", - "/api/v2/tasks/{taskID}/runs", - "/api/v2/tasks/{taskID}/runs/{runID}", - "/api/v2/tasks/{taskID}/runs/{runID}/logs", - "/api/v2/tasks/{taskID}/runs/{runID}/retry" - ], - "title": "/api/v2/tasks\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "tasks" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml" - } - }, - { - "path": "api-v2-telegraf", - "fields": { - "name": "/api/v2/telegraf", - "describes": [ - "/api/v2/telegraf/plugins" - ], - "title": "/api/v2/telegraf\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "telegraf" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml" - } - }, - { - "path": "api-v2-telegrafs", - "fields": { - "name": "/api/v2/telegrafs", - "describes": [ - "/api/v2/telegrafs", - "/api/v2/telegrafs/{telegrafID}", - "/api/v2/telegrafs/{telegrafID}/labels", - "/api/v2/telegrafs/{telegrafID}/labels/{labelID}", - "/api/v2/telegrafs/{telegrafID}/members", - "/api/v2/telegrafs/{telegrafID}/members/{userID}", - "/api/v2/telegrafs/{telegrafID}/owners", - "/api/v2/telegrafs/{telegrafID}/owners/{userID}" - ], - "title": "/api/v2/telegrafs\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "telegrafs" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml" - } - }, - { - "path": "api-v2-templates", - "fields": { - "name": "/api/v2/templates", - "describes": [ - "/api/v2/templates/apply", - "/api/v2/templates/export" - ], - "title": "/api/v2/templates\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "templates" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml" - } - }, - { - "path": "api-v2-users", - "fields": { - "name": "/api/v2/users", - "describes": [ - "/api/v2/users", - "/api/v2/users/{userID}", - "/api/v2/users/{userID}/password" - ], - "title": "/api/v2/users\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "users" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml" - } - }, - { - "path": "api-v2-variables", - "fields": { - "name": "/api/v2/variables", - "describes": [ - "/api/v2/variables", - "/api/v2/variables/{variableID}", - "/api/v2/variables/{variableID}/labels", - "/api/v2/variables/{variableID}/labels/{labelID}" - ], - "title": "/api/v2/variables\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "variables" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml" - } - }, - { - "path": "api-v2-write", - "fields": { - "name": "/api/v2/write", - "describes": [ - "/api/v2/write" - ], - "title": "/api/v2/write\nInfluxDB Cloud API Service", - "tags": [ - "api-v2", - "write" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml" - } - }, - { - "path": "api-v2", - "fields": { - "name": "/api/v2", - "describes": [ - "/api/v2" - ], - "title": "/api/v2\nInfluxDB Cloud API Service", - "tags": [ - "api", - "v2" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml" - } - }, - { - "path": "legacy-authorizations", - "fields": { - "name": "/legacy/authorizations", - "describes": [ - "/legacy/authorizations", - "/legacy/authorizations/{authID}", - "/legacy/authorizations/{authID}/password" - ], - "title": "/legacy/authorizations\nInfluxDB Cloud API Service", - "tags": [ - "legacy", - "authorizations" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml" - } - }, - { - "path": "ping", - "fields": { - "name": "/ping", - "describes": [ - "/ping" - ], - "title": "/ping\nInfluxDB Cloud API Service", - "tags": [ - "", - "ping" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-ping.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-ping.yaml" - } - }, - { - "path": "query", - "fields": { - "name": "/query", - "describes": [ - "/query" - ], - "title": "/query\nInfluxDB Cloud API Service", - "tags": [ - "", - "query" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-query.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-query.yaml" - } - }, - { - "path": "write", - "fields": { - "name": "/write", - "describes": [ - "/write" - ], - "title": "/write\nInfluxDB Cloud API Service", - "tags": [ - "", - "write" - ], - "source": "static/openapi/influxdb-cloud-v2/paths/ref-write.yaml", - "staticFilePath": "/openapi/influxdb-cloud-v2/paths/ref-write.yaml" - } - } - ] -} \ No newline at end of file diff --git a/data/article_data/influxdb/cloud-dedicated/articles.yml b/data/article_data/influxdb/cloud-dedicated/articles.yml new file mode 100644 index 0000000000..e655739bf6 --- /dev/null +++ b/data/article_data/influxdb/cloud-dedicated/articles.yml @@ -0,0 +1,24 @@ +articles: + - path: api/accounts/{accountId}/clusters + fields: + name: /accounts/{accountId}/clusters + describes: + - /accounts/{accountId}/clusters/{clusterId}/databases + - /accounts/{accountId}/clusters/{clusterId}/databases/{databaseName} + - >- + /accounts/{accountId}/clusters/{clusterId}/databases/{databaseName}/tables + - /accounts/{accountId}/clusters/{clusterId}/tokens + - /accounts/{accountId}/clusters/{clusterId}/tokens/{tokenId} + menuName: /accounts/{accountId}/clusters + title: /accounts/{accountId}/clusters + tags: + - accounts-{accountId} + - clusters + apiTags: + - Databases + - Tables + - Database tokens + source: >- + static/openapi/influxdb-cloud-dedicated/paths/openapi-accounts-{accountId}-clusters.yaml + staticFilePath: >- + /openapi/influxdb-cloud-dedicated/paths/openapi-accounts-{accountId}-clusters.yaml diff --git a/data/article_data/influxdb/cloud-v2/articles.yml b/data/article_data/influxdb/cloud-v2/articles.yml new file mode 100644 index 0000000000..0f188ea905 --- /dev/null +++ b/data/article_data/influxdb/cloud-v2/articles.yml @@ -0,0 +1,499 @@ +articles: + - path: api/v2/authorizations + fields: + name: /api/v2/authorizations + describes: + - /api/v2/authorizations + - /api/v2/authorizations/{authID} + title: |- + /api/v2/authorizations + InfluxDB Cloud API Service + tags: + - api-v2 + - authorizations + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-authorizations.yaml + - path: api/v2/buckets + fields: + name: /api/v2/buckets + describes: + - /api/v2/buckets + - /api/v2/buckets/{bucketID} + - /api/v2/buckets/{bucketID}/labels + - /api/v2/buckets/{bucketID}/labels/{labelID} + - /api/v2/buckets/{bucketID}/members + - /api/v2/buckets/{bucketID}/members/{userID} + - /api/v2/buckets/{bucketID}/owners + - /api/v2/buckets/{bucketID}/owners/{userID} + - /api/v2/buckets/{bucketID}/schema/measurements + - /api/v2/buckets/{bucketID}/schema/measurements/{measurementID} + title: |- + /api/v2/buckets + InfluxDB Cloud API Service + tags: + - api-v2 + - buckets + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-buckets.yaml + - path: api/v2/checks + fields: + name: /api/v2/checks + describes: + - /api/v2/checks + - /api/v2/checks/{checkID} + - /api/v2/checks/{checkID}/labels + - /api/v2/checks/{checkID}/labels/{labelID} + - /api/v2/checks/{checkID}/query + title: |- + /api/v2/checks + InfluxDB Cloud API Service + tags: + - api-v2 + - checks + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-checks.yaml + - path: api/v2/dashboards + fields: + name: /api/v2/dashboards + describes: + - /api/v2/dashboards + - /api/v2/dashboards/{dashboardID} + - /api/v2/dashboards/{dashboardID}/cells + - /api/v2/dashboards/{dashboardID}/cells/{cellID} + - /api/v2/dashboards/{dashboardID}/cells/{cellID}/view + - /api/v2/dashboards/{dashboardID}/labels + - /api/v2/dashboards/{dashboardID}/labels/{labelID} + - /api/v2/dashboards/{dashboardID}/members + - /api/v2/dashboards/{dashboardID}/members/{userID} + - /api/v2/dashboards/{dashboardID}/owners + - /api/v2/dashboards/{dashboardID}/owners/{userID} + title: |- + /api/v2/dashboards + InfluxDB Cloud API Service + tags: + - api-v2 + - dashboards + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-dashboards.yaml + - path: api/v2/dbrps + fields: + name: /api/v2/dbrps + describes: + - /api/v2/dbrps + - /api/v2/dbrps/{dbrpID} + title: |- + /api/v2/dbrps + InfluxDB Cloud API Service + tags: + - api-v2 + - dbrps + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-dbrps.yaml + - path: api/v2/delete + fields: + name: /api/v2/delete + describes: + - /api/v2/delete + title: |- + /api/v2/delete + InfluxDB Cloud API Service + tags: + - api-v2 + - delete + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-delete.yaml + - path: api/v2/flags + fields: + name: /api/v2/flags + describes: + - /api/v2/flags + title: |- + /api/v2/flags + InfluxDB Cloud API Service + tags: + - api-v2 + - flags + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-flags.yaml + - path: api/v2/labels + fields: + name: /api/v2/labels + describes: + - /api/v2/labels + - /api/v2/labels/{labelID} + title: |- + /api/v2/labels + InfluxDB Cloud API Service + tags: + - api-v2 + - labels + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-labels.yaml + - path: api/v2/maps + fields: + name: /api/v2/maps + describes: + - /api/v2/maps/mapToken + title: |- + /api/v2/maps + InfluxDB Cloud API Service + tags: + - api-v2 + - maps + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-maps.yaml + - path: api/v2/me + fields: + name: /api/v2/me + describes: + - /api/v2/me + - /api/v2/me/password + title: |- + /api/v2/me + InfluxDB Cloud API Service + tags: + - api-v2 + - me + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-me.yaml + - path: api/v2/notificationEndpoints + fields: + name: /api/v2/notificationEndpoints + describes: + - /api/v2/notificationEndpoints + - /api/v2/notificationEndpoints/{endpointID} + - /api/v2/notificationEndpoints/{endpointID}/labels + - /api/v2/notificationEndpoints/{endpointID}/labels/{labelID} + title: |- + /api/v2/notificationEndpoints + InfluxDB Cloud API Service + tags: + - api-v2 + - notificationEndpoints + source: >- + static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationEndpoints.yaml + - path: api/v2/notificationRules + fields: + name: /api/v2/notificationRules + describes: + - /api/v2/notificationRules + - /api/v2/notificationRules/{ruleID} + - /api/v2/notificationRules/{ruleID}/labels + - /api/v2/notificationRules/{ruleID}/labels/{labelID} + - /api/v2/notificationRules/{ruleID}/query + title: |- + /api/v2/notificationRules + InfluxDB Cloud API Service + tags: + - api-v2 + - notificationRules + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-notificationRules.yaml + - path: api/v2/orgs + fields: + name: /api/v2/orgs + describes: + - /api/v2/orgs + - /api/v2/orgs/{orgID} + - /api/v2/orgs/{orgID}/limits + - /api/v2/orgs/{orgID}/members + - /api/v2/orgs/{orgID}/members/{userID} + - /api/v2/orgs/{orgID}/owners + - /api/v2/orgs/{orgID}/owners/{userID} + - /api/v2/orgs/{orgID}/secrets + - /api/v2/orgs/{orgID}/secrets/delete + - /api/v2/orgs/{orgID}/secrets/{secretID} + - /api/v2/orgs/{orgID}/usage + title: |- + /api/v2/orgs + InfluxDB Cloud API Service + tags: + - api-v2 + - orgs + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-orgs.yaml + - path: api/v2/query + fields: + name: /api/v2/query + describes: + - /api/v2/query + - /api/v2/query/analyze + - /api/v2/query/ast + - /api/v2/query/suggestions + - /api/v2/query/suggestions/{name} + title: |- + /api/v2/query + InfluxDB Cloud API Service + tags: + - api-v2 + - query + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-query.yaml + - path: api/v2/resources + fields: + name: /api/v2/resources + describes: + - /api/v2/resources + title: |- + /api/v2/resources + InfluxDB Cloud API Service + tags: + - api-v2 + - resources + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-resources.yaml + - path: api/v2/scripts + fields: + name: /api/v2/scripts + describes: + - /api/v2/scripts + - /api/v2/scripts/{scriptID} + - /api/v2/scripts/{scriptID}/invoke + - /api/v2/scripts/{scriptID}/params + title: |- + /api/v2/scripts + InfluxDB Cloud API Service + tags: + - api-v2 + - scripts + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-scripts.yaml + - path: api/v2/setup + fields: + name: /api/v2/setup + describes: + - /api/v2/setup + - /api/v2/setup/user + title: |- + /api/v2/setup + InfluxDB Cloud API Service + tags: + - api-v2 + - setup + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-setup.yaml + - path: api/v2/signin + fields: + name: /api/v2/signin + describes: + - /api/v2/signin + title: |- + /api/v2/signin + InfluxDB Cloud API Service + tags: + - api-v2 + - signin + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-signin.yaml + - path: api/v2/signout + fields: + name: /api/v2/signout + describes: + - /api/v2/signout + title: |- + /api/v2/signout + InfluxDB Cloud API Service + tags: + - api-v2 + - signout + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-signout.yaml + - path: api/v2/stacks + fields: + name: /api/v2/stacks + describes: + - /api/v2/stacks + - /api/v2/stacks/{stack_id} + - /api/v2/stacks/{stack_id}/uninstall + title: |- + /api/v2/stacks + InfluxDB Cloud API Service + tags: + - api-v2 + - stacks + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-stacks.yaml + - path: api/v2/tasks + fields: + name: /api/v2/tasks + describes: + - /api/v2/tasks + - /api/v2/tasks/{taskID} + - /api/v2/tasks/{taskID}/labels + - /api/v2/tasks/{taskID}/labels/{labelID} + - /api/v2/tasks/{taskID}/logs + - /api/v2/tasks/{taskID}/members + - /api/v2/tasks/{taskID}/members/{userID} + - /api/v2/tasks/{taskID}/owners + - /api/v2/tasks/{taskID}/owners/{userID} + - /api/v2/tasks/{taskID}/runs + - /api/v2/tasks/{taskID}/runs/{runID} + - /api/v2/tasks/{taskID}/runs/{runID}/logs + - /api/v2/tasks/{taskID}/runs/{runID}/retry + title: |- + /api/v2/tasks + InfluxDB Cloud API Service + tags: + - api-v2 + - tasks + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-tasks.yaml + - path: api/v2/telegraf + fields: + name: /api/v2/telegraf + describes: + - /api/v2/telegraf/plugins + title: |- + /api/v2/telegraf + InfluxDB Cloud API Service + tags: + - api-v2 + - telegraf + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-telegraf.yaml + - path: api/v2/telegrafs + fields: + name: /api/v2/telegrafs + describes: + - /api/v2/telegrafs + - /api/v2/telegrafs/{telegrafID} + - /api/v2/telegrafs/{telegrafID}/labels + - /api/v2/telegrafs/{telegrafID}/labels/{labelID} + - /api/v2/telegrafs/{telegrafID}/members + - /api/v2/telegrafs/{telegrafID}/members/{userID} + - /api/v2/telegrafs/{telegrafID}/owners + - /api/v2/telegrafs/{telegrafID}/owners/{userID} + title: |- + /api/v2/telegrafs + InfluxDB Cloud API Service + tags: + - api-v2 + - telegrafs + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-telegrafs.yaml + - path: api/v2/templates + fields: + name: /api/v2/templates + describes: + - /api/v2/templates/apply + - /api/v2/templates/export + title: |- + /api/v2/templates + InfluxDB Cloud API Service + tags: + - api-v2 + - templates + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-templates.yaml + - path: api/v2/users + fields: + name: /api/v2/users + describes: + - /api/v2/users + - /api/v2/users/{userID} + - /api/v2/users/{userID}/password + title: |- + /api/v2/users + InfluxDB Cloud API Service + tags: + - api-v2 + - users + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-users.yaml + - path: api/v2/variables + fields: + name: /api/v2/variables + describes: + - /api/v2/variables + - /api/v2/variables/{variableID} + - /api/v2/variables/{variableID}/labels + - /api/v2/variables/{variableID}/labels/{labelID} + title: |- + /api/v2/variables + InfluxDB Cloud API Service + tags: + - api-v2 + - variables + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-variables.yaml + - path: api/v2/write + fields: + name: /api/v2/write + describes: + - /api/v2/write + title: |- + /api/v2/write + InfluxDB Cloud API Service + tags: + - api-v2 + - write + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2-write.yaml + - path: api/v2 + fields: + name: /api/v2 + describes: + - /api/v2 + title: |- + /api/v2 + InfluxDB Cloud API Service + tags: + - api + - v2 + source: static/openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-api-v2.yaml + - path: legacy/authorizations + fields: + name: /legacy/authorizations + describes: + - /legacy/authorizations + - /legacy/authorizations/{authID} + - /legacy/authorizations/{authID}/password + title: |- + /legacy/authorizations + InfluxDB Cloud API Service + tags: + - legacy + - authorizations + source: static/openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-legacy-authorizations.yaml + - path: ping + fields: + name: /ping + describes: + - /ping + title: |- + /ping + InfluxDB Cloud API Service + tags: + - '' + - ping + source: static/openapi/influxdb-cloud-v2/paths/ref-ping.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-ping.yaml + - path: query + fields: + name: /query + describes: + - /query + title: |- + /query + InfluxDB Cloud API Service + tags: + - '' + - query + source: static/openapi/influxdb-cloud-v2/paths/ref-query.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-query.yaml + - path: write + fields: + name: /write + describes: + - /write + title: |- + /write + InfluxDB Cloud API Service + tags: + - '' + - write + source: static/openapi/influxdb-cloud-v2/paths/ref-write.yaml + staticFilePath: /openapi/influxdb-cloud-v2/paths/ref-write.yaml diff --git a/data/article_data/influxdb/clustered/articles.yml b/data/article_data/influxdb/clustered/articles.yml new file mode 100644 index 0000000000..b2d6347131 --- /dev/null +++ b/data/article_data/influxdb/clustered/articles.yml @@ -0,0 +1,47 @@ +articles: + - path: api/databases/{databaseName}/tables + fields: + name: /databases/{databaseName}/tables + describes: + - /databases/{databaseName}/tables + menuName: /databases/{databaseName}/tables + title: /databases/{databaseName}/tables + tags: + - databases-{databaseName} + - tables + apiTags: + - Tables + source: >- + static/openapi/influxdb-clustered/paths/openapi-databases-{databaseName}-tables.yaml + staticFilePath: >- + /openapi/influxdb-clustered/paths/openapi-databases-{databaseName}-tables.yaml + - path: api/databases + fields: + name: /databases + describes: + - /databases + - /databases/{databaseName} + menuName: /databases + title: /databases + tags: + - '' + - databases + apiTags: + - Databases + source: static/openapi/influxdb-clustered/paths/openapi-databases.yaml + staticFilePath: /openapi/influxdb-clustered/paths/openapi-databases.yaml + - path: api/tokens + fields: + name: /tokens + describes: + - /tokens + - /tokens/{tokenId} + menuName: /tokens + title: /tokens + tags: + - '' + - tokens + apiTags: + - Database tokens + source: static/openapi/influxdb-clustered/paths/openapi-tokens.yaml + staticFilePath: /openapi/influxdb-clustered/paths/openapi-tokens.yaml diff --git a/data/article_data/influxdb/influxdb3_core/articles.yml b/data/article_data/influxdb/influxdb3_core/articles.yml new file mode 100644 index 0000000000..75f32fb05c --- /dev/null +++ b/data/article_data/influxdb/influxdb3_core/articles.yml @@ -0,0 +1,1094 @@ +articles: + - path: api/authentication + fields: + name: Authentication + describes: + - /api/v3/configure/token/admin + - /api/v3/configure/token/admin/regenerate + - /api/v3/configure/token + - /api/v3/configure/token/named_admin + title: Authentication + description: > + Depending on your workflow, use one of the following schemes to + authenticate to the InfluxDB 3 API: + + + | Authentication scheme | Works with | + + |:----------------------|:-----------| + + | Bearer authentication | All endpoints | + + | Token authentication | v1, v2 endpoints | + + | Basic authentication | v1 endpoints | + + | Querystring authentication | v1 endpoints | + + + See the **Security Schemes** section below for details on each + authentication method. + tag: Authentication + isConceptual: true + menuGroup: Concepts + operations: + - operationId: PostCreateAdminToken + method: POST + path: /api/v3/configure/token/admin + summary: Create admin token + tags: + - Authentication + - Token + - operationId: PostRegenerateAdminToken + method: POST + path: /api/v3/configure/token/admin/regenerate + summary: Regenerate admin token + tags: + - Authentication + - Token + - operationId: DeleteToken + method: DELETE + path: /api/v3/configure/token + summary: Delete token + tags: + - Authentication + - Token + - operationId: PostCreateNamedAdminToken + method: POST + path: /api/v3/configure/token/named_admin + summary: Create named admin token + tags: + - Authentication + - Token + tagDescription: > + Depending on your workflow, use one of the following schemes to + authenticate to the InfluxDB 3 API: + + + | Authentication scheme | Works with | + + |:----------------------|:-----------| + + | Bearer authentication | All endpoints | + + | Token authentication | v1, v2 endpoints | + + | Basic authentication | v1 endpoints | + + | Querystring authentication | v1 endpoints | + + + See the **Security Schemes** section below for details on each + authentication method. + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-authentication.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-authentication.yaml + - path: api/cache-data + fields: + name: Cache data + describes: + - /api/v3/configure/distinct_cache + - /api/v3/configure/last_cache + title: Cache data + 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. + + + #### Related guides + + + - [Manage the Distinct Value + Cache](/influxdb3/core/admin/distinct-value-cache/) + + - [Manage the Last Value Cache](/influxdb3/core/admin/last-value-cache/) + tag: Cache data + isConceptual: false + menuGroup: Data Operations + operations: + - operationId: PostConfigureDistinctCache + method: POST + path: /api/v3/configure/distinct_cache + summary: Create distinct cache + tags: + - Cache data + - Table + - operationId: DeleteConfigureDistinctCache + method: DELETE + path: /api/v3/configure/distinct_cache + summary: Delete distinct cache + tags: + - Cache data + - Table + - operationId: PostConfigureLastCache + method: POST + path: /api/v3/configure/last_cache + summary: Create last cache + tags: + - Cache data + - Table + - operationId: DeleteConfigureLastCache + method: DELETE + path: /api/v3/configure/last_cache + summary: Delete last cache + tags: + - Cache data + - Table + tagDescription: > + 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. + + + #### Related guides + + + - [Manage the Distinct Value + Cache](/influxdb3/core/admin/distinct-value-cache/) + + - [Manage the Last Value Cache](/influxdb3/core/admin/last-value-cache/) + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-cache-data.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-cache-data.yaml + - path: api/compatibility-endpoints + fields: + name: Compatibility endpoints + describes: + - /write + - /api/v2/write + - /query + - /api/v1/health + title: Compatibility endpoints + description: > + InfluxDB 3 provides compatibility endpoints for InfluxDB 1.x and + InfluxDB 2.x workloads and clients. + + + ### Write data using v1- or v2-compatible endpoints + + + - [`/api/v2/write` endpoint](#operation/PostV2Write) + for InfluxDB v2 clients and when you bring existing InfluxDB v2 write workloads to InfluxDB 3. + - [`/write` endpoint](#operation/PostV1Write) for InfluxDB v1 clients + and when you bring existing InfluxDB v1 write workloads to InfluxDB 3. + + + For new workloads, use the [`/api/v3/write_lp` + endpoint](#operation/PostWriteLP). + + + All endpoints accept the same line protocol format. + + + ### Query data + + + Use the HTTP [`/query`](#operation/GetV1ExecuteQuery) endpoint for + InfluxDB v1 clients and v1 query workloads using InfluxQL. + + + For new workloads, use one of the following: + + + - HTTP [`/api/v3/query_sql` endpoint](#operation/GetExecuteQuerySQL) for + new query workloads using SQL. + + - HTTP [`/api/v3/query_influxql` + endpoint](#operation/GetExecuteInfluxQLQuery) for new query workloads + using InfluxQL. + + - Flight SQL and InfluxDB 3 _Flight+gRPC_ APIs for querying with SQL or + InfluxQL. For more information about using Flight APIs, see [InfluxDB 3 + client + libraries](https://github.com/InfluxCommunity?q=influxdb3&type=public&language=&sort=). + + + ### Server information + + + Server information endpoints such as `/health` and `metrics` are + compatible with InfluxDB 1.x and InfluxDB 2.x clients. + tag: Compatibility endpoints + isConceptual: false + menuGroup: Compatibility + operations: + - operationId: PostV1Write + method: POST + path: /write + summary: Write line protocol (v1-compatible) + tags: + - Compatibility endpoints + - Write data + - operationId: PostV2Write + method: POST + path: /api/v2/write + summary: Write line protocol (v2-compatible) + tags: + - Compatibility endpoints + - Write data + - operationId: GetV1ExecuteQuery + method: GET + path: /query + summary: Execute InfluxQL query (v1-compatible) + tags: + - Query data + - Compatibility endpoints + - operationId: PostExecuteV1Query + method: POST + path: /query + summary: Execute InfluxQL query (v1-compatible) + tags: + - Query data + - Compatibility endpoints + - operationId: GetHealthV1 + method: GET + path: /api/v1/health + summary: Health check (v1) + tags: + - Server information + - Compatibility endpoints + tagDescription: > + InfluxDB 3 provides compatibility endpoints for InfluxDB 1.x and + InfluxDB 2.x workloads and clients. + + + ### Write data using v1- or v2-compatible endpoints + + + - [`/api/v2/write` endpoint](#operation/PostV2Write) + for InfluxDB v2 clients and when you bring existing InfluxDB v2 write workloads to InfluxDB 3. + - [`/write` endpoint](#operation/PostV1Write) for InfluxDB v1 clients + and when you bring existing InfluxDB v1 write workloads to InfluxDB 3. + + + For new workloads, use the [`/api/v3/write_lp` + endpoint](#operation/PostWriteLP). + + + All endpoints accept the same line protocol format. + + + ### Query data + + + Use the HTTP [`/query`](#operation/GetV1ExecuteQuery) endpoint for + InfluxDB v1 clients and v1 query workloads using InfluxQL. + + + For new workloads, use one of the following: + + + - HTTP [`/api/v3/query_sql` endpoint](#operation/GetExecuteQuerySQL) for + new query workloads using SQL. + + - HTTP [`/api/v3/query_influxql` + endpoint](#operation/GetExecuteInfluxQLQuery) for new query workloads + using InfluxQL. + + - Flight SQL and InfluxDB 3 _Flight+gRPC_ APIs for querying with SQL or + InfluxQL. For more information about using Flight APIs, see [InfluxDB 3 + client + libraries](https://github.com/InfluxCommunity?q=influxdb3&type=public&language=&sort=). + + + ### Server information + + + Server information endpoints such as `/health` and `metrics` are + compatible with InfluxDB 1.x and InfluxDB 2.x clients. + source: >- + static/openapi/influxdb-influxdb3-core/tags/tags/ref-compatibility-endpoints.yaml + staticFilePath: >- + /openapi/influxdb-influxdb3-core/tags/tags/ref-compatibility-endpoints.yaml + - path: api/database + fields: + name: Database + describes: + - /api/v3/configure/database + - /api/v3/configure/database/retention_period + title: Database + description: Manage databases + tag: Database + isConceptual: false + menuGroup: Administration + operations: + - operationId: GetConfigureDatabase + method: GET + path: /api/v3/configure/database + summary: List databases + tags: + - Database + - operationId: PostConfigureDatabase + method: POST + path: /api/v3/configure/database + summary: Create a database + tags: + - Database + - operationId: DeleteConfigureDatabase + method: DELETE + path: /api/v3/configure/database + summary: Delete a database + tags: + - Database + - operationId: DeleteDatabaseRetentionPeriod + method: DELETE + path: /api/v3/configure/database/retention_period + summary: Remove database retention period + tags: + - Database + tagDescription: Manage databases + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-database.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-database.yaml + - path: api/headers-and-parameters + fields: + name: Headers and parameters + describes: [] + title: Headers and parameters + 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. | + tag: Headers and parameters + isConceptual: true + menuGroup: Concepts + operations: [] + tagDescription: > + 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. | + source: >- + static/openapi/influxdb-influxdb3-core/tags/tags/ref-headers-and-parameters.yaml + staticFilePath: >- + /openapi/influxdb-influxdb3-core/tags/tags/ref-headers-and-parameters.yaml + - path: api/processing-engine + fields: + name: Processing engine + describes: + - /api/v3/configure/processing_engine_trigger + - /api/v3/configure/processing_engine_trigger/disable + - /api/v3/configure/processing_engine_trigger/enable + - /api/v3/configure/plugin_environment/install_packages + - /api/v3/configure/plugin_environment/install_requirements + - /api/v3/plugin_test/wal + - /api/v3/plugin_test/schedule + - /api/v3/engine/{request_path} + - /api/v3/plugins/files + - /api/v3/plugins/directory + title: Processing engine + 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. + + + To get started with the processing engine, see the [Processing engine + and Python plugins](/influxdb3/core/processing-engine/) guide. + tag: Processing engine + isConceptual: false + menuGroup: Processing Engine + operations: + - operationId: PostConfigureProcessingEngineTrigger + method: POST + path: /api/v3/configure/processing_engine_trigger + summary: Create processing engine trigger + tags: + - Processing engine + - operationId: DeleteConfigureProcessingEngineTrigger + method: DELETE + path: /api/v3/configure/processing_engine_trigger + summary: Delete processing engine trigger + tags: + - Processing engine + - operationId: PostDisableProcessingEngineTrigger + method: POST + path: /api/v3/configure/processing_engine_trigger/disable + summary: Disable processing engine trigger + tags: + - Processing engine + - operationId: PostEnableProcessingEngineTrigger + method: POST + path: /api/v3/configure/processing_engine_trigger/enable + summary: Enable processing engine trigger + tags: + - Processing engine + - operationId: PostInstallPluginPackages + method: POST + path: /api/v3/configure/plugin_environment/install_packages + summary: Install plugin packages + tags: + - Processing engine + - operationId: PostInstallPluginRequirements + method: POST + path: /api/v3/configure/plugin_environment/install_requirements + summary: Install plugin requirements + tags: + - Processing engine + - operationId: PostTestWALPlugin + method: POST + path: /api/v3/plugin_test/wal + summary: Test WAL plugin + tags: + - Processing engine + - operationId: PostTestSchedulingPlugin + method: POST + path: /api/v3/plugin_test/schedule + summary: Test scheduling plugin + tags: + - Processing engine + - operationId: GetProcessingEnginePluginRequest + method: GET + path: /api/v3/engine/{request_path} + summary: On Request processing engine plugin request + tags: + - Processing engine + - operationId: PostProcessingEnginePluginRequest + method: POST + path: /api/v3/engine/{request_path} + summary: On Request processing engine plugin request + tags: + - Processing engine + - operationId: PutPluginFile + method: PUT + path: /api/v3/plugins/files + summary: Update plugin file + tags: + - Processing engine + - operationId: PutPluginDirectory + method: PUT + path: /api/v3/plugins/directory + summary: Update plugin directory + tags: + - Processing engine + tagDescription: > + 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. + + + To get started with the processing engine, see the [Processing engine + and Python plugins](/influxdb3/core/processing-engine/) guide. + source: >- + static/openapi/influxdb-influxdb3-core/tags/tags/ref-processing-engine.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-processing-engine.yaml + - path: api/query-data + fields: + name: Query data + describes: + - /api/v3/query_sql + - /api/v3/query_influxql + - /query + title: Query data + description: Query data using SQL or InfluxQL + tag: Query data + isConceptual: false + menuGroup: Data Operations + operations: + - operationId: GetExecuteQuerySQL + method: GET + path: /api/v3/query_sql + summary: Execute SQL query + tags: + - Query data + - operationId: PostExecuteQuerySQL + method: POST + path: /api/v3/query_sql + summary: Execute SQL query + tags: + - Query data + - operationId: GetExecuteInfluxQLQuery + method: GET + path: /api/v3/query_influxql + summary: Execute InfluxQL query + tags: + - Query data + - operationId: PostExecuteQueryInfluxQL + method: POST + path: /api/v3/query_influxql + summary: Execute InfluxQL query + tags: + - Query data + - operationId: GetV1ExecuteQuery + method: GET + path: /query + summary: Execute InfluxQL query (v1-compatible) + tags: + - Query data + - Compatibility endpoints + - operationId: PostExecuteV1Query + method: POST + path: /query + summary: Execute InfluxQL query (v1-compatible) + tags: + - Query data + - Compatibility endpoints + tagDescription: Query data using SQL or InfluxQL + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-query-data.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-query-data.yaml + - path: api/quick-start + fields: + name: Quick start + describes: [] + title: Quick start + description: > + 1. [Create an admin token](#section/Authentication) to authorize API + requests. + + ```bash + curl -X POST "http://localhost:8181/api/v3/configure/token/admin" + ``` + 2. [Check the status](#section/Server-information) of the InfluxDB + server. + + ```bash + curl "http://localhost:8181/health" \ + --header "Authorization: Bearer ADMIN_TOKEN" + ``` + + 3. [Write data](#operation/PostWriteLP) 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](#operation/GetExecuteQuerySQL) 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. + tag: Quick start + isConceptual: true + menuGroup: Concepts + operations: [] + tagDescription: > + 1. [Create an admin token](#section/Authentication) to authorize API + requests. + + ```bash + curl -X POST "http://localhost:8181/api/v3/configure/token/admin" + ``` + 2. [Check the status](#section/Server-information) of the InfluxDB + server. + + ```bash + curl "http://localhost:8181/health" \ + --header "Authorization: Bearer ADMIN_TOKEN" + ``` + + 3. [Write data](#operation/PostWriteLP) 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](#operation/GetExecuteQuerySQL) 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. + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-quick-start.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-quick-start.yaml + - path: api/server-information + fields: + name: Server information + describes: + - /health + - /api/v1/health + - /ping + - /metrics + title: Server information + description: Retrieve server metrics, status, and version information + tag: Server information + isConceptual: false + menuGroup: Server + operations: + - operationId: GetHealth + method: GET + path: /health + summary: Health check + tags: + - Server information + - operationId: GetHealthV1 + method: GET + path: /api/v1/health + summary: Health check (v1) + tags: + - Server information + - Compatibility endpoints + - operationId: GetPing + method: GET + path: /ping + summary: Ping the server + tags: + - Server information + - operationId: GetMetrics + method: GET + path: /metrics + summary: Metrics + tags: + - Server information + tagDescription: Retrieve server metrics, status, and version information + source: >- + static/openapi/influxdb-influxdb3-core/tags/tags/ref-server-information.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-server-information.yaml + - path: api/table + fields: + name: Table + describes: + - /api/v3/configure/table + - /api/v3/configure/distinct_cache + - /api/v3/configure/last_cache + title: Table + description: Manage table schemas and data + tag: Table + isConceptual: false + menuGroup: Administration + operations: + - operationId: PostConfigureTable + method: POST + path: /api/v3/configure/table + summary: Create a table + tags: + - Table + - operationId: DeleteConfigureTable + method: DELETE + path: /api/v3/configure/table + summary: Delete a table + tags: + - Table + - operationId: PostConfigureDistinctCache + method: POST + path: /api/v3/configure/distinct_cache + summary: Create distinct cache + tags: + - Cache data + - Table + - operationId: DeleteConfigureDistinctCache + method: DELETE + path: /api/v3/configure/distinct_cache + summary: Delete distinct cache + tags: + - Cache data + - Table + - operationId: PostConfigureLastCache + method: POST + path: /api/v3/configure/last_cache + summary: Create last cache + tags: + - Cache data + - Table + - operationId: DeleteConfigureLastCache + method: DELETE + path: /api/v3/configure/last_cache + summary: Delete last cache + tags: + - Cache data + - Table + tagDescription: Manage table schemas and data + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-table.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-table.yaml + - path: api/token + fields: + name: Token + describes: + - /api/v3/configure/token/admin + - /api/v3/configure/token/admin/regenerate + - /api/v3/configure/token + - /api/v3/configure/token/named_admin + title: Token + description: Manage tokens for authentication and authorization + tag: Token + isConceptual: false + menuGroup: Administration + operations: + - operationId: PostCreateAdminToken + method: POST + path: /api/v3/configure/token/admin + summary: Create admin token + tags: + - Authentication + - Token + - operationId: PostRegenerateAdminToken + method: POST + path: /api/v3/configure/token/admin/regenerate + summary: Regenerate admin token + tags: + - Authentication + - Token + - operationId: DeleteToken + method: DELETE + path: /api/v3/configure/token + summary: Delete token + tags: + - Authentication + - Token + - operationId: PostCreateNamedAdminToken + method: POST + path: /api/v3/configure/token/named_admin + summary: Create named admin token + tags: + - Authentication + - Token + tagDescription: Manage tokens for authentication and authorization + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-token.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-token.yaml + - path: api/write-data + fields: + name: Write data + describes: + - /write + - /api/v2/write + - /api/v3/write_lp + title: Write data + description: > + Write data to InfluxDB 3 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. + tag: Write data + isConceptual: false + menuGroup: Data Operations + operations: + - operationId: PostV1Write + method: POST + path: /write + summary: Write line protocol (v1-compatible) + tags: + - Compatibility endpoints + - Write data + - operationId: PostV2Write + method: POST + path: /api/v2/write + summary: Write line protocol (v2-compatible) + tags: + - Compatibility endpoints + - Write data + - operationId: PostWriteLP + method: POST + path: /api/v3/write_lp + summary: Write line protocol + tags: + - Write data + tagDescription: > + Write data to InfluxDB 3 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. + source: static/openapi/influxdb-influxdb3-core/tags/tags/ref-write-data.yaml + staticFilePath: /openapi/influxdb-influxdb3-core/tags/tags/ref-write-data.yaml diff --git a/data/article_data/influxdb/influxdb3_enterprise/articles.yml b/data/article_data/influxdb/influxdb3_enterprise/articles.yml new file mode 100644 index 0000000000..1a31bf3533 --- /dev/null +++ b/data/article_data/influxdb/influxdb3_enterprise/articles.yml @@ -0,0 +1,246 @@ +articles: + - path: api/v1/health + fields: + name: /api/v1/health + describes: + - /api/v1/health + menuName: /api/v1/health + title: /api/v1/health + tags: + - api-v1 + - health + apiTags: + - Server information + - Compatibility endpoints + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v1-health.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v1-health.yaml + - path: api/v2/write + fields: + name: /api/v2/write + describes: + - /api/v2/write + menuName: /api/v2/write + title: /api/v2/write + tags: + - api-v2 + - write + apiTags: + - Compatibility endpoints + - Write data + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v2-write.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v2-write.yaml + - path: api/v3/configure + fields: + name: /api/v3/configure + describes: + - /api/v3/configure/database + - /api/v3/configure/database/retention_period + - /api/v3/configure/database/{db} + - /api/v3/configure/distinct_cache + - /api/v3/configure/enterprise/token + - /api/v3/configure/last_cache + - /api/v3/configure/plugin_environment/install_packages + - /api/v3/configure/plugin_environment/install_requirements + - /api/v3/configure/processing_engine_trigger + - /api/v3/configure/processing_engine_trigger/disable + - /api/v3/configure/processing_engine_trigger/enable + - /api/v3/configure/table + - /api/v3/configure/token + - /api/v3/configure/token/admin + - /api/v3/configure/token/admin/regenerate + - /api/v3/configure/token/named_admin + menuName: /api/v3/configure + title: /api/v3/configure + tags: + - api-v3 + - configure + apiTags: + - Database + - Cache data + - Table + - Authentication + - Token + - Processing engine + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-configure.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-configure.yaml + - path: api/v3/engine + fields: + name: /api/v3/engine + describes: + - /api/v3/engine/{request_path} + menuName: /api/v3/engine + title: /api/v3/engine + tags: + - api-v3 + - engine + apiTags: + - Processing engine + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-engine.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-engine.yaml + - path: api/v3/plugin_test + fields: + name: /api/v3/plugin_test + describes: + - /api/v3/plugin_test/schedule + - /api/v3/plugin_test/wal + menuName: /api/v3/plugin_test + title: /api/v3/plugin_test + tags: + - api-v3 + - plugin_test + apiTags: + - Processing engine + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-plugin_test.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-plugin_test.yaml + - path: api/v3/plugins + fields: + name: /api/v3/plugins + describes: + - /api/v3/plugins/directory + - /api/v3/plugins/files + menuName: /api/v3/plugins + title: /api/v3/plugins + tags: + - api-v3 + - plugins + apiTags: + - Processing engine + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-plugins.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-plugins.yaml + - path: api/v3/query_influxql + fields: + name: /api/v3/query_influxql + describes: + - /api/v3/query_influxql + menuName: /api/v3/query_influxql + title: /api/v3/query_influxql + tags: + - api-v3 + - query_influxql + apiTags: + - Query data + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-query_influxql.yaml + staticFilePath: >- + /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-query_influxql.yaml + - path: api/v3/query_sql + fields: + name: /api/v3/query_sql + describes: + - /api/v3/query_sql + menuName: /api/v3/query_sql + title: /api/v3/query_sql + tags: + - api-v3 + - query_sql + apiTags: + - Query data + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-query_sql.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-query_sql.yaml + - path: api/v3/show + fields: + name: /api/v3/show + describes: + - /api/v3/show/license + menuName: /api/v3/show + title: /api/v3/show + tags: + - api-v3 + - show + apiTags: + - Server information + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-show.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-show.yaml + - path: api/v3/write_lp + fields: + name: /api/v3/write_lp + describes: + - /api/v3/write_lp + menuName: /api/v3/write_lp + title: /api/v3/write_lp + tags: + - api-v3 + - write_lp + apiTags: + - Write data + source: >- + static/openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-write_lp.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-api-v3-write_lp.yaml + - path: api/health + fields: + name: /health + describes: + - /health + menuName: /health + title: /health + tags: + - '' + - health + apiTags: + - Server information + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-health.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-health.yaml + - path: api/metrics + fields: + name: /metrics + describes: + - /metrics + menuName: /metrics + title: /metrics + tags: + - '' + - metrics + apiTags: + - Server information + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-metrics.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-metrics.yaml + - path: api/ping + fields: + name: /ping + describes: + - /ping + menuName: /ping + title: /ping + tags: + - '' + - ping + apiTags: + - Server information + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-ping.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-ping.yaml + - path: api/query + fields: + name: /query + describes: + - /query + menuName: /query + title: /query + tags: + - '' + - query + apiTags: + - Query data + - Compatibility endpoints + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-query.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-query.yaml + - path: api/write + fields: + name: /write + describes: + - /write + menuName: /write + title: /write + tags: + - '' + - write + apiTags: + - Compatibility endpoints + - Write data + source: static/openapi/influxdb-influxdb3-enterprise/paths/ref-write.yaml + staticFilePath: /openapi/influxdb-influxdb3-enterprise/paths/ref-write.yaml diff --git a/data/article_data/influxdb/oss-v2/articles.yml b/data/article_data/influxdb/oss-v2/articles.yml new file mode 100644 index 0000000000..aa37b5a614 --- /dev/null +++ b/data/article_data/influxdb/oss-v2/articles.yml @@ -0,0 +1,757 @@ +articles: + - path: api/v2/authorizations + fields: + name: /api/v2/authorizations + describes: + - /api/v2/authorizations + - /api/v2/authorizations/{authID} + title: |- + /api/v2/authorizations + InfluxDB OSS API Service + tags: + - api-v2 + - authorizations + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-authorizations.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-authorizations.yaml + - path: api/v2/backup + fields: + name: /api/v2/backup + describes: + - /api/v2/backup/kv + - /api/v2/backup/metadata + - /api/v2/backup/shards/{shardID} + title: |- + /api/v2/backup + InfluxDB OSS API Service + tags: + - api-v2 + - backup + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-backup.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-backup.yaml + - path: api/v2/buckets + fields: + name: /api/v2/buckets + describes: + - /api/v2/buckets + - /api/v2/buckets/{bucketID} + - /api/v2/buckets/{bucketID}/labels + - /api/v2/buckets/{bucketID}/labels/{labelID} + - /api/v2/buckets/{bucketID}/members + - /api/v2/buckets/{bucketID}/members/{userID} + - /api/v2/buckets/{bucketID}/owners + - /api/v2/buckets/{bucketID}/owners/{userID} + title: |- + /api/v2/buckets + InfluxDB OSS API Service + tags: + - api-v2 + - buckets + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-buckets.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-buckets.yaml + - path: api/v2/checks + fields: + name: /api/v2/checks + describes: + - /api/v2/checks + - /api/v2/checks/{checkID} + - /api/v2/checks/{checkID}/labels + - /api/v2/checks/{checkID}/labels/{labelID} + - /api/v2/checks/{checkID}/query + title: |- + /api/v2/checks + InfluxDB OSS API Service + tags: + - api-v2 + - checks + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-checks.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-checks.yaml + - path: api/v2/config + fields: + name: /api/v2/config + describes: + - /api/v2/config + title: |- + /api/v2/config + InfluxDB OSS API Service + tags: + - api-v2 + - config + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-config.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-config.yaml + - path: api/v2/dashboards + fields: + name: /api/v2/dashboards + describes: + - /api/v2/dashboards + - /api/v2/dashboards/{dashboardID} + - /api/v2/dashboards/{dashboardID}/cells + - /api/v2/dashboards/{dashboardID}/cells/{cellID} + - /api/v2/dashboards/{dashboardID}/cells/{cellID}/view + - /api/v2/dashboards/{dashboardID}/labels + - /api/v2/dashboards/{dashboardID}/labels/{labelID} + - /api/v2/dashboards/{dashboardID}/members + - /api/v2/dashboards/{dashboardID}/members/{userID} + - /api/v2/dashboards/{dashboardID}/owners + - /api/v2/dashboards/{dashboardID}/owners/{userID} + title: |- + /api/v2/dashboards + InfluxDB OSS API Service + tags: + - api-v2 + - dashboards + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-dashboards.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-dashboards.yaml + - path: api/v2/dbrps + fields: + name: /api/v2/dbrps + describes: + - /api/v2/dbrps + - /api/v2/dbrps/{dbrpID} + title: |- + /api/v2/dbrps + InfluxDB OSS API Service + tags: + - api-v2 + - dbrps + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-dbrps.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-dbrps.yaml + - path: api/v2/delete + fields: + name: /api/v2/delete + describes: + - /api/v2/delete + title: |- + /api/v2/delete + InfluxDB OSS API Service + tags: + - api-v2 + - delete + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-delete.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-delete.yaml + - path: api/v2/flags + fields: + name: /api/v2/flags + describes: + - /api/v2/flags + title: |- + /api/v2/flags + InfluxDB OSS API Service + tags: + - api-v2 + - flags + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-flags.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-flags.yaml + - path: api/v2/labels + fields: + name: /api/v2/labels + describes: + - /api/v2/labels + - /api/v2/labels/{labelID} + title: |- + /api/v2/labels + InfluxDB OSS API Service + tags: + - api-v2 + - labels + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-labels.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-labels.yaml + - path: api/v2/maps + fields: + name: /api/v2/maps + describes: + - /api/v2/maps/mapToken + title: |- + /api/v2/maps + InfluxDB OSS API Service + tags: + - api-v2 + - maps + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-maps.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-maps.yaml + - path: api/v2/me + fields: + name: /api/v2/me + describes: + - /api/v2/me + - /api/v2/me/password + title: |- + /api/v2/me + InfluxDB OSS API Service + tags: + - api-v2 + - me + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-me.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-me.yaml + - path: api/v2/notificationEndpoints + fields: + name: /api/v2/notificationEndpoints + describes: + - /api/v2/notificationEndpoints + - /api/v2/notificationEndpoints/{endpointID} + - /api/v2/notificationEndpoints/{endpointID}/labels + - /api/v2/notificationEndpoints/{endpointID}/labels/{labelID} + title: |- + /api/v2/notificationEndpoints + InfluxDB OSS API Service + tags: + - api-v2 + - notificationEndpoints + source: >- + static/openapi/influxdb-oss-v2/paths/ref-api-v2-notificationEndpoints.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-notificationEndpoints.yaml + - path: api/v2/notificationRules + fields: + name: /api/v2/notificationRules + describes: + - /api/v2/notificationRules + - /api/v2/notificationRules/{ruleID} + - /api/v2/notificationRules/{ruleID}/labels + - /api/v2/notificationRules/{ruleID}/labels/{labelID} + - /api/v2/notificationRules/{ruleID}/query + title: |- + /api/v2/notificationRules + InfluxDB OSS API Service + tags: + - api-v2 + - notificationRules + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-notificationRules.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-notificationRules.yaml + - path: api/v2/orgs + fields: + name: /api/v2/orgs + describes: + - /api/v2/orgs + - /api/v2/orgs/{orgID} + - /api/v2/orgs/{orgID}/members + - /api/v2/orgs/{orgID}/members/{userID} + - /api/v2/orgs/{orgID}/owners + - /api/v2/orgs/{orgID}/owners/{userID} + - /api/v2/orgs/{orgID}/secrets + - /api/v2/orgs/{orgID}/secrets/delete + - /api/v2/orgs/{orgID}/secrets/{secretID} + title: |- + /api/v2/orgs + InfluxDB OSS API Service + tags: + - api-v2 + - orgs + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-orgs.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-orgs.yaml + - path: api/v2/query + fields: + name: /api/v2/query + describes: + - /api/v2/query + - /api/v2/query/analyze + - /api/v2/query/ast + - /api/v2/query/suggestions + - /api/v2/query/suggestions/{name} + title: |- + /api/v2/query + InfluxDB OSS API Service + tags: + - api-v2 + - query + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-query.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-query.yaml + - path: api/v2/remotes + fields: + name: /api/v2/remotes + describes: + - /api/v2/remotes + - /api/v2/remotes/{remoteID} + title: |- + /api/v2/remotes + InfluxDB OSS API Service + tags: + - api-v2 + - remotes + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-remotes.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-remotes.yaml + - path: api/v2/replications + fields: + name: /api/v2/replications + describes: + - /api/v2/replications + - /api/v2/replications/{replicationID} + - /api/v2/replications/{replicationID}/validate + title: |- + /api/v2/replications + InfluxDB OSS API Service + tags: + - api-v2 + - replications + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-replications.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-replications.yaml + - path: api/v2/resources + fields: + name: /api/v2/resources + describes: + - /api/v2/resources + title: |- + /api/v2/resources + InfluxDB OSS API Service + tags: + - api-v2 + - resources + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-resources.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-resources.yaml + - path: api/v2/restore + fields: + name: /api/v2/restore + describes: + - /api/v2/restore/bucket/{bucketID} + - /api/v2/restore/bucketMetadata + - /api/v2/restore/kv + - /api/v2/restore/shards/{shardID} + - /api/v2/restore/sql + title: |- + /api/v2/restore + InfluxDB OSS API Service + tags: + - api-v2 + - restore + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-restore.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-restore.yaml + - path: api/v2/scrapers + fields: + name: /api/v2/scrapers + describes: + - /api/v2/scrapers + - /api/v2/scrapers/{scraperTargetID} + - /api/v2/scrapers/{scraperTargetID}/labels + - /api/v2/scrapers/{scraperTargetID}/labels/{labelID} + - /api/v2/scrapers/{scraperTargetID}/members + - /api/v2/scrapers/{scraperTargetID}/members/{userID} + - /api/v2/scrapers/{scraperTargetID}/owners + - /api/v2/scrapers/{scraperTargetID}/owners/{userID} + title: |- + /api/v2/scrapers + InfluxDB OSS API Service + tags: + - api-v2 + - scrapers + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-scrapers.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-scrapers.yaml + - path: api/v2/setup + fields: + name: /api/v2/setup + describes: + - /api/v2/setup + title: |- + /api/v2/setup + InfluxDB OSS API Service + tags: + - api-v2 + - setup + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-setup.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-setup.yaml + - path: api/v2/signin + fields: + name: /api/v2/signin + describes: + - /api/v2/signin + title: |- + /api/v2/signin + InfluxDB OSS API Service + tags: + - api-v2 + - signin + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-signin.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-signin.yaml + - path: api/v2/signout + fields: + name: /api/v2/signout + describes: + - /api/v2/signout + title: |- + /api/v2/signout + InfluxDB OSS API Service + tags: + - api-v2 + - signout + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-signout.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-signout.yaml + - path: api/v2/sources + fields: + name: /api/v2/sources + describes: + - /api/v2/sources + - /api/v2/sources/{sourceID} + - /api/v2/sources/{sourceID}/buckets + - /api/v2/sources/{sourceID}/health + title: |- + /api/v2/sources + InfluxDB OSS API Service + tags: + - api-v2 + - sources + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-sources.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-sources.yaml + - path: api/v2/stacks + fields: + name: /api/v2/stacks + describes: + - /api/v2/stacks + - /api/v2/stacks/{stack_id} + - /api/v2/stacks/{stack_id}/uninstall + title: |- + /api/v2/stacks + InfluxDB OSS API Service + tags: + - api-v2 + - stacks + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-stacks.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-stacks.yaml + - path: api/v2/tasks + fields: + name: /api/v2/tasks + describes: + - /api/v2/tasks + - /api/v2/tasks/{taskID} + - /api/v2/tasks/{taskID}/labels + - /api/v2/tasks/{taskID}/labels/{labelID} + - /api/v2/tasks/{taskID}/logs + - /api/v2/tasks/{taskID}/members + - /api/v2/tasks/{taskID}/members/{userID} + - /api/v2/tasks/{taskID}/owners + - /api/v2/tasks/{taskID}/owners/{userID} + - /api/v2/tasks/{taskID}/runs + - /api/v2/tasks/{taskID}/runs/{runID} + - /api/v2/tasks/{taskID}/runs/{runID}/logs + - /api/v2/tasks/{taskID}/runs/{runID}/retry + title: |- + /api/v2/tasks + InfluxDB OSS API Service + tags: + - api-v2 + - tasks + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-tasks.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-tasks.yaml + - path: api/v2/telegraf + fields: + name: /api/v2/telegraf + describes: + - /api/v2/telegraf/plugins + title: |- + /api/v2/telegraf + InfluxDB OSS API Service + tags: + - api-v2 + - telegraf + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-telegraf.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-telegraf.yaml + - path: api/v2/telegrafs + fields: + name: /api/v2/telegrafs + describes: + - /api/v2/telegrafs + - /api/v2/telegrafs/{telegrafID} + - /api/v2/telegrafs/{telegrafID}/labels + - /api/v2/telegrafs/{telegrafID}/labels/{labelID} + - /api/v2/telegrafs/{telegrafID}/members + - /api/v2/telegrafs/{telegrafID}/members/{userID} + - /api/v2/telegrafs/{telegrafID}/owners + - /api/v2/telegrafs/{telegrafID}/owners/{userID} + title: |- + /api/v2/telegrafs + InfluxDB OSS API Service + tags: + - api-v2 + - telegrafs + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-telegrafs.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-telegrafs.yaml + - path: api/v2/templates + fields: + name: /api/v2/templates + describes: + - /api/v2/templates/apply + - /api/v2/templates/export + title: |- + /api/v2/templates + InfluxDB OSS API Service + tags: + - api-v2 + - templates + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-templates.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-templates.yaml + - path: api/v2/users + fields: + name: /api/v2/users + describes: + - /api/v2/users + - /api/v2/users/{userID} + - /api/v2/users/{userID}/password + title: |- + /api/v2/users + InfluxDB OSS API Service + tags: + - api-v2 + - users + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-users.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-users.yaml + - path: api/v2/variables + fields: + name: /api/v2/variables + describes: + - /api/v2/variables + - /api/v2/variables/{variableID} + - /api/v2/variables/{variableID}/labels + - /api/v2/variables/{variableID}/labels/{labelID} + title: |- + /api/v2/variables + InfluxDB OSS API Service + tags: + - api-v2 + - variables + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-variables.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-variables.yaml + - path: api/v2/write + fields: + name: /api/v2/write + describes: + - /api/v2/write + title: |- + /api/v2/write + InfluxDB OSS API Service + tags: + - api-v2 + - write + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2-write.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2-write.yaml + - path: api/v2 + fields: + name: /api/v2 + describes: + - /api/v2 + title: |- + /api/v2 + InfluxDB OSS API Service + tags: + - api + - v2 + source: static/openapi/influxdb-oss-v2/paths/ref-api-v2.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-api-v2.yaml + - path: debug/pprof/all + fields: + name: /debug/pprof/all + describes: + - /debug/pprof/all + title: |- + /debug/pprof/all + InfluxDB OSS API Service + tags: + - debug-pprof + - all + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-all.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-all.yaml + - path: debug/pprof/allocs + fields: + name: /debug/pprof/allocs + describes: + - /debug/pprof/allocs + title: |- + /debug/pprof/allocs + InfluxDB OSS API Service + tags: + - debug-pprof + - allocs + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-allocs.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-allocs.yaml + - path: debug/pprof/block + fields: + name: /debug/pprof/block + describes: + - /debug/pprof/block + title: |- + /debug/pprof/block + InfluxDB OSS API Service + tags: + - debug-pprof + - block + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-block.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-block.yaml + - path: debug/pprof/cmdline + fields: + name: /debug/pprof/cmdline + describes: + - /debug/pprof/cmdline + title: |- + /debug/pprof/cmdline + InfluxDB OSS API Service + tags: + - debug-pprof + - cmdline + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-cmdline.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-cmdline.yaml + - path: debug/pprof/goroutine + fields: + name: /debug/pprof/goroutine + describes: + - /debug/pprof/goroutine + title: |- + /debug/pprof/goroutine + InfluxDB OSS API Service + tags: + - debug-pprof + - goroutine + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-goroutine.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-goroutine.yaml + - path: debug/pprof/heap + fields: + name: /debug/pprof/heap + describes: + - /debug/pprof/heap + title: |- + /debug/pprof/heap + InfluxDB OSS API Service + tags: + - debug-pprof + - heap + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-heap.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-heap.yaml + - path: debug/pprof/mutex + fields: + name: /debug/pprof/mutex + describes: + - /debug/pprof/mutex + title: |- + /debug/pprof/mutex + InfluxDB OSS API Service + tags: + - debug-pprof + - mutex + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-mutex.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-mutex.yaml + - path: debug/pprof/profile + fields: + name: /debug/pprof/profile + describes: + - /debug/pprof/profile + title: |- + /debug/pprof/profile + InfluxDB OSS API Service + tags: + - debug-pprof + - profile + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-profile.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-profile.yaml + - path: debug/pprof/threadcreate + fields: + name: /debug/pprof/threadcreate + describes: + - /debug/pprof/threadcreate + title: |- + /debug/pprof/threadcreate + InfluxDB OSS API Service + tags: + - debug-pprof + - threadcreate + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-threadcreate.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-threadcreate.yaml + - path: debug/pprof/trace + fields: + name: /debug/pprof/trace + describes: + - /debug/pprof/trace + title: |- + /debug/pprof/trace + InfluxDB OSS API Service + tags: + - debug-pprof + - trace + source: static/openapi/influxdb-oss-v2/paths/ref-debug-pprof-trace.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-debug-pprof-trace.yaml + - path: health + fields: + name: /health + describes: + - /health + title: |- + /health + InfluxDB OSS API Service + tags: + - '' + - health + source: static/openapi/influxdb-oss-v2/paths/ref-health.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-health.yaml + - path: legacy/authorizations + fields: + name: /legacy/authorizations + describes: + - /legacy/authorizations + - /legacy/authorizations/{authID} + - /legacy/authorizations/{authID}/password + title: |- + /legacy/authorizations + InfluxDB OSS API Service + tags: + - legacy + - authorizations + source: static/openapi/influxdb-oss-v2/paths/ref-legacy-authorizations.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-legacy-authorizations.yaml + - path: metrics + fields: + name: /metrics + describes: + - /metrics + title: |- + /metrics + InfluxDB OSS API Service + tags: + - '' + - metrics + source: static/openapi/influxdb-oss-v2/paths/ref-metrics.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-metrics.yaml + - path: ping + fields: + name: /ping + describes: + - /ping + title: |- + /ping + InfluxDB OSS API Service + tags: + - '' + - ping + source: static/openapi/influxdb-oss-v2/paths/ref-ping.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-ping.yaml + - path: query + fields: + name: /query + describes: + - /query + title: |- + /query + InfluxDB OSS API Service + tags: + - '' + - query + source: static/openapi/influxdb-oss-v2/paths/ref-query.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-query.yaml + - path: ready + fields: + name: /ready + describes: + - /ready + title: |- + /ready + InfluxDB OSS API Service + tags: + - '' + - ready + source: static/openapi/influxdb-oss-v2/paths/ref-ready.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-ready.yaml + - path: write + fields: + name: /write + describes: + - /write + title: |- + /write + InfluxDB OSS API Service + tags: + - '' + - write + source: static/openapi/influxdb-oss-v2/paths/ref-write.yaml + staticFilePath: /openapi/influxdb-oss-v2/paths/ref-write.yaml diff --git a/layouts/partials/api/sidebar-nav.html b/layouts/partials/api/sidebar-nav.html new file mode 100644 index 0000000000..0acb89d756 --- /dev/null +++ b/layouts/partials/api/sidebar-nav.html @@ -0,0 +1,244 @@ +{{/* + API Reference Navigation for Sidebar + + Displays a collapsible navigation tree for API endpoints, + organized by functional groups defined in data/api_nav_groups.yml. + + Uses Hugo data from data/article-data/ to build navigation. + Supports both tag-based (new) and path-based (legacy) article data. +*/}} + +{{/* Get product path data for data lookup */}} +{{ $productPathData := findRE "[^/]+.*?" .RelPermalink }} +{{ $product := index $productPathData 0 }} +{{ $version := index $productPathData 1 }} + +{{/* Build data key for article data lookup */}} +{{/* Hugo converts hyphens to underscores in data file keys */}} +{{ $dataKey := "" }} +{{ if eq $product "influxdb3" }} + {{ $dataKey = print "influxdb3_" $version }} +{{ else if eq $product "influxdb" }} + {{ $dataKey = print $version }} +{{ else }} + {{ $dataKey = $product }} +{{ end }} + +{{/* Get article data for this product */}} +{{/* + Hugo data path: data/article_data/influxdb/influxdb3_core/articles.yml + Access: .Site.Data.article_data.influxdb.influxdb3_core.articles.articles + + The double "articles" is because: + - First "articles" is the filename (articles.yml) + - Second "articles" is the key inside the YAML file +*/}} +{{ $articles := slice }} +{{ with .Site.Data.article_data }} + {{ with index . "influxdb" }} + {{ with index . $dataKey }} + {{ with index . "articles" }} + {{ with index . "articles" }} + {{ $articles = . }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} + +{{/* Get navigation groups configuration */}} +{{ $navGroups := .Site.Data.api_nav_groups.groups }} + +{{/* Check if articles use tag-based structure */}} +{{ $isTagBased := false }} +{{ if gt (len $articles) 0 }} + {{ $firstArticle := index $articles 0 }} + {{ if reflect.IsMap $firstArticle }} + {{ with index $firstArticle "fields" }} + {{ if reflect.IsMap . }} + {{ if isset . "tag" }} + {{ $isTagBased = true }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} + +{{ if and (gt (len $articles) 0) $navGroups $isTagBased }} +{{/* Tag-based navigation */}} + +{{ else if gt (len $articles) 0 }} +{{/* Legacy path-based navigation (fallback) */}} + +{{ end }} From 8b39a3cfb567925c8c3b614f35698db0013dd4b5 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:07:55 -0600 Subject: [PATCH 11/33] feat(api): Update config, styles, and content for API docs uplift - Update hugo.yml config for API docs settings - Simplify _api-overrides.scss (removed hardcoded content styles) - Import _api-layout.scss in styles-default.scss - Update API landing pages for Core and Enterprise with redirects - Update OpenAPI spec files - Update dependencies --- .gitignore | 1 + api-docs/influxdb3/core/v3/ref.yml | 13 +- api-docs/influxdb3/enterprise/v3/ref.yml | 13 +- assets/styles/layouts/_api-overrides.scss | 271 ++---------------- assets/styles/styles-default.scss | 3 +- config/_default/hugo.yml | 2 +- .../influxdb3/core/reference/api/_index.md | 22 +- .../enterprise/reference/api/_index.md | 20 +- package.json | 2 +- yarn.lock | 2 +- 10 files changed, 51 insertions(+), 298 deletions(-) diff --git a/.gitignore b/.gitignore index caeee60bd5..8688142320 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ package-lock.json # Content generation /content/influxdb*/**/api/**/*.html +/content/influxdb*/**/api/**/*.md !api-docs/**/.config.yml /api-docs/redoc-static.html* diff --git a/api-docs/influxdb3/core/v3/ref.yml b/api-docs/influxdb3/core/v3/ref.yml index 2d8a66b4ad..cee744e5f8 100644 --- a/api-docs/influxdb3/core/v3/ref.yml +++ b/api-docs/influxdb3/core/v3/ref.yml @@ -49,12 +49,13 @@ tags: Depending on your workflow, use one of the following schemes to authenticate to the InfluxDB 3 API: | Authentication scheme | Works with | - |:-------------------|:-----------| - | [Bearer authentication](#section/Authentication/BearerAuthentication) | All endpoints | - | [Token authentication](#section/Authentication/TokenAuthentication) | v1, v2 endpoints | - | [Basic authentication](#section/Authentication/BasicAuthentication) | v1 endpoints | - | [Querystring authentication](#section/Authentication/QuerystringAuthentication) | v1 endpoints | - + |:----------------------|:-----------| + | Bearer authentication | All endpoints | + | Token authentication | v1, v2 endpoints | + | Basic authentication | v1 endpoints | + | Querystring authentication | v1 endpoints | + + See the **Security Schemes** section below for details on each authentication method. x-traitTag: true - name: Cache data description: | diff --git a/api-docs/influxdb3/enterprise/v3/ref.yml b/api-docs/influxdb3/enterprise/v3/ref.yml index 4b2122d944..15062312fe 100644 --- a/api-docs/influxdb3/enterprise/v3/ref.yml +++ b/api-docs/influxdb3/enterprise/v3/ref.yml @@ -49,12 +49,13 @@ tags: Depending on your workflow, use one of the following schemes to authenticate to the InfluxDB 3 API: | Authentication scheme | Works with | - |:-------------------|:-----------| - | [Bearer authentication](#section/Authentication/BearerAuthentication) | All endpoints | - | [Token authentication](#section/Authentication/TokenAuthentication) | v1, v2 endpoints | - | [Basic authentication](#section/Authentication/BasicAuthentication) | v1 endpoints | - | [Querystring authentication](#section/Authentication/QuerystringAuthentication) | v1 endpoints | - + |:----------------------|:-----------| + | Bearer authentication | All endpoints | + | Token authentication | v1, v2 endpoints | + | Basic authentication | v1 endpoints | + | Querystring authentication | v1 endpoints | + + See the **Security Schemes** section below for details on each authentication method. x-traitTag: true - name: Cache data description: | diff --git a/assets/styles/layouts/_api-overrides.scss b/assets/styles/layouts/_api-overrides.scss index bc220588f8..f108911e24 100644 --- a/assets/styles/layouts/_api-overrides.scss +++ b/assets/styles/layouts/_api-overrides.scss @@ -1,9 +1,17 @@ +//////////////////////////////////////////////////////////////////////////////// +// API Documentation Style Overrides +// +// Provides loading spinner and reusable API-related styles. +// Note: Legacy Redoc-specific overrides have been removed in favor of +// Scalar/RapiDoc renderers which use CSS custom properties for theming. +//////////////////////////////////////////////////////////////////////////////// + @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 +30,7 @@ $bold: 700; } @keyframes spinner { - to {transform: rotate(360deg);} + to { transform: rotate(360deg); } } .spinner:before { @@ -41,256 +49,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; diff --git a/assets/styles/styles-default.scss b/assets/styles/styles-default.scss index 8852a240c3..94c897f291 100644 --- a/assets/styles/styles-default.scss +++ b/assets/styles/styles-default.scss @@ -32,7 +32,8 @@ "layouts/v1-overrides", "layouts/notifications", "layouts/code-controls", - "layouts/v3-wayfinding"; + "layouts/v3-wayfinding", + "layouts/api-layout"; // Import Components @import "components/influxdb-version-detector", diff --git a/config/_default/hugo.yml b/config/_default/hugo.yml index 08859dea60..12b0f0ad17 100644 --- a/config/_default/hugo.yml +++ b/config/_default/hugo.yml @@ -99,7 +99,7 @@ params: env: development environment: development # API documentation renderer: "scalar" (default) or "rapidoc" - apiRenderer: scalar + apiRenderer: rapidoc # Configure the server for development server: diff --git a/content/influxdb3/core/reference/api/_index.md b/content/influxdb3/core/reference/api/_index.md index 6a2200b1e5..13724ba97f 100644 --- a/content/influxdb3/core/reference/api/_index.md +++ b/content/influxdb3/core/reference/api/_index.md @@ -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/ --- - +This page has moved to [InfluxDB HTTP API](/influxdb3/core/api/). diff --git a/content/influxdb3/enterprise/reference/api/_index.md b/content/influxdb3/enterprise/reference/api/_index.md index ea78867f6d..a5a831de48 100644 --- a/content/influxdb3/enterprise/reference/api/_index.md +++ b/content/influxdb3/enterprise/reference/api/_index.md @@ -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/ --- - +This page has moved to [InfluxDB HTTP API](/influxdb3/enterprise/api/). diff --git a/package.json b/package.json index 5ee36aa39f..92a162df9a 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", - "typescript": "^5.8.3", + "typescript": "^5.9.3", "typescript-eslint": "^8.32.1", "unified": "^11.0.5", "winston": "^3.16.0" diff --git a/yarn.lock b/yarn.lock index 9761223ff4..ae7323bb1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6033,7 +6033,7 @@ typescript-eslint@^8.32.1: "@typescript-eslint/typescript-estree" "8.47.0" "@typescript-eslint/utils" "8.47.0" -typescript@^5.8.3: +typescript@^5.9.3: version "5.9.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== From b0b1afb8aeb81d6d9bf7a9b98260610fe87d0209 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:08:09 -0600 Subject: [PATCH 12/33] test(api): Update Cypress tests for API reference pages - Update tests for new tag-based API page structure - Add tests for operations list rendering - Add tests for sidebar navigation groups --- cypress/e2e/content/api-reference.cy.js | 156 +++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/content/api-reference.cy.js b/cypress/e2e/content/api-reference.cy.js index ceeaffeffc..e2798563c4 100644 --- a/cypress/e2e/content/api-reference.cy.js +++ b/cypress/e2e/content/api-reference.cy.js @@ -1,4 +1,16 @@ /// + +/** + * API Reference Documentation E2E Tests + * + * Tests both: + * 1. Legacy API reference pages (link validation, content structure) + * 2. New 3-column layout with tabs and TOC (for InfluxDB 3 Core/Enterprise) + * + * 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: () => {} @@ -64,7 +76,8 @@ describe('API reference content', () => { it(`has API info`, function () { 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') @@ -109,3 +122,144 @@ describe('API reference content', () => { }); }); }); + +/** + * 3-Column API Reference Layout Tests + * Tests the new layout for InfluxDB 3 Core/Enterprise API documentation + * Tests individual API endpoint pages which use the 3-column layout with tabs + */ +describe('API reference 3-column layout', () => { + // Individual API endpoint pages (not index pages) have the 3-column layout + const layoutSubjects = [ + '/influxdb3/core/api/v3/engine/', + '/influxdb3/enterprise/api/v3/engine/', + ]; + + 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').should('exist'); + }); + + it('displays TOC on desktop viewport', () => { + cy.viewport(1280, 800); + cy.get('.api-toc').should('be.visible'); + }); + + it('hides TOC on mobile viewport', () => { + cy.viewport(375, 667); + cy.get('.api-toc').should('not.be.visible'); + }); + }); + + describe('API Navigation', () => { + it('displays API navigation section', () => { + cy.get('.api-nav').should('exist'); + }); + + it('has collapsible navigation groups', () => { + cy.get('.api-nav-group').should('have.length.at.least', 1); + }); + + it('toggles group expand/collapse', () => { + cy.get('.api-nav-group-header').first().as('header'); + cy.get('@header').click(); + cy.get('@header') + .should('have.attr', 'aria-expanded') + .and('match', /true|false/); + }); + }); + + describe('Tab Navigation', () => { + it('displays tabs', () => { + cy.get('.api-tabs-wrapper').should('exist'); + }); + + it('shows Operations tab content by default', () => { + cy.get('[data-tab-panel="operations"]').should('be.visible'); + }); + + it('switches tabs on click without page jump', () => { + // Get initial scroll position + cy.window().then((win) => { + const initialScroll = win.scrollY; + + // Click the second tab + cy.get('.api-tabs-nav a').eq(1).click(); + + // Verify tabs are still visible (not jumped away) + cy.get('.api-tabs-wrapper').should('be.visible'); + + // Verify the clicked tab is now active + cy.get('.api-tabs-nav a').eq(1).should('have.class', 'is-active'); + + // Verify the first tab is no longer active + cy.get('.api-tabs-nav a').eq(0).should('not.have.class', 'is-active'); + }); + }); + + it('updates URL hash when switching tabs', () => { + cy.get('.api-tabs-nav a[data-tab="server"]').click(); + cy.url().should('include', '#server'); + }); + + it('restores tab from URL hash on page load', () => { + // Use the current subject URL with hash instead of hardcoded old reference URL + cy.visit(`${subject}#authentication`); + cy.get('.api-tabs-nav a[data-tab="authentication"]').should('have.class', 'is-active'); + cy.get('[data-tab-panel="authentication"]').should('be.visible'); + }); + }); + + describe('Table of Contents', () => { + it('displays TOC header', () => { + cy.viewport(1280, 800); + cy.get('.api-toc-header').should('contain', 'ON THIS PAGE'); + }); + + it('generates TOC from headings', () => { + cy.viewport(1280, 800); + cy.wait(500); // Wait for component initialization + cy.get('.api-toc-nav').should('exist'); + }); + }); + + describe('API Renderer', () => { + it('loads API documentation renderer', () => { + cy.get('.api-reference-container, rapi-doc, .api-reference-wrapper').should('exist'); + }); + + it('displays spec download link', () => { + cy.get('.api-spec-download').should('exist'); + }); + }); + + describe('Accessibility', () => { + it('has ARIA attributes on nav groups', () => { + cy.get('.api-nav-group-header').each(($header) => { + cy.wrap($header).should('have.attr', 'aria-expanded'); + }); + }); + }); + }); + }); +}); From a3081748b1e6a777e13d8d5d510142fcbf6227e1 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:08:33 -0600 Subject: [PATCH 13/33] chore: Rebuild API generator scripts and update tests --- .../scripts/dist/generate-openapi-articles.js | 880 +++++++------- .../dist/openapi-paths-to-hugo-data/index.js | 1046 +++++++++-------- api-docs/scripts/generate-openapi-articles.ts | 15 +- .../openapi-paths-to-hugo-data/index.ts | 36 +- cypress/e2e/content/api-reference.cy.js | 68 +- 5 files changed, 1107 insertions(+), 938 deletions(-) diff --git a/api-docs/scripts/dist/generate-openapi-articles.js b/api-docs/scripts/dist/generate-openapi-articles.js index 8fc31252b3..7ed7c26c4f 100644 --- a/api-docs/scripts/dist/generate-openapi-articles.js +++ b/api-docs/scripts/dist/generate-openapi-articles.js @@ -1,5 +1,5 @@ #!/usr/bin/env node -"use strict"; +'use strict'; /** * Generate OpenAPI Articles Script * @@ -20,47 +20,70 @@ * * @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; +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 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; + 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 }); + })(); +Object.defineProperty(exports, '__esModule', { value: true }); exports.productConfigs = void 0; exports.processProduct = processProduct; exports.generateDataFromOpenAPI = generateDataFromOpenAPI; exports.generatePagesFromArticleData = generatePagesFromArticleData; -const child_process_1 = require("child_process"); -const path = __importStar(require("path")); -const fs = __importStar(require("fs")); +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'); // Calculate the relative paths @@ -74,20 +97,19 @@ const API_DOCS_ROOT = 'api-docs'; * @throws Exits process with code 1 on error */ function execCommand(command, description) { - try { - if (description) { - console.log(`\n${description}...`); - } - console.log(`Executing: ${command}\n`); - (0, child_process_1.execSync)(command, { stdio: 'inherit' }); + try { + if (description) { + console.log(`\n${description}...`); } - catch (error) { - console.error(`\n❌ Error executing command: ${command}`); - if (error instanceof Error) { - console.error(error.message); - } - process.exit(1); + 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); + } } /** * Generate Hugo data files from OpenAPI specification @@ -97,14 +119,14 @@ function execCommand(command, description) { * @param articleOutPath - Output path for article metadata */ function generateDataFromOpenAPI(specFile, dataOutPath, articleOutPath) { - if (!fs.existsSync(dataOutPath)) { - fs.mkdirSync(dataOutPath, { recursive: true }); - } - openapiPathsToHugo.generateHugoData({ - dataOutPath, - articleOutPath, - specFile, - }); + if (!fs.existsSync(dataOutPath)) { + fs.mkdirSync(dataOutPath, { recursive: true }); + } + openapiPathsToHugo.generateHugoData({ + dataOutPath, + articleOutPath, + specFile, + }); } /** * Generate Hugo content pages from article data @@ -115,107 +137,122 @@ function generateDataFromOpenAPI(specFile, dataOutPath, articleOutPath) { * @param options - Generation options */ function generatePagesFromArticleData(options) { - const { articlesPath, contentPath, menuKey, menuParent, productDescription, skipParentMenu, } = 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; - } - // Read articles data - 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; - } - // Ensure content directory exists - if (!fs.existsSync(contentPath)) { - fs.mkdirSync(contentPath, { recursive: true }); + const { + articlesPath, + contentPath, + menuKey, + menuParent, + productDescription, + skipParentMenu, + } = 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; + } + // Read articles data + 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; + } + // Ensure content directory exists + if (!fs.existsSync(contentPath)) { + fs.mkdirSync(contentPath, { recursive: true }); + } + // Determine the API parent directory from the first article's path + // e.g., if article path is "api/v1/health", the API root is "api" + const firstArticlePath = data.articles[0]?.path || ''; + const apiRootDir = firstArticlePath.split('/')[0]; + // Generate parent _index.md for the API section + if (apiRootDir) { + const apiParentDir = path.join(contentPath, apiRootDir); + const parentIndexFile = path.join(apiParentDir, '_index.md'); + if (!fs.existsSync(apiParentDir)) { + fs.mkdirSync(apiParentDir, { recursive: true }); } - // Determine the API parent directory from the first article's path - // e.g., if article path is "api/v1/health", the API root is "api" - const firstArticlePath = data.articles[0]?.path || ''; - const apiRootDir = firstArticlePath.split('/')[0]; - // Generate parent _index.md for the API section - if (apiRootDir) { - const apiParentDir = path.join(contentPath, apiRootDir); - const parentIndexFile = path.join(apiParentDir, '_index.md'); - if (!fs.existsSync(apiParentDir)) { - fs.mkdirSync(apiParentDir, { recursive: true }); - } - if (!fs.existsSync(parentIndexFile)) { - const parentFrontmatter = { - title: menuParent || 'HTTP API', - description: productDescription || - 'API reference documentation for all available endpoints.', - weight: 104, - }; - // Add menu entry for parent page (unless skipParentMenu is true) - if (menuKey && !skipParentMenu) { - parentFrontmatter.menu = { - [menuKey]: { - name: menuParent || 'HTTP API', - }, - }; - } - const parentContent = `--- + if (!fs.existsSync(parentIndexFile)) { + const parentFrontmatter = { + title: menuParent || 'HTTP API', + description: + productDescription || + 'API reference documentation for all available endpoints.', + weight: 104, + }; + // Add menu entry for parent page (unless skipParentMenu is true) + if (menuKey && !skipParentMenu) { + parentFrontmatter.menu = { + [menuKey]: { + name: menuParent || 'HTTP API', + }, + }; + } + const parentContent = `--- ${yaml.dump(parentFrontmatter)}--- `; - fs.writeFileSync(parentIndexFile, parentContent); - console.log(`✓ Generated parent index at ${parentIndexFile}`); - } + fs.writeFileSync(parentIndexFile, parentContent); + console.log(`✓ Generated parent index at ${parentIndexFile}`); } - // Generate a page for each article - for (const article of data.articles) { - const pagePath = path.join(contentPath, article.path); - const pageFile = path.join(pagePath, '_index.md'); - // Create directory if needed - if (!fs.existsSync(pagePath)) { - fs.mkdirSync(pagePath, { recursive: true }); - } - // Build frontmatter object - // Use menuName for display (actual endpoint path like /health) - // Fall back to name or path if menuName is not set - const displayName = article.fields.menuName || article.fields.name || article.path; - const frontmatter = { - title: displayName, - description: `API reference for ${displayName}`, - type: 'api', - // Use explicit layout to override Hugo's default section template lookup - // (Hugo's section lookup ignores `type`, so we need `layout` for the 3-column API layout) - layout: 'list', - staticFilePath: article.fields.staticFilePath, - weight: 100, - }; - // Add menu entry if menuKey is provided - // Use menuName for menu display (shows actual endpoint path like /health) - if (menuKey) { - frontmatter.menu = { - [menuKey]: { - name: displayName, - ...(menuParent && { parent: menuParent }), - }, - }; - } - // Add related links if present in article fields - if (article.fields.related && - Array.isArray(article.fields.related) && - article.fields.related.length > 0) { - frontmatter.related = article.fields.related; - } - // Add OpenAPI tags if present in article fields (for frontmatter metadata) - if (article.fields.apiTags && - Array.isArray(article.fields.apiTags) && - article.fields.apiTags.length > 0) { - frontmatter.api_tags = article.fields.apiTags; - } - const pageContent = `--- + } + // Generate a page for each article + for (const article of data.articles) { + const pagePath = path.join(contentPath, article.path); + const pageFile = path.join(pagePath, '_index.md'); + // Create directory if needed + if (!fs.existsSync(pagePath)) { + fs.mkdirSync(pagePath, { recursive: true }); + } + // Build frontmatter object + // Use menuName for display (actual endpoint path like /health) + // Fall back to name or path if menuName is not set + const displayName = + article.fields.menuName || article.fields.name || article.path; + const frontmatter = { + title: displayName, + description: `API reference for ${displayName}`, + type: 'api', + // Use explicit layout to override Hugo's default section template lookup + // (Hugo's section lookup ignores `type`, so we need `layout` for the 3-column API layout) + layout: 'list', + staticFilePath: article.fields.staticFilePath, + weight: 100, + }; + // Add menu entry if menuKey is provided + // Use menuName for menu display (shows actual endpoint path like /health) + if (menuKey) { + frontmatter.menu = { + [menuKey]: { + name: displayName, + ...(menuParent && { parent: menuParent }), + }, + }; + } + // Add related links if present in article fields + if ( + article.fields.related && + Array.isArray(article.fields.related) && + article.fields.related.length > 0 + ) { + frontmatter.related = article.fields.related; + } + // Add OpenAPI tags if present in article fields (for frontmatter metadata) + if ( + article.fields.apiTags && + Array.isArray(article.fields.apiTags) && + article.fields.apiTags.length > 0 + ) { + frontmatter.api_tags = article.fields.apiTags; + } + const pageContent = `--- ${yaml.dump(frontmatter)}--- `; - fs.writeFileSync(pageFile, pageContent); - } - console.log(`✓ Generated ${data.articles.length} content pages in ${contentPath}`); + fs.writeFileSync(pageFile, pageContent); + } + console.log( + `✓ Generated ${data.articles.length} content pages in ${contentPath}` + ); } /** * Generate Hugo content pages from tag-based article data @@ -227,98 +264,114 @@ ${yaml.dump(frontmatter)}--- * @param options - Generation options */ function generateTagPagesFromArticleData(options) { - const { articlesPath, contentPath, menuKey, menuParent, productDescription, skipParentMenu, } = 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 { + articlesPath, + contentPath, + menuKey, + menuParent, + productDescription, + skipParentMenu, + } = 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; + } + // Read articles data + 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; + } + // Ensure content directory exists + if (!fs.existsSync(contentPath)) { + fs.mkdirSync(contentPath, { recursive: true }); + } + // Generate parent _index.md for the API section + const apiParentDir = path.join(contentPath, 'api'); + const parentIndexFile = path.join(apiParentDir, '_index.md'); + if (!fs.existsSync(apiParentDir)) { + fs.mkdirSync(apiParentDir, { recursive: true }); + } + if (!fs.existsSync(parentIndexFile)) { + const parentFrontmatter = { + title: menuParent || 'HTTP API', + description: + productDescription || + 'API reference documentation for all available endpoints.', + weight: 104, + }; + // Add menu entry for parent page (unless skipParentMenu is true) + if (menuKey && !skipParentMenu) { + parentFrontmatter.menu = { + [menuKey]: { + name: menuParent || 'HTTP API', + }, + }; } - // Read articles data - 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; + const parentContent = `--- +${yaml.dump(parentFrontmatter)}--- +`; + fs.writeFileSync(parentIndexFile, parentContent); + console.log(`✓ Generated parent index at ${parentIndexFile}`); + } + // 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'); + // Create directory if needed + if (!fs.existsSync(pagePath)) { + fs.mkdirSync(pagePath, { recursive: true }); } - // Ensure content directory exists - if (!fs.existsSync(contentPath)) { - fs.mkdirSync(contentPath, { recursive: true }); + // Build frontmatter object + const title = article.fields.title || article.fields.name || article.path; + const isConceptual = article.fields.isConceptual === true; + const frontmatter = { + title, + description: article.fields.description || `API reference for ${title}`, + type: 'api', + layout: isConceptual ? 'single' : 'list', + staticFilePath: article.fields.staticFilePath, + weight: 100, + // Tag-based fields + tag: article.fields.tag, + isConceptual, + menuGroup: article.fields.menuGroup, + }; + // Add operations for TOC generation (only for non-conceptual pages) + if ( + !isConceptual && + article.fields.operations && + article.fields.operations.length > 0 + ) { + frontmatter.operations = article.fields.operations; } - // Generate parent _index.md for the API section - const apiParentDir = path.join(contentPath, 'api'); - const parentIndexFile = path.join(apiParentDir, '_index.md'); - if (!fs.existsSync(apiParentDir)) { - fs.mkdirSync(apiParentDir, { recursive: true }); + // Add tag description for conceptual pages + if (isConceptual && article.fields.tagDescription) { + frontmatter.tagDescription = article.fields.tagDescription; } - if (!fs.existsSync(parentIndexFile)) { - const parentFrontmatter = { - title: menuParent || 'HTTP API', - description: productDescription || - 'API reference documentation for all available endpoints.', - weight: 104, - }; - // Add menu entry for parent page (unless skipParentMenu is true) - if (menuKey && !skipParentMenu) { - parentFrontmatter.menu = { - [menuKey]: { - name: menuParent || 'HTTP API', - }, - }; - } - const parentContent = `--- -${yaml.dump(parentFrontmatter)}--- -`; - fs.writeFileSync(parentIndexFile, parentContent); - console.log(`✓ Generated parent index at ${parentIndexFile}`); + // Note: We deliberately don't add menu entries for tag-based API pages. + // The API sidebar navigation (api/sidebar-nav.html) handles navigation + // for API reference pages, avoiding conflicts with existing menu items + // like "Query data" and "Write data" that exist in the main sidebar. + // Add related links if present in article fields + if ( + article.fields.related && + Array.isArray(article.fields.related) && + article.fields.related.length > 0 + ) { + frontmatter.related = article.fields.related; } - // 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'); - // Create directory if needed - if (!fs.existsSync(pagePath)) { - fs.mkdirSync(pagePath, { recursive: true }); - } - // Build frontmatter object - const title = article.fields.title || article.fields.name || article.path; - const isConceptual = article.fields.isConceptual === true; - const frontmatter = { - title, - description: article.fields.description || `API reference for ${title}`, - type: 'api', - layout: isConceptual ? 'single' : 'list', - staticFilePath: article.fields.staticFilePath, - weight: 100, - // Tag-based fields - tag: article.fields.tag, - isConceptual, - menuGroup: article.fields.menuGroup, - }; - // Add operations for TOC generation (only for non-conceptual pages) - if (!isConceptual && article.fields.operations && article.fields.operations.length > 0) { - frontmatter.operations = article.fields.operations; - } - // Add tag description for conceptual pages - if (isConceptual && article.fields.tagDescription) { - frontmatter.tagDescription = article.fields.tagDescription; - } - // Note: We deliberately don't add menu entries for tag-based API pages. - // The API sidebar navigation (api/sidebar-nav.html) handles navigation - // for API reference pages, avoiding conflicts with existing menu items - // like "Query data" and "Write data" that exist in the main sidebar. - // Add related links if present in article fields - if (article.fields.related && - Array.isArray(article.fields.related) && - article.fields.related.length > 0) { - frontmatter.related = article.fields.related; - } - const pageContent = `--- + const pageContent = `--- ${yaml.dump(frontmatter)}--- `; - fs.writeFileSync(pageFile, pageContent); - } - console.log(`✓ Generated ${data.articles.length} tag-based content pages in ${contentPath}`); + fs.writeFileSync(pageFile, pageContent); + } + console.log( + `✓ Generated ${data.articles.length} tag-based content pages in ${contentPath}` + ); } /** * Product configurations for all InfluxDB editions @@ -326,61 +379,70 @@ ${yaml.dump(frontmatter)}--- * Maps product identifiers to their OpenAPI specs and content directories */ const productConfigs = { - // TODO: v2 products (cloud-v2, oss-v2) are disabled for now because they - // have existing Redoc-based API reference at /reference/api/ - // Uncomment when ready to migrate v2 products to Scalar - // 'cloud-v2': { - // specFile: path.join(API_DOCS_ROOT, 'influxdb/cloud/v2/ref.yml'), - // pagesDir: path.join(DOCS_ROOT, 'content/influxdb/cloud/api'), - // description: 'InfluxDB Cloud (v2 API)', - // menuKey: 'influxdb_cloud', - // }, - // 'oss-v2': { - // specFile: path.join(API_DOCS_ROOT, 'influxdb/v2/v2/ref.yml'), - // pagesDir: path.join(DOCS_ROOT, 'content/influxdb/v2/api'), - // description: 'InfluxDB OSS v2', - // menuKey: 'influxdb_v2', - // }, - // InfluxDB 3 products use tag-based generation for better UX - 'influxdb3-core': { - specFile: path.join(API_DOCS_ROOT, 'influxdb3/core/v3/ref.yml'), - pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/core'), - description: 'InfluxDB 3 Core', - menuKey: 'influxdb3_core', - useTagBasedGeneration: true, - }, - 'influxdb3-enterprise': { - specFile: path.join(API_DOCS_ROOT, 'influxdb3/enterprise/v3/ref.yml'), - pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/enterprise'), - description: 'InfluxDB 3 Enterprise', - menuKey: 'influxdb3_enterprise', - useTagBasedGeneration: true, - }, - // Note: Cloud Dedicated, Serverless, and Clustered use management APIs - // with paths like /accounts/{accountId}/... so we put them under /api/ - // These products have existing /reference/api/ pages with menu entries, - // so we skip adding menu entries to the generated parent pages. - 'cloud-dedicated': { - specFile: path.join(API_DOCS_ROOT, 'influxdb3/cloud-dedicated/management/openapi.yml'), - pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-dedicated/api'), - description: 'InfluxDB Cloud Dedicated', - menuKey: 'influxdb3_cloud_dedicated', - skipParentMenu: true, - }, - 'cloud-serverless': { - specFile: path.join(API_DOCS_ROOT, 'influxdb3/cloud-serverless/management/openapi.yml'), - pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-serverless/api'), - description: 'InfluxDB Cloud Serverless', - menuKey: 'influxdb3_cloud_serverless', - skipParentMenu: true, - }, - clustered: { - specFile: path.join(API_DOCS_ROOT, 'influxdb3/clustered/management/openapi.yml'), - pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/clustered/api'), - description: 'InfluxDB Clustered', - menuKey: 'influxdb3_clustered', - skipParentMenu: true, - }, + // TODO: v2 products (cloud-v2, oss-v2) are disabled for now because they + // have existing Redoc-based API reference at /reference/api/ + // Uncomment when ready to migrate v2 products to Scalar + // 'cloud-v2': { + // specFile: path.join(API_DOCS_ROOT, 'influxdb/cloud/v2/ref.yml'), + // pagesDir: path.join(DOCS_ROOT, 'content/influxdb/cloud/api'), + // description: 'InfluxDB Cloud (v2 API)', + // menuKey: 'influxdb_cloud', + // }, + // 'oss-v2': { + // specFile: path.join(API_DOCS_ROOT, 'influxdb/v2/v2/ref.yml'), + // pagesDir: path.join(DOCS_ROOT, 'content/influxdb/v2/api'), + // description: 'InfluxDB OSS v2', + // menuKey: 'influxdb_v2', + // }, + // InfluxDB 3 products use tag-based generation for better UX + 'influxdb3-core': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/core/v3/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/core'), + description: 'InfluxDB 3 Core', + menuKey: 'influxdb3_core', + useTagBasedGeneration: true, + }, + 'influxdb3-enterprise': { + specFile: path.join(API_DOCS_ROOT, 'influxdb3/enterprise/v3/ref.yml'), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/enterprise'), + description: 'InfluxDB 3 Enterprise', + menuKey: 'influxdb3_enterprise', + useTagBasedGeneration: true, + }, + // Note: Cloud Dedicated, Serverless, and Clustered use management APIs + // with paths like /accounts/{accountId}/... so we put them under /api/ + // These products have existing /reference/api/ pages with menu entries, + // so we skip adding menu entries to the generated parent pages. + 'cloud-dedicated': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-dedicated/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-dedicated/api'), + description: 'InfluxDB Cloud Dedicated', + menuKey: 'influxdb3_cloud_dedicated', + skipParentMenu: true, + }, + 'cloud-serverless': { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/cloud-serverless/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/cloud-serverless/api'), + description: 'InfluxDB Cloud Serverless', + menuKey: 'influxdb3_cloud_serverless', + skipParentMenu: true, + }, + clustered: { + specFile: path.join( + API_DOCS_ROOT, + 'influxdb3/clustered/management/openapi.yml' + ), + pagesDir: path.join(DOCS_ROOT, 'content/influxdb3/clustered/api'), + description: 'InfluxDB Clustered', + menuKey: 'influxdb3_clustered', + skipParentMenu: true, + }, }; exports.productConfigs = productConfigs; /** @@ -390,127 +452,145 @@ exports.productConfigs = productConfigs; * @param config - Product configuration */ function processProduct(productKey, config) { - console.log('\n' + '='.repeat(80)); - console.log(`Processing ${config.description || productKey}`); - console.log('='.repeat(80)); - const staticPath = path.join(DOCS_ROOT, 'static/openapi'); - const staticSpecPath = path.join(staticPath, `influxdb-${productKey}.yml`); - const staticJsonSpecPath = path.join(staticPath, `influxdb-${productKey}.json`); - const staticPathsPath = path.join(staticPath, `influxdb-${productKey}/paths`); - const articlesPath = path.join(DOCS_ROOT, `data/article-data/influxdb/${productKey}`); - // Check if spec file exists - if (!fs.existsSync(config.specFile)) { - console.warn(`⚠️ Spec file not found: ${config.specFile}`); - console.log('Skipping this product. Run getswagger.sh first if needed.\n'); - return; + console.log('\n' + '='.repeat(80)); + console.log(`Processing ${config.description || productKey}`); + console.log('='.repeat(80)); + const staticPath = path.join(DOCS_ROOT, 'static/openapi'); + const staticSpecPath = path.join(staticPath, `influxdb-${productKey}.yml`); + const staticJsonSpecPath = path.join( + staticPath, + `influxdb-${productKey}.json` + ); + const staticPathsPath = path.join(staticPath, `influxdb-${productKey}/paths`); + const articlesPath = path.join( + DOCS_ROOT, + `data/article-data/influxdb/${productKey}` + ); + // Check if spec file exists + if (!fs.existsSync(config.specFile)) { + console.warn(`⚠️ Spec file not found: ${config.specFile}`); + console.log('Skipping this product. Run getswagger.sh first if needed.\n'); + return; + } + try { + // Step 1: Execute the getswagger.sh script to fetch/bundle the spec + // Note: getswagger.sh must run from api-docs/ because it uses relative paths + const getswaggerScript = path.join(API_DOCS_ROOT, 'getswagger.sh'); + if (fs.existsSync(getswaggerScript)) { + execCommand( + `cd ${API_DOCS_ROOT} && ./getswagger.sh ${productKey} -B`, + `Fetching OpenAPI spec for ${productKey}` + ); + } else { + console.log(`⚠️ getswagger.sh not found, skipping fetch step`); } - try { - // Step 1: Execute the getswagger.sh script to fetch/bundle the spec - // Note: getswagger.sh must run from api-docs/ because it uses relative paths - const getswaggerScript = path.join(API_DOCS_ROOT, 'getswagger.sh'); - if (fs.existsSync(getswaggerScript)) { - execCommand(`cd ${API_DOCS_ROOT} && ./getswagger.sh ${productKey} -B`, `Fetching OpenAPI spec for ${productKey}`); - } - else { - console.log(`⚠️ getswagger.sh not found, skipping fetch step`); - } - // Step 2: Ensure static directory exists - if (!fs.existsSync(staticPath)) { - fs.mkdirSync(staticPath, { recursive: true }); - } - // Step 3: Copy the generated OpenAPI spec to static folder (YAML) - if (fs.existsSync(config.specFile)) { - fs.copyFileSync(config.specFile, staticSpecPath); - console.log(`✓ Copied spec to ${staticSpecPath}`); - // Step 4: Generate JSON version of the spec - try { - const yaml = require('js-yaml'); - const specContent = fs.readFileSync(config.specFile, 'utf8'); - const specObject = yaml.load(specContent); - fs.writeFileSync(staticJsonSpecPath, JSON.stringify(specObject, null, 2)); - console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`); - } - catch (jsonError) { - console.warn(`⚠️ Could not generate JSON spec: ${jsonError}`); - } - } - // Step 5: Generate Hugo data from OpenAPI spec - if (config.useTagBasedGeneration) { - // Tag-based generation: group operations by OpenAPI tag - const staticTagsPath = path.join(staticPath, `influxdb-${productKey}/tags`); - console.log(`\n📋 Using tag-based generation for ${productKey}...`); - openapiPathsToHugo.generateHugoDataByTag({ - specFile: config.specFile, - dataOutPath: staticTagsPath, - articleOutPath: articlesPath, - includePaths: true, // Also generate path-based files for backwards compatibility - }); - // Step 6: Generate Hugo content pages from tag-based article data - generateTagPagesFromArticleData({ - articlesPath, - contentPath: config.pagesDir, - menuKey: config.menuKey, - menuParent: 'InfluxDB HTTP API', - skipParentMenu: config.skipParentMenu, - }); - } - else { - // Path-based generation: group paths by URL prefix (legacy) - generateDataFromOpenAPI(config.specFile, staticPathsPath, articlesPath); - // Step 6: Generate Hugo content pages from path-based article data - generatePagesFromArticleData({ - articlesPath, - contentPath: config.pagesDir, - menuKey: config.menuKey, - menuParent: 'InfluxDB HTTP API', - skipParentMenu: config.skipParentMenu, - }); - } - console.log(`\n✅ Successfully processed ${config.description || productKey}\n`); + // Step 2: Ensure static directory exists + if (!fs.existsSync(staticPath)) { + fs.mkdirSync(staticPath, { recursive: true }); + } + // Step 3: Copy the generated OpenAPI spec to static folder (YAML) + if (fs.existsSync(config.specFile)) { + fs.copyFileSync(config.specFile, staticSpecPath); + console.log(`✓ Copied spec to ${staticSpecPath}`); + // Step 4: Generate JSON version of the spec + try { + const yaml = require('js-yaml'); + const specContent = fs.readFileSync(config.specFile, 'utf8'); + const specObject = yaml.load(specContent); + fs.writeFileSync( + staticJsonSpecPath, + JSON.stringify(specObject, null, 2) + ); + console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`); + } catch (jsonError) { + console.warn(`⚠️ Could not generate JSON spec: ${jsonError}`); + } } - catch (error) { - console.error(`\n❌ Error processing ${productKey}:`, error); - process.exit(1); + // Step 5: Generate Hugo data from OpenAPI spec + if (config.useTagBasedGeneration) { + // Tag-based generation: group operations by OpenAPI tag + const staticTagsPath = path.join( + staticPath, + `influxdb-${productKey}/tags` + ); + console.log(`\n📋 Using tag-based generation for ${productKey}...`); + openapiPathsToHugo.generateHugoDataByTag({ + specFile: config.specFile, + dataOutPath: staticTagsPath, + articleOutPath: articlesPath, + includePaths: true, // Also generate path-based files for backwards compatibility + }); + // Step 6: Generate Hugo content pages from tag-based article data + generateTagPagesFromArticleData({ + articlesPath, + contentPath: config.pagesDir, + menuKey: config.menuKey, + menuParent: 'InfluxDB HTTP API', + skipParentMenu: config.skipParentMenu, + }); + } else { + // Path-based generation: group paths by URL prefix (legacy) + generateDataFromOpenAPI(config.specFile, staticPathsPath, articlesPath); + // Step 6: Generate Hugo content pages from path-based article data + generatePagesFromArticleData({ + articlesPath, + contentPath: config.pagesDir, + menuKey: config.menuKey, + menuParent: 'InfluxDB HTTP API', + skipParentMenu: config.skipParentMenu, + }); } + console.log( + `\n✅ Successfully processed ${config.description || productKey}\n` + ); + } catch (error) { + console.error(`\n❌ Error processing ${productKey}:`, error); + process.exit(1); + } } /** * Main execution function */ function main() { - const args = process.argv.slice(2); - // Determine which products to process - let productsToProcess; - if (args.length === 0) { - // No arguments: process all products - productsToProcess = Object.keys(productConfigs); - console.log('\n📋 Processing all products...\n'); - } - else { - // Arguments provided: process only specified products - productsToProcess = args; - console.log(`\n📋 Processing specified products: ${productsToProcess.join(', ')}\n`); - } - // Validate product keys - const invalidProducts = productsToProcess.filter((key) => !productConfigs[key]); - if (invalidProducts.length > 0) { - console.error(`\n❌ Invalid product identifier(s): ${invalidProducts.join(', ')}`); - console.error('\nValid products:'); - Object.keys(productConfigs).forEach((key) => { - console.error(` - ${key}: ${productConfigs[key].description}`); - }); - process.exit(1); - } - // Process each product - productsToProcess.forEach((productKey) => { - const config = productConfigs[productKey]; - processProduct(productKey, config); + const args = process.argv.slice(2); + // Determine which products to process + let productsToProcess; + if (args.length === 0) { + // No arguments: process all products + productsToProcess = Object.keys(productConfigs); + console.log('\n📋 Processing all products...\n'); + } else { + // Arguments provided: process only specified products + productsToProcess = args; + console.log( + `\n📋 Processing specified products: ${productsToProcess.join(', ')}\n` + ); + } + // Validate product keys + const invalidProducts = productsToProcess.filter( + (key) => !productConfigs[key] + ); + if (invalidProducts.length > 0) { + console.error( + `\n❌ Invalid product identifier(s): ${invalidProducts.join(', ')}` + ); + console.error('\nValid products:'); + Object.keys(productConfigs).forEach((key) => { + console.error(` - ${key}: ${productConfigs[key].description}`); }); - console.log('\n' + '='.repeat(80)); - console.log('✅ All products processed successfully!'); - console.log('='.repeat(80) + '\n'); + process.exit(1); + } + // Process each product + productsToProcess.forEach((productKey) => { + const config = productConfigs[productKey]; + processProduct(productKey, config); + }); + 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(); + main(); } -//# sourceMappingURL=generate-openapi-articles.js.map \ No newline at end of file +//# sourceMappingURL=generate-openapi-articles.js.map diff --git a/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js b/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js index cd977cfce3..b81d170304 100644 --- a/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js +++ b/api-docs/scripts/dist/openapi-paths-to-hugo-data/index.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; /** * OpenAPI to Hugo Data Converter * @@ -7,45 +7,68 @@ * * @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; +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 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; + 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 }); + })(); +Object.defineProperty(exports, '__esModule', { value: true }); exports.generateHugoDataByTag = generateHugoDataByTag; exports.generateHugoData = generateHugoData; -const yaml = __importStar(require("js-yaml")); -const fs = __importStar(require("fs")); -const path = __importStar(require("path")); +const yaml = __importStar(require('js-yaml')); +const fs = __importStar(require('fs')); +const path = __importStar(require('path')); /** * Read a YAML file and parse it * @@ -54,8 +77,8 @@ const path = __importStar(require("path")); * @returns Parsed YAML content */ function readFile(filepath, encoding = 'utf8') { - const content = fs.readFileSync(filepath, encoding); - return yaml.load(content); + const content = fs.readFileSync(filepath, encoding); + return yaml.load(content); } /** * Write data to a YAML file @@ -64,7 +87,7 @@ function readFile(filepath, encoding = 'utf8') { * @param outputTo - Output file path */ function writeDataFile(data, outputTo) { - fs.writeFileSync(outputTo, yaml.dump(data)); + fs.writeFileSync(outputTo, yaml.dump(data)); } /** * Write data to a JSON file @@ -73,22 +96,22 @@ function writeDataFile(data, outputTo) { * @param outputTo - Output file path */ function writeJsonFile(data, outputTo) { - fs.writeFileSync(outputTo, JSON.stringify(data, null, 2)); + 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); - }, + /** + * 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 @@ -97,35 +120,35 @@ const openapiUtils = { * @returns URL-friendly slug (e.g., "write-data", "processing-engine") */ function slugifyTag(tagName) { - return tagName - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, ''); + 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', + // 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 @@ -134,20 +157,20 @@ const TAG_MENU_GROUPS = { * @returns Menu group name or 'Other' if not mapped */ function getMenuGroupForTag(tagName) { - return TAG_MENU_GROUPS[tagName] || 'Other'; + return TAG_MENU_GROUPS[tagName] || 'Other'; } /** * HTTP methods to check for operations */ const HTTP_METHODS = [ - 'get', - 'post', - 'put', - 'patch', - 'delete', - 'options', - 'head', - 'trace', + 'get', + 'post', + 'put', + 'patch', + 'delete', + 'options', + 'head', + 'trace', ]; /** * Extract all operations from an OpenAPI document grouped by tag @@ -156,29 +179,29 @@ const HTTP_METHODS = [ * @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 || [], - }; - // Add operation to each of its tags - (operation.tags || []).forEach((tag) => { - if (!tagOperations.has(tag)) { - tagOperations.set(tag, []); - } - tagOperations.get(tag).push(opMeta); - }); - } + 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 || [], + }; + // 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; + }); + return tagOperations; } /** * Write OpenAPI specs grouped by tag to separate files @@ -189,79 +212,81 @@ function extractOperationsByTag(openapi) { * @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)) { - filteredPathItem[method] = operation; - 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); + 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)) { + filteredPathItem[method] = operation; + hasOperations = true; } - }); - // 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); - } + }); + // 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); + } + } + }); } /** * Write OpenAPI specs grouped by path to separate files @@ -272,79 +297,80 @@ function writeTagOpenapis(openapi, prefix, outPath) { * @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]; + 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); + // 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 @@ -353,107 +379,119 @@ function writePathOpenapis(openapi, prefix, outPath) { * @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))); + 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) + ) + ); } - // 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); - } - }); - } + // 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; + }); + // 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 @@ -464,49 +502,50 @@ function createArticleDataForPathGroup(openapi) { * @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); + /** + * 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); + } } /** * Create article data for a tag-based grouping @@ -517,33 +556,36 @@ function writeOpenapiArticleData(sourcePath, targetPath, opts) { * @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: 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, - })), - }, - }; - // Add tag description for conceptual pages - if (tagMeta?.description) { - article.fields.tagDescription = tagMeta.description; - } - return article; + 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: + 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, + })), + }, + }; + // Add tag description for conceptual pages + if (tagMeta?.description) { + article.fields.tagDescription = tagMeta.description; + } + return article; } /** * Write tag-based OpenAPI article metadata to Hugo data files @@ -555,65 +597,73 @@ function createArticleDataForTag(openapi, operations, tagMeta) { * @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) { - operations.push({ - operationId: operation.operationId || `${method}-${pathKey}`, - method: method.toUpperCase(), - path: pathKey, - summary: operation.summary || '', - tags: operation.tags || [], - }); - } - }); - }); - const article = createArticleDataForTag(tagOpenapi, operations, tagMeta); - article.fields.source = filePath; - article.fields.staticFilePath = filePath.replace(/^static\//, '/'); - return article; + 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) { + operations.push({ + operationId: operation.operationId || `${method}-${pathKey}`, + method: method.toUpperCase(), + path: pathKey, + summary: operation.summary || '', + tags: operation.tags || [], + }); + } + }); }); - 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); + 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 @@ -627,24 +677,28 @@ function writeOpenapiTagArticleData(sourcePath, targetPath, openapi, opts) { * @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'); + 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 @@ -658,19 +712,21 @@ function generateHugoDataByTag(options) { * @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'); + 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'); } // CommonJS export for backward compatibility module.exports = { - generateHugoData, - generateHugoDataByTag, + generateHugoData, + generateHugoDataByTag, }; -//# sourceMappingURL=index.js.map \ No newline at end of file +//# sourceMappingURL=index.js.map diff --git a/api-docs/scripts/generate-openapi-articles.ts b/api-docs/scripts/generate-openapi-articles.ts index a0b83af027..317910de62 100644 --- a/api-docs/scripts/generate-openapi-articles.ts +++ b/api-docs/scripts/generate-openapi-articles.ts @@ -308,7 +308,9 @@ interface GenerateTagPagesOptions { * * @param options - Generation options */ -function generateTagPagesFromArticleData(options: GenerateTagPagesOptions): void { +function generateTagPagesFromArticleData( + options: GenerateTagPagesOptions +): void { const { articlesPath, contentPath, @@ -417,7 +419,11 @@ ${yaml.dump(parentFrontmatter)}--- }; // Add operations for TOC generation (only for non-conceptual pages) - if (!isConceptual && article.fields.operations && article.fields.operations.length > 0) { + if ( + !isConceptual && + article.fields.operations && + article.fields.operations.length > 0 + ) { frontmatter.operations = article.fields.operations; } @@ -595,7 +601,10 @@ function processProduct(productKey: string, config: ProductConfig): void { // Step 5: Generate Hugo data from OpenAPI spec if (config.useTagBasedGeneration) { // Tag-based generation: group operations by OpenAPI tag - const staticTagsPath = path.join(staticPath, `influxdb-${productKey}/tags`); + const staticTagsPath = path.join( + staticPath, + `influxdb-${productKey}/tags` + ); console.log(`\n📋 Using tag-based generation for ${productKey}...`); openapiPathsToHugo.generateHugoDataByTag({ specFile: config.specFile, diff --git a/api-docs/scripts/openapi-paths-to-hugo-data/index.ts b/api-docs/scripts/openapi-paths-to-hugo-data/index.ts index 3d0d0f27ff..7e7187768b 100644 --- a/api-docs/scripts/openapi-paths-to-hugo-data/index.ts +++ b/api-docs/scripts/openapi-paths-to-hugo-data/index.ts @@ -366,7 +366,7 @@ function slugifyTag(tagName: string): string { const TAG_MENU_GROUPS: Record = { // Concepts group 'Quick start': 'Concepts', - 'Authentication': 'Concepts', + Authentication: 'Concepts', 'Headers and parameters': 'Concepts', 'Response codes': 'Concepts', // Data Operations group @@ -374,9 +374,9 @@ const TAG_MENU_GROUPS: Record = { 'Query data': 'Data Operations', 'Cache data': 'Data Operations', // Administration group - 'Database': 'Administration', - 'Table': 'Administration', - 'Token': 'Administration', + Database: 'Administration', + Table: 'Administration', + Token: 'Administration', // Processing Engine group 'Processing engine': 'Processing Engine', // Server group @@ -865,7 +865,7 @@ function createArticleDataForTag( operations: OperationMeta[], tagMeta?: Tag ): Article { - const tagName = openapi['x-tagGroup'] as string || ''; + const tagName = (openapi['x-tagGroup'] as string) || ''; const tagSlug = slugifyTag(tagName); const isConceptual = tagMeta?.['x-traitTag'] === true; @@ -875,7 +875,10 @@ function createArticleDataForTag( name: tagName, describes: Object.keys(openapi.paths), title: tagName, - description: tagMeta?.description || openapi.info?.description || `API reference for ${tagName}`, + description: + tagMeta?.description || + openapi.info?.description || + `API reference for ${tagName}`, tag: tagName, isConceptual, menuGroup: getMenuGroupForTag(tagName), @@ -939,7 +942,8 @@ function writeOpenapiTagArticleData( ) .map((filePath) => { const tagOpenapi = readFile(filePath); - const tagName = tagOpenapi['x-tagGroup'] as string || tagOpenapi.info?.title || ''; + const tagName = + (tagOpenapi['x-tagGroup'] as string) || tagOpenapi.info?.title || ''; const tagMeta = tagMetaMap.get(tagName); // Extract operations from the tag-filtered spec @@ -959,7 +963,11 @@ function writeOpenapiTagArticleData( }); }); - const article = createArticleDataForTag(tagOpenapi, operations, tagMeta); + const article = createArticleDataForTag( + tagOpenapi, + operations, + tagMeta + ); article.fields.source = filePath; article.fields.staticFilePath = filePath.replace(/^static\//, '/'); return article; @@ -978,7 +986,9 @@ function writeOpenapiTagArticleData( writeDataFile(articleCollection, yamlPath); writeJsonFile(articleCollection, jsonPath); - console.log(`Generated ${articles.length} tag-based articles in ${targetPath}`); + console.log( + `Generated ${articles.length} tag-based articles in ${targetPath}` + ); } catch (e) { console.error('Error writing tag article data:', e); } @@ -1003,13 +1013,17 @@ export interface GenerateHugoDataByTagOptions extends GenerateHugoDataOptions { * * @param options - Generation options */ -export function generateHugoDataByTag(options: GenerateHugoDataByTagOptions): void { +export function generateHugoDataByTag( + options: GenerateHugoDataByTagOptions +): void { 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}....`); + console.log( + `\nGenerating OpenAPI path files in ${options.dataOutPath}....` + ); writePathOpenapis(sourceFile, filenamePrefix, options.dataOutPath); } diff --git a/cypress/e2e/content/api-reference.cy.js b/cypress/e2e/content/api-reference.cy.js index e2798563c4..85a4ce957d 100644 --- a/cypress/e2e/content/api-reference.cy.js +++ b/cypress/e2e/content/api-reference.cy.js @@ -13,8 +13,8 @@ const fakeGoogleTagManager = { trackingOptIn: () => {}, - trackingOptOut: () => {} -} + trackingOptOut: () => {}, +}; describe('API reference content', () => { const subjects = [ @@ -49,43 +49,47 @@ describe('API reference content', () => { '/influxdb3/enterprise/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); // 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); + 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 () { @@ -101,8 +105,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 () { @@ -213,7 +216,9 @@ describe('API reference 3-column layout', () => { cy.get('.api-tabs-nav a').eq(1).should('have.class', 'is-active'); // Verify the first tab is no longer active - cy.get('.api-tabs-nav a').eq(0).should('not.have.class', 'is-active'); + cy.get('.api-tabs-nav a') + .eq(0) + .should('not.have.class', 'is-active'); }); }); @@ -225,7 +230,10 @@ describe('API reference 3-column layout', () => { it('restores tab from URL hash on page load', () => { // Use the current subject URL with hash instead of hardcoded old reference URL cy.visit(`${subject}#authentication`); - cy.get('.api-tabs-nav a[data-tab="authentication"]').should('have.class', 'is-active'); + cy.get('.api-tabs-nav a[data-tab="authentication"]').should( + 'have.class', + 'is-active' + ); cy.get('[data-tab-panel="authentication"]').should('be.visible'); }); }); @@ -245,7 +253,9 @@ describe('API reference 3-column layout', () => { describe('API Renderer', () => { it('loads API documentation renderer', () => { - cy.get('.api-reference-container, rapi-doc, .api-reference-wrapper').should('exist'); + cy.get( + '.api-reference-container, rapi-doc, .api-reference-wrapper' + ).should('exist'); }); it('displays spec download link', () => { From f1f2e00ca533ddb30398e6309132698b3fe21781 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:34:25 -0600 Subject: [PATCH 14/33] fix(api): Extract first sentence for header summary, add Overview section - Header summary now shows only the first sentence from description using regex extraction with fallback to first line for descriptions without sentence-ending punctuation - Added Overview section with full description after endpoints list --- layouts/api/list.html | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/layouts/api/list.html b/layouts/api/list.html index 9be024de96..47f5589a8f 100644 --- a/layouts/api/list.html +++ b/layouts/api/list.html @@ -31,13 +31,21 @@

{{ .Title }}

- {{/* Summary - brief description at top */}} + {{/* Summary - first sentence only at top */}} {{ with .Params.summary }}

{{ . | markdownify }}

{{ else }} - {{/* Fallback to first line of description if no summary */}} + {{/* Extract first sentence from description (or full text if no sentence ending) */}} {{ with .Description }} -

{{ . | truncate 200 | markdownify }}

+ {{ $matches := findRE `^[^.!?]*[.!?]` . 1 }} + {{ if gt (len $matches) 0 }} + {{ $firstSentence := index $matches 0 }} +

{{ $firstSentence | markdownify }}

+ {{ else }} + {{/* No sentence ending found - use first line or full description */}} + {{ $firstLine := index (split . "\n") 0 }} +

{{ $firstLine | markdownify }}

+ {{ end }} {{ end }} {{ end }}
@@ -90,6 +98,14 @@

Endpoints

{{ end }} + {{/* Overview - full tag description after endpoints */}} + {{ with .Description }} +
+

Overview

+ {{ . | markdownify }} +
+ {{ end }} + {{/* Hugo page content if any */}} {{ with .Content }}
From 45e7d5ff1b078d82e98b7f2aeccefc5b435d57a7 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:36:50 -0600 Subject: [PATCH 15/33] fix(api): Show operations in sidebar nav, fix CSS for menu and summary - Sidebar nav now shows operations with method badges and paths instead of duplicating tag names when groups are expanded - Added background color to nav group items to match sidebar - Increased max-height for expanded groups to accommodate all operations - Added styles for operation items in sidebar nav (.api-nav-operation) - Fixed summary paragraph width by setting flex-basis to 100% --- assets/styles/layouts/_api-layout.scss | 50 +++++++++++++++++++++++--- layouts/partials/api/sidebar-nav.html | 25 ++++++++++--- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/assets/styles/layouts/_api-layout.scss b/assets/styles/layouts/_api-layout.scss index bd44ba1ca2..310a4282ef 100644 --- a/assets/styles/layouts/_api-layout.scss +++ b/assets/styles/layouts/_api-layout.scss @@ -270,9 +270,10 @@ max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; + background: $body-bg; // Match sidebar background &.is-open { - max-height: 1000px; // Large enough to show all items + max-height: 2000px; // Large enough to show all operations } } } @@ -301,7 +302,7 @@ font-weight: $medium; } - // HTTP method badge + // HTTP method badge (legacy class) .method-badge { display: inline-block; font-size: 0.65rem; @@ -319,6 +320,41 @@ &.patch { background-color: $y-thunder; color: rgba($g5-pepper, 0.75); } &.delete { background-color: $r-curacao; color: #fff; } } + + // Operation items with method badges and paths + &.api-nav-operation { + a { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.85rem; + } + + .api-method { + display: inline-block; + font-size: 0.55rem; + font-weight: $bold; + text-transform: uppercase; + padding: 0.15rem 0.3rem; + border-radius: 3px; + min-width: 2rem; + text-align: center; + flex-shrink: 0; + + &--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; } + } + + .api-path { + font-family: $code; + font-size: 0.75rem; + word-break: break-all; + color: inherit; + } + } } } @@ -336,8 +372,14 @@ } .article--header-text { - flex: 1; - min-width: 200px; + 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 diff --git a/layouts/partials/api/sidebar-nav.html b/layouts/partials/api/sidebar-nav.html index 0acb89d756..31fb525af3 100644 --- a/layouts/partials/api/sidebar-nav.html +++ b/layouts/partials/api/sidebar-nav.html @@ -105,19 +105,36 @@

API Reference

{{ if $article }} {{ $path := index $article "path" }} {{ $fields := index $article "fields" }} - {{ $menuName := $tagName }} - {{ if and (reflect.IsMap $fields) (isset $fields "menuName") }} - {{ $menuName = index $fields "menuName" }} - {{ end }} {{ $isConceptual := false }} {{ if and (reflect.IsMap $fields) (isset $fields "isConceptual") }} {{ $isConceptual = index $fields "isConceptual" }} {{ end }} + {{/* Show operations from article data */}} + {{ $operations := slice }} + {{ if and (reflect.IsMap $fields) (isset $fields "operations") }} + {{ $operations = index $fields "operations" }} + {{ end }} + {{ if gt (len $operations) 0 }} + {{ range $operations }} +
  • + + {{ upper .method }} + {{ .path }} + +
  • + {{ end }} + {{ else }} + {{/* Fallback for conceptual pages or pages without operations */}} + {{ $menuName := $tagName }} + {{ if and (reflect.IsMap $fields) (isset $fields "menuName") }} + {{ $menuName = index $fields "menuName" }} + {{ end }} + {{ end }} {{ end }} {{ end }} From 1b79222c5d51fa589d178b7f4916533a447e43e1 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 14:38:54 -0600 Subject: [PATCH 16/33] fix(api): Match sidebar nav font sizes for operation items - Changed operation link font-size from 0.85rem to 0.95rem to match sidebar - Changed path code font-size from 0.75rem to 0.85rem for consistency - Adjusted method badge font-size from 0.55rem to 0.6rem for readability --- assets/styles/layouts/_api-layout.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/styles/layouts/_api-layout.scss b/assets/styles/layouts/_api-layout.scss index 310a4282ef..3c3c11ce94 100644 --- a/assets/styles/layouts/_api-layout.scss +++ b/assets/styles/layouts/_api-layout.scss @@ -327,17 +327,17 @@ display: flex; align-items: center; gap: 0.4rem; - font-size: 0.85rem; + font-size: 0.95rem; // Match sidebar nav font size } .api-method { display: inline-block; - font-size: 0.55rem; + font-size: 0.6rem; font-weight: $bold; text-transform: uppercase; padding: 0.15rem 0.3rem; border-radius: 3px; - min-width: 2rem; + min-width: 2.2rem; text-align: center; flex-shrink: 0; @@ -350,7 +350,7 @@ .api-path { font-family: $code; - font-size: 0.75rem; + font-size: 0.85rem; // Match sidebar nav font size word-break: break-all; color: inherit; } From f725421d0efa0678fedfa32f3a0e5d0bde872f3f Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 8 Dec 2025 15:37:13 -0600 Subject: [PATCH 17/33] fix(api): Reset button styles for dark mode, increase operation font size - Add button reset styles to .api-nav-group-header for dark mode compatibility (background: none, border: none, width: 100%) - Increase operation link font-size from 0.95rem to 1rem to match sidebar nav-item font size (18px base) - Increase api-path code font-size from 0.85rem to 0.9rem --- assets/styles/layouts/_api-layout.scss | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/assets/styles/layouts/_api-layout.scss b/assets/styles/layouts/_api-layout.scss index 3c3c11ce94..c4993f4b6e 100644 --- a/assets/styles/layouts/_api-layout.scss +++ b/assets/styles/layouts/_api-layout.scss @@ -240,6 +240,13 @@ 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: inherit; + font-family: inherit; &:hover { color: $nav-category-hover; @@ -327,7 +334,7 @@ display: flex; align-items: center; gap: 0.4rem; - font-size: 0.95rem; // Match sidebar nav font size + font-size: 1rem; // Match sidebar nav-item font size (18px base) } .api-method { @@ -350,7 +357,7 @@ .api-path { font-family: $code; - font-size: 0.85rem; // Match sidebar nav font size + font-size: 0.9rem; // Slightly smaller than link text for hierarchy word-break: break-all; color: inherit; } From 98ef7656252c893a7c061c25ffc1c90eb3d7d158 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 9 Dec 2025 16:05:53 -0600 Subject: [PATCH 18/33] feat(api): Integrate API navigation into Hugo menu system Add API nav items as children of "InfluxDB HTTP API" menu item: - New api-menu-items.html partial generates nav from data/articles.yml - Modified nested-menu.html to inject API nav for API parent menu item - Updated api_nav_groups.yml to add url for Administration group - Created Administration landing pages for Core and Enterprise - Updated .gitignore to allow hand-crafted API conceptual pages The Administration page uses layout: list and isConceptual: true to render content directly without RapiDoc wrapper. --- .gitignore | 4 + .../core/api/administration/_index.md | 26 +++ .../enterprise/api/administration/_index.md | 26 +++ data/api_nav_groups.yml | 7 + layouts/partials/sidebar/api-menu-items.html | 216 ++++++++++++++++++ layouts/partials/sidebar/nested-menu.html | 29 ++- 6 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 content/influxdb3/core/api/administration/_index.md create mode 100644 content/influxdb3/enterprise/api/administration/_index.md create mode 100644 layouts/partials/sidebar/api-menu-items.html diff --git a/.gitignore b/.gitignore index 8688142320..7a973e3116 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ package-lock.json /content/influxdb3/*/reference/api/** /static/openapi +# 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 diff --git a/content/influxdb3/core/api/administration/_index.md b/content/influxdb3/core/api/administration/_index.md new file mode 100644 index 0000000000..ba791c152a --- /dev/null +++ b/content/influxdb3/core/api/administration/_index.md @@ -0,0 +1,26 @@ +--- +title: Administration +description: Endpoints for managing databases, tables, and tokens. +type: api +layout: list +weight: 105 +isConceptual: true +--- + +Use the Administration API to manage InfluxDB resources: + + diff --git a/content/influxdb3/enterprise/api/administration/_index.md b/content/influxdb3/enterprise/api/administration/_index.md new file mode 100644 index 0000000000..833443bd44 --- /dev/null +++ b/content/influxdb3/enterprise/api/administration/_index.md @@ -0,0 +1,26 @@ +--- +title: Administration +description: Endpoints for managing databases, tables, and tokens. +type: api +layout: list +weight: 105 +isConceptual: true +--- + +Use the Administration API to manage InfluxDB resources: + + diff --git a/data/api_nav_groups.yml b/data/api_nav_groups.yml index 9598e548cc..637284c7eb 100644 --- a/data/api_nav_groups.yml +++ b/data/api_nav_groups.yml @@ -1,6 +1,12 @@ # API Navigation Groups # Defines sidebar navigation structure for API reference documentation # Tags are grouped by function/task for better UX +# +# Group fields: +# name: Display name in the navigation +# weight: Sort order (lower = higher) +# tags: List of tag names that belong to this group +# url: (optional) URL path suffix for the group page (relative to api/) groups: - name: Concepts @@ -27,6 +33,7 @@ groups: - name: Administration weight: 5 + url: administration tags: - Database - Table diff --git a/layouts/partials/sidebar/api-menu-items.html b/layouts/partials/sidebar/api-menu-items.html new file mode 100644 index 0000000000..2850e26560 --- /dev/null +++ b/layouts/partials/sidebar/api-menu-items.html @@ -0,0 +1,216 @@ +{{/* + API Reference Menu Items for Hugo Navigation + + Generates + + {{ else }} + {{/* Multi-tag group: group label (or link if url defined) with tag pages as children */}} + {{ $groupUrl := "" }} + {{ $groupIsActive := false }} + {{ with $group.url }} + {{ $groupUrl = print "/" $product "/" $version "/api/" . "/" | relURL }} + {{ $groupIsActive = eq $currentPage.RelPermalink (print "/" $product "/" $version "/api/" . "/") }} + {{ end }} + + {{ end }} + {{ end }} + {{ end }} +{{ end }} diff --git a/layouts/partials/sidebar/nested-menu.html b/layouts/partials/sidebar/nested-menu.html index 67cf9a1e00..12dd9eec14 100644 --- a/layouts/partials/sidebar/nested-menu.html +++ b/layouts/partials/sidebar/nested-menu.html @@ -1,22 +1,39 @@ {{ $page := .page }} {{ $menu := .menu }} +{{ $siteData := .siteData }} {{ define "recursiveMenu" }} {{ $menuContext := .menu }} {{ $currentPage := .currentPage }} + {{ $site := .site }} + {{ $siteData := .siteData }} {{ $depth := add .depth 1 }} {{ $navClass := cond (gt $depth 1) "item" "category" }} {{ range $menuContext }} + {{/* Check if this is the InfluxDB HTTP API menu item for InfluxDB 3 products */}} + {{ $isApiParent := and (eq .Name "InfluxDB HTTP API") (or (hasPrefix .URL "/influxdb3/") (hasPrefix .URL "/influxdb/")) }} + {{ end }} @@ -199,7 +205,13 @@ {{ end }} From ae50b8aaa709e729f7a5bc565f1812a0d051be59 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Thu, 11 Dec 2025 17:25:38 -0600 Subject: [PATCH 22/33] feat(api): Add RapiDoc Mini component for API operation pages The feature is shippable, but needs a few small fixes and we'll need to update or alias all API docs links for Core and Ent3. - Add rapidoc-mini.ts TypeScript component with CDN loading and theme sync - Add api-operation layout for standalone operation pages - Add rapidoc-mini.html partial for reusable RapiDoc rendering - Add rapidoc-custom.css for RapiDoc style overrides - Register rapidoc-mini component in main.js - Add article data for cloud-dedicated and clustered products - Update API reference Cypress tests --- api-docs/influxdb3/core/v3/ref.yml | 40 +- api-docs/influxdb3/enterprise/v3/ref.yml | 173 +-------- .../scripts/dist/generate-openapi-articles.js | 128 ++++++- .../dist/openapi-paths-to-hugo-data/index.js | 16 + api-docs/scripts/generate-openapi-articles.ts | 176 ++++++++- .../openapi-paths-to-hugo-data/index.ts | 20 + assets/js/components/rapidoc-mini.ts | 334 +++++++++++++++++ assets/js/main.js | 2 + assets/styles/layouts/_api-layout.scss | 53 ++- assets/styles/layouts/_sidebar.scss | 15 +- cypress/e2e/content/api-reference.cy.js | 156 ++++++++ .../influxdb/cloud-dedicated/articles.json | 30 ++ .../influxdb/clustered/articles.json | 66 ++++ .../influxdb/influxdb3_core/articles.json | 170 +++------ .../influxdb/influxdb3_core/articles.yml | 295 +++++---------- .../influxdb3_enterprise/articles.json | 194 +++------- .../influxdb3_enterprise/articles.yml | 354 +++++------------- layouts/api-operation/operation.html | 73 ++++ layouts/api/list.html | 18 +- layouts/partials/api/rapidoc-mini.html | 135 +++++++ layouts/partials/api/rapidoc.html | 150 +++++++- layouts/partials/sidebar/api-menu-items.html | 56 ++- static/css/rapidoc-custom.css | 18 + 23 files changed, 1693 insertions(+), 979 deletions(-) create mode 100644 assets/js/components/rapidoc-mini.ts create mode 100644 data/article_data/influxdb/cloud-dedicated/articles.json create mode 100644 data/article_data/influxdb/clustered/articles.json create mode 100644 layouts/api-operation/operation.html create mode 100644 layouts/partials/api/rapidoc-mini.html create mode 100644 static/css/rapidoc-custom.css diff --git a/api-docs/influxdb3/core/v3/ref.yml b/api-docs/influxdb3/core/v3/ref.yml index 8a7e4907f7..cf43a9c9bb 100644 --- a/api-docs/influxdb3/core/v3/ref.yml +++ b/api-docs/influxdb3/core/v3/ref.yml @@ -90,33 +90,20 @@ tags: - [Manage the Distinct Value Cache](/influxdb3/core/admin/distinct-value-cache/) - [Manage the Last Value Cache](/influxdb3/core/admin/last-value-cache/) - - name: Compatibility endpoints + - name: Migrate from InfluxDB v1 or v2 + x-traitTag: true description: | - InfluxDB 3 provides compatibility endpoints for InfluxDB 1.x and InfluxDB 2.x workloads and clients. - - ### Write data using v1- or v2-compatible endpoints - - - [`/api/v2/write` endpoint](#operation/PostV2Write) - for InfluxDB v2 clients and when you bring existing InfluxDB v2 write workloads to InfluxDB 3. - - [`/write` endpoint](#operation/PostV1Write) for InfluxDB v1 clients and when you bring existing InfluxDB v1 write workloads to InfluxDB 3. - - For new workloads, use the [`/api/v3/write_lp` endpoint](#operation/PostWriteLP). - - All endpoints accept the same line protocol format. - - ### Query data - - Use the HTTP [`/query`](#operation/GetV1ExecuteQuery) endpoint for InfluxDB v1 clients and v1 query workloads using InfluxQL. - - For new workloads, use one of the following: + Migrate your existing InfluxDB v1 or v2 workloads to InfluxDB 3. - - HTTP [`/api/v3/query_sql` endpoint](#operation/GetExecuteQuerySQL) for new query workloads using SQL. - - HTTP [`/api/v3/query_influxql` endpoint](#operation/GetExecuteInfluxQLQuery) for new query workloads using InfluxQL. - - Flight SQL and InfluxDB 3 _Flight+gRPC_ APIs for querying with SQL or InfluxQL. For more information about using Flight APIs, see [InfluxDB 3 client libraries](https://github.com/InfluxCommunity?q=influxdb3&type=public&language=&sort=). + InfluxDB 3 provides compatibility endpoints that work with InfluxDB 1.x and 2.x client libraries and tools. + Operations marked with v1 or v2 badges are compatible with the respective InfluxDB version. - ### Server information + ### Migration guides - Server information endpoints such as `/health` and `metrics` are compatible with InfluxDB 1.x and InfluxDB 2.x clients. + - [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 - name: Database description: Manage databases - description: | @@ -340,7 +327,6 @@ paths: '413': description: Request entity too large. tags: - - Compatibility endpoints - Write data x-influxdata-guides: - title: Use compatibility APIs to write data @@ -429,7 +415,6 @@ paths: '413': description: Request entity too large. tags: - - Compatibility endpoints - Write data x-influxdata-guides: - title: Use compatibility APIs to write data @@ -852,7 +837,6 @@ paths: description: Unprocessable entity. tags: - Query data - - Compatibility endpoints x-influxdata-guides: - title: Use the InfluxDB v1 HTTP query API and InfluxQL to query data href: /influxdb3/core/query-data/execute-queries/influxdb-v1-api/ @@ -970,7 +954,6 @@ paths: description: Unprocessable entity. tags: - Query data - - Compatibility endpoints x-influxdata-guides: - title: Use the InfluxDB v1 HTTP query API and InfluxQL to query data href: /influxdb3/core/query-data/execute-queries/influxdb-v1-api/ @@ -999,7 +982,6 @@ paths: description: Service is unavailable. tags: - Server information - - Compatibility endpoints /ping: get: operationId: GetPing @@ -2627,10 +2609,10 @@ x-tagGroups: tags: - Quick start - Authentication + - Migrate from InfluxDB v1 or v2 - Cache data - Common parameters - Response codes - - Compatibility endpoints - Database - Processing engine - Server information diff --git a/api-docs/influxdb3/enterprise/v3/ref.yml b/api-docs/influxdb3/enterprise/v3/ref.yml index 24cdf5c053..f29e601dcb 100644 --- a/api-docs/influxdb3/enterprise/v3/ref.yml +++ b/api-docs/influxdb3/enterprise/v3/ref.yml @@ -5,18 +5,15 @@ info: The InfluxDB HTTP API for InfluxDB 3 Enterprise provides a programmatic interface for interacting with InfluxDB 3 Enterprise databases and resources. Use this API to: - - Write data to InfluxDB 3 Enterprise databases - Query data using SQL or InfluxQL - Process data using Processing engine plugins - Manage databases, tables, and Processing engine triggers - Perform administrative tasks and access system information - The API includes endpoints under the following paths: - `/api/v3`: InfluxDB 3 Enterprise native endpoints - `/`: Compatibility endpoints for InfluxDB v1 workloads and clients - `/api/v2/write`: Compatibility endpoint for InfluxDB v2 workloads and clients -