diff --git a/.ci/link-checker/production.lycherc.toml b/.ci/link-checker/production.lycherc.toml index c838590df2..cd13f166a1 100644 --- a/.ci/link-checker/production.lycherc.toml +++ b/.ci/link-checker/production.lycherc.toml @@ -73,6 +73,20 @@ exclude = [ "^https?://claude\\.ai", "^https?://.*\\.claude\\.ai", + # Microsoft Learn documentation (bot detection/rate limiting) + "^https?://learn\\.microsoft\\.com", + "^https?://.*\\.microsoft\\.com/.*", + + # Dremio download URLs (403 errors for automated requests) + "^https?://download\\.dremio\\.com", + + # Scarf analytics tracking pixels (certificate/network errors) + "^https?://static\\.scarf\\.sh", + + # Grafana documentation (bot detection/rate limiting) + "^https?://grafana\\.com", + "^https?://.*\\.grafana\\.com", + # Production site URLs (when testing locally, these should be relative) # This excludes canonical URLs and other absolute production URLs # TODO: Remove after fixing canonical URL generation or link-checker domain replacement diff --git a/.ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt b/.ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt index a560770213..8a451ac332 100644 --- a/.ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt +++ b/.ci/vale/styles/config/vocabularies/InfluxDataDocs/accept.txt @@ -36,6 +36,7 @@ Redoc SQLAlchemy SQLAlchemy Splunk +System.Data.Odbc [Ss]uperset TBs? \bUI\b diff --git a/.github/agents/typescript-hugo-agent.md b/.github/agents/typescript-hugo-agent.md new file mode 100644 index 0000000000..44be6d621d --- /dev/null +++ b/.github/agents/typescript-hugo-agent.md @@ -0,0 +1,545 @@ +# TypeScript & Hugo Development Agent + +You are a specialized TypeScript and Hugo development expert for the InfluxData documentation site. Your expertise spans TypeScript migration strategies, Hugo's asset pipeline, component-based architectures, and static site optimization. + +## Core Expertise + +### TypeScript Development +- **Migration Strategy**: Guide incremental TypeScript adoption in existing ES6 modules +- **Type Systems**: Create robust type definitions for documentation components +- **Configuration**: Set up optimal `tsconfig.json` for Hugo browser environments +- **Integration**: Configure TypeScript compilation within Hugo's asset pipeline +- **Compatibility**: Ensure backward compatibility during migration phases + +### Hugo Static Site Generator +- **Asset Pipeline**: Deep understanding of Hugo's extended asset processing +- **Build Process**: Optimize TypeScript compilation for Hugo's build system +- **Shortcodes**: Integrate TypeScript components with Hugo shortcodes +- **Templates**: Handle Hugo template data in TypeScript components +- **Performance**: Optimize for both development (`hugo server`) and production builds + +### Component Architecture +- **Registry Pattern**: Maintain and enhance the existing component registry system +- **Data Attributes**: Preserve `data-component` initialization pattern +- **Module System**: Work with ES6 modules and TypeScript module resolution +- **Service Layer**: Type and enhance services for API interactions +- **Utilities**: Create strongly-typed utility functions + +## Primary Responsibilities + +### 1. TypeScript Migration Setup + +#### Initial Configuration +```typescript +// tsconfig.json configuration for Hugo +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "moduleResolution": "node", + "baseUrl": "./assets/js", + "paths": { + "@components/*": ["components/*"], + "@services/*": ["services/*"], + "@utils/*": ["utils/*"] + }, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "incremental": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "outDir": "./assets/js/dist", + "rootDir": "./assets/js" + }, + "include": ["assets/js/**/*"], + "exclude": ["node_modules", "public", "resources"] +} +``` + +#### Hugo Pipeline Integration +```yaml +# config/_default/config.yaml +build: + useResourceCacheWhen: fallback + writeStats: true + +params: + assets: + typescript: + enabled: true + sourceMap: true + minify: production +``` + +### 2. Component Migration Pattern + +#### TypeScript Component Template +```typescript +// components/example-component/example-component.ts +interface ExampleComponentConfig { + apiEndpoint?: string; + refreshInterval?: number; + onUpdate?: (data: unknown) => void; +} + +interface ExampleComponentElements { + root: HTMLElement; + trigger?: HTMLButtonElement; + content?: HTMLDivElement; +} + +export class ExampleComponent { + private config: Required; + private elements: ExampleComponentElements; + private state: Map = new Map(); + + constructor(element: HTMLElement, config: ExampleComponentConfig = {}) { + this.elements = { root: element }; + this.config = this.mergeConfig(config); + this.init(); + } + + private mergeConfig(config: ExampleComponentConfig): Required { + return { + apiEndpoint: config.apiEndpoint ?? '/api/default', + refreshInterval: config.refreshInterval ?? 5000, + onUpdate: config.onUpdate ?? (() => {}) + }; + } + + private init(): void { + this.cacheElements(); + this.bindEvents(); + this.render(); + } + + private cacheElements(): void { + this.elements.trigger = this.elements.root.querySelector('[data-trigger]') ?? undefined; + this.elements.content = this.elements.root.querySelector('[data-content]') ?? undefined; + } + + private bindEvents(): void { + this.elements.trigger?.addEventListener('click', this.handleClick.bind(this)); + } + + private handleClick(event: MouseEvent): void { + event.preventDefault(); + this.updateContent(); + } + + private async updateContent(): Promise { + // Implementation + } + + private render(): void { + // Implementation + } + + public destroy(): void { + this.elements.trigger?.removeEventListener('click', this.handleClick.bind(this)); + this.state.clear(); + } +} + +// Register with component registry +import { registerComponent } from '@utils/component-registry'; +registerComponent('example-component', ExampleComponent); +``` + +#### Migration Strategy for Existing Components +```typescript +// Step 1: Add type definitions alongside existing JS +// types/components.d.ts +declare module '@components/url-select' { + export class UrlSelect { + constructor(element: HTMLElement); + destroy(): void; + } +} + +// Step 2: Create wrapper with types +// components/url-select/url-select.ts +import { UrlSelect as UrlSelectJS } from './url-select.js'; + +export interface UrlSelectConfig { + storageKey?: string; + defaultUrl?: string; +} + +export class UrlSelect extends UrlSelectJS { + constructor(element: HTMLElement, config?: UrlSelectConfig) { + super(element, config); + } +} + +// Step 3: Gradually migrate internals to TypeScript +``` + +### 3. Hugo Asset Pipeline Configuration + +#### TypeScript Processing with Hugo Pipes +```javascript +// assets/js/main.ts (entry point) +import { ComponentRegistry } from './utils/component-registry'; +import './components/index'; // Auto-register all components + +// Initialize on DOM ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', ComponentRegistry.initializeAll); +} else { + ComponentRegistry.initializeAll(); +} + +// Hugo template integration +{{ $opts := dict "targetPath" "js/main.js" "minify" (eq hugo.Environment "production") }} +{{ $ts := resources.Get "js/main.ts" | js.Build $opts }} +{{ if eq hugo.Environment "development" }} + +{{ else }} + {{ $ts = $ts | fingerprint }} + +{{ end }} +``` + +#### Build Performance Optimization +```typescript +// utils/lazy-loader.ts +export class LazyLoader { + private static cache = new Map>(); + + static async loadComponent(name: string): Promise { + if (!this.cache.has(name)) { + this.cache.set(name, + import(/* webpackChunkName: "[request]" */ `@components/${name}/${name}`) + ); + } + return this.cache.get(name); + } +} + +// Usage in component registry +async function initializeComponent(element: HTMLElement): Promise { + const componentName = element.dataset.component; + if (!componentName) return; + + const module = await LazyLoader.loadComponent(componentName); + const Component = module.default || module[componentName]; + new Component(element); +} +``` + +### 4. Type Definitions for Hugo Context + +```typescript +// types/hugo.d.ts +interface HugoPage { + title: string; + description: string; + permalink: string; + section: string; + params: Record; +} + +interface HugoSite { + baseURL: string; + languageCode: string; + params: { + influxdb_urls: Array<{ + url: string; + name: string; + cloud?: boolean; + }>; + }; +} + +declare global { + interface Window { + Hugo: { + page: HugoPage; + site: HugoSite; + }; + docsData: { + page: HugoPage; + site: HugoSite; + }; + } +} + +export {}; +``` + +### 5. Testing Integration + +#### Cypress with TypeScript +```typescript +// cypress/support/commands.ts +Cypress.Commands.add('initComponent', (componentName: string, config?: Record) => { + cy.window().then((win) => { + const element = win.document.querySelector(`[data-component="${componentName}"]`); + if (element) { + // Initialize component with TypeScript + const event = new CustomEvent('component:init', { detail: config }); + element.dispatchEvent(event); + } + }); +}); + +// cypress/support/index.d.ts +declare namespace Cypress { + interface Chainable { + initComponent(componentName: string, config?: Record): Chainable; + } +} +``` + +### 6. Development Workflow + +#### NPM Scripts for TypeScript Development +```json +{ + "scripts": { + "ts:check": "tsc --noEmit", + "ts:build": "tsc", + "ts:watch": "tsc --watch", + "dev": "concurrently \"yarn ts:watch\" \"hugo server\"", + "build": "yarn ts:build && hugo --minify", + "lint:ts": "eslint 'assets/js/**/*.ts'", + "format:ts": "prettier --write 'assets/js/**/*.ts'" + } +} +``` + +#### VSCode Configuration +```json +// .vscode/settings.json +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + } + }, + "typescript.preferences.importModuleSpecifier": "relative", + "typescript.preferences.quoteStyle": "single" +} +``` + +## Migration Guidelines + +### Phase 1: Setup (Week 1) +1. Install TypeScript and type definitions +2. Configure `tsconfig.json` for Hugo environment +3. Set up build scripts and Hugo pipeline +4. Create type definitions for existing globals + +### Phase 2: Type Definitions (Week 2) +1. Create `.d.ts` files for existing JS modules +2. Add type definitions for Hugo context +3. Type external dependencies +4. Set up ambient declarations + +### Phase 3: Incremental Migration (Weeks 3-8) +1. Start with utility modules (pure functions) +2. Migrate service layer (API interactions) +3. Convert leaf components (no dependencies) +4. Migrate complex components +5. Update component registry + +### Phase 4: Optimization (Week 9-10) +1. Implement code splitting +2. Set up lazy loading +3. Optimize build performance +4. Configure production builds + +## Best Practices + +### TypeScript Conventions +- Use strict mode from the start +- Prefer interfaces over type aliases for objects +- Use const assertions for literal types +- Implement proper error boundaries +- Use discriminated unions for state management + +### Hugo Integration +- Leverage Hugo's build stats for optimization +- Use resource bundling for related assets +- Implement proper cache busting +- Utilize Hugo's minification in production +- Keep source maps in development only + +### Component Guidelines +- One component per file +- Co-locate types with implementation +- Use composition over inheritance +- Implement cleanup in destroy methods +- Follow single responsibility principle + +### Performance Considerations +- Use dynamic imports for large components +- Implement intersection observer for lazy loading +- Minimize bundle size with tree shaking +- Use TypeScript's `const enum` for better optimization +- Leverage browser caching strategies + +## Debugging Strategies + +### Development Tools +```typescript +// utils/debug.ts +export const debug = { + log: (component: string, message: string, data?: unknown): void => { + if (process.env.NODE_ENV === 'development') { + console.log(`[${component}]`, message, data); + } + }, + + time: (label: string): void => { + if (process.env.NODE_ENV === 'development') { + console.time(label); + } + }, + + timeEnd: (label: string): void => { + if (process.env.NODE_ENV === 'development') { + console.timeEnd(label); + } + } +}; +``` + +### Source Maps Configuration +```javascript +// hugo.config.js for development +module.exports = { + module: { + rules: [{ + test: /\.ts$/, + use: { + loader: 'ts-loader', + options: { + compilerOptions: { + sourceMap: true + } + } + } + }] + }, + devtool: 'inline-source-map' +}; +``` + +## Common Patterns + +### State Management +```typescript +// utils/state-manager.ts +export class StateManager> { + private state: T; + private listeners: Set<(state: T) => void> = new Set(); + + constructor(initialState: T) { + this.state = { ...initialState }; + } + + get current(): Readonly { + return Object.freeze({ ...this.state }); + } + + update(updates: Partial): void { + this.state = { ...this.state, ...updates }; + this.notify(); + } + + subscribe(listener: (state: T) => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private notify(): void { + this.listeners.forEach(listener => listener(this.current)); + } +} +``` + +### API Service Pattern +```typescript +// services/base-service.ts +export abstract class BaseService { + protected baseURL: string; + protected headers: HeadersInit; + + constructor(baseURL: string = '') { + this.baseURL = baseURL; + this.headers = { + 'Content-Type': 'application/json' + }; + } + + protected async request( + endpoint: string, + options: RequestInit = {} + ): Promise { + const url = `${this.baseURL}${endpoint}`; + const config: RequestInit = { + ...options, + headers: { ...this.headers, ...options.headers } + }; + + const response = await fetch(url, config); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); + } +} +``` + +## Troubleshooting Guide + +### Common Issues and Solutions + +1. **Module Resolution Issues** + - Check `tsconfig.json` paths configuration + - Verify Hugo's asset directory structure + - Ensure proper file extensions in imports + +2. **Type Definition Conflicts** + - Use namespace isolation for global types + - Check for duplicate declarations + - Verify ambient module declarations + +3. **Build Performance** + - Enable incremental compilation + - Use project references for large codebases + - Implement proper code splitting + +4. **Runtime Errors** + - Verify TypeScript target matches browser support + - Check for proper polyfills + - Ensure correct module format for Hugo + +5. **Hugo Integration Issues** + - Verify resource pipeline configuration + - Check for proper asset fingerprinting + - Ensure correct build environment detection + +## Reference Documentation + +- [TypeScript Documentation](https://www.typescriptlang.org/docs/) +- [Hugo Pipes Documentation](https://gohugo.io/hugo-pipes/) +- [ESBuild with Hugo](https://gohugo.io/hugo-pipes/js/) +- [TypeScript ESLint](https://typescript-eslint.io/) +- [Cypress TypeScript](https://docs.cypress.io/guides/tooling/typescript-support) \ No newline at end of file diff --git a/.github/instructions/content.instructions.md b/.github/instructions/content.instructions.md index cd6c3019fe..458daf82b4 100644 --- a/.github/instructions/content.instructions.md +++ b/.github/instructions/content.instructions.md @@ -201,6 +201,7 @@ those frontmatter keys. Frontmatter defined on the page overrides frontmatter #### Use shared content in a page +This repository makes heavy use of shared content to avoid duplication across InfluxDB editions and versions. Use the `source` frontmatter to specify a shared file to use to populate the page content. Shared files are typically stored in the `/content/shared` directory. @@ -225,6 +226,9 @@ The keyword `version` gets replaced during the build process with the appropriat [CLI serve command](/influxdb3/{{% product-key %}}/reference/cli/influxdb3/serve/) ``` +Don't list links to related content at the bottom of shared content files. +Instead, add the `related` frontmatter to the individual pages that use the shared content. + #### Shortcodes in Markdown files For the complete shortcodes reference, see `/.github/instructions/shortcodes-reference.instructions.md`. diff --git a/.github/instructions/contributing.instructions.md b/.github/instructions/contributing.instructions.md index 80a4715d6c..6c17d9fdc4 100644 --- a/.github/instructions/contributing.instructions.md +++ b/.github/instructions/contributing.instructions.md @@ -176,4 +176,3 @@ docker compose run -T vale --config=content/influxdb/cloud-dedicated/.vale.ini - #### Configure style rules _See full CONTRIBUTING.md for complete details._ - diff --git a/.gitignore b/.gitignore index 32765da72f..4e0b03b35f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ tmp .idea **/config.toml +# TypeScript build output +**/dist/ + # User context files for AI assistant tools .context/* !.context/README.md diff --git a/TESTING.md b/TESTING.md index 233bb3a367..b012eb664d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -431,7 +431,7 @@ LEFTHOOK=0 git commit yarn test:e2e # Run specific E2E specs -node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/article-links.cy.js" +node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/index.cy.js" ``` ### JavaScript Testing and Debugging diff --git a/assets/js/influxdb-version-detector.ts b/assets/js/influxdb-version-detector.ts new file mode 100644 index 0000000000..9f94babb5b --- /dev/null +++ b/assets/js/influxdb-version-detector.ts @@ -0,0 +1,2407 @@ +/** + * InfluxDB Version Detector Component + * + * Helps users identify which InfluxDB product they're using through a + * guided questionnaire with URL detection and scoring-based recommendations. + * + * DECISION TREE LOGIC (from .context/drafts/influxdb-version-detector/influxdb-decision-tree.md): + * + * ## Primary Detection Flow + * + * START: User enters URL + * | + * ├─→ URL matches known cloud patterns? + * │ │ + * │ ├─→ YES: Contains "influxdb.io" → **InfluxDB Cloud Dedicated** ✓ + * │ ├─→ YES: Contains "cloud2.influxdata.com" regions → **InfluxDB Cloud Serverless** ✓ + * │ ├─→ YES: Contains "influxcloud.net" → **InfluxDB Cloud 1** ✓ + * │ └─→ YES: Contains other cloud2 regions → **InfluxDB Cloud (TSM)** ✓ + * │ + * └─→ NO: Check port and try /ping endpoint + * │ + * ├─→ Port 8181 detected? → Strong indicator of v3 (Core/Enterprise) + * | | Returns 200 (auth successful or disabled)? + * | │ │--> `x-influxdb-build: Enterprise` -> **InfluxDB 3 Enterprise** ✓ (definitive) + * | │ │--> `x-influxdb-build: Core` -> **InfluxDB 3 Core** ✓ (definitive) + * │ │ + * │ ├─→ Returns 401 Unauthorized (default - auth required)? + * │ │ + * │ └─→ Ask "Paid or Free?" + * │ ├─→ Paid → **InfluxDB 3 Enterprise** ✓ (definitive) + * │ └─→ Free → **InfluxDB 3 Core** ✓ (definitive) + * | + * ├─→ Port 8086 detected? → Strong indicator of legacy (OSS/Enterprise) + * │ │ ⚠️ NOTE: v1.x ping auth optional (ping-auth-enabled), v2.x always open + * │ │ + * │ ├─→ Returns 401 Unauthorized? + * │ │ │ Could be v1.x with ping-auth-enabled=true OR Enterprise + * │ │ │ + * │ │ └─→ Ask "Paid or Free?" → Show ranked results + * │ │ + * │ ├─→ Returns 200/204 (accessible)? + * │ │ │ Likely v2.x OSS (always open) or v1.x with ping-auth-enabled=false + * │ │ │ + * │ │ └─→ Continue to questionnaire + * │ + * └─→ Blocked/Can't detect? + * │ + * └─→ Start questionnaire + * + * ## Questionnaire Flow (No URL or after detection) + * + * Q1: Which type of license do you have? + * ├─→ Paid/Commercial License + * ├─→ Free/Open Source (including free cloud tiers) + * └─→ I'm not sure + * + * Q2: Is your InfluxDB hosted by InfluxData (cloud) or self-hosted? + * ├─→ Cloud service (hosted by InfluxData) + * ├─→ Self-hosted (on your own servers) + * └─→ I'm not sure + * + * Q3: How long has your server been in place? + * ├─→ Recently installed (less than 1 year) + * ├─→ 1-5 years + * ├─→ More than 5 years + * └─→ I'm not sure + * + * Q4: Which query language(s) do you use? + * ├─→ SQL + * ├─→ InfluxQL + * ├─→ Flux + * ├─→ Multiple languages + * └─→ I'm not sure + * + * ## Definitive Determinations (Stop immediately, no more questions) + * + * 1. **401 + Port 8181 + Paid** → InfluxDB 3 Enterprise ✓ + * 2. **401 + Port 8181 + Free** → InfluxDB 3 Core ✓ + * 3. **URL matches cloud pattern** → Specific cloud product ✓ + * 4. **x-influxdb-build header** → Definitive product identification ✓ + * + * ## Scoring System (When not definitive) + * + * ### Elimination Rules + * - **Free + Self-hosted** → Eliminates all cloud products + * - **Free** → Eliminates: 3 Enterprise, Enterprise, Clustered, Cloud Dedicated, Cloud 1 + * - **Paid + Self-hosted** → Eliminates all cloud products + * - **Paid + Cloud** → Eliminates all self-hosted products + * - **Free + Cloud** → Eliminates all self-hosted products, favors Serverless/TSM + * + * ### Strong Signals (High points) + * - **401 Response**: +50 for v3 products, +30 for Clustered + * - **Port 8181**: +30 for v3 products + * - **Port 8086**: +20 for legacy products + * - **SQL Language**: +40 for v3 products, eliminates v1/v2 + * - **Flux Language**: +30 for v2 era, eliminates v1 and v3 + * - **Server Age 5+ years**: +30 for v1 products, -50 for v3 + * + * ### Ranking Display Rules + * - Only show "Most Likely" if: + * - Top score > 30 (not low confidence) + * - AND difference between #1 and #2 is ≥ 15 points + * - Show manual verification commands only if: + * - Confidence is not high (score < 60) + * - AND it's a self-hosted product + * - AND user didn't say it's cloud + */ + +import { getInfluxDBUrls } from './services/local-storage.js'; + +interface QueryLanguageConfig { + required_params: string[]; + optional_params?: string[]; +} + +interface ProductConfig { + name?: string; + query_languages: Record; + characteristics: string[]; + placeholder_host?: string; + detection?: { + url_contains?: string[]; + ping_headers?: Record; + }; +} + +interface Products { + [key: string]: ProductConfig; +} + +interface Answers { + context?: string | null; + portClue?: string | null; + isCloud?: boolean; + isDocker?: boolean; + paid?: string; + hosted?: string; + age?: string; + language?: string; + auth?: string; + data?: string; + version?: string; + [key: string]: string | boolean | null | undefined; +} + +interface ComponentOptions { + component: HTMLElement; +} + +interface AnalyticsEventData { + detected_product?: string; + detection_method?: string; + interaction_type: string; + section?: string; + completion_status?: string; + question_id?: string; + answer_value?: string; +} + +// Global gtag function type declaration +declare global { + interface Window { + gtag?: ( + _event: string, + _action: string, + _parameters?: Record + ) => void; + } +} + +class InfluxDBVersionDetector { + private container: HTMLElement; + private products: Products; + private influxdbUrls: Record; + private answers: Answers = {}; + private initialized: boolean = false; + private questionFlow: string[] = []; + private currentQuestionIndex = 0; + private questionHistory: string[] = []; // Track question history for back navigation + private progressBar: HTMLElement | null = null; + private resultDiv: HTMLElement | null = null; + private restartBtn: HTMLElement | null = null; + private currentContext: 'questionnaire' | 'result' = 'questionnaire'; + + constructor(options: ComponentOptions) { + this.container = options.component; + + // Parse data attributes from the component element + const { products, influxdbUrls } = this.parseComponentData(); + + this.products = products; + this.influxdbUrls = influxdbUrls; + + // Check if component is in a modal + const modal = this.container.closest('.modal-content'); + if (modal) { + // If in modal, wait for modal to be opened before initializing + this.initializeForModal(); + } else { + // If not in modal, initialize immediately + this.init(); + } + } + + private parseComponentData(): { + products: Products; + influxdbUrls: Record; + } { + let products: Products = {}; + let influxdbUrls: Record = {}; + + // Parse products data - Hugo always provides this data + const productsData = this.container.getAttribute('data-products'); + if (productsData) { + try { + products = JSON.parse(productsData); + } catch (error) { + console.warn('Failed to parse products data:', error); + } + } + + // Parse influxdb URLs data + const influxdbUrlsData = this.container.getAttribute('data-influxdb-urls'); + if (influxdbUrlsData && influxdbUrlsData !== '#ZgotmplZ') { + try { + influxdbUrls = JSON.parse(influxdbUrlsData); + } catch (error) { + console.warn('Failed to parse influxdb_urls data:', error); + influxdbUrls = {}; // Fallback to empty object + } + } else { + console.debug( + 'InfluxDB URLs data not available or blocked by template security. ' + + 'This is expected when Hugo data is unavailable.' + ); + influxdbUrls = {}; // Fallback to empty object + } + + return { products, influxdbUrls }; + } + + private init(): void { + this.render(); + this.setupPlaceholders(); + this.attachEventListeners(); + this.showQuestion('q-url-known'); + this.initialized = true; + + // Track modal opening + this.trackAnalyticsEvent({ + interaction_type: 'modal_opened', + section: this.getCurrentPageSection(), + }); + } + + private setupPlaceholders(): void { + // This method is called at init but some placeholders need to be set + // when questions are actually displayed since DOM elements don't exist yet + } + + private setupPingHeadersPlaceholder(): void { + const pingHeaders = this.container.querySelector('#ping-headers'); + if (pingHeaders) { + const exampleContent = [ + '# Replace this with your actual response headers', + '# Example formats:', + '', + '# InfluxDB 3 Core:', + 'HTTP/1.1 200 OK', + 'x-influxdb-build: core', + 'x-influxdb-version: 3.1.0', + '', + '# InfluxDB 3 Enterprise:', + 'HTTP/1.1 200 OK', + 'x-influxdb-build: enterprise', + 'x-influxdb-version: 3.1.0', + '', + '# InfluxDB v2 OSS:', + 'HTTP/1.1 204 No Content', + 'X-Influxdb-Build: OSS', + 'X-Influxdb-Version: 2.7.8', + '', + '# InfluxDB v1:', + 'HTTP/1.1 204 No Content', + 'X-Influxdb-Version: 1.8.10', + ].join('\n'); + + (pingHeaders as HTMLTextAreaElement).value = exampleContent; + + // Select all text when user clicks in the textarea so they can easily replace it + pingHeaders.addEventListener('focus', () => { + (pingHeaders as HTMLTextAreaElement).select(); + }); + } + } + + private setupDockerOutputPlaceholder(): void { + const dockerOutput = this.container.querySelector('#docker-output'); + if (dockerOutput) { + const exampleContent = [ + '# Replace this with your actual command output', + '# Example formats:', + '', + '# Version command output:', + 'InfluxDB 3.1.0 (git: abc123def)', + 'or', + 'InfluxDB v2.7.8 (git: 407fa622e)', + '', + '# Ping headers from curl -I:', + 'HTTP/1.1 200 OK', + 'x-influxdb-build: core', + 'x-influxdb-version: 3.1.0', + '', + '# Startup logs:', + '2024-01-01T00:00:00.000Z info InfluxDB starting', + '2024-01-01T00:00:00.000Z info InfluxDB 3.1.0 (git: abc123)', + ].join('\n'); + + (dockerOutput as HTMLTextAreaElement).value = exampleContent; + + // Select all text when user clicks in the textarea so they can easily replace it + dockerOutput.addEventListener('focus', () => { + (dockerOutput as HTMLTextAreaElement).select(); + }); + } + } + + private getCurrentPageSection(): string { + // Extract meaningful section from current page + const path = window.location.pathname; + const pathSegments = path.split('/').filter((segment) => segment); + + // Try to get a meaningful section name + if (pathSegments.length >= 3) { + return pathSegments.slice(0, 3).join('/'); // e.g., "influxdb3/core/visualize-data" + } else if (pathSegments.length >= 2) { + return pathSegments.slice(0, 2).join('/'); // e.g., "influxdb3/core" + } + + return path || 'unknown'; + } + + private trackAnalyticsEvent(eventData: AnalyticsEventData): void { + // Track Google Analytics events following the pattern from code-controls.js + try { + // Get current page context + const currentUrl = new URL(window.location.href); + const path = window.location.pathname; + + // Determine product context from current page + let pageContext = 'other'; + if (/\/influxdb\/cloud\//.test(path)) { + pageContext = 'cloud'; + } else if (/\/influxdb3\/core/.test(path)) { + pageContext = 'core'; + } else if (/\/influxdb3\/enterprise/.test(path)) { + pageContext = 'enterprise'; + } else if (/\/influxdb3\/cloud-serverless/.test(path)) { + pageContext = 'serverless'; + } else if (/\/influxdb3\/cloud-dedicated/.test(path)) { + pageContext = 'dedicated'; + } else if (/\/influxdb3\/clustered/.test(path)) { + pageContext = 'clustered'; + } else if (/\/(enterprise_|influxdb).*\/v[1-2]\//.test(path)) { + pageContext = 'oss/enterprise'; + } + + // Add tracking parameters to URL (following code-controls.js pattern) + if (eventData.detected_product) { + switch (eventData.detected_product) { + case 'core': + currentUrl.searchParams.set('dl', 'oss3'); + break; + case 'enterprise': + currentUrl.searchParams.set('dl', 'enterprise'); + break; + case 'cloud': + case 'cloud-v1': + case 'cloud-v2-tsm': + currentUrl.searchParams.set('dl', 'cloud'); + break; + case 'serverless': + currentUrl.searchParams.set('dl', 'serverless'); + break; + case 'dedicated': + currentUrl.searchParams.set('dl', 'dedicated'); + break; + case 'clustered': + currentUrl.searchParams.set('dl', 'clustered'); + break; + case 'oss': + case 'oss-v1': + case 'oss-v2': + currentUrl.searchParams.set('dl', 'oss'); + break; + } + } + + // Add additional tracking parameters + if (eventData.detection_method) { + currentUrl.searchParams.set( + 'detection_method', + eventData.detection_method + ); + } + if (eventData.completion_status) { + currentUrl.searchParams.set('completion', eventData.completion_status); + } + if (eventData.section) { + currentUrl.searchParams.set( + 'section', + encodeURIComponent(eventData.section) + ); + } + + // Update browser history without triggering page reload + if (window.history && window.history.replaceState) { + window.history.replaceState(null, '', currentUrl.toString()); + } + + // Send custom Google Analytics event if gtag is available + if (typeof window.gtag !== 'undefined') { + window.gtag('event', 'influxdb_version_detector', { + interaction_type: eventData.interaction_type, + detected_product: eventData.detected_product, + detection_method: eventData.detection_method, + completion_status: eventData.completion_status, + question_id: eventData.question_id, + answer_value: eventData.answer_value, + section: eventData.section, + page_context: pageContext, + custom_map: { + dimension1: eventData.detected_product, + dimension2: eventData.detection_method, + dimension3: pageContext, + }, + }); + } + } catch (error) { + // Silently handle analytics errors to avoid breaking functionality + console.debug('Analytics tracking error:', error); + } + } + + private initializeForModal(): void { + // Set up event listener to initialize when modal opens + const modalContent = this.container.closest('.modal-content'); + if (!modalContent) return; + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if ( + mutation.type === 'attributes' && + mutation.attributeName === 'style' + ) { + const target = mutation.target as HTMLElement; + const isVisible = + target.style.display !== 'none' && target.style.display !== ''; + + if (isVisible && !this.initialized) { + // Modal just opened and component not yet initialized + this.init(); + observer.disconnect(); + } + } + }); + }); + + // Start observing the modal content for style changes + observer.observe(modalContent, { + attributes: true, + attributeFilter: ['style'], + }); + + // Also check if modal is already visible + const computedStyle = window.getComputedStyle(modalContent); + if (computedStyle.display !== 'none' && !this.initialized) { + this.init(); + observer.disconnect(); + } + } + + private getBasicUrlSuggestion(): string { + // Provide a basic placeholder URL suggestion based on common patterns + return 'https://your-influxdb-host.com:8086'; + } + + private getProductDisplayName(product: string): string { + const displayNames: Record = { + // Simplified product keys (used in detection results) + 'oss-v1': 'InfluxDB OSS v1.x', + 'oss-v2': 'InfluxDB OSS v2.x', + oss: 'InfluxDB OSS (version unknown)', + cloud: 'InfluxDB Cloud', + 'cloud-v1': 'InfluxDB Cloud v1', + 'cloud-v2-tsm': 'InfluxDB Cloud v2 (TSM)', + serverless: 'InfluxDB Cloud Serverless', + core: 'InfluxDB 3 Core', + enterprise: 'InfluxDB 3 Enterprise', + dedicated: 'InfluxDB Cloud Dedicated', + clustered: 'InfluxDB Clustered', + custom: 'Custom URL', + + // Raw product keys from products.yml (used in scoring) + influxdb3_core: 'InfluxDB 3 Core', + influxdb3_enterprise: 'InfluxDB 3 Enterprise', + influxdb3_cloud_serverless: 'InfluxDB Cloud Serverless', + influxdb3_cloud_dedicated: 'InfluxDB Cloud Dedicated', + influxdb3_clustered: 'InfluxDB Clustered', + influxdb_v1: 'InfluxDB OSS v1.x', + influxdb_v2: 'InfluxDB OSS v2.x', + enterprise_influxdb: 'InfluxDB Enterprise v1.x', + influxdb: 'InfluxDB OSS v2.x', + }; + displayNames['core or enterprise'] = + `${displayNames.core} or ${displayNames.enterprise}`; + return displayNames[product] || product; + } + + private generateConfigurationGuidance(productKey: string): string { + // Map from result product names to products.yml keys + const productMapping: Record = { + core: 'influxdb3_core', + enterprise: 'influxdb3_enterprise', + serverless: 'influxdb3_cloud_serverless', + dedicated: 'influxdb3_cloud_dedicated', + clustered: 'influxdb3_clustered', + 'oss-v1': 'influxdb_v1', + 'oss-v2': 'influxdb_v2', + }; + + const dataKey = productMapping[productKey]; + if (!dataKey || !this.products[dataKey]) { + return ''; + } + + const productConfig = this.products[dataKey]; + const productName = this.getProductDisplayName(productKey); + + if ( + !productConfig.query_languages || + Object.keys(productConfig.query_languages).length === 0 + ) { + return ''; + } + + let html = ` +
+

Configuration Parameter Meanings for ${productName}

+

When configuring Grafana or other tools to connect to your ${productName} instance, these parameters mean:

+ `; + + // Add HOST explanation + const hostExample = this.getHostExample(dataKey); + html += ` +
+ HOST/URL: The network address where your ${productName} instance is running
+ + For your setup, this would typically be: ${hostExample} + +
+ `; + + // Add database/bucket terminology explanation + const usesDatabase = this.usesDatabaseTerminology(productConfig); + if (usesDatabase) { + html += ` +
+ DATABASE: The named collection where your data is stored
+ + ${productName} uses "database" terminology for organizing your time series data + +
+ `; + } else { + html += ` +
+ BUCKET: The named collection where your data is stored
+ + ${productName} uses "bucket" terminology for organizing your time series data + +
+ `; + } + + // Add authentication explanation + const authInfo = this.getAuthenticationInfo(productConfig); + html += ` +
+ AUTHENTICATION: ${authInfo.description}
+ + ${authInfo.details} + +
+ `; + + // Add query language explanation + const languages = Object.keys(productConfig.query_languages).join(', '); + html += ` +
+ QUERY LANGUAGE: The syntax used to retrieve your data
+ + ${productName} supports: ${languages} + +
+ `; + + html += '
'; + return html; + } + + private getHostExample(productDataKey: string): string { + // Extract placeholder_host from the products data if available + const productData = this.products[productDataKey]; + + // Use placeholder_host from the product configuration if available + if (productData?.placeholder_host) { + // Add protocol if not present + const host = productData.placeholder_host; + if (host.startsWith('http://') || host.startsWith('https://')) { + return host; + } else { + // Default to http for localhost, https for others + return host.includes('localhost') + ? `http://${host}` + : `https://${host}`; + } + } + + // Fallback based on product type + const hostExamples: Record = { + influxdb3_core: 'http://localhost:8181', + influxdb3_enterprise: 'http://localhost:8181', + influxdb3_cloud_serverless: 'https://cloud2.influxdata.com', + influxdb3_cloud_dedicated: 'https://cluster-id.a.influxdb.io', + influxdb3_clustered: 'https://cluster-host.com', + influxdb_v1: 'http://localhost:8086', + influxdb_v2: 'http://localhost:8086', + }; + + return hostExamples[productDataKey] || 'http://localhost:8086'; + } + + private usesDatabaseTerminology(productConfig: ProductConfig): boolean { + // Check if any query language uses 'Database' parameter + for (const language of Object.values(productConfig.query_languages)) { + if (language.required_params.includes('Database')) { + return true; + } + } + return false; + } + + private getAuthenticationInfo(productConfig: ProductConfig): { + description: string; + details: string; + } { + // Check if any query language requires Token + const requiresToken = Object.values(productConfig.query_languages).some( + (lang) => lang.required_params.includes('Token') + ); + + // Determine if this product uses "database" or "bucket" terminology + const usesDatabaseTerm = this.usesDatabaseTerminology(productConfig); + const resourceName = usesDatabaseTerm ? 'database' : 'bucket'; + + if (requiresToken) { + return { + description: 'Token-based authentication required', + details: `You need a valid API token with appropriate permissions for your ${resourceName}`, + }; + } else { + return { + description: 'No authentication required by default', + details: + 'This instance typically runs without authentication, though it may be optionally configured', + }; + } + } + + private detectEnterpriseFeatures(): { + likelyProduct: string; + confidence: number; + } | null { + // According to the decision tree, we cannot reliably distinguish + // Core vs Enterprise from URL alone. The real differentiator is: + // - Both Enterprise and Core: /ping requires auth by default (opt-out possible) + // - Definitive identification requires x-influxdb-build header from 200 response + // + // Since this component cannot make HTTP requests to test /ping, + // we return null to indicate we cannot distinguish them from URL alone. + + return null; + } + + private analyzeUrlPatterns(url: string): { + likelyProduct: string | null; + confidence: number; + suggestion?: string; + } { + if (!url || !this.influxdbUrls) { + return { likelyProduct: null, confidence: 0 }; + } + + const urlLower = url.toLowerCase(); + + // PRIORITY 1: Check for definitive cloud patterns first (per decision tree) + // These should be checked before localhost patterns for accuracy + + // InfluxDB Cloud Dedicated: Contains "influxdb.io" + if (urlLower.includes('influxdb.io')) { + return { likelyProduct: 'dedicated', confidence: 1.0 }; + } + + // InfluxDB Cloud Serverless: Contains "cloud2.influxdata.com" regions + if (urlLower.includes('cloud2.influxdata.com')) { + // Check for specific Serverless regions + const serverlessRegions = [ + 'us-east-1-1.aws.cloud2.influxdata.com', + 'eu-central-1-1.aws.cloud2.influxdata.com', + ]; + + for (const region of serverlessRegions) { + if (urlLower.includes(region.toLowerCase())) { + return { likelyProduct: 'serverless', confidence: 1.0 }; + } + } + + // Other cloud2 regions default to InfluxDB Cloud v2 (TSM) + return { likelyProduct: 'cloud-v2-tsm', confidence: 0.9 }; + } + + // InfluxDB Cloud v1 (legacy): Contains "influxcloud.net" + if (urlLower.includes('influxcloud.net')) { + return { likelyProduct: 'cloud-v1', confidence: 1.0 }; + } + + // PRIORITY 2: Check for localhost/port-based patterns (OSS, Core, Enterprise) + // Note: localhost URLs cannot be cloud versions - they're always self-hosted + if (urlLower.includes('localhost') || urlLower.includes('127.0.0.1')) { + // OSS default port + if (urlLower.includes(':8086')) { + return { + likelyProduct: 'oss', + confidence: 0.8, + suggestion: 'version-check', + }; + } + + // Core/Enterprise default port - both use 8181 + if (urlLower.includes(':8181')) { + // Try to distinguish between Core and Enterprise + const enterpriseResult = this.detectEnterpriseFeatures(); + if (enterpriseResult) { + return enterpriseResult; + } + + // Can't distinguish from URL alone - suggest ping test + return { + likelyProduct: 'core or enterprise', + confidence: 0.7, + suggestion: 'ping-test', + }; + } + } + + // Then check cloud products with provider regions + // Skip this check if URL is localhost (cannot be cloud) + const isLocalhost = + urlLower.includes('localhost') || urlLower.includes('127.0.0.1'); + if (!isLocalhost) { + for (const [productKey, productData] of Object.entries( + this.influxdbUrls + )) { + if (!productData || typeof productData !== 'object') continue; + + const providers = (productData as Record).providers; + if (!Array.isArray(providers)) continue; + + for (const provider of providers) { + if (!provider.regions) continue; + + for (const region of provider.regions) { + if (region.url) { + const patternUrl = region.url.toLowerCase(); + + // Exact match + if (urlLower === patternUrl) { + return { likelyProduct: productKey, confidence: 1.0 }; + } + + // Domain match for cloud URLs + if ( + productKey === 'cloud' && + urlLower.includes('cloud2.influxdata.com') + ) { + return { likelyProduct: 'cloud', confidence: 0.9 }; + } + } + } + } + } + } + + // Additional heuristics based on common patterns + // Special handling for user inputs like "cloud 2", "cloud v2", etc. + // Skip cloud heuristics for localhost URLs + if (!isLocalhost) { + if (urlLower.match(/cloud\s*[v]?2/)) { + return { likelyProduct: 'cloud', confidence: 0.8 }; + } + + if ( + urlLower.includes('cloud') || + urlLower.includes('aws') || + urlLower.includes('azure') || + urlLower.includes('gcp') + ) { + return { likelyProduct: 'cloud', confidence: 0.6 }; + } + } + + // Port-based suggestions for unknown/invalid URLs + if (urlLower.includes(':8086')) { + return { + likelyProduct: 'oss-port', + confidence: 0.4, + suggestion: 'multiple-candidates-8086', + }; + } + + if (urlLower.includes(':8181')) { + return { + likelyProduct: 'v3-port', + confidence: 0.4, + suggestion: 'multiple-candidates-8181', + }; + } + + return { likelyProduct: null, confidence: 0 }; + } + + private render(): void { + this.container.innerHTML = ` +
+

+ InfluxDB product detector +

+

+ Answer a few questions to identify which InfluxDB product you're using +

+ +
+
+
+ +
+ +
+
+ Do you know the URL of your InfluxDB server? +
+ + + + +
+ + +
+
+ Please enter your InfluxDB server URL: +
+
+ +
+ + +
+ + +
+
+ For airgapped environments, run this command from a machine that can + access your InfluxDB: +
+
curl -I http://your-influxdb-url:8086/ping
+
+ Then paste the response headers here: +
+ +
+ + +
+
+ + +
+
+ For Docker/Kubernetes environments, run these commands to identify your InfluxDB version: +
+
+ First, find your container: +
+
docker ps | grep influx
+
+ Then run one of these commands (replace <container> with your container name/ID): +
+
# Get version info: +docker exec <container> influxd version + +# Get ping headers: +docker exec <container> curl -I localhost:8086/ping + +# Or check startup logs: +docker logs <container> 2>&1 | head -20
+
+ Paste the output here: +
+ +
+ + +
+
+ + +
+
+ Which type of InfluxDB license do you have? +
+ + + + +
+ + +
+
+ Is your InfluxDB instance hosted by InfluxData (cloud) or + self-hosted? +
+ + + + +
+ + +
+
How long has your InfluxDB server been in place?
+ + + + + +
+ + +
+
Which query language(s) do you use with InfluxDB?
+ + + + + + +
+
+ +
+ + +
+ `; + + // Cache DOM elements + this.progressBar = this.container.querySelector('#progress-bar'); + this.resultDiv = this.container.querySelector('#result'); + this.restartBtn = this.container.querySelector('#restart-btn'); + } + + private attachEventListeners(): void { + this.container.addEventListener('click', (e) => { + const target = e.target as HTMLElement; + + if ( + target.classList.contains('option-button') || + target.classList.contains('submit-button') || + target.classList.contains('back-button') + ) { + const action = target.dataset.action; + + switch (action) { + case 'url-known': + this.trackAnalyticsEvent({ + interaction_type: 'question_answered', + question_id: 'url-known', + answer_value: target.dataset.value || '', + section: this.getCurrentPageSection(), + }); + this.handleUrlKnown(target.dataset.value); + break; + case 'go-back': + this.trackAnalyticsEvent({ + interaction_type: 'navigation', + section: this.getCurrentPageSection(), + }); + this.goBack(); + break; + case 'detect-url': + this.trackAnalyticsEvent({ + interaction_type: 'url_detection_attempt', + detection_method: 'url_analysis', + section: this.getCurrentPageSection(), + }); + this.detectByUrl(); + break; + case 'analyze-headers': + this.trackAnalyticsEvent({ + interaction_type: 'manual_analysis', + detection_method: 'ping_headers', + section: this.getCurrentPageSection(), + }); + this.analyzePingHeaders(); + break; + case 'analyze-docker': + this.trackAnalyticsEvent({ + interaction_type: 'manual_analysis', + detection_method: 'docker_output', + section: this.getCurrentPageSection(), + }); + this.analyzeDockerOutput(); + break; + case 'answer': + this.trackAnalyticsEvent({ + interaction_type: 'question_answered', + question_id: target.dataset.category || '', + answer_value: target.dataset.value || '', + section: this.getCurrentPageSection(), + }); + this.answerQuestion( + target.dataset.category!, + target.dataset.value! + ); + break; + case 'auth-help-answer': + this.trackAnalyticsEvent({ + interaction_type: 'auth_help_response', + question_id: target.dataset.category || '', + answer_value: target.dataset.value || '', + section: this.getCurrentPageSection(), + }); + this.handleAuthorizationHelp( + target.dataset.category!, + target.dataset.value! + ); + break; + case 'restart': + this.trackAnalyticsEvent({ + interaction_type: 'restart', + section: this.getCurrentPageSection(), + }); + this.restart(); + break; + case 'start-questionnaire': { + this.trackAnalyticsEvent({ + interaction_type: 'start_questionnaire', + section: this.getCurrentPageSection(), + }); + // Hide result and restart button first + if (this.resultDiv) { + this.resultDiv.classList.remove('show'); + } + if (this.restartBtn) { + this.restartBtn.style.display = 'none'; + } + // Start questionnaire with the detected context + this.startQuestionnaire(target.dataset.context || null); + // Focus on the component heading + const heading = document.getElementById('detector-title'); + if (heading) { + heading.focus(); + heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + break; + } + } + } + }); + } + + private updateProgress(): void { + const totalQuestions = this.questionFlow.length || 5; + const progress = ((this.currentQuestionIndex + 1) / totalQuestions) * 100; + if (this.progressBar) { + this.progressBar.style.width = `${progress}%`; + } + } + + private showQuestion(questionId: string, addToHistory: boolean = true): void { + const questions = this.container.querySelectorAll('.question'); + questions.forEach((q) => q.classList.remove('active')); + + const activeQuestion = this.container.querySelector(`#${questionId}`); + if (activeQuestion) { + activeQuestion.classList.add('active'); + + // Add smart suggestions for URL input question + if (questionId === 'q-url-input') { + this.enhanceUrlInputWithSuggestions(); + } + } + + // Track question history for back navigation + if (addToHistory) { + this.questionHistory.push(questionId); + } + + this.updateProgress(); + } + + private enhanceUrlInputWithSuggestions(): void { + const urlInputQuestion = this.container.querySelector('#q-url-input'); + if (!urlInputQuestion) return; + + const urlInput = urlInputQuestion.querySelector( + '#url-input' + ) as HTMLInputElement; + if (!urlInput) return; + + // Check for existing URL in localStorage + const storedUrls = getInfluxDBUrls(); + const currentProduct = this.getCurrentProduct(); + const storedUrl = storedUrls[currentProduct] || storedUrls.custom; + + if (storedUrl && storedUrl !== 'http://localhost:8086') { + urlInput.value = storedUrl; + // Add indicator that URL was pre-filled (only if one doesn't already exist) + const existingIndicator = urlInput.parentElement?.querySelector( + '.url-prefilled-indicator' + ); + if (!existingIndicator) { + const indicator = document.createElement('div'); + indicator.className = 'url-prefilled-indicator'; + indicator.textContent = 'Using previously saved URL'; + urlInput.parentElement?.insertBefore(indicator, urlInput); + + // Hide indicator when user starts typing + const originalValue = urlInput.value; + urlInput.addEventListener('input', () => { + if (urlInput.value !== originalValue) { + indicator.style.display = 'none'; + } + }); + } + } else { + // Set a basic placeholder suggestion + const suggestedUrl = this.getBasicUrlSuggestion(); + urlInput.placeholder = `for example, ${suggestedUrl}`; + } + } + + private getCurrentProduct(): string { + // Try to determine current product context from page or default + // This could be enhanced to detect from page context + return 'core'; // Default to core for now + } + + private handleUrlKnown(value: string | undefined): void { + this.currentQuestionIndex++; + + if (value === 'true') { + this.showQuestion('q-url-input'); + } else if (value === 'airgapped') { + this.showQuestion('q-ping-manual'); + // Set up placeholder after question is shown + setTimeout(() => this.setupPingHeadersPlaceholder(), 0); + } else if (value === 'docker') { + this.answers.isDocker = true; + this.showQuestion('q-docker-manual'); + // Set up placeholder after question is shown + setTimeout(() => this.setupDockerOutputPlaceholder(), 0); + } else { + // Start the questionnaire + this.answers = {}; + this.questionFlow = ['q-paid', 'q-hosted', 'q-age', 'q-language']; + this.currentQuestionIndex = 0; + this.showQuestion('q-paid'); + } + } + + private goBack(): void { + // Remove current question from history + if (this.questionHistory.length > 0) { + this.questionHistory.pop(); + } + + // Go to previous question if available + if (this.questionHistory.length > 0) { + const previousQuestion = + this.questionHistory[this.questionHistory.length - 1]; + // Remove it from history before showing (showQuestion will re-add it) + this.questionHistory.pop(); + + // Decrement question index + if (this.currentQuestionIndex > 0) { + this.currentQuestionIndex--; + } + + // Show previous question + this.showQuestion(previousQuestion); + } else { + // No history - go to first question + this.currentQuestionIndex = 0; + this.showQuestion('q-url-known'); + } + } + + private async detectByUrl(): Promise { + const urlInput = ( + this.container.querySelector('#url-input') as HTMLInputElement + )?.value.trim(); + + if (!urlInput) { + this.showResult('error', 'Please enter a valid URL'); + return; + } + + // Use improved URL pattern analysis + const analysisResult = this.analyzeUrlPatterns(urlInput); + + // Store URL detection results for scoring system + if (analysisResult.likelyProduct && analysisResult.likelyProduct !== null) { + this.answers.detectedProduct = analysisResult.likelyProduct; + this.answers.detectedConfidence = analysisResult.confidence.toString(); + } + + if (analysisResult.likelyProduct && analysisResult.likelyProduct !== null) { + if (analysisResult.suggestion === 'ping-test') { + // Show ping test suggestion for Core/Enterprise detection + this.showPingTestSuggestion(urlInput, analysisResult.likelyProduct); + return; + } else if (analysisResult.suggestion === 'version-check') { + // Show OSS version check suggestion + this.showOSSVersionCheckSuggestion(urlInput); + return; + } else if (analysisResult.suggestion === 'multiple-candidates-8086') { + // Show multiple product suggestions for port 8086 + this.showMultipleCandidatesSuggestion(urlInput, '8086'); + return; + } else if (analysisResult.suggestion === 'multiple-candidates-8181') { + // Show multiple product suggestions for port 8181 + this.showMultipleCandidatesSuggestion(urlInput, '8181'); + return; + } else { + // Direct detection + this.showDetectedVersion(analysisResult.likelyProduct); + return; + } + } + + // URL not recognized - start questionnaire with context + this.showResult('info', 'Analyzing your InfluxDB server...'); + + // Check if this is a cloud context (like "cloud 2") + const contextResult = this.detectContext(urlInput); + if (contextResult.likelyProduct === 'cloud') { + // Start questionnaire with cloud context + setTimeout(() => { + this.startQuestionnaireWithCloudContext(); + }, 2000); + } else { + // For other URLs, use the regular questionnaire + setTimeout(() => { + this.startQuestionnaire('manual', this.detectPortFromUrl(urlInput)); + }, 2000); + } + } + + private detectContext(urlInput: string): { likelyProduct?: string } { + const input = urlInput.toLowerCase(); + + // Check for cloud indicators + if (input.includes('cloud') || input.includes('influxdata.com')) { + return { likelyProduct: 'cloud' }; + } + + // Check for other patterns like "cloud 2" + if (/cloud\s*[v]?2/.test(input)) { + return { likelyProduct: 'cloud' }; + } + + return {}; + } + + private detectPortFromUrl(urlString: string): string | null { + try { + const url = new URL(urlString); + const port = url.port || (url.protocol === 'https:' ? '443' : '80'); + + if (port === '8181') { + return 'v3'; // InfluxDB 3 Core/Enterprise typically use 8181 + } else if (port === '8086') { + return 'legacy'; // OSS v1/v2 or Enterprise v1 typically use 8086 + } + } catch { + // Invalid URL + } + return null; + } + + private startQuestionnaire( + context: string | null = null, + portClue: string | null = null + ): void { + this.answers = {}; + this.answers.context = context; + this.answers.portClue = portClue; + this.answers.isCloud = false; + this.questionFlow = ['q-paid', 'q-age', 'q-language']; + this.currentQuestionIndex = 0; + this.showQuestion('q-paid'); + } + + private startQuestionnaireWithCloudContext(): void { + this.answers = {}; + this.answers.context = 'cloud'; + this.answers.hosted = 'cloud'; // Pre-set cloud hosting + this.answers.isCloud = true; + this.questionFlow = ['q-paid', 'q-age', 'q-language']; + this.currentQuestionIndex = 0; + this.showQuestion('q-paid'); + } + + private answerQuestion(category: string, answer: string): void { + this.answers[category] = answer; + + // Determine next question or show results + if (category === 'paid') { + if (!this.answers.context) { + // No URL provided - ask about cloud vs self-hosted + this.currentQuestionIndex = 1; + this.showQuestion('q-hosted'); + } else { + // We have context from URL - go to age + this.currentQuestionIndex = 1; + this.showQuestion('q-age'); + } + } else if (category === 'hosted') { + this.currentQuestionIndex = 2; + this.showQuestion('q-age'); + } else if (category === 'age') { + this.currentQuestionIndex = 3; + this.showQuestion('q-language'); + } else if (category === 'language') { + // All questions answered - show ranked results + this.showRankedResults(); + } + } + + private handleAuthorizationHelp(category: string, answer: string): void { + // Store the answer + this.answers[category] = answer; + + // Check if we're in the context of localhost:8181 detection + // If so, we can provide a high-confidence result + const currentUrl = + ( + this.container.querySelector('#url-input') as HTMLInputElement + )?.value?.toLowerCase() || ''; + const isLocalhost8181 = + (currentUrl.includes('localhost') || currentUrl.includes('127.0.0.1')) && + currentUrl.includes(':8181'); + + if (isLocalhost8181) { + // For localhost:8181, we can give high-confidence results based on license + if (answer === 'free') { + // High confidence it's InfluxDB 3 Core + const html = ` + Based on your localhost:8181 server and free license:

+ ${this.generateProductResult('core', true, 'High', false)} +
+ Want to confirm this result? + +
+ `; + this.showResult('success', html); + } else if (answer === 'paid') { + // High confidence it's InfluxDB 3 Enterprise + const html = ` + Based on your localhost:8181 server and paid license:

+ ${this.generateProductResult('enterprise', true, 'High', false)} +
+ Want to confirm this result? + +
+ `; + this.showResult('success', html); + } + } else { + // Original behavior for non-localhost:8181 cases + const resultDiv = this.container.querySelector('#result'); + if (resultDiv) { + // Add a message about what the license answer means + const licenseGuidance = document.createElement('div'); + licenseGuidance.className = 'license-guidance'; + licenseGuidance.style.marginTop = '1rem'; + licenseGuidance.style.padding = '0.75rem'; + licenseGuidance.style.backgroundColor = + 'rgba(var(--article-link-rgb, 0, 163, 255), 0.1)'; + licenseGuidance.style.borderLeft = + '4px solid var(--article-link, #00A3FF)'; + licenseGuidance.style.borderRadius = '4px'; + + if (answer === 'free') { + licenseGuidance.innerHTML = ` + Free/Open Source License: +

This suggests you're using InfluxDB 3 Core or InfluxDB OSS.

+ + `; + } else if (answer === 'paid') { + licenseGuidance.innerHTML = ` + Paid/Commercial License: +

This suggests you're using InfluxDB 3 Enterprise or a paid cloud service.

+ + `; + } + + // Remove any existing guidance + const existingGuidance = resultDiv.querySelector('.license-guidance'); + if (existingGuidance) { + existingGuidance.remove(); + } + + // Add the new guidance + resultDiv.appendChild(licenseGuidance); + + // Focus on the guidance message for accessibility + licenseGuidance.focus(); + } + } + } + + private showRankedResults(): void { + const scores: Record = {}; + + // Initialize all products with base score using their full display names + // The scoring logic uses full names like 'InfluxDB 3 Core', not keys like 'influxdb3_core' + Object.entries(this.products).forEach(([key, config]) => { + const fullName = config.name || key; + scores[fullName] = 0; + }); + + // Apply scoring logic based on answers + this.applyScoring(scores); + + // Check if user answered "unknown" to all questions + const allUnknown = + (!this.answers.paid || this.answers.paid === 'unknown') && + (!this.answers.hosted || this.answers.hosted === 'unknown') && + (!this.answers.age || this.answers.age === 'unknown') && + (!this.answers.language || this.answers.language === 'unknown'); + + // Sort by score and filter out vague products + const ranked = Object.entries(scores) + .filter(([product, score]) => { + // Filter by score threshold + if (score <= -50) return false; + // Exclude generic "InfluxDB" product (too vague for results) + if (product === 'InfluxDB') return false; + return true; + }) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + + // Display results + this.displayRankedResults(ranked, allUnknown); + } + + /** + * Gets the Grafana documentation link for a given product + */ + private getGrafanaLink(productName: string): string | null { + const GRAFANA_LINKS: Record = { + 'InfluxDB 3 Core': '/influxdb3/core/visualize-data/grafana/', + 'InfluxDB 3 Enterprise': '/influxdb3/enterprise/visualize-data/grafana/', + 'InfluxDB Cloud Dedicated': + '/influxdb3/cloud-dedicated/visualize-data/grafana/', + 'InfluxDB Cloud Serverless': + '/influxdb3/cloud-serverless/visualize-data/grafana/', + 'InfluxDB OSS 1.x': '/influxdb/v1/tools/grafana/', + 'InfluxDB OSS 2.x': '/influxdb/v2/visualize-data/grafana/', + 'InfluxDB Enterprise': '/influxdb/enterprise/visualize-data/grafana/', + 'InfluxDB Clustered': '/influxdb3/clustered/visualize-data/grafana/', + 'InfluxDB Cloud (TSM)': '/influxdb/cloud/visualize-data/grafana/', + 'InfluxDB Cloud v1': '/influxdb/cloud/visualize-data/grafana/', + }; + + return GRAFANA_LINKS[productName] || null; + } + + /** + * Generates a unified product result block with characteristics and Grafana link + */ + private generateProductResult( + productName: string, + isTopResult: boolean = false, + confidence?: string, + showRanking?: boolean + ): string { + const displayName = this.getProductDisplayName(productName) || productName; + const grafanaLink = this.getGrafanaLink(displayName); + const resultClass = isTopResult + ? 'product-ranking top-result' + : 'product-ranking'; + + // Get characteristics from products data + const characteristics = this.products[productName]?.characteristics; + + let html = `
`; + + if (showRanking) { + html += `
${displayName}
`; + if (isTopResult) { + html += 'Most Likely'; + } + } else { + html += `
${displayName}
`; + if (isTopResult) { + html += 'Detected'; + } + } + + // Add characteristics and confidence + const details = []; + if (confidence) details.push(`Confidence: ${confidence}`); + if (characteristics) { + details.push(characteristics.slice(0, 3).join(', ')); + } + + if (details.length > 0) { + html += `
${details.join(' • ')}
`; + } + + // Add Grafana link if available + if (grafanaLink) { + html += ` + + `; + } + + html += '
'; + + // Add configuration guidance for top results + if (isTopResult) { + const configGuidance = this.generateConfigurationGuidance(productName); + if (configGuidance) { + html += configGuidance; + } + } + + return html; + } + + /** + * Maps simple product keys (used in URL detection) to full product names (used in scoring) + */ + private mapProductKeyToFullName(productKey: string): string | null { + const KEY_TO_FULL_NAME_MAP: Record = { + core: 'InfluxDB 3 Core', + enterprise: 'InfluxDB 3 Enterprise', + serverless: 'InfluxDB Cloud Serverless', + dedicated: 'InfluxDB Cloud Dedicated', + clustered: 'InfluxDB Clustered', + 'cloud-v2-tsm': 'InfluxDB Cloud (TSM)', + 'cloud-v1': 'InfluxDB Cloud v1', + oss: 'InfluxDB OSS 2.x', + 'oss-1x': 'InfluxDB OSS 1.x', + 'enterprise-1x': 'InfluxDB Enterprise', + }; + + return KEY_TO_FULL_NAME_MAP[productKey] || null; + } + + private applyScoring(scores: Record): void { + // Product release dates for time-aware scoring + const PRODUCT_RELEASE_DATES: Record = { + 'InfluxDB 3 Core': new Date('2025-01-01'), + 'InfluxDB 3 Enterprise': new Date('2025-01-01'), + 'InfluxDB Cloud Serverless': new Date('2024-01-01'), + 'InfluxDB Cloud Dedicated': new Date('2024-01-01'), + 'InfluxDB Clustered': new Date('2024-01-01'), + 'InfluxDB OSS 2.x': new Date('2020-11-01'), + 'InfluxDB Cloud (TSM)': new Date('2020-11-01'), + 'InfluxDB OSS 1.x': new Date('2016-09-01'), + 'InfluxDB Enterprise': new Date('2016-09-01'), + }; + + const currentDate = new Date(); + + // Apply URL detection boost if available + if (this.answers.detectedProduct && this.answers.detectedConfidence) { + const detectedProduct = this.answers.detectedProduct as string; + const confidence = + typeof this.answers.detectedConfidence === 'number' + ? this.answers.detectedConfidence + : parseFloat(this.answers.detectedConfidence as string); + + // Determine confidence boost value + let boostValue = 0; + if (confidence >= 1.0) { + boostValue = 100; // Definitive match + } else if (confidence >= 0.9) { + boostValue = 80; // Very high confidence + } else if (confidence >= 0.7) { + boostValue = 60; // High confidence + } else if (confidence >= 0.5) { + boostValue = 40; // Medium confidence + } + + // Handle special case: 'core or enterprise' should boost BOTH products equally + if (detectedProduct === 'core or enterprise') { + scores['InfluxDB 3 Core'] += boostValue; + scores['InfluxDB 3 Enterprise'] += boostValue; + } else { + // Normal case: boost single detected product + const fullProductName = this.mapProductKeyToFullName(detectedProduct); + if (fullProductName && scores[fullProductName] !== undefined) { + scores[fullProductName] += boostValue; + } + } + } + + // Cloud vs self-hosted + if (this.answers.hosted === 'cloud') { + scores['InfluxDB 3 Core'] = -1000; + scores['InfluxDB 3 Enterprise'] = -1000; + scores['InfluxDB OSS 1.x'] = -1000; + scores['InfluxDB OSS 2.x'] = -1000; + scores['InfluxDB Enterprise'] = -1000; + scores['InfluxDB Clustered'] = -1000; + } else if (this.answers.hosted === 'self' || !this.answers.isCloud) { + scores['InfluxDB Cloud Dedicated'] = -1000; + scores['InfluxDB Cloud Serverless'] = -1000; + scores['InfluxDB Cloud (TSM)'] = -1000; + } + + // Paid vs Free + if (this.answers.paid === 'free') { + scores['InfluxDB 3 Core'] += 25; + scores['InfluxDB OSS 1.x'] += 25; + scores['InfluxDB OSS 2.x'] += 25; + scores['InfluxDB'] += 25; // Generic InfluxDB (OSS v2.x) + scores['InfluxDB Cloud Serverless'] += 10; + scores['InfluxDB Cloud (TSM)'] += 10; + + scores['InfluxDB 3 Enterprise'] = -1000; + scores['InfluxDB Enterprise'] = -1000; + scores['InfluxDB Clustered'] = -1000; + scores['InfluxDB Cloud Dedicated'] = -1000; + } else if (this.answers.paid === 'paid') { + scores['InfluxDB 3 Enterprise'] += 25; + scores['InfluxDB Enterprise'] += 20; + scores['InfluxDB Clustered'] += 15; + scores['InfluxDB Cloud Dedicated'] += 20; + scores['InfluxDB Cloud Serverless'] += 15; + scores['InfluxDB Cloud (TSM)'] += 15; + + scores['InfluxDB 3 Core'] = -1000; + scores['InfluxDB OSS 1.x'] = -1000; + scores['InfluxDB OSS 2.x'] = -1000; + scores['InfluxDB'] = -1000; // Generic InfluxDB (OSS v2.x) + } + + // Time-aware age-based scoring + Object.entries(scores).forEach(([product]) => { + const releaseDate = PRODUCT_RELEASE_DATES[product]; + if (!releaseDate) return; + + const yearsSinceRelease = + (currentDate.getTime() - releaseDate.getTime()) / + (365.25 * 24 * 60 * 60 * 1000); + + if (this.answers.age === 'recent') { + // Favor products released within last year + if (yearsSinceRelease < 1) { + scores[product] += 40; // Very new product + } else if (yearsSinceRelease < 3) { + scores[product] += 25; // Relatively new + } + } else if (this.answers.age === '1-5') { + // Check if product existed in this timeframe + if (yearsSinceRelease >= 1 && yearsSinceRelease <= 5) { + scores[product] += 25; + } else if (yearsSinceRelease < 1) { + scores[product] -= 30; // Too new for this age range + } + } else if (this.answers.age === '5+') { + // Only penalize if product didn't exist 5+ years ago + if (yearsSinceRelease < 5) { + scores[product] -= 100; // Product didn't exist 5 years ago + } else { + scores[product] += 30; // Product was available 5+ years ago + } + } + }); + + // Query language scoring + if (this.answers.language === 'sql') { + scores['InfluxDB 3 Core'] += 40; + scores['InfluxDB 3 Enterprise'] += 40; + scores['InfluxDB Cloud Dedicated'] += 30; + scores['InfluxDB Cloud Serverless'] += 30; + scores['InfluxDB Clustered'] += 30; + + scores['InfluxDB OSS 1.x'] = -1000; + scores['InfluxDB OSS 2.x'] = -1000; + scores['InfluxDB'] = -1000; // Generic InfluxDB (OSS v2.x) + scores['InfluxDB Enterprise'] = -1000; + scores['InfluxDB Cloud (TSM)'] = -1000; + } else if (this.answers.language === 'flux') { + scores['InfluxDB OSS 2.x'] += 30; + scores['InfluxDB'] += 30; // Generic InfluxDB (OSS v2.x) + scores['InfluxDB Cloud (TSM)'] += 40; + scores['InfluxDB Cloud Serverless'] += 20; + scores['InfluxDB Enterprise'] += 20; // v1.x Enterprise supports Flux + + scores['InfluxDB OSS 1.x'] = -1000; + scores['InfluxDB 3 Core'] = -1000; + scores['InfluxDB 3 Enterprise'] = -1000; + scores['InfluxDB Cloud Dedicated'] = -1000; + scores['InfluxDB Clustered'] = -1000; + } else if (this.answers.language === 'influxql') { + // InfluxQL is supported by all products except pure Flux products + scores['InfluxDB OSS 1.x'] += 30; + scores['InfluxDB Enterprise'] += 30; + scores['InfluxDB OSS 2.x'] += 20; + scores['InfluxDB'] += 20; // Generic InfluxDB (OSS v2.x) + scores['InfluxDB Cloud (TSM)'] += 20; + scores['InfluxDB 3 Core'] += 25; + scores['InfluxDB 3 Enterprise'] += 25; + scores['InfluxDB Cloud Dedicated'] += 25; + scores['InfluxDB Cloud Serverless'] += 25; + scores['InfluxDB Clustered'] += 25; + } + } + + private displayRankedResults( + ranked: [string, number][], + allUnknown: boolean = false + ): void { + const topScore = ranked[0]?.[1] || 0; + const secondScore = ranked[1]?.[1] || 0; + const hasStandout = topScore > 30 && topScore - secondScore >= 15; + + let html = ''; + + // If all answers were "I'm not sure", show a helpful message + if (allUnknown) { + html = + 'Unable to determine your InfluxDB product

' + + '

Since you answered "I\'m not sure" to all questions, we don\'t have enough information to identify your InfluxDB product.

' + + '

Please check the InfluxDB version quick reference table below to identify your product based on its characteristics.


'; + } else { + html = + 'Based on your answers, here are the most likely InfluxDB products:

'; + } + + // Only show ranked products if we have meaningful answers + if (!allUnknown) { + ranked.forEach(([product, score], index) => { + const confidence = score > 60 ? 'High' : score > 30 ? 'Medium' : 'Low'; + const isTopResult = index === 0 && hasStandout; + + // Use unified product result generation with ranking number + let productHtml = this.generateProductResult( + product, + isTopResult, + confidence, + true + ); + + // Add ranking number to the product title + productHtml = productHtml.replace( + '
', + `
${index + 1}. ` + ); + + html += productHtml; + }); + } + + // Add Quick Reference table (open by default if all answers unknown) + html += ` +
+ + + InfluxDB version quick reference + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProductLicenseHostingPortPing requires authQuery languages
InfluxDB 3 EnterprisePaid onlySelf-hosted8181Yes (opt-out)SQL, InfluxQL
InfluxDB 3 CoreFree onlySelf-hosted8181Yes (opt-out)SQL, InfluxQL
InfluxDB EnterprisePaid onlySelf-hosted8086Yes (required)InfluxQL, Flux
InfluxDB ClusteredPaid onlySelf-hostedVariesNoSQL, InfluxQL
InfluxDB OSS 1.xFree onlySelf-hosted8086No (optional)InfluxQL
InfluxDB OSS 2.xFree onlySelf-hosted8086NoInfluxQL, Flux
InfluxDB Cloud DedicatedPaid onlyCloudN/ANoSQL, InfluxQL
InfluxDB Cloud ServerlessFree + PaidCloudN/AN/ASQL, InfluxQL, Flux
InfluxDB Cloud (TSM)Free + PaidCloudN/AN/AInfluxQL, Flux
+ +
+ `; + + this.showResult('success', html); + } + + private analyzePingHeaders(): void { + const headersText = ( + this.container.querySelector('#ping-headers') as HTMLTextAreaElement + )?.value.trim(); + + if (!headersText) { + this.showResult('error', 'Please paste the ping response headers'); + return; + } + + // Check if user is trying to analyze the example content + if ( + headersText.includes( + '# Replace this with your actual response headers' + ) || + headersText.includes('# Example formats:') + ) { + this.showResult( + 'error', + 'Please replace the example content with your actual ping response headers' + ); + return; + } + + // Check for 401/403 unauthorized responses + if (headersText.includes('401') || headersText.includes('403')) { + this.showResult( + 'info', + ` + Authentication Required Detected

+ The ping endpoint requires authentication, which indicates you're likely using one of:

+
+
+ InfluxDB 3 Enterprise - Requires auth by default (opt-out possible) +
+
+ InfluxDB 3 Core - Requires auth by default (opt-out possible) +
+
+ Please use the guided questions to narrow down your specific version. + ` + ); + return; + } + + // Parse headers and check against patterns + const headers: Record = {}; + headersText.split('\n').forEach((line) => { + const colonIndex = line.indexOf(':'); + if (colonIndex > -1) { + const key = line.substring(0, colonIndex).trim().toLowerCase(); + const value = line.substring(colonIndex + 1).trim(); + headers[key] = value; + } + }); + + // PRIORITY: Check for definitive x-influxdb-build header (per decision tree) + const buildHeader = headers['x-influxdb-build']; + if (buildHeader) { + if (buildHeader.toLowerCase().includes('enterprise')) { + this.showDetectedVersion('InfluxDB 3 Enterprise'); + return; + } else if (buildHeader.toLowerCase().includes('core')) { + this.showDetectedVersion('InfluxDB 3 Core'); + return; + } + } + + // Check against product patterns + let detectedProduct: string | null = null; + for (const [productName, config] of Object.entries(this.products)) { + if (config.detection?.ping_headers) { + let matches = true; + for (const [header, pattern] of Object.entries( + config.detection.ping_headers + )) { + const regex = new RegExp(pattern); + if (!headers[header] || !regex.test(headers[header])) { + matches = false; + break; + } + } + if (matches) { + detectedProduct = productName; + break; + } + } + } + + if (detectedProduct) { + this.showDetectedVersion(detectedProduct); + } else { + this.showResult( + 'warning', + 'Unable to determine version from headers. Consider using the guided questions instead.' + ); + } + } + + private showResult(type: string, message: string): void { + if (this.resultDiv) { + this.resultDiv.className = `result ${type} show`; + this.resultDiv.innerHTML = message; + } + if (this.restartBtn) { + this.restartBtn.style.display = 'block'; + } + } + + private analyzeDockerOutput(): void { + const dockerOutput = ( + this.container.querySelector('#docker-output') as HTMLTextAreaElement + )?.value.trim(); + + if (!dockerOutput) { + this.showResult('error', 'Please paste the Docker command output'); + return; + } + + // Check if user is trying to analyze the example content + if ( + dockerOutput.includes('# Replace this with your actual command output') || + dockerOutput.includes('# Example formats:') + ) { + this.showResult( + 'error', + 'Please replace the example content with your actual Docker command output' + ); + return; + } + + let detectedProduct: string | null = null; + + // Check for version patterns in the output + if (dockerOutput.includes('InfluxDB 3 Core')) { + detectedProduct = 'InfluxDB 3 Core'; + } else if (dockerOutput.includes('InfluxDB 3 Enterprise')) { + detectedProduct = 'InfluxDB 3 Enterprise'; + } else if (dockerOutput.includes('InfluxDB v3')) { + // Generic v3 detection - need more info + detectedProduct = 'InfluxDB 3 Core or Enterprise'; + } else if ( + dockerOutput.includes('InfluxDB v2') || + dockerOutput.includes('InfluxDB 2.') + ) { + detectedProduct = 'InfluxDB OSS 2.x'; + } else if ( + dockerOutput.includes('InfluxDB v1') || + dockerOutput.includes('InfluxDB 1.') + ) { + if (dockerOutput.includes('Enterprise')) { + detectedProduct = 'InfluxDB Enterprise'; + } else { + detectedProduct = 'InfluxDB OSS 1.x'; + } + } + + // Also check for ping header patterns (case-insensitive) + if (!detectedProduct) { + // First check for x-influxdb-build header (definitive identification) + const buildMatch = dockerOutput.match(/x-influxdb-build:\s*(\w+)/i); + if (buildMatch) { + const build = buildMatch[1].toLowerCase(); + if (build === 'enterprise') { + detectedProduct = 'InfluxDB 3 Enterprise'; + } else if (build === 'core') { + detectedProduct = 'InfluxDB 3 Core'; + } + } + + // If no build header, check version headers (case-insensitive) + if (!detectedProduct) { + const versionMatch = dockerOutput.match( + /x-influxdb-version:\s*([\d.]+)/i + ); + if (versionMatch) { + const version = versionMatch[1]; + if (version.startsWith('3.')) { + detectedProduct = 'InfluxDB 3 Core or InfluxDB 3Enterprise'; + } else if (version.startsWith('2.')) { + detectedProduct = 'InfluxDB OSS 2.x'; + } else if (version.startsWith('1.')) { + detectedProduct = dockerOutput.includes('Enterprise') + ? 'InfluxDB Enterprise' + : 'InfluxDB OSS 1.x'; + } + } + } + } + + if (detectedProduct) { + this.showDetectedVersion(detectedProduct); + } else { + this.showResult( + 'warning', + 'Unable to determine version from Docker output. Consider using the guided questions instead.' + ); + } + } + + private showPingTestSuggestion(url: string, productName: string): void { + // Convert product key to display name + const displayName = this.getProductDisplayName(productName) || productName; + const html = ` + Port 8181 detected - likely ${displayName}

+ +

To distinguish between InfluxDB 3 Core and Enterprise, run one of these commands:

+ +
+# Direct API call: +curl -I ${url}/ping +
+ +
+ + View Docker/Container Commands + +
+# With Docker Compose: +docker compose exec influxdb3 curl -I http://localhost:8181/ping + +# With Docker (replace <container> with your container name): +docker exec <container> curl -I localhost:8181/ping +
+
+ +
+
Expected results:
+ • X-Influxdb-Build: Enterprise → InfluxDB 3 Enterprise (definitive)
+ • X-Influxdb-Build: Core → InfluxDB 3 Core (definitive)
+ • 401 Unauthorized → Use the license information below +
+ +
+
If you get 401 Unauthorized:
+

What type of license do you have?

+ + +
+ +
+ Can't run the command? + +
+ `; + this.showResult('success', html); + } + + private showOSSVersionCheckSuggestion(url: string): void { + const html = ` + Port 8086 detected - likely InfluxDB OSS

+ +

To determine if this is InfluxDB OSS v1.x or v2.x, run one of these commands:

+ +
+# Check version directly: +influxd version + +# Or check via API: +curl -I ${url}/ping +
+ +
+
Expected version patterns:
+ • v1.x.x → ${this.getProductDisplayName('oss-v1')}
+ • v2.x.x → ${this.getProductDisplayName('oss-v2')}
+
+ +
+ + Docker/Container Commands + +
+# Get version info: +docker exec <container> influxd version + +# Get ping headers: +docker exec <container> curl -I localhost:8086/ping + +# Or check startup logs: +docker logs <container> 2>&1 | head -20 +
+

+ Replace <container> with your actual container name or ID. +

+
+ +
+ Can't run these commands? + +
+ `; + this.showResult('success', html); + } + + private showMultipleCandidatesSuggestion(url: string, port: string): void { + let candidates: string[] = []; + let portDescription = ''; + + if (port === '8086') { + candidates = [ + 'InfluxDB OSS 1.x', + 'InfluxDB OSS 2.x', + 'InfluxDB Enterprise', + ]; + portDescription = + 'Port 8086 is used by InfluxDB OSS v1.x, OSS v2.x, and Enterprise v1.x'; + } else if (port === '8181') { + candidates = ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise']; + portDescription = 'Port 8181 is used by InfluxDB 3 Core and Enterprise'; + } + + const candidatesList = candidates + .map((product) => + this.generateProductResult(product, false, 'Medium', false) + ) + .join(''); + + const html = ` + Based on the port pattern in your URL, here are the possible products:

+ +

${portDescription}. Without additional information, we cannot determine which specific version you're using.

+ +
+ Possible products:
+ ${candidatesList} +
+ +
+ To narrow this down: + +
+ `; + this.showResult('info', html); + } + + private showDetectedVersion(productName: string): void { + // Track successful detection + this.trackAnalyticsEvent({ + interaction_type: 'product_detected', + detected_product: productName.toLowerCase().replace(/\s+/g, '_'), + completion_status: 'success', + section: this.getCurrentPageSection(), + }); + + const html = ` + Based on your input, we believe the InfluxDB product you are using is most likely:

+ ${this.generateProductResult(productName, true, 'High', false)} + `; + this.showResult('success', html); + } + + private restart(): void { + this.answers = {}; + this.questionFlow = []; + this.currentQuestionIndex = 0; + this.questionHistory = []; + + // Clear inputs + const urlInput = this.container.querySelector( + '#url-input' + ) as HTMLInputElement; + const pingHeaders = this.container.querySelector( + '#ping-headers' + ) as HTMLTextAreaElement; + const dockerOutput = this.container.querySelector( + '#docker-output' + ) as HTMLTextAreaElement; + + if (urlInput) urlInput.value = ''; + if (pingHeaders) pingHeaders.value = ''; + if (dockerOutput) dockerOutput.value = ''; + + // Remove URL prefilled indicator if present + const indicator = this.container.querySelector('.url-prefilled-indicator'); + if (indicator) { + indicator.remove(); + } + + // Hide result + if (this.resultDiv) { + this.resultDiv.classList.remove('show'); + } + if (this.restartBtn) { + this.restartBtn.style.display = 'none'; + } + + // Show first question + this.showQuestion('q-url-known'); + + // Reset progress + if (this.progressBar) { + this.progressBar.style.width = '0%'; + } + } +} + +// Export as component initializer +export default function initInfluxDBVersionDetector( + options: ComponentOptions +): InfluxDBVersionDetector { + return new InfluxDBVersionDetector(options); +} diff --git a/assets/js/main.js b/assets/js/main.js index ca99dff48f..c1b8a70886 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -35,6 +35,7 @@ import DocSearch from './components/doc-search.js'; import FeatureCallout from './feature-callouts.js'; import FluxGroupKeysDemo from './flux-group-keys.js'; import FluxInfluxDBVersionsTrigger from './flux-influxdb-versions.js'; +import InfluxDBVersionDetector from './influxdb-version-detector.ts'; import KeyBinding from './keybindings.js'; import ListFilters from './list-filters.js'; import ProductSelector from './version-selector.js'; @@ -64,6 +65,7 @@ const componentRegistry = { 'feature-callout': FeatureCallout, 'flux-group-keys-demo': FluxGroupKeysDemo, 'flux-influxdb-versions-trigger': FluxInfluxDBVersionsTrigger, + 'influxdb-version-detector': InfluxDBVersionDetector, keybinding: KeyBinding, 'list-filters': ListFilters, 'product-selector': ProductSelector, @@ -113,7 +115,10 @@ function initComponents(globals) { if (ComponentConstructor) { // Initialize the component and store its instance in the global namespace try { - const instance = ComponentConstructor({ component }); + // Prepare component options + const options = { component }; + + const instance = ComponentConstructor(options); globals[componentName] = ComponentConstructor; // Optionally store component instances for future reference diff --git a/assets/styles/components/_influxdb-version-detector.scss b/assets/styles/components/_influxdb-version-detector.scss new file mode 100644 index 0000000000..c64d3f21d5 --- /dev/null +++ b/assets/styles/components/_influxdb-version-detector.scss @@ -0,0 +1,647 @@ +// InfluxDB Version Detector Component Styles + +.influxdb-version-detector { + // CSS Custom Properties + --transition-fast: 0.2s ease; + --transition-normal: 0.3s ease; + --spacing-sm: 0.625rem; + --spacing-md: 1.25rem; + + margin: 2rem auto; + + .detector-title { + color: $article-heading; + margin-bottom: 0.625rem; + font-size: 1.8em; + font-weight: 600; + } + + .detector-subtitle { + color: $article-text; + margin-bottom: 1.875rem; + font-size: 0.95em; + opacity: 0.8; + } + + // Progress bar + .progress { + margin-bottom: 1.5625rem; + height: 6px; + background: $article-hr; + border-radius: 3px; + overflow: hidden; + + .progress-bar { + height: 100%; + background: $article-link; + transition: width var(--transition-normal); + } + } + + // Question container + .question-container { + min-height: 150px; + + .question { + display: none; + animation: fadeIn var(--transition-normal); + + &.active { + display: block; + } + + .question-text { + font-size: 1.1em; + color: $article-heading; + margin-bottom: 1.25rem; + font-weight: 500; + } + } + } + + // Buttons - Base styles and variants + %button-base { + border: none; + border-radius: var(--border-radius); + cursor: pointer; + transition: all var(--transition-fast); + font-family: inherit; + + &:focus { + outline: 2px solid $article-link; + outline-offset: 2px; + } + } + + .option-button { + @extend %button-base; + display: block; + width: 100%; + text-align: left; + margin-bottom: 0.75rem; + padding: 0.875rem 1.125rem; + background: $article-bg; + color: $article-text; + border: 2px solid $article-hr; + font-size: 15px; + + &:hover { + border-color: $article-link; + background: $article-bg; + transform: translateX(3px); + } + + &:active { + transform: translateX(1px); + } + } + + .submit-button { + @extend %button-base; + background: $article-link; + color: $g20-white; + padding: 0.75rem 1.5rem; + font-size: 15px; + font-weight: 500; + + &:hover { + background: $b-ocean; + color: $g20-white; + } + + &:disabled { + background: $g8-storm; + cursor: not-allowed; + } + } + + .back-button { + @extend %button-base; + background: $g8-storm; + color: $g20-white; + padding: var(--spacing-sm) var(--spacing-md); + font-size: 14px; + margin-right: var(--spacing-sm); + + &:hover { + background: $g9-mountain; + } + } + + .restart-button { + @extend .back-button; + margin-top: var(--spacing-md); + margin-right: 0; + } + + // Input fields + %input-base { + width: 100%; + border: 2px solid $article-hr; + border-radius: var(--border-radius); + transition: border-color var(--transition-fast); + background: $article-bg; + color: $article-text; + + &:focus { + outline: none; + border-color: $article-link; + } + } + + .input-group { + margin-bottom: var(--spacing-md); + + label { + display: block; + margin-bottom: 0.5rem; + color: $article-text; + font-weight: 500; + } + + input { + @extend %input-base; + padding: 0.75rem; + font-size: 14px; + } + } + + textarea { + @extend %input-base; + padding: var(--spacing-sm); + font-family: var(--font-mono, 'Courier New', monospace); + font-size: 12px; + resize: vertical; + min-height: 120px; + + &::placeholder { + color: rgba($article-text, 0.6); + opacity: 1; // Firefox fix + } + + &::-webkit-input-placeholder { + color: rgba($article-text, 0.6); + } + + &::-moz-placeholder { + color: rgba($article-text, 0.6); + opacity: 1; + } + + &:-ms-input-placeholder { + color: rgba($article-text, 0.6); + } + } + + // Code block - match site standards + .code-block { + background: $article-code-bg; + color: $article-code; + padding: 1.75rem 1.75rem 1.25rem; + border-radius: $radius; + font-family: $code; + font-size: 1rem; + margin: 2rem 0 2.25rem; + overflow-x: scroll; + overflow-y: hidden; + line-height: 1.7rem; + white-space: pre; + } + + // URL pattern hint + .url-pattern-hint { + margin-bottom: var(--spacing-sm); + padding: var(--spacing-sm); + background: $article-note-base; + border: 1px solid $article-note-base; + border-radius: var(--border-radius); + color: $article-note-text; + font-size: 13px; + } + + // URL suggestions + .url-suggestions { + margin-bottom: var(--spacing-md); + + .suggestions-header { + color: $article-heading; + margin-bottom: var(--spacing-sm); + font-size: 14px; + } + + .suggestion-button { + @extend %button-base; + display: block; + width: 100%; + text-align: left; + margin-bottom: var(--spacing-sm); + padding: var(--spacing-sm); + background: $article-bg; + border: 1px solid $article-hr; + + &:hover { + border-color: $article-link; + background: $article-bg; + } + + .suggestion-url { + font-family: var(--font-mono, 'Courier New', monospace); + font-size: 13px; + color: $article-link; + margin-bottom: 2px; + } + + .suggestion-product { + font-size: 12px; + color: $article-text; + opacity: 0.8; + } + + .suggestion-pattern { + font-size: 11px; + color: $article-link; + font-style: italic; + margin-top: 2px; + } + } + } + + // Results + .result { + display: none; + margin-top: var(--spacing-sm); + padding: var(--spacing-md); + border-radius: var(--border-radius); + animation: fadeIn var(--transition-normal); + + &.show { + display: block; + } + + &.success { + background: $article-bg; + border-left: 3px solid $article-note-base; + color: $article-text; + } + + &.error { + background: $r-flan; + border-left: 3px solid $article-caution-base; + color: $r-basalt; + } + + &.info { + background: $article-note-base; + border-left: 3px solid $article-note-base; + color: $article-note-text; + } + + &.warning { + background: $article-warning-bg; + border-left: 3px solid $article-warning-base; + color: $article-warning-text; + } + } + + .detected-version { + font-size: 1.3em; + font-weight: bold; + color: $article-link; + margin-bottom: var(--spacing-sm); + padding: var(--spacing-sm); + background: rgba($article-link, 0.1); + border-radius: 4px; + border-left: 4px solid $article-link; + } + + // URL pre-filled indicator + .url-prefilled-indicator { + font-size: 0.85em; + color: $article-note-text; + margin-bottom: 8px; + padding: 4px 8px; + background: rgba($article-link, 0.1); + border-left: 3px solid $article-link; + } + + // Loading animation + .loading { + display: inline-block; + margin-left: var(--spacing-sm); + + &:after { + content: '...'; + animation: dots 1.5s steps(4, end) infinite; + } + } + + @keyframes dots { + 0%, 20% { + content: '.'; + } + 40% { + content: '..'; + } + 60%, 100% { + content: '...'; + } + } + + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + + // Responsive design + @media (max-width: 768px) { + padding: 1.5rem; + + .detector-title { + font-size: 1.5em; + } + + .option-button { + padding: 0.75rem 1rem; + font-size: 14px; + } + + .submit-button, + .back-button { + padding: var(--spacing-sm) var(--spacing-md); + font-size: 14px; + } + } + + @media (max-width: 480px) { + padding: 1rem; + + .detector-title { + font-size: 1.3em; + } + + .detector-subtitle { + font-size: 0.9em; + } + + .question-text { + font-size: 1em; + } + } + + + // Product ranking results + .product-ranking { + margin-bottom: var(--spacing-sm); + padding: 0.75rem; + border-radius: var(--border-radius); + border-left: 4px solid $article-hr; + background: $article-bg; + + &.top-result { + background: rgba($article-link, 0.1); + border-color: $article-link; + } + + .product-title { + font-weight: 600; + margin-bottom: 0.25rem; + } + + .most-likely-label { + color: $article-link; + font-size: 0.9em; + margin-left: var(--spacing-sm); + } + + .product-details { + color: $article-text; + font-size: 0.9em; + margin-top: 0.25rem; + opacity: 0.8; + } + } + + // Grafana networking tips + .grafana-tips { + margin-top: var(--spacing-md); + padding: 1rem; + background: rgba($article-link, 0.1); + border-left: 4px solid $article-link; + border-radius: var(--border-radius); + + .tips-title { + margin: 0 0 var(--spacing-sm) 0; + color: $article-link; + font-size: 1.1em; + } + + .tips-description { + margin: 0 0 var(--spacing-sm) 0; + font-size: 0.9em; + } + + .tips-list { + margin: 0; + padding-left: 1.25rem; + font-size: 0.85em; + + li { + margin-bottom: 0.25rem; + } + + code { + background: rgba($article-link, 0.15); + padding: 0.125rem 0.25rem; + border-radius: 3px; + font-size: 0.9em; + } + } + + .tips-link { + margin: var(--spacing-sm) 0 0 0; + font-size: 0.85em; + } + } + + // Expected results section + .expected-results { + margin: 1rem 0; + + .results-title { + font-weight: 600; + margin-bottom: 0.5rem; + } + + .results-list { + margin: 0; + padding-left: 1rem; + font-size: 0.9em; + + li { + margin-bottom: 0.25rem; + } + } + } + + // Question text styling + .question-text-spaced { + margin-top: 1rem; + font-weight: normal; + font-size: 0.95em; + } + + .question-options { + margin-top: 1rem; + } + + // Command help section + .command-help { + margin-top: var(--spacing-md); + } + + // Grafana links styling + .grafana-link { + color: $article-link; + text-decoration: underline; + + &:hover { + color: $article-link-hover; + } + } + + // Manual command output + .manual-output { + margin: 1rem 0; + padding: var(--spacing-sm); + background: $article-bg; + border-left: 4px solid $article-link; + border-radius: var(--border-radius); + } + + // Action section with buttons + .action-section { + margin-top: var(--spacing-md); + } + + // Quick Reference expandable section + .quick-reference { + margin-top: 2rem; + + details { + border: 1px solid $article-hr; + border-radius: var(--border-radius); + padding: 0.5rem; + } + + .reference-summary { + cursor: pointer; + font-weight: 600; + padding: 0.5rem 0; + user-select: none; + color: $article-link; + + &:hover { + color: $article-link-hover; + } + } + } + + // Expandable summary styling (for Docker Commands, etc.) + .expandable-summary { + cursor: pointer; + font-weight: 600; + padding: 0.5rem 0; + user-select: none; + color: $article-link; + position: relative; + padding-left: 1.5rem; // Make room for custom icon + + &:hover { + color: $article-link-hover; + } + + // Hide the default disclosure triangle + &::marker, + &::-webkit-details-marker { + display: none; + } + + // Add custom plus/minus icon + &::before { + content: '+'; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 1rem; + height: 1rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: bold; + color: $article-link; + border: 1px solid $article-link; + border-radius: 3px; + background: transparent; + } + + // Change to minus when expanded + details[open] & { + &::before { + content: '−'; + } + } + + &:hover::before { + color: $article-link-hover; + border-color: $article-link-hover; + } + } + + // Quick Reference expandable section + .quick-reference { + margin-top: 2rem; + + details { + border: 1px solid $article-hr; + border-radius: var(--border-radius); + padding: 0.5rem; + } + + .reference-table { + margin-top: 1rem; + width: 100%; + border-collapse: collapse; + font-size: 0.9em; + + th, td { + padding: 0.5rem; + text-align: left; + border: 1px solid $article-hr; + } + + th { + padding: 0.75rem 0.5rem; + background: rgba($article-link, 0.1); + font-weight: 600; + } + + tbody tr:nth-child(even) { + background: rgba($article-text, 0.02); + } + + .product-name { + font-weight: 600; + } + } + } + +} \ No newline at end of file diff --git a/assets/styles/layouts/_modals.scss b/assets/styles/layouts/_modals.scss index 5a46d5b6c5..2a149c378b 100644 --- a/assets/styles/layouts/_modals.scss +++ b/assets/styles/layouts/_modals.scss @@ -26,7 +26,8 @@ .modal-body { position: relative; display: flex; - overflow: hidden; + overflow-y: auto; + overflow-x: hidden; // width: 100%; max-width: 650px; max-height: 97.5vh; @@ -37,6 +38,27 @@ color: $article-text; font-size: 1rem; transition: margin .4s; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; // iOS smooth scrolling + + // Custom scrollbar styling + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: rgba($article-hr, 0.2); + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + background: rgba($article-text, 0.3); + border-radius: 4px; + + &:hover { + background: rgba($article-text, 0.5); + } + } } &.open { @@ -62,6 +84,7 @@ overflow: visible; width: 586px; max-width: 100%; + flex-shrink: 0; h3 { color: $article-heading; diff --git a/assets/styles/styles-default.scss b/assets/styles/styles-default.scss index 5fd3eed2d0..6f873fb20f 100644 --- a/assets/styles/styles-default.scss +++ b/assets/styles/styles-default.scss @@ -34,3 +34,6 @@ "layouts/code-controls", "layouts/v3-wayfinding"; +// Import Components +@import "components/influxdb-version-detector"; + diff --git a/content/enterprise_influxdb/v1/tools/grafana.md b/content/enterprise_influxdb/v1/tools/grafana.md index 2712796266..4c2377f297 100644 --- a/content/enterprise_influxdb/v1/tools/grafana.md +++ b/content/enterprise_influxdb/v1/tools/grafana.md @@ -8,128 +8,134 @@ menu: name: Grafana weight: 60 parent: Tools +related: + - /flux/v0/get-started/, Get started with Flux + - https://grafana.com/docs/, Grafana documentation +alt_links: + core: /influxdb3/core/visualize-data/grafana/ + enterprise: /influxdb3/enterprise/visualize-data/grafana/ + cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ + cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ + clustered: /influxdb3/clustered/process-data/visualize/grafana/ canonical: /influxdb/v2/tools/grafana/ --- Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/products/cloud/) to visualize data from your **InfluxDB Enterprise** cluster. -{{% note %}} -#### Required -- The instructions in this guide require **Grafana Cloud** or **Grafana v10.3+**. - For information about using InfluxDB with other versions of Grafana, - see the [Grafana documentation](https://grafana.com/docs/grafana/latest/datasources/influxdb/). -- To use **Flux**, use **InfluxDB 1.8.1+** and [enable Flux](/influxdb/v1/flux/installation/) - in your InfluxDB configuration file. -{{% /note %}} - -1. [Set up an InfluxDB Enterprise cluster](/enterprise_influxdb/v1/introduction/installation/). -2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or - [download and install Grafana](https://grafana.com/grafana/download). -3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, - [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit - in your browser. -4. In the left navigation of the Grafana UI, expand the **Connections** section - and click **Add new connection**. -5. Select **InfluxDB** from the list of available data sources and click - **Add data source**. -6. On the **Data Source configuration page**, enter a **name** for your InfluxDB data source. -7. In the **Query Language** drop-down menu, select one of the query languages - supported by InfluxDB {{< current-version >}} (InfluxQL or Flux): - - {{% note %}} -SQL is only supported in InfluxDB 3. - {{% /note %}} +> [!Note] +> {{< influxdb-version-detector >}} + +> [!Note] +> #### Required +> - The instructions in this guide require **Grafana Cloud** or **Grafana v10.3+**. +> For information about using InfluxDB with other versions of Grafana, +> see the [Grafana documentation](https://grafana.com/docs/grafana/latest/datasources/influxdb/). +> - To use **Flux**, use **InfluxDB 1.8.1+** and [enable Flux](/enterprise_influxdb/v1/flux/installation/) +> in your InfluxDB data nodes. + +- [Install Grafana](#install-grafana) +- [Create an InfluxDB data source](#create-an-influxdb-data-source) +- [Query and visualize data](#query-and-visualize-data) + +## Install Grafana + +1. [Set up an InfluxDB Enterprise cluster](/enterprise_influxdb/v1/introduction/installation/). +2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or + [download and install Grafana](https://grafana.com/grafana/download). +3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, + [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit + in your browser. + +> [!Note] +> #### Using Grafana Cloud with a local InfluxDB instance +> +> If you need to keep your database local, consider running Grafana locally instead of using Grafana Cloud, +> as this avoids the need to expose your database to the internet. +> +> To use InfluxDB running on your private network with Grafana Cloud, you must +> [configure a private data source](https://grafana.com/docs/grafana-cloud/data-sources/private-data-sources/). +> See the Grafana documentation for instructions on configuring a Grafana Cloud private data source +> with {{% product-name %}} running on `http://localhost:8086`. + +> [!Note] +> #### Query language support +> - InfluxQL is supported in InfluxDB Enterprise v1.8.x and later. +> - Flux is supported in InfluxDB Enterprise v1.8.1 and later. +> - SQL is only supported in InfluxDB 3. For more information, see how to [get started with InfluxDB 3 Enterprise](/influxdb3/enterprise/get-started/). + +## Create an InfluxDB data source + +1. In your Grafana interface, click **Connections** in the left sidebar +2. Click **Data sources** +3. Click **Add new connection** +4. Search for and select **InfluxDB**. The InfluxDB data source configuration page displays. +5. In the **Settings** tab, configure the following: + + - **Name**: A descriptive name for your data source + - **URL**: Your server or load balancer URL--for example, `https://{{< influxdb/host >}}` + - **Product**: From the dropdown, select **InfluxDB Enterprise 1.x** + - **Query Language**: Select **InfluxQL** or **Flux** + +### Configure database settings + +The fields in this section change based on your query language selection. {{< tabs-wrapper >}} {{% tabs %}} [InfluxQL](#) [Flux](#) {{% /tabs %}} - {{% tab-content %}} -## Configure Grafana to use InfluxQL - -With **InfluxQL** selected as the query language in your InfluxDB data source settings: - -1. Under **HTTP**, enter the following: - - - **URL**: Your **InfluxDB Enterprise URL** or **load balancer URL**. - - ```sh - http://localhost:8086 - ``` + -2. Under **InfluxDB Details**, enter the following: +## Configure Grafana to use InfluxQL - - **Database**: your database name - - **User**: your InfluxDB username _(if [authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/))_ - - **Password**: your InfluxDB password _(if [authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/))_ - - **HTTP Method**: Select **GET** or **POST** _(for differences between the two, - see the [query HTTP endpoint documentation](/enterprise_influxdb/v1/tools/api/#query-http-endpoint))_ +When you select **InfluxQL** as the query language, configure the following: -3. Provide a **[Min time interval](https://grafana.com/docs/grafana/latest/datasources/influxdb/#min-time-interval)** - (default is 10s). +- **Database**: Your database name +- **User**: Your InfluxDB username _(if [authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/))_ +- **Password**: Your InfluxDB password _(if [authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/))_ - {{< img-hd src="/img/influxdb/v1-tools-grafana-influxql.png" />}} +{{< img-hd src="/img/influxdb3/enterprise-v1-grafana-product-dropdown-flux.png" alt="InfluxQL configuration for InfluxDB Enterprise 1.x" />}} -4. Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns - the result of the test. +Click **Save & Test**. Grafana attempts to connect to InfluxDB Enterprise and returns the result of the test. -{{% /tab-content %}} - +{{% /tab-content %}} {{% tab-content %}} -## Configure Grafana to use Flux - -With **Flux** selected as the query language in your InfluxDB data source, -configure your InfluxDB connection: - -1. Ensure [Flux is enabled](/enterprise_influxdb/v1/flux/installation/) in - your InfluxDB Enterprise data nodes. + -2. Under **HTTP**, enter the following: +## Configure Grafana to use Flux - - **URL**: Your **InfluxDB Enterprise URL** or **load balancer URL**. +When you select **Flux** as the query language, configure the following: - ```sh - http://localhost:8086 - ``` +1. Ensure [Flux is enabled](/enterprise_influxdb/v1/flux/installation/) in your InfluxDB Enterprise data nodes. -3. Under **InfluxDB Details**, enter the following: +2. Configure the database settings: - - **Organization**: Provide an arbitrary value. - - **Token**: If [InfluxDB authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/), - provide your InfluxDB username and password using the following syntax: + - **Organization**: Provide an arbitrary value (InfluxDB Enterprise 1.x does not use organizations) + - **Default Bucket**: Provide a default database and retention policy + - **Token**: If [InfluxDB authentication is enabled](/enterprise_influxdb/v1/administration/authentication_and_authorization/) - ```sh - # Syntax - username:password +{{< img-hd src="/img/influxdb3/enterprise-v1-grafana-product-dropdown-flux.png" alt="Flux configuration for InfluxDB Enterprise 1.x" />}} - # Example - johndoe:mY5uP3rS3crE7pA5Sw0Rd - ``` +Click **Save & Test**. Grafana attempts to connect to InfluxDB Enterprise and returns the result of the test. - If authentication is not enabled, leave blank. + +{{% /tab-content %}} +{{< /tabs-wrapper >}} - - **Default Bucket**: Provide a default database and retention policy combination - using the following syntax: +## Query and visualize data - ```sh - # Syntax - database-name/retention-policy-name +With your InfluxDB connection configured, use Grafana to query and visualize time series data. - # Examples - example-db/example-rp - telegraf/autogen - ``` +### Query inspection in Grafana - - **Min time interval**: [Grafana minimum time interval](https://grafana.com/docs/grafana/latest/features/datasources/influxdb/#min-time-interval). +To learn about query management and inspection in Grafana, see the +[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). - {{< img-hd src="/img/influxdb/v1-tools-grafana-flux.png" />}} +### Build visualizations with Grafana -3. Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns - the result of the test. -{{% /tab-content %}} - -{{< /tabs-wrapper >}} +For a comprehensive walk-through of creating visualizations with +Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). diff --git a/content/influxdb/cloud/tools/grafana.md b/content/influxdb/cloud/tools/grafana.md index 898db072b8..981be27bd5 100644 --- a/content/influxdb/cloud/tools/grafana.md +++ b/content/influxdb/cloud/tools/grafana.md @@ -12,8 +12,14 @@ related: - https://grafana.com/docs/, Grafana documentation - /influxdb/cloud/query-data/get-started/ - /influxdb/cloud/query-data/influxql/ + - /flux/v0/get-started/, Get started with Flux alt_links: - cloud-serverless: /influxdb3/cloud-serverless/visualize-data/grafana/ + v1: /influxdb/v1/tools/grafana/ + enterprise_v1: /enterprise_influxdb/v1/tools/grafana/ + v2: /influxdb/v2/tools/grafana/ + core: /influxdb3/core/visualize-data/grafana/ + enterprise: /influxdb3/enterprise/visualize-data/grafana/ + cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ clustered: /influxdb3/clustered/process-data/visualize/grafana/ source: /shared/influxdb-v2/tools/grafana.md diff --git a/content/influxdb/v1/tools/grafana.md b/content/influxdb/v1/tools/grafana.md index b19bb6e182..55c3689eb0 100644 --- a/content/influxdb/v1/tools/grafana.md +++ b/content/influxdb/v1/tools/grafana.md @@ -1,136 +1,139 @@ --- title: Use Grafana with InfluxDB -seotitle: Use Grafana with InfluxDB v1.11 +seotitle: Use Grafana with InfluxDB v1.x description: > - Configure Grafana to query and visualize data from InfluxDB v1.11. + Configure Grafana to query and visualize data from InfluxDB v1.x. menu: influxdb_v1: name: Grafana weight: 60 parent: Tools +related: + - /flux/v0/get-started/, Get started with Flux alt_links: v2: /influxdb/v2/tools/grafana/ + core: /influxdb3/core/visualize-data/grafana/ + enterprise: /influxdb3/enterprise/visualize-data/grafana/ + cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ + cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ + clustered: /influxdb3/clustered/process-data/visualize/grafana/ canonical: /influxdb/v2/tools/grafana/ --- Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/products/cloud/) -to visualize data from your **InfluxDB v1.11** instance. - -{{% note %}} -#### Required -- The instructions in this guide require **Grafana Cloud** or **Grafana v10.3+**. - For information about using InfluxDB with other versions of Grafana, - see the [Grafana documentation](https://grafana.com/docs/grafana/latest/datasources/influxdb/). -- To use **Flux**, use **InfluxDB 1.8.1+** and [enable Flux](/influxdb/v1/flux/installation/) - in your InfluxDB configuration file. -{{% /note %}} - -1. [Start InfluxDB](/influxdb/v1/introduction/get-started/). -2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or - [download and install Grafana](https://grafana.com/grafana/download). -3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, - [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit - in your browser. -4. In the left navigation of the Grafana UI, expand the **Connections** section - and click **Add new connection**. -5. Select **InfluxDB** from the list of available data sources and click - **Add data source**. -6. On the **Data Source configuration page**, enter a **name** for your InfluxDB data source. -7. In the **Query Language** drop-down menu, select one of the query languages - supported by InfluxDB {{< current-version >}} (InfluxQL or Flux): - - {{% note %}} -SQL is only supported in InfluxDB 3. - {{% /note %}} +to visualize data from your {{% product-name %}} instance. + +> [!Note] +> {{< influxdb-version-detector >}} + +> [!Note] +> #### Required +> - The instructions in this guide require **Grafana Cloud** or **Grafana v10.3+**. +> For information about using InfluxDB with other versions of Grafana, +> see the [Grafana documentation](https://grafana.com/docs/grafana/latest/datasources/influxdb/). +> - To use **Flux**, use **InfluxDB 1.8.1+** and [enable Flux](/influxdb/v1/flux/installation/) +> in your InfluxDB configuration file. + +- [Install Grafana](#install-grafana) +- [Create an InfluxDB data source](#create-an-influxdb-data-source) +- [Query and visualize data](#query-and-visualize-data) + +## Install Grafana + +1. [Start InfluxDB](/influxdb/v1/introduction/get-started/). +2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or + [download and install Grafana](https://grafana.com/grafana/download). +3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, + [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit + in your browser. + +> [!Note] +> #### Using Grafana Cloud with a local InfluxDB instance +> +> If you need to keep your database local, consider running Grafana locally instead of using Grafana Cloud, +> as this avoids the need to expose your database to the internet. +> +> To use InfluxDB running on your private network with Grafana Cloud, you must +> [configure a private data source](https://grafana.com/docs/grafana-cloud/data-sources/private-data-sources/). +> See the Grafana documentation for instructions on configuring a Grafana Cloud private data source +> with {{% product-name %}} running on `http://localhost:8086`. + +> [!Note] +> SQL is only supported in InfluxDB 3. +> For more information, see how to [get started with InfluxDB 3 Core](/influxdb3/core/get-started/). + +## Create an InfluxDB data source + +1. In your Grafana interface, click **Connections** in the left sidebar +2. Click **Data sources** +3. Click **Add new connection** +4. Search for and select **InfluxDB**. The InfluxDB data source configuration page displays. +5. In the **Settings** tab, configure the following: + + - **Name**: A descriptive name for your data source + - **URL**: Your server URL--for example, `https://{{< influxdb/host >}}` + - **Product**: From the dropdown, select **InfluxDB OSS 1.x** + - **Query Language**: Select **InfluxQL** or **Flux** + +### Configure database settings + +The fields in this section change based on your query language selection. {{< tabs-wrapper >}} {{% tabs %}} [InfluxQL](#) [Flux](#) {{% /tabs %}} - {{% tab-content %}} -## Configure Grafana to use InfluxQL - -With **InfluxQL** selected as the query language in your InfluxDB data source settings: - -1. Under **HTTP**, enter the following: - - - **URL**: Your **InfluxDB URL**. - - ```sh - http://localhost:8086 - ``` + -2. Under **InfluxDB Details**, enter the following: +## Configure Grafana to use InfluxQL - - **Database**: your database name - - **User**: your InfluxDB username _(if [authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/))_ - - **Password**: your InfluxDB password _(if [authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/))_ - - **HTTP Method**: Select **GET** or **POST** _(for differences between the two, - see the [query HTTP endpoint documentation](/influxdb/v1/tools/api/#query-http-endpoint))_ +When you select **InfluxQL** as the query language, configure the following: -3. Provide a **[Min time interval](https://grafana.com/docs/grafana/latest/datasources/influxdb/#min-time-interval)** - (default is 10s). +- **Database**: Your database name +- **User**: Your InfluxDB username _(if [authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/)); leave blank if authentication is disabled._ +- **Password**: Your InfluxDB password _(if [authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/)); leave blank if authentication is disabled._ - {{< img-hd src="/img/influxdb/v1-tools-grafana-influxql.png" />}} +{{< img-hd src="/img/influxdb3/OSS-v1-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for InfluxDB OSS 1.x" />}} -4. Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns - the result of the test. +Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns the result of the test. -{{% /tab-content %}} - +{{% /tab-content %}} {{% tab-content %}} -## Configure Grafana to use Flux - -With **Flux** selected as the query language in your InfluxDB data source, -configure your InfluxDB connection: - -1. Ensure [Flux is enabled](/influxdb/v1/flux/installation/) in InfluxDB. + -2. Under **HTTP**, enter the following: +## Configure Grafana to use Flux - - **URL**: Your **InfluxDB URL**. +When you select **Flux** as the query language, configure the following: - ```sh - http://localhost:8086 - ``` +1. Ensure [Flux is enabled](/influxdb/v1/flux/installation/) in your InfluxDB configuration file. -3. Under **InfluxDB Details**, enter the following: +2. Configure the database settings: - - **Organization**: Provide an arbitrary value. - - **Token**: If [InfluxDB authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/), - provide your InfluxDB username and password using the following syntax: + - **Organization**: Provide an arbitrary value (InfluxDB 1.x does not use organizations) + - **Default Bucket**: Provide a default database and retention policy + - **Token**: If [InfluxDB authentication is enabled](/influxdb/v1/administration/authentication_and_authorization/) provide your InfluxDB username and password - ```sh - # Syntax - username:password +{{< img-hd src="/img/influxdb3/OSS-v1-grafana-product-dropdown-flux.png" alt="Flux configuration for InfluxDB OSS 1.x" />}} - # Example - johndoe:mY5uP3rS3crE7pA5Sw0Rd - ``` +Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns the result of the test. - If authentication is not enabled, leave blank. + +{{% /tab-content %}} +{{< /tabs-wrapper >}} - - **Default Bucket**: Provide a default database and retention policy combination - using the following syntax: +## Query and visualize data - ```sh - # Syntax - database-name/retention-policy-name +With your InfluxDB connection configured, use Grafana to query and visualize time series data. - # Examples - example-db/example-rp - telegraf/autogen - ``` +### Query inspection in Grafana - - **Min time interval**: [Grafana minimum time interval](https://grafana.com/docs/grafana/latest/features/datasources/influxdb/#min-time-interval). +To learn about query management and inspection in Grafana, see the +[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). - {{< img-hd src="/img/influxdb/v1-tools-grafana-flux.png" />}} +### Build visualizations with Grafana -3. Click **Save & Test**. Grafana attempts to connect to InfluxDB and returns - the result of the test. -{{% /tab-content %}} - -{{< /tabs-wrapper >}} +For a comprehensive walk-through of creating visualizations with +Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). diff --git a/content/influxdb/v2/tools/grafana.md b/content/influxdb/v2/tools/grafana.md index 439b6115cc..6430ec7996 100644 --- a/content/influxdb/v2/tools/grafana.md +++ b/content/influxdb/v2/tools/grafana.md @@ -13,6 +13,15 @@ aliases: related: - https://grafana.com/docs/, Grafana documentation - /influxdb/v2/query-data/get-started/ +alt_links: + v1: /influxdb/v1/tools/grafana/ + enterprise_v1: /enterprise_influxdb/v1/tools/grafana/ + cloud: /influxdb/cloud/tools/grafana/ + core: /influxdb3/core/visualize-data/grafana/ + enterprise: /influxdb3/enterprise/visualize-data/grafana/ + cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ + cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ + clustered: /influxdb3/clustered/process-data/visualize/grafana/ source: /shared/influxdb-v2/tools/grafana.md --- diff --git a/content/influxdb3/cloud-dedicated/process-data/visualize/chronograf.md b/content/influxdb3/cloud-dedicated/process-data/visualize/chronograf.md index 207a9be33c..d0a0fe9325 100644 --- a/content/influxdb3/cloud-dedicated/process-data/visualize/chronograf.md +++ b/content/influxdb3/cloud-dedicated/process-data/visualize/chronograf.md @@ -9,6 +9,8 @@ menu: name: Use Chronograf parent: Visualize data weight: 202 +aliases: + - /influxdb3/cloud-dedicated/visualize-data/chronograf/ related: - /chronograf/v1/ metadata: [InfluxQL only] @@ -84,14 +86,14 @@ If you haven't already, [download and install Chronograf](/chronograf/v1/introdu > schema information may not be available in the Data Explorer. > This limits the Data Explorer's query building functionality and requires you to > build queries manually using -> [fully-qualified measurements](/influxdb3/cloud-dedicated/reference/influxql/select/#fully-qualified-measurement) +> [fully qualified measurements](/influxdb3/cloud-dedicated/reference/influxql/select/#fully-qualified-measurement) > in the `FROM` clause. For example: > > ```sql -> -- Fully-qualified measurement +> -- Fully qualified measurement > SELECT * FROM "db-name"."rp-name"."measurement-name" -> -> -- Fully-qualified measurement shorthand (use the default retention policy) +> +> -- Fully qualified measurement shorthand (use the default retention policy) > SELECT * FROM "db-name".."measurement-name" > ``` > diff --git a/content/influxdb3/cloud-dedicated/process-data/visualize/grafana.md b/content/influxdb3/cloud-dedicated/process-data/visualize/grafana.md index 4fc0cb47e5..a727fa2e91 100644 --- a/content/influxdb3/cloud-dedicated/process-data/visualize/grafana.md +++ b/content/influxdb3/cloud-dedicated/process-data/visualize/grafana.md @@ -15,6 +15,7 @@ aliases: - /influxdb3/cloud-dedicated/query-data/sql/execute-queries/grafana/ - /influxdb3/cloud-dedicated/query-data/influxql/execute-queries/grafana - /influxdb3/cloud-dedicated/process-data/tools/grafana/ + - /influxdb3/cloud-dedicated/visualize-data/grafana/ alt_links: v2: /influxdb/v2/tools/grafana/ cloud: /influxdb/cloud/tools/grafana/ diff --git a/content/influxdb3/cloud-dedicated/process-data/visualize/superset.md b/content/influxdb3/cloud-dedicated/process-data/visualize/superset.md index 887fb8d649..45de33bca9 100644 --- a/content/influxdb3/cloud-dedicated/process-data/visualize/superset.md +++ b/content/influxdb3/cloud-dedicated/process-data/visualize/superset.md @@ -13,6 +13,7 @@ menu: influxdb3/cloud-dedicated/tags: [Flight client, query, flightsql, superset] aliases: - /influxdb3/cloud-dedicated/query-data/execute-queries/flight-sql/superset/ + - /influxdb3/cloud-dedicated/visualize-data/superset/ - /influxdb3/cloud-dedicated/query-data/tools/superset/ - /influxdb3/cloud-dedicated/query-data/sql/execute-queries/superset/ - /influxdb3/cloud-dedicated/process-data/tools/superset/ diff --git a/content/influxdb3/cloud-dedicated/process-data/visualize/tableau.md b/content/influxdb3/cloud-dedicated/process-data/visualize/tableau.md index 4ca2ce9ba7..3b317ca700 100644 --- a/content/influxdb3/cloud-dedicated/process-data/visualize/tableau.md +++ b/content/influxdb3/cloud-dedicated/process-data/visualize/tableau.md @@ -13,6 +13,7 @@ menu: influxdb3/cloud-dedicated/tags: [Flight client, query, flightsql, tableau, sql] aliases: - /influxdb3/cloud-dedicated/query-data/execute-queries/flight-sql/tableau/ + - /influxdb3/cloud-dedicated/visualize-data/tableau/ - /influxdb3/cloud-dedicated/query-data/tools/tableau/ - /influxdb3/cloud-dedicated/query-data/sql/execute-queries/tableau/ - /influxdb3/cloud-dedicated/process-data/tools/tableau/ diff --git a/content/influxdb3/cloud-dedicated/query-data/execute-queries/odbc.md b/content/influxdb3/cloud-dedicated/query-data/execute-queries/odbc.md new file mode 100644 index 0000000000..0bf0ad5bf5 --- /dev/null +++ b/content/influxdb3/cloud-dedicated/query-data/execute-queries/odbc.md @@ -0,0 +1,20 @@ +--- +title: Execute SQL queries with ODBC +description: > + Use the Arrow Flight SQL ODBC driver to execute SQL queries against {{% product-name %}} from + ODBC-compatible applications and programming languages. +menu: + influxdb3_cloud_dedicated: + name: Use ODBC + parent: Execute queries +weight: 351 +influxdb3/cloud-dedicated/tags: [query, sql, odbc] +metadata: [SQL] + +related: + - /influxdb3/cloud-dedicated/reference/sql/ + - /influxdb3/cloud-dedicated/query-data/ +source: /shared/influxdb3-query-guides/execute-queries/odbc.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/cloud-dedicated/query-data/execute-queries/visualization-tools.md b/content/influxdb3/cloud-dedicated/query-data/execute-queries/visualization-tools.md index 846a829841..96e8be6e76 100644 --- a/content/influxdb3/cloud-dedicated/query-data/execute-queries/visualization-tools.md +++ b/content/influxdb3/cloud-dedicated/query-data/execute-queries/visualization-tools.md @@ -9,7 +9,7 @@ menu: parent: Execute queries name: Use visualization tools identifier: query-with-visualization-tools -influxdb3/cloud-dedicated/tags: [query, sql, influxql] +influxdb3/cloud-dedicated/tags: [query, sql, influxql, visualization] metadata: [SQL, InfluxQL] aliases: - /influxdb3/cloud-dedicated/query-data/influxql/execute-queries/visualization-tools/ @@ -27,6 +27,7 @@ Use visualization tools to query data stored in {{% product-name %}} with SQL. The following visualization tools support querying InfluxDB with SQL: - [Grafana](/influxdb3/cloud-dedicated/process-data/visualize/grafana/) +- [Power BI](/influxdb3/cloud-dedicated/process-data/visualize/powerbi/) - [Superset](/influxdb3/cloud-dedicated/process-data/visualize/superset/) - [Tableau](/influxdb3/cloud-dedicated/process-data/visualize/tableau/) diff --git a/content/influxdb3/cloud-dedicated/visualize-data/powerbi.md b/content/influxdb3/cloud-dedicated/visualize-data/powerbi.md new file mode 100644 index 0000000000..d4bb06ee8f --- /dev/null +++ b/content/influxdb3/cloud-dedicated/visualize-data/powerbi.md @@ -0,0 +1,20 @@ +--- +title: Use Power BI to visualize data +description: > + Use Microsoft Power BI Desktop with the InfluxDB 3 custom connector to query and + visualize data from {{% product-name %}}. +menu: + influxdb3_cloud_dedicated: + name: Power BI + parent: Visualize data +weight: 104 +influxdb3/cloud-dedicated/tags: [visualization, powerbi, sql] +metadata: [SQL] +related: + - https://learn.microsoft.com/en-us/power-bi/desktop/, Power BI documentation + - /influxdb3/cloud-dedicated/query-data/sql/ + - /influxdb3/cloud-dedicated/query-data/execute-queries/odbc/ +source: /shared/influxdb3-visualize/powerbi.md +--- + + diff --git a/content/influxdb3/cloud-serverless/process-data/visualize/chronograf.md b/content/influxdb3/cloud-serverless/process-data/visualize/chronograf.md index 6c526fac2a..5967265d79 100644 --- a/content/influxdb3/cloud-serverless/process-data/visualize/chronograf.md +++ b/content/influxdb3/cloud-serverless/process-data/visualize/chronograf.md @@ -9,6 +9,8 @@ menu: name: Use Chronograf parent: Visualize data weight: 202 +aliases: + - /influxdb3/cloud-serverless/visualize-data/chronograf/ related: - /chronograf/v1/ metadata: [InfluxQL only] @@ -77,14 +79,14 @@ If you haven't already, [download and install Chronograf](/chronograf/v1/introdu > schema information may not be available in the Data Explorer. > This limits the Data Explorer's query building functionality and requires you to > build queries manually using -> [fully-qualified measurements](/influxdb3/cloud-serverless/reference/influxql/select/#fully-qualified-measurement) +> [fully qualified measurements](/influxdb3/cloud-serverless/reference/influxql/select/#fully-qualified-measurement) > in the `FROM` clause. For example: > > ```sql -> -- Fully-qualified measurement +> -- Fully qualified measurement > SELECT * FROM "db-name"."rp-name"."measurement-name" -> -> -- Fully-qualified measurement shorthand (use the default retention policy) +> +> -- Fully qualified measurement shorthand (use the default retention policy) > SELECT * FROM "db-name".."measurement-name" > ``` > diff --git a/content/influxdb3/cloud-serverless/process-data/visualize/superset.md b/content/influxdb3/cloud-serverless/process-data/visualize/superset.md index 87760d8235..7d0bd440c1 100644 --- a/content/influxdb3/cloud-serverless/process-data/visualize/superset.md +++ b/content/influxdb3/cloud-serverless/process-data/visualize/superset.md @@ -13,6 +13,7 @@ influxdb3/cloud-serverless/tags: [Flight client, query, flightsql, superset] aliases: - /influxdb3/cloud-serverless/query-data/tools/superset/ - /influxdb3/cloud-serverless/query-data/sql/execute-queries/superset/ + - /influxdb3/cloud-serverless/visualize-data/superset/ - /influxdb3/cloud-serverless/process-data/tools/superset/ alt_links: core: /influxdb3/core/visualize-data/superset/ diff --git a/content/influxdb3/cloud-serverless/process-data/visualize/tableau.md b/content/influxdb3/cloud-serverless/process-data/visualize/tableau.md index 11876b5bcf..c09a6b3e96 100644 --- a/content/influxdb3/cloud-serverless/process-data/visualize/tableau.md +++ b/content/influxdb3/cloud-serverless/process-data/visualize/tableau.md @@ -11,6 +11,7 @@ menu: influxdb3/cloud-serverless/tags: [Flight client, query, flightsql, tableau, sql] aliases: - /influxdb3/cloud-serverless/query-data/sql/execute-queries/tableau/ + - /influxdb3/cloud-serverless/visualize-data/tableau/ alt_links: core: /influxdb3/core/visualize-data/tableau/ enterprise: /influxdb3/enterprise/visualize-data/tableau/ diff --git a/content/influxdb3/cloud-serverless/query-data/execute-queries/odbc.md b/content/influxdb3/cloud-serverless/query-data/execute-queries/odbc.md new file mode 100644 index 0000000000..ceabe9fdd6 --- /dev/null +++ b/content/influxdb3/cloud-serverless/query-data/execute-queries/odbc.md @@ -0,0 +1,19 @@ +--- +title: Execute SQL queries with ODBC +description: > + Use the Arrow Flight SQL ODBC driver to execute SQL queries against {{% product-name %}} from + ODBC-compatible applications and programming languages. +menu: + influxdb3_cloud_serverless: + name: Use ODBC + parent: Execute queries +weight: 351 +influxdb3/cloud-serverless/tags: [query, sql, odbc] +metadata: [SQL] +related: + - /influxdb3/cloud-serverless/reference/sql/ + - /influxdb3/cloud-serverless/query-data/ +source: /shared/influxdb3-query-guides/execute-queries/odbc.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/cloud-serverless/query-data/execute-queries/visualization-tools.md b/content/influxdb3/cloud-serverless/query-data/execute-queries/visualization-tools.md index 765c281766..039134e2e0 100644 --- a/content/influxdb3/cloud-serverless/query-data/execute-queries/visualization-tools.md +++ b/content/influxdb3/cloud-serverless/query-data/execute-queries/visualization-tools.md @@ -9,7 +9,7 @@ menu: parent: Execute queries name: Use visualization tools identifier: query-with-visualization-tools -influxdb3/cloud-serverless/tags: [query, sql, influxql] +influxdb3/cloud-serverless/tags: [query, sql, influxql, visualization] metadata: [SQL, InfluxQL] aliases: - /influxdb3/cloud-serverless/query-data/influxql/execute-queries/visualization-tools/ @@ -27,6 +27,7 @@ Use visualization tools to query data stored in {{% product-name %}}. The following visualization tools support querying InfluxDB with SQL: - [Grafana](/influxdb3/cloud-serverless/process-data/visualize/grafana/) +- [Power BI](/influxdb3/cloud-serverless/process-data/visualize/powerbi/) - [Superset](/influxdb3/cloud-serverless/process-data/visualize/superset/) - [Tableau](/influxdb3/cloud-serverless/process-data/visualize/tableau/) diff --git a/content/influxdb3/cloud-serverless/visualize-data/powerbi.md b/content/influxdb3/cloud-serverless/visualize-data/powerbi.md new file mode 100644 index 0000000000..ffa7beed98 --- /dev/null +++ b/content/influxdb3/cloud-serverless/visualize-data/powerbi.md @@ -0,0 +1,20 @@ +--- +title: Use Power BI to visualize data +description: > + Use Microsoft Power BI Desktop with the InfluxDB 3 custom connector to query and + visualize data from {{% product-name %}}. +menu: + influxdb3_cloud_serverless: + name: Power BI + parent: Visualize data +weight: 104 +influxdb3/cloud-serverless/tags: [visualization, powerbi, sql] +metadata: [SQL] +related: + - https://learn.microsoft.com/en-us/power-bi/desktop/, Power BI documentation + - /influxdb3/cloud-serverless/query-data/sql/ + - /influxdb3/cloud-serverless/query-data/execute-queries/odbc/ +source: /shared/influxdb3-visualize/powerbi.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/clustered/process-data/visualize/chronograf.md b/content/influxdb3/clustered/process-data/visualize/chronograf.md index cf6e8bd65b..957f1b8df0 100644 --- a/content/influxdb3/clustered/process-data/visualize/chronograf.md +++ b/content/influxdb3/clustered/process-data/visualize/chronograf.md @@ -9,6 +9,8 @@ menu: name: Use Chronograf parent: Visualize data weight: 202 +aliases: + - /influxdb3/clustered/visualize-data/chronograf/ related: - /chronograf/v1/ metadata: [InfluxQL only] @@ -84,14 +86,14 @@ If you haven't already, [download and install Chronograf](/chronograf/v1/introdu > schema information may not be available in the Data Explorer. > This limits the Data Explorer's query building functionality and requires you to > build queries manually using -> [fully-qualified measurements](/influxdb3/clustered/reference/influxql/select/#fully-qualified-measurement) +> [fully qualified measurements](/influxdb3/clustered/reference/influxql/select/#fully-qualified-measurement) > in the `FROM` clause. For example: > > ```sql -> -- Fully-qualified measurement +> -- Fully qualified measurement > SELECT * FROM "db-name"."rp-name"."measurement-name" -> -> -- Fully-qualified measurement shorthand (use the default retention policy) +> +> -- Fully qualified measurement shorthand (use the default retention policy) > SELECT * FROM "db-name".."measurement-name" > ``` > diff --git a/content/influxdb3/clustered/process-data/visualize/grafana.md b/content/influxdb3/clustered/process-data/visualize/grafana.md index 4818070bfd..cb31b28b6c 100644 --- a/content/influxdb3/clustered/process-data/visualize/grafana.md +++ b/content/influxdb3/clustered/process-data/visualize/grafana.md @@ -15,6 +15,7 @@ aliases: - /influxdb3/clustered/query-data/sql/execute-queries/grafana/ - /influxdb3/clustered/query-data/influxql/execute-queries/grafana - /influxdb3/clustered/process-data/tools/grafana/ + - /influxdb3/clustered/visualize-data/grafana/ alt_links: v2: /influxdb/v2/tools/grafana/ cloud: /influxdb/cloud/tools/grafana/ diff --git a/content/influxdb3/clustered/process-data/visualize/superset.md b/content/influxdb3/clustered/process-data/visualize/superset.md index baf5433e4f..6beb8161f0 100644 --- a/content/influxdb3/clustered/process-data/visualize/superset.md +++ b/content/influxdb3/clustered/process-data/visualize/superset.md @@ -13,6 +13,7 @@ menu: influxdb3/clustered/tags: [Flight client, query, flightsql, superset] aliases: - /influxdb3/clustered/query-data/execute-queries/flight-sql/superset/ + - /influxdb3/clustered/visualize-data/superset/ - /influxdb3/clustered/query-data/tools/superset/ - /influxdb3/clustered/query-data/sql/execute-queries/superset/ - /influxdb3/clustered/process-data/tools/superset/ diff --git a/content/influxdb3/clustered/process-data/visualize/tableau.md b/content/influxdb3/clustered/process-data/visualize/tableau.md index 0e3cbaadd3..49cbe226eb 100644 --- a/content/influxdb3/clustered/process-data/visualize/tableau.md +++ b/content/influxdb3/clustered/process-data/visualize/tableau.md @@ -13,6 +13,7 @@ menu: influxdb3/clustered/tags: [Flight client, query, flightsql, tableau, sql] aliases: - /influxdb3/clustered/query-data/execute-queries/flight-sql/tableau/ + - /influxdb3/clustered/visualize-data/tableau/ - /influxdb3/clustered/query-data/tools/tableau/ - /influxdb3/clustered/query-data/sql/execute-queries/tableau/ - /influxdb3/clustered/process-data/tools/tableau/ diff --git a/content/influxdb3/clustered/query-data/execute-queries/odbc.md b/content/influxdb3/clustered/query-data/execute-queries/odbc.md new file mode 100644 index 0000000000..9ac4d94b10 --- /dev/null +++ b/content/influxdb3/clustered/query-data/execute-queries/odbc.md @@ -0,0 +1,19 @@ +--- +title: Execute SQL queries with ODBC +description: > + Use the Arrow Flight SQL ODBC driver to execute SQL queries against {{% product-name %}} from + ODBC-compatible applications and programming languages. +menu: + influxdb3_clustered: + name: Use ODBC + parent: Execute queries +weight: 351 +influxdb3/clustered/tags: [query, sql, odbc] +metadata: [SQL] +related: + - /influxdb3/clustered/reference/sql/ + - /influxdb3/clustered/query-data/ +source: /shared/influxdb3-query-guides/execute-queries/odbc.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/clustered/query-data/execute-queries/visualization-tools.md b/content/influxdb3/clustered/query-data/execute-queries/visualization-tools.md index 74115ab12e..e942ded977 100644 --- a/content/influxdb3/clustered/query-data/execute-queries/visualization-tools.md +++ b/content/influxdb3/clustered/query-data/execute-queries/visualization-tools.md @@ -9,7 +9,7 @@ menu: parent: Execute queries name: Use visualization tools identifier: query-with-visualization-tools -influxdb3/clustered/tags: [query, sql, influxql] +influxdb3/clustered/tags: [query, sql, influxql, visualization] metadata: [SQL, InfluxQL] aliases: - /influxdb3/clustered/query-data/influxql/execute-queries/visualization-tools/ @@ -27,6 +27,7 @@ Use visualization tools to query data stored in {{% product-name %}} with SQL. The following visualization tools support querying InfluxDB with SQL: - [Grafana](/influxdb3/clustered/process-data/visualize/grafana/) +- [Power BI](/influxdb3/clustered/process-data/visualize/powerbi/) - [Superset](/influxdb3/clustered/process-data/visualize/superset/) - [Tableau](/influxdb3/clustered/process-data/visualize/tableau/) diff --git a/content/influxdb3/clustered/visualize-data/powerbi.md b/content/influxdb3/clustered/visualize-data/powerbi.md new file mode 100644 index 0000000000..2889b31245 --- /dev/null +++ b/content/influxdb3/clustered/visualize-data/powerbi.md @@ -0,0 +1,20 @@ +--- +title: Use Power BI to visualize data +description: > + Use Microsoft Power BI Desktop with the InfluxDB 3 custom connector to query and + visualize data from {{% product-name %}}. +menu: + influxdb3_clustered: + name: Power BI + parent: Visualize data +weight: 104 +influxdb3/clustered/tags: [visualization, powerbi, sql] +metadata: [SQL] +related: + - https://learn.microsoft.com/en-us/power-bi/desktop/, Power BI documentation + - /influxdb3/clustered/query-data/sql/ + - /influxdb3/clustered/query-data/execute-queries/odbc/ +source: /shared/influxdb3-visualize/powerbi.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/core/query-data/execute-queries/odbc.md b/content/influxdb3/core/query-data/execute-queries/odbc.md new file mode 100644 index 0000000000..1bc000d63d --- /dev/null +++ b/content/influxdb3/core/query-data/execute-queries/odbc.md @@ -0,0 +1,20 @@ +--- +title: Execute SQL queries with ODBC +description: > + Use the Arrow Flight SQL ODBC driver to execute SQL queries against {{% product-name %}} from + ODBC-compatible applications and programming languages. +menu: + influxdb3_core: + name: Use ODBC + parent: Execute queries +weight: 351 +influxdb3/core/tags: [query, sql, odbc] +metadata: [SQL] +related: + - /influxdb3/core/reference/sql/ + - /influxdb3/core/query-data/ + - /influxdb3/core/visualize-data/powerbi/ +source: /shared/influxdb3-query-guides/execute-queries/odbc.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/core/visualize-data/grafana.md b/content/influxdb3/core/visualize-data/grafana.md index 66ae09bcf3..b4fc89c67d 100644 --- a/content/influxdb3/core/visualize-data/grafana.md +++ b/content/influxdb3/core/visualize-data/grafana.md @@ -11,15 +11,15 @@ menu: parent: Visualize data influxdb3/core/tags: [query, visualization] alt_links: - v1: /influxdb/v1/tools/grafana/ - v2: /influxdb/v2/tools/grafana/ - cloud: /influxdb/cloud/tools/grafana/ + enterprise: /influxdb3/enterprise/visualize-data/grafana/ + enterprise_v1: /enterprise_influxdb/v1/tools/grafana/ cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ clustered: /influxdb3/clustered/process-data/visualize/grafana/ -source: /shared/influxdb3-visualize/grafana.md + v1: /influxdb/v1/tools/grafana/ + v2: /influxdb/v2/tools/grafana/ + cloud: /influxdb/cloud/tools/grafana/ +source: /content/shared/v3-process-data/visualize/grafana.md --- - + diff --git a/content/influxdb3/core/visualize-data/powerbi.md b/content/influxdb3/core/visualize-data/powerbi.md new file mode 100644 index 0000000000..f2ca5d3b9f --- /dev/null +++ b/content/influxdb3/core/visualize-data/powerbi.md @@ -0,0 +1,20 @@ +--- +title: Use Power BI to visualize data +description: > + Use Microsoft Power BI Desktop with the InfluxDB 3 custom connector to query and + visualize data from {{% product-name %}}. +menu: + influxdb3_core: + name: Power BI + parent: Visualize data +weight: 104 +influxdb3/core/tags: [visualization, powerbi, sql] +metadata: [SQL] +related: + - https://learn.microsoft.com/en-us/power-bi/desktop/, Power BI documentation + - /influxdb3/core/query-data/sql/ + - /influxdb3/core/query-data/execute-queries/odbc/ +source: /shared/influxdb3-visualize/powerbi.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/enterprise/query-data/execute-queries/odbc.md b/content/influxdb3/enterprise/query-data/execute-queries/odbc.md new file mode 100644 index 0000000000..d15b40c63b --- /dev/null +++ b/content/influxdb3/enterprise/query-data/execute-queries/odbc.md @@ -0,0 +1,20 @@ +--- +title: Execute SQL queries with ODBC +description: > + Use the Arrow Flight SQL ODBC driver to execute SQL queries against {{% product-name %}} from + ODBC-compatible applications and programming languages. +menu: + influxdb3_enterprise: + name: Use ODBC + parent: Execute queries +weight: 351 +influxdb3/enterprise/tags: [query, sql, odbc] +metadata: [SQL] +related: + - /influxdb3/enterprise/reference/sql/ + - /influxdb3/enterprise/query-data/ + - /influxdb3/enterprise/visualize-data/powerbi/ +source: /shared/influxdb3-query-guides/execute-queries/odbc.md +--- + + \ No newline at end of file diff --git a/content/influxdb3/enterprise/visualize-data/grafana.md b/content/influxdb3/enterprise/visualize-data/grafana.md index 0d1baf5532..5834510011 100644 --- a/content/influxdb3/enterprise/visualize-data/grafana.md +++ b/content/influxdb3/enterprise/visualize-data/grafana.md @@ -11,15 +11,15 @@ menu: parent: Visualize data influxdb3/enterprise/tags: [query, visualization] alt_links: - v1: /influxdb/v1/tools/grafana/ - v2: /influxdb/v2/tools/grafana/ - cloud: /influxdb/cloud/tools/grafana/ + core: /influxdb3/core/visualize-data/grafana/ cloud-serverless: /influxdb3/cloud-serverless/process-data/visualize/grafana/ cloud-dedicated: /influxdb3/cloud-dedicated/process-data/visualize/grafana/ clustered: /influxdb3/clustered/process-data/visualize/grafana/ -source: /shared/influxdb3-visualize/grafana.md + enterprise_v1: /enterprise_influxdb/v1/tools/grafana/ + v1: /influxdb/v1/tools/grafana/ + v2: /influxdb/v2/tools/grafana/ + cloud: /influxdb/cloud/tools/grafana/ +source: /content/shared/v3-process-data/visualize/grafana.md --- - + diff --git a/content/influxdb3/enterprise/visualize-data/powerbi.md b/content/influxdb3/enterprise/visualize-data/powerbi.md new file mode 100644 index 0000000000..3413071bdc --- /dev/null +++ b/content/influxdb3/enterprise/visualize-data/powerbi.md @@ -0,0 +1,20 @@ +--- +title: Use Power BI to visualize data +description: > + Use Microsoft Power BI Desktop with the InfluxDB 3 custom connector to query and + visualize data from {{% product-name %}}. +menu: + influxdb3_enterprise: + name: Power BI + parent: Visualize data +weight: 104 +influxdb3/enterprise/tags: [visualization, powerbi, sql] +metadata: [SQL] +related: + - https://learn.microsoft.com/en-us/power-bi/desktop/, Power BI documentation + - /influxdb3/enterprise/query-data/sql/ + - /influxdb3/enterprise/query-data/execute-queries/odbc/ +source: /shared/influxdb3-visualize/powerbi.md +--- + + \ No newline at end of file diff --git a/content/shared/influxdb-client-libraries-reference/flight/_index.md b/content/shared/influxdb-client-libraries-reference/flight/_index.md index edf8ffdb6d..636ab063f6 100644 --- a/content/shared/influxdb-client-libraries-reference/flight/_index.md +++ b/content/shared/influxdb-client-libraries-reference/flight/_index.md @@ -14,6 +14,14 @@ Using InfluxDB 3's IOx-specific Flight RPC protocol, clients send a single `DoGe **Flight SQL clients** use the [Flight SQL protocol](https://arrow.apache.org/docs/format/FlightSql.html) for querying an SQL database server. They can use SQL to query data stored in an {{% product-name %}} database, but they can't use InfuxQL. +> [!Important] +> #### Flight SQL requires HTTP/2 +> +> Flight SQL uses gRPC, which requires **HTTP/2**. +> If you connect to {{% product-name %}} through a proxy (such as HAProxy, nginx, or a load balancer), +> verify that your proxy is configured to support HTTP/2. +> Without HTTP/2 support, Flight SQL connections will fail. + Clients are maintained by Apache Arrow projects or third-parties. For specifics about a Flight client, see the client's GitHub repository. diff --git a/content/shared/influxdb-v2/tools/grafana.md b/content/shared/influxdb-v2/tools/grafana.md index 621fa6c3df..28fe66898e 100644 --- a/content/shared/influxdb-v2/tools/grafana.md +++ b/content/shared/influxdb-v2/tools/grafana.md @@ -1,183 +1,143 @@ Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/products/cloud/) to visualize data from your **InfluxDB {{< current-version >}}** instance. -{{% note %}} -The instructions in this guide require **Grafana Cloud** or **Grafana 10.3+**. -{{% /note %}} - -1. {{% show-in "cloud,cloud-serverless" %}}[Log into InfluxDB Cloud](https://cloud2.influxdata.com).{{% /show-in %}} - {{% show-in "v2" %}}[Start InfluxDB](/influxdb/version/install/#configure-and-start-influxdb).{{% /show-in %}} -2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or - [download and install Grafana](https://grafana.com/grafana/download). -3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, - [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit - in your browser. -4. In the left navigation of the Grafana UI, open the **Connections** section - and select **Add new connection**. -5. Select **InfluxDB** from the list of available data sources and click - **Add new data source**. -6. On the **Data Source configuration page**, enter a **name** for your InfluxDB data source. -7. In the **Query Language** drop-down menu, select one of the query languages - supported by InfluxDB {{< current-version >}} (Flux or InfluxQL): - - {{% note %}} -SQL is only supported in InfluxDB 3. - {{% /note %}} +> [!Note] +> {{< influxdb-version-detector >}} -{{< tabs-wrapper >}} -{{% tabs %}} -[Flux](#) -[InfluxQL](#) -{{% /tabs %}} -{{% tab-content %}} -## Configure Grafana to use Flux - -With **Flux** selected as the query language in your InfluxDB data source, -configure your InfluxDB connection: - -1. Under **HTTP**, enter the following: +> [!Note] +> The instructions in this guide require **Grafana Cloud** or **Grafana 10.3+**. - - **URL**: Your - {{% show-in "v2" %}}[InfluxDB URL](/influxdb/version/reference/urls/).{{% /show-in %}} - {{% show-in "cloud,cloud-serverless" %}}[InfluxDB Cloud region URL](/influxdb/version/reference/regions/).{{% /show-in %}} +- [Install Grafana](#install-grafana) +- [Create an InfluxDB data source](#create-an-influxdb-data-source) +- [Query and visualize data](#query-and-visualize-data) - ```sh - http://localhost:8086/ - ``` +## Install Grafana -2. Under **InfluxDB Details**, enter the following: +1. [Start InfluxDB OSS 2.x](/influxdb/v2/install/#configure-and-start-influxdb). +2. [Sign up for Grafana Cloud](https://grafana.com/products/cloud/) or + [download and install Grafana](https://grafana.com/grafana/download). +3. Visit your **Grafana Cloud user interface** (UI) or, if running Grafana locally, + [start Grafana](https://grafana.com/docs/grafana/latest/installation/) and visit + in your browser. - - **Organization**: Your InfluxDB [organization name **or** ID](/influxdb/version/admin/organizations/view-orgs/). - - **Token**: Your InfluxDB [API token](/influxdb/version/admin/tokens/). - - **Default Bucket**: The default [bucket](/influxdb/version/admin/buckets/) to - use in Flux queries. - - **Min time interval**: The [Grafana minimum time interval](https://grafana.com/docs/grafana/latest/features/datasources/influxdb/#min-time-interval). - Default is `10s` - - **Max series**: The maximum number of series or tables Grafana will process. - Default is `1000`. +{{% show-in "v2" %}} +> [!Note] +> #### Using Grafana Cloud with a local InfluxDB instance +> +> If you need to keep your database local, consider running Grafana locally instead of using Grafana Cloud, +> as this avoids the need to expose your database to the internet. +> +> To use InfluxDB running on your private network with Grafana Cloud, you must +> [configure a private data source](https://grafana.com/docs/grafana-cloud/data-sources/private-data-sources/). +> See the Grafana documentation for instructions on configuring a Grafana Cloud private data source +> with {{% product-name %}} running on `http://localhost:8086`. +{{% /show-in %}} -3. Click **Save & Test**. Grafana attempts to connect to the InfluxDB {{< current-version >}} - datasource and returns the results of the test. +> [!Note] +> SQL is only supported in InfluxDB 3. +> {{% show-in "v2" %}}For more information, see how to [get-started with InfluxDB 3 Core](/influxdb3/core/get-started/).{{% /show-in %}}{{% show-in "cloud" %}}For more information, see how to upgrade to [InfluxDB Cloud Serverless](/influxdb/cloud/upgrade/v2-to-cloud/).{{% /show-in %}} -{{% show-in "cloud,cloud-serverless" %}} - {{< img-hd src="/img/influxdb/cloud-tools-grafana-flux.png" alt="Use Grafana with InfluxDB Cloud and Flux" />}} -{{% /show-in %}} +## Quick reference -{{% show-in "v2" %}} - {{< img-hd src="/img/influxdb/version-tools-grafana-flux.png" alt="Use Grafana with InfluxDB and Flux" />}} -{{% /show-in %}} +| Configuration | Value | +|:------------- |:----- | +| **Product selection** | {{% show-in "v2" %}}**InfluxDB OSS 2.x**{{% /show-in %}}{{% show-in "cloud" %}}**InfluxDB Cloud (TSM)**{{% /show-in %}} | +| **URL** | {{% show-in "v2" %}}[Server URL](/influxdb/v2/reference/urls/)--for example, `https://{{< influxdb/host >}}`{{% /show-in %}}{{% show-in "cloud" %}}[Region URL](/influxdb/cloud/reference/regions/)--for example, `https://us-east-2-1.aws.cloud2.influxdata.com`{{% /show-in %}} | +| **Query languages** | Flux, InfluxQL | +| **Authentication** | API token or v1 username/password | +| **Organization** | Organization name or ID (Flux only) | +| **Default Bucket** | Default bucket for Flux queries (Flux only) | +| **Database** | Database name mapped to bucket (InfluxQL only) | -{{% /tab-content %}} - - -{{% tab-content %}} +## Create an InfluxDB data source -## Configure Grafana to use InfluxQL +1. In your Grafana interface, click **Connections** in the left sidebar +2. Click **Data sources** +3. Click **Add new connection** +4. Search for and select **InfluxDB**. The InfluxDB data source configuration page displays. +5. In the **Settings** tab, configure the following: - - - -{{% show-in "v2" %}} + - **Name**: A descriptive name for your data source + - **URL**: Your {{% show-in "v2" %}}[server URL](/influxdb/v2/reference/urls/)--for example, `https://{{< influxdb/host >}}`{{% /show-in %}}{{% show-in "cloud" %}}[region URL](/influxdb/cloud/reference/regions/)--for example, `https://us-east-2-1.aws.cloud2.influxdata.com`{{% /show-in %}} + - **Product**: From the dropdown, select {{% show-in "v2" %}}**InfluxDB OSS 2.x**{{% /show-in %}}{{% show-in "cloud" %}}**InfluxDB Cloud (TSM)**{{% /show-in %}} + - **Query Language**: Select **Flux** or **InfluxQL** -To query InfluxDB {{< current-version >}} with InfluxQL, find your use case below, -and then complete the instructions to configure Grafana: +### Configure database settings -- [Installed a new InfluxDB {{< current-version >}} instance](#installed-a-new-influxdb-instance) -- [Upgraded from InfluxDB 1.x to {{< current-version >}} (following the official upgrade)](#upgraded-from-influxdb-1x-to-2x) -- [Manually migrated from InfluxDB 1.x to {{< current-version >}}](#manually-migrated-from-influxdb-1x-to-2x) +The fields in this section change based on your query language selection. -### Installed a new InfluxDB instance +{{< tabs-wrapper >}} +{{% tabs %}} +[Flux](#) +[InfluxQL](#) +{{% /tabs %}} +{{% tab-content %}} + -To configure Grafana to use InfluxQL with a new install of InfluxDB {{< current-version >}}, do the following: +## Configure Grafana to use Flux -1. [Authenticate with InfluxDB {{< current-version >}} tokens](/influxdb/version/admin/tokens/). -2. [Manually create DBRP mappings](#view-and-create-influxdb-dbrp-mappings). +When you select **Flux** as the query language, configure the following: -### Upgraded from InfluxDB 1.x to 2.x +- **Organization**: Your InfluxDB [organization name or ID](/influxdb/v2/admin/organizations/view-orgs/) +- **Default Bucket**: The default [bucket](/influxdb/v2/admin/buckets/) to use in Flux queries +- **Token**: Your InfluxDB [API token](/influxdb/v2/admin/tokens/) -To configure Grafana to use InfluxQL when you've upgraded from InfluxDB 1.x to -InfluxDB {{< current-version >}} (following an [official upgrade guide](/influxdb/version/upgrade/v1-to-v2/)): +{{% show-in "v2" %}}{{< img-hd src="/img/influxdb/OSS-v2-grafana-product-dropdown-flux.png" alt="Flux configuration for InfluxDB OSS 2.x" />}}{{% /show-in %}} +{{% show-in "cloud" %}}{{< img-hd src="/img/influxdb/influxdb-v2-cloud-flux.png" alt="Flux configuration for InfluxDB Cloud (TSM)" />}}{{% /show-in %}} -1. Authenticate using the _non-admin_ [v1 compatible authentication credentials](#view-and-create-influxdb-v1-authorizations) - created during the upgrade process. -2. Use the DBRP mappings InfluxDB automatically created in the upgrade process (no action necessary). +Click **Save & Test**. Grafana attempts to connect to {{% show-in "v2" %}}InfluxDB OSS 2.x{{% /show-in %}}{{% show-in "cloud" %}}InfluxDB Cloud{{% /show-in %}} and returns the results of the test. -### Manually migrated from InfluxDB 1.x to 2.x + +{{% /tab-content %}} +{{% tab-content %}} + -To configure Grafana to use InfluxQL when you've manually migrated from InfluxDB -1.x to InfluxDB {{< current-version >}}, do the following: +## Configure Grafana to use InfluxQL -1. If your InfluxDB 1.x instance required authentication, - [create v1 compatible authentication credentials](#view-and-create-influxdb-v1-authorizations) - to match your previous 1.x username and password. - Otherwise, use [InfluxDB v2 token authentication](/influxdb/version/admin/tokens/). -2. [Manually create DBRP mappings](#view-and-create-influxdb-dbrp-mappings). +> [!Important] +> #### DBRP mapping required +> +> To query InfluxDB OSS 2.x with InfluxQL, you must first create Database and Retention Policy (DBRP) mappings. +> The configuration form displays a warning if DBRP mapping is required. -{{< expand-wrapper >}} -{{% expand "View and create InfluxDB v1 authorizations" %}} +When you select **InfluxQL** as the query language, you can authenticate using either tokens or username/password credentials. -InfluxDB {{< current-version >}} provides a 1.x compatible authentication API that lets you -authenticate with a username and password like InfluxDB 1.x -_(separate from the credentials used to log into the InfluxDB user interface)_. +### Token authentication (recommended) -#### View existing v1 authorizations +Configure the following fields: -Use the [`influx v1 auth list`](/influxdb/version/reference/cli/influx/v1/auth/list/) -to list existing InfluxDB v1 compatible authorizations. +- **Database**: The database name [mapped to your InfluxDB bucket](#create-dbrp-mappings) +- **User**: Enter any string (this field is required by the form) +- **Password**: Your InfluxDB [API token](/influxdb/v2/admin/tokens/) -```sh -influx v1 auth list -``` +### Username and password authentication -#### Create a v1 authorization +Configure the following fields: -Use the [`influx v1 auth create` command](/influxdb/version/reference/cli/influx/v1/auth/create/) -to grant read/write permissions to specific buckets. Provide the following: +- **Database**: The database name [mapped to your InfluxDB bucket](#create-dbrp-mappings) +- **User**: Your [v1 authorization username](#create-v1-authorizations) +- **Password**: Your [v1 authorization password](#create-v1-authorizations) -- [bucket IDs](/influxdb/version/admin/buckets/view-buckets/) to grant read - or write permissions to -- new username -- new password _(when prompted)_ +{{% show-in "v2" %}}{{< img-hd src="/img/influxdb/OSS-v2-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for InfluxDB OSS 2.x with DBRP warning" />}}{{% /show-in %}} +{{% show-in "cloud" %}}{{< img-hd src="/img/influxdb/influxdb-v2-cloud-influxql.png" alt="InfluxQL configuration for InfluxDB Cloud (TSM) with v1 auth" />}}{{% /show-in %}} - -```sh -influx v1 auth create \ - --read-bucket 00xX00o0X001 \ - --write-bucket 00xX00o0X001 \ - --username example-user -``` -{{% /expand %}} -{{< expand "View and create InfluxDB DBRP mappings" >}} +Click **Save & Test**. Grafana attempts to connect to {{% show-in "v2" %}}InfluxDB OSS 2.x{{% /show-in %}}{{% show-in "cloud" %}}InfluxDB Cloud{{% /show-in %}} and returns the results of the test. -When using InfluxQL to query InfluxDB, the query must specify a database and a retention policy. -InfluxDB DBRP mappings associate database and retention policy combinations with -InfluxDB {{< current-version >}} [buckets](/influxdb/version/reference/glossary/#bucket). +### Create DBRP mappings -DBRP mappings do not affect the retention period of the target bucket. -These mappings allow queries following InfluxDB 1.x conventions to successfully -query InfluxDB {{< current-version >}} buckets. +When using InfluxQL to query InfluxDB, the query must specify a database and retention policy. +InfluxDB DBRP mappings associate database and retention policy combinations with InfluxDB OSS 2.x buckets. #### View existing DBRP mappings -Use the [`influx v1 dbrp list`](/influxdb/version/reference/cli/influx/v1/dbrp/list/) -to list existing DBRP mappings. - +Use the [`influx v1 dbrp list`](/influxdb/v2/reference/cli/influx/v1/dbrp/list/) command: ```sh influx v1 dbrp list ``` -#### Create a DBRP mapping - -Use the [`influx v1 dbrp create` command](/influxdb/version/reference/cli/influx/v1/dbrp/create/) -command to create a DBRP mapping. -Provide the following: - -- database name -- retention policy name _(not retention period)_ -- [bucket ID](/influxdb/version/admin/buckets/view-buckets/) -- _(optional)_ `--default` flag if you want the retention policy to be the default retention - policy for the specified database +### Create a DBRP mappings +Use the influx v1 dbrp create command: ```sh influx v1 dbrp create \ --db example-db \ @@ -185,175 +145,63 @@ influx v1 dbrp create \ --bucket-id 00xX00o0X001 \ --default ``` + +Provide: -{{% note %}} -#### Repeat for each DBRP combination - -Each unique database and retention policy combination used by Grafana must be -mapped to an InfluxDB {{< current-version >}} bucket. -If you have multiple retention policies for a single bucket, set one of the the -retention polices as the default using the `--default` flag. -{{% /note %}} - -_For more information about DBRP mapping, see -[Database and retention policy mapping](/influxdb/version/reference/api/influxdb-1x/dbrp/)._ - -{{< /expand >}} -{{< /expand-wrapper >}} - -{{% /show-in %}} - - - +- `--db:` Database name +- `--rp:` Retention policy name (not retention period) +- `--bucket-id:` Bucket ID +- `--default:` (Optional) Make this the default retention policy for the database - - - -{{% show-in "cloud,cloud-serverless" %}} +> [!Note] +> **Repeat for each DBRP combination** +> Each unique database and retention policy combination used by Grafana must be mapped to an InfluxDB OSS 2.x bucket. -To query InfluxDB Cloud from Grafana using InfluxQL: +For more information, see [Database and retention policy mapping](/influxdb/v2/api-guide/influxdb-1x/dbrp/). -1. [Download and set up the `influx` CLI](#download-and-set-up-the-influx-cli) -2. [Create an InfluxDB DBRP mapping](#create-an-influxdb-dbrp-mapping) -3. [Configure your InfluxDB connection](#configure-your-influxdb-connection) +### Create v1 authorizations -### Download and set up the influx CLI +InfluxDB OSS 2.x provides a v1-compatible authentication API for username/password authentication. -1. [Download the latest version of the `influx` CLI](/influxdb/cloud/sign-up/#optional-download-install-and-use-the-influx-cli) - appropriate for your local operating system. -2. Create a CLI configuration that provides the required InfluxDB Cloud **host**, - **organization**, and **API token** to all CLI commands. - Use the [`influx config create` command](/influxdb/cloud/reference/cli/influx/config/create/) - and provide the following: - - - [InfluxDB Cloud URL](/influxdb/cloud/reference/regions/) - - [organization name](/influxdb/cloud/admin/organizations/) _(by default, your email address)_ - - [API token](/influxdb/cloud/admin/tokens/) - - ```sh - influx config create \ - --config-name example-config-name \ - --host-url https://{{< influxdb/host >}} \ - --org example-org \ - --token My5uP3rSeCr37t0k3n - ``` - - For more information about `influx` CLI configurations, - see [`influx config`](/influxdb/cloud/reference/cli/influx/config/). - -### Create an InfluxDB DBRP mapping - -When using InfluxQL to query InfluxDB Cloud, the query must specify a database and a retention policy. -Use the [`influx v1 dbrp create` command](/influxdb/cloud/reference/cli/influx/v1/dbrp/create/) -command to create a database/retention policy (DBRP) mapping that associates a database -and retention policy combination with an InfluxDB Cloud [bucket](/influxdb/cloud/reference/glossary/#bucket). - -DBRP mappings do not affect the retention period of the target bucket. -These mappings allow queries following InfluxDB 1.x conventions to successfully -query InfluxDB Cloud buckets. - -{{% note %}} -##### Automatically create DBRP mappings on write - -When using the InfluxDB 1.x compatibility API to write data to InfluxDB Cloud, -InfluxDB Cloud automatically creates DBRP mappings for buckets whose names match the -`db/rp` naming pattern of the database and retention policy specified in the write request. -For more information, see [Database and retention policy mapping – Writing data](/influxdb/cloud/reference/api/influxdb-1x/dbrp/#when-writing-data). -{{% /note %}} - -Provide the following: - -- database name -- [retention policy](/influxdb/v1/concepts/glossary/#retention-policy-rp) name _(not retention period)_ -- [bucket ID](/influxdb/cloud/admin/buckets/view-buckets/) -- _(optional)_ `--default` flag if you want the retention policy to be the default retention - policy for the specified database +#### View existing v1 authorizations +Use the [`influx v1 auth list`](/influxdb/v2/reference/cli/influx/v1/auth/list/) command: ```sh -influx v1 dbrp create \ - --db example-db \ - --rp example-rp \ - --bucket-id 00xX00o0X001 \ - --default +influx v1 auth list ``` -{{% note %}} -#### Repeat for each DBRP combination - -Each unique database and retention policy combination used by Grafana must be -mapped to an InfluxDB {{< current-version >}} bucket. -If you have multiple retention policies for a single bucket, set one of the the -retention polices as the default using the `--default` flag. -{{% /note %}} - -_For more information about DBRP mapping, see -[Database and retention policy mapping](/influxdb/cloud/reference/api/influxdb-1x/dbrp/)._ - -{{% /show-in %}} - - - - - -### Configure your InfluxDB connection - -With **InfluxQL** selected as the query language in your InfluxDB data source settings: - -1. Under **HTTP**, enter the following: - - - **URL**: Your [InfluxDB URL](/influxdb/version/reference/urls/). - - ```sh - http://localhost:8086/ - ``` - -2. Configure InfluxDB authentication: - - - ##### Token authentication - - - Under **Custom HTTP Headers**, select **{{< icon "plus" >}}Add Header**. Provide your InfluxDB API token: - - - **Header**: Enter `Authorization` - - **Value**: Use the `Token` schema and provide your [InfluxDB API token](/influxdb/version/admin/tokens/). - For example: - - ``` - Token y0uR5uP3rSecr3tT0k3n - ``` - - - Under **InfluxDB Details**, do the following: - - - **Database**: Enter the database name [mapped to your InfluxDB {{< current-version >}} bucket](#view-and-create-influxdb-dbrp-mappings) - - **HTTP Method**: Select **GET** - - - ##### Authenticate with username and password - - Under **InfluxDB Details**, do the following: +#### Create a v1 authorization - - **Database**: Enter the database name [mapped to your InfluxDB {{< current-version >}} bucket](#view-and-create-influxdb-dbrp-mappings) - - **User**: Enter the username associated with your [InfluxDB 1.x compatibility authorization](#view-and-create-influxdb-v1-authorizations) - - **Password**: Enter the password associated with your [InfluxDB 1.x compatibility authorization](#view-and-create-influxdb-dbrp-mappings) - - **HTTP Method**: Select **GET** +Use the [`influx v1 auth create`](/influxdb/v2/reference/cli/influx/v1/auth/create/) command: +```sh +influx v1 auth create \ + --read-bucket 00xX00o0X001 \ + --write-bucket 00xX00o0X001 \ + --username example-user +``` -3. Click **Save & Test**. Grafana attempts to connect to the InfluxDB {{< current-version >}} data source - and returns the results of the test. +Provide: -{{% show-in "cloud,cloud-serverless" %}} - {{< img-hd src="/img/influxdb/cloud-tools-grafana-influxql.png" alt="Use Grafana with InfluxDB Cloud and Flux" />}} -{{% /show-in %}} +- `--read-bucket`: Bucket ID to grant read permissions +- `--write-bucket`: Bucket ID to grant write permissions +- `--username`: New username -{{< show-in "v2" >}} - {{< img-hd src="/img/influxdb/version-tools-grafana-influxql.png" alt="Use Grafana with InfluxDB and Flux" />}} -{{< /show-in >}} +You'll be prompted to enter a password. + {{% /tab-content %}} - {{< /tabs-wrapper >}} ## Query and visualize data -With your InfluxDB connection configured, use Grafana and Flux to query and -visualize time series data stored in your **InfluxDB** instance. +With your InfluxDB connection configured, use Grafana to query and visualize time series data. + +### Query inspection in Grafana + +To learn about query management and inspection in Grafana, see the +[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). + +### Build visualizations with Grafana -For more information about using Grafana, see the [Grafana documentation](https://grafana.com/docs/). -If you're just learning Flux, see [Get started with Flux](/flux/v0/get-started/). +For a comprehensive walk-through of creating visualizations with +Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). diff --git a/content/shared/influxdb3-query-guides/execute-queries/odbc.md b/content/shared/influxdb3-query-guides/execute-queries/odbc.md new file mode 100644 index 0000000000..2b66aea2ba --- /dev/null +++ b/content/shared/influxdb3-query-guides/execute-queries/odbc.md @@ -0,0 +1,533 @@ +Use the Arrow Flight SQL ODBC driver to execute SQL queries against +{{% product-name %}} from ODBC-compatible applications and programming languages. + +ODBC (Open Database Connectivity) is a standard API for accessing database +management systems. The Arrow Flight SQL ODBC driver enables ODBC-compatible +applications to connect to {{% product-name %}} and query data using SQL. + +- [Download and install the ODBC driver](#download-and-install-the-odbc-driver) +- [Configure a data source](#configure-a-data-source) +- [Connect and query from applications](#connect-and-query-from-applications) +- [Use ODBC with programming languages](#use-odbc-with-programming-languages) + +## Download and install the ODBC driver + +{{% product-name %}} uses the Arrow Flight SQL ODBC driver to enable ODBC connectivity. + +> [!Note] +> For more information about the Arrow Flight SQL ODBC Driver, see the [Dremio documentation](https://docs.dremio.com/current/client-applications/drivers/arrow-flight-sql-odbc-driver/). + +{{< tabs-wrapper >}} +{{% tabs %}} +[Windows (PowerShell)](#) +[Windows (Manual)](#) +[macOS and Linux](#) +{{% /tabs %}} + +{{% tab-content %}} +Run the following PowerShell commands to download and install: + +{{% code-placeholders "YOUR_USER" %}} +```powershell +# Set the driver path +$driverPath = "C:\Users\YOUR_USER\Downloads\arrow-flight-sql-odbc-0.9.7.1195-win64.msi" + +# Download the driver +Invoke-WebRequest -Uri "https://docs.influxdata.com/downloads/arrow-flight-sql-odbc-0.9.7.1195-win64.msi" ` + -OutFile $driverPath + +# Mark as trusted +Unblock-File $driverPath + +# Install +Start-Process msiexec.exe -Wait -ArgumentList "/i `"$driverPath`"" +``` +{{% /code-placeholders %}} + +Replace the following: + +- {{% code-placeholder-key %}}`YOUR_USER`{{% /code-placeholder-key %}}: Your Windows username + +#### Verify installation + +1. Open **ODBC Data Source Administrator (64-bit)** +2. Navigate to the **Drivers** tab +3. Verify **Arrow Flight SQL ODBC Driver** appears in the list + +{{% /tab-content %}} + +{{% tab-content %}} +Download the Arrow Flight SQL ODBC driver + +1. Run the downloaded `.msi` installer +2. Follow the installation wizard using default settings +3. Complete the installation + +#### Verify installation + +1. Open **ODBC Data Source Administrator (64-bit)** +2. Navigate to the **Drivers** tab +3. Verify **Arrow Flight SQL ODBC Driver** appears in the list + +{{% /tab-content %}} + +{{% tab-content %}} +Download from Dremio: + +- [**macOS (Universal)**](https://download.dremio.com/arrow-flight-sql-odbc-driver/arrow-flight-sql-odbc-LATEST-universal.pkg) +- [**Linux (x86_64)**](https://download.dremio.com/arrow-flight-sql-odbc-driver/arrow-flight-sql-odbc-LATEST-linux-x86_64.tar.gz) + +#### Install on macOS + +1. Run the downloaded `.pkg` installer +2. Follow the installation prompts +3. Enter your administrator password when prompted +4. Complete the installation + +#### Install on Linux + +1. Extract the downloaded archive: + + ```bash + tar -xzf arrow-flight-sql-odbc-LATEST-linux-x86_64.tar.gz + ``` + +2. Install the driver (installation location may vary by distribution): + + ```bash + sudo mkdir -p /opt/arrow-flight-sql-odbc + sudo cp -r lib /opt/arrow-flight-sql-odbc/ + ``` + +3. Configure the driver in `/etc/odbcinst.ini`: + + ```ini + [Arrow Flight SQL ODBC Driver] + Description = Arrow Flight SQL ODBC Driver + Driver = /opt/arrow-flight-sql-odbc/lib/libarrow-odbc.so + ``` + +#### Verify installation + +To verify the driver is installed correctly, run: + +```bash +odbcinst -q -d +``` + +The output should include **Arrow Flight SQL**. + +{{% /tab-content %}} +{{< /tabs-wrapper >}} + +## Configure a data source + +After installing the Arrow Flight SQL ODBC driver, configure a data source to +connect to {{% product-name %}}. + +{{< tabs-wrapper >}} +{{% tabs %}} +[Windows](#) +[macOS and Linux](#) +{{% /tabs %}} + +{{% tab-content %}} +1. Open **ODBC Data Source Administrator (64-bit)** +2. Navigate to the **System DSN** or **User DSN** tab +3. Click **Add** +4. Select **Arrow Flight SQL ODBC Driver** and click **Finish** +5. Configure the connection: + + - **Data Source Name**: Provide a descriptive name (for example, `InfluxDB3`) + - **Host**: Your {{% product-name %}} host (for example, {{% show-in "cloud-serverless" %}}`us-west-2-1.aws.cloud2.influxdata.com`{{% /show-in %}}{{% show-in "enterprise,core" %}}`localhost`{{% /show-in %}}{{% show-in "cloud-dedicated" %}}`cluster-id.a.influxdb.io`{{% /show-in %}}{{% show-in "clustered" %}}`cluster-host.com`{{% /show-in %}}) + - **Port**: Your InfluxDB URL port {{% show-in "cloud-serverless,cloud-dedicated,clustered" %}}(for example, `443` (HTTPS){{% /show-in %}}{{% show-in "enterprise,core" %}}`8181` (default){{% /show-in %}}) + - **Database**: Your database name + - **Auth Token**: Your {{% show-in "cloud-dedicated,clustered" %}}{{% token-link "database" %}}{{% /show-in %}}{{% show-in "cloud-serverless" %}}{{% token-link %}}{{% /show-in %}}{{% show-in "core,enterprise" %}}{{% token-link "admin" "database" %}}{{% /show-in %}}{{% show-in "cloud-dedicated,clustered,enterprise" %}} with query permissions for the target database{{% /show-in %}} + - **Use Encryption**: Enable for HTTPS connections + +6. Click **Test** to verify the connection +7. Click **OK** to save + +{{% /tab-content %}} + +{{% tab-content %}} +Create or edit `~/.odbc.ini` (user DSN) or `/etc/odbc.ini` (system DSN): + +{{% show-in "enterprise,core" %}} +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = localhost +Port = 8181 +Database = DATABASE_NAME +AuthToken = DATABASE_TOKEN +UseEncryption = 1 +``` +{{% /code-placeholders %}} +{{% /show-in %}} + +{{% show-in "cloud-serverless" %}} +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = us-west-2-1.aws.cloud2.influxdata.com +Port = 443 +Database = DATABASE_NAME +AuthToken = DATABASE_TOKEN +UseEncryption = 1 +``` +{{% /code-placeholders %}} +{{% /show-in %}} + +{{% show-in "cloud-dedicated" %}} +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = cluster-id.a.influxdb.io +Port = 443 +Database = DATABASE_NAME +AuthToken = DATABASE_TOKEN +UseEncryption = 1 +``` +{{% /code-placeholders %}} +{{% /show-in %}} + +{{% show-in "clustered" %}} +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = cluster-host.com +Port = 443 +Database = DATABASE_NAME +AuthToken = DATABASE_TOKEN +UseEncryption = 1 +``` +{{% /code-placeholders %}} +{{% /show-in %}} + +Replace the following: + +- {{% code-placeholder-key %}}`DATABASE_NAME`{{% /code-placeholder-key %}}: Your database name +- {{% code-placeholder-key %}}`DATABASE_TOKEN`{{% /code-placeholder-key %}}: Your {{% token-link "database" %}}{{% show-in "enterprise" %}} with query permissions{{% /show-in %}} + +Test the connection: + +```bash +isql -v InfluxDB3 +``` + +{{% /tab-content %}} +{{< /tabs-wrapper >}} + +## Connect and query from applications + +After configuring a data source, connect from ODBC-compatible applications: + +### Power BI + +See [Use Power BI to visualize data](/influxdb3/version/visualize-data/powerbi/). + +### Tableau + +See [Use Tableau to visualize data](/influxdb3/version/visualize-data/tableau/). + +### Excel + +1. Open Excel +2. Go to **Data** > **Get Data** > **From Other Sources** > **From ODBC** +3. Select your InfluxDB data source +4. Enter credentials if prompted +5. Select tables and load data + +### DBeaver + +1. Create a new database connection +2. Select **ODBC** as the connection type +3. Configure the connection: + - **Database/Schema**: Your InfluxDB database + - **ODBC DSN**: Your configured data source name +4. Test and save the connection + +## Use ODBC with programming languages + +### Python with pyodbc + +```python +import pyodbc + +# Connect to InfluxDB +conn = pyodbc.connect( + 'DSN=InfluxDB3', + autocommit=True +) + +# Create cursor +cursor = conn.cursor() + +# Execute query +cursor.execute(""" + SELECT + time, + temp, + location + FROM + home + WHERE + time >= now() - INTERVAL '1 hour' + ORDER BY + time DESC +""") + +# Fetch results +for row in cursor.fetchall(): + print(row) + +# Close connection +cursor.close() +conn.close() +``` + +### R with RODBC + +```r +library(RODBC) + +# Connect to InfluxDB +conn <- odbcConnect("InfluxDB3") + +# Execute query +result <- sqlQuery(conn, " + SELECT + time, + temp, + location + FROM + home + WHERE + time >= now() - INTERVAL '1 hour' + ORDER BY + time DESC +") + +# View results +print(result) + +# Close connection +odbcClose(conn) +``` + +### C# with `System.Data.Odbc` + +```csharp +using System; +using System.Data.Odbc; + +class Program +{ + static void Main() + { + string connectionString = "DSN=InfluxDB3"; + + using (OdbcConnection conn = new OdbcConnection(connectionString)) + { + conn.Open(); + + string query = @" + SELECT + time, + temp, + location + FROM + home + WHERE + time >= now() - INTERVAL '1 hour' + ORDER BY + time DESC + "; + + using (OdbcCommand cmd = new OdbcCommand(query, conn)) + using (OdbcDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + Console.WriteLine($"{reader["time"]} - {reader["temp"]} - {reader["location"]}"); + } + } + } + } +} +``` + +## Connection string format + +For applications that use connection strings directly: + +``` +Driver={Arrow Flight SQL ODBC Driver};Host=HOST;Port=PORT;Database=DATABASE;AuthToken=TOKEN;UseEncryption=1 +``` + +Example: + +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +``` +Driver={Arrow Flight SQL ODBC Driver};Host=localhost;Port=8181;Database=DATABASE_NAME;AuthToken=DATABASE_TOKEN;UseEncryption=1 +``` +{{% /code-placeholders %}} + +{{% show-in "cloud-serverless,cloud-dedicated" %}} +For {{% product-name %}}, use port `443`: + +{{% code-placeholders "DATABASE_NAME|DATABASE_TOKEN" %}} +``` +Driver={Arrow Flight SQL ODBC Driver};Host=cluster-id.a.influxdb.io;Port=443;Database=DATABASE_NAME;AuthToken=DATABASE_TOKEN;UseEncryption=1 +``` +{{% /code-placeholders %}} +{{% /show-in %}} + +## Configuration options + +### Connection parameters + +| Parameter | Description | Default | +| -------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------- | +| `Host` | InfluxDB server hostname | Required | +| `Port` | InfluxDB server port | Required {{% show-in "core,enterprise" %}}(`8181`){{% /show-in %}} | +| `Database` | Database name | Required | +| `AuthToken` | Authentication token | Required | +| `UseEncryption` | Use encrypted connection | `1` (enabled) | +| `TrustedCerts` | Path to trusted CA certificates | System default | +| `DisableCertificateVerification` | Skip certificate verification | `0` (disabled) | + +### Advanced options + +Add these to your DSN configuration or connection string as needed: + +{{% show-in "enterprise,core" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = localhost +Port = 8181 +Database = mydb +AuthToken = your-token +UseEncryption = 1 +DisableCertificateVerification = 0 +``` +{{% /show-in %}} + +{{% show-in "cloud-serverless" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = us-west-2-1.aws.cloud2.influxdata.com +Port = 443 +Database = mydb +AuthToken = your-token +UseEncryption = 1 +DisableCertificateVerification = 0 +``` +{{% /show-in %}} + +{{% show-in "cloud-dedicated" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = cluster-id.a.influxdb.io +Port = 443 +Database = mydb +AuthToken = your-token +UseEncryption = 1 +DisableCertificateVerification = 0 +``` +{{% /show-in %}} + +{{% show-in "clustered" %}} +```ini +[InfluxDB3] +Driver = Arrow Flight SQL ODBC Driver +Host = cluster-host.com +Port = 443 +Database = mydb +AuthToken = your-token +UseEncryption = 1 +DisableCertificateVerification = 0 +``` +{{% /show-in %}} + +## Troubleshooting + +### Driver not found + +If applications cannot find the Arrow Flight SQL ODBC driver: + +{{< tabs-wrapper >}} +{{% tabs %}} +[Windows](#) +[macOS and Linux](#) +{{% /tabs %}} + +{{% tab-content %}} +1. Open **ODBC Data Source Administrator (64-bit)** +2. Navigate to the **Drivers** tab +3. Verify **Arrow Flight SQL ODBC Driver** appears in the list +4. If not listed, reinstall the driver + +{{% /tab-content %}} + +{{% tab-content %}} +1. Run the following command to list installed drivers: + + ```bash + odbcinst -q -d + ``` + +2. Verify **Arrow Flight SQL** appears in the output +3. Check `/etc/odbcinst.ini` for proper driver configuration +4. Ensure the driver library path is correct + +{{% /tab-content %}} +{{< /tabs-wrapper >}} + +### Connection failures + +If you cannot connect to {{% product-name %}}: + +- Verify your {{% product-name %}} instance is running and accessible +- Check host and port settings: + - Local instances: `localhost:8181` + - {{% show-in "cloud-serverless,cloud-dedicated" %}}{{% product-name %}}: Use your cluster URL with port `443`{{% /show-in %}} +- Ensure `UseEncryption` is set correctly for your connection type +- Verify network connectivity and firewall rules allow connections + +### Authentication errors + +If authentication fails: + +- Confirm your token is valid and not expired +- Ensure the token is specified in the `AuthToken` parameter (not `Token`) +- {{% show-in "enterprise" %}}Verify the token has query permissions for the target database{{% /show-in %}} +- Check that the token was copied correctly without extra spaces or characters + +### Query errors + +If queries fail or return errors: + +- Verify SQL syntax is correct for InfluxDB SQL +- Check that referenced tables (measurements) exist in the database +- Ensure column names match your schema +- Review the [SQL reference](/influxdb3/version/reference/sql/) for supported features +- For large result sets, consider adding `LIMIT` clauses + +### Performance issues + +For slow queries or connection timeouts: + +- Add time range filters to limit data scanned +- Use appropriate indexes if available +- Increase timeout values in your DSN configuration +- Monitor query execution plans for optimization opportunities + diff --git a/content/shared/influxdb3-visualize/grafana.md b/content/shared/influxdb3-visualize/grafana.md deleted file mode 100644 index 06dbf82bca..0000000000 --- a/content/shared/influxdb3-visualize/grafana.md +++ /dev/null @@ -1,195 +0,0 @@ -Use [Grafana](https://grafana.com/) to query and visualize data from -{{% product-name %}}. - -> [Grafana] enables you to query, visualize, alert on, and explore your metrics, -> logs, and traces wherever they are stored. -> [Grafana] provides you with tools to turn your time-series database (TSDB) -> data into insightful graphs and visualizations. -> -> {{% cite %}}-- [Grafana documentation](https://grafana.com/docs/grafana/latest/introduction/){{% /cite %}} - -- [Install Grafana or login to Grafana Cloud](#install-grafana-or-login-to-grafana-cloud) -- [InfluxDB data source](#influxdb-data-source) -- [Create an InfluxDB data source](#create-an-influxdb-data-source) -- [Query InfluxDB with Grafana](#query-influxdb-with-grafana) -- [Build visualizations with Grafana](#build-visualizations-with-grafana) - -## Install Grafana or login to Grafana Cloud - -If using the open source version of **Grafana**, follow the -[Grafana installation instructions](https://grafana.com/docs/grafana/latest/setup-grafana/installation/) -to install Grafana for your operating system. -If using **Grafana Cloud**, login to your Grafana Cloud instance. - -## InfluxDB data source - -The InfluxDB data source plugin is included in the Grafana core distribution. -Use the plugin to query and visualize data from {{< product-name >}} with -both SQL and InfluxQL. - -> [!Note] -> #### Grafana 10.3+ -> -> The instructions below are for **Grafana 10.3+** which introduced the newest -> version of the InfluxDB core plugin. -> The updated plugin includes **SQL support** for InfluxDB 3-based products such -> as {{< product-name >}}. - -## Create an InfluxDB data source - -1. In your Grafana user interface (UI), navigate to **Data Sources**. -2. Click **Add new data source**. -3. Search for and select the **InfluxDB** plugin. -4. Provide a name for your data source. -5. Under **Query Language**, select either **SQL** or **InfluxQL**: - -{{< tabs-wrapper >}} -{{% tabs %}} -[SQL](#) -[InfluxQL](#) -{{% /tabs %}} -{{% tab-content %}} - - -When creating an InfluxDB data source that uses SQL to query data: - -1. Under **HTTP**: - - - **URL**: Provide your {{% product-name %}} URL: - - ``` - http://{{< influxdb/host >}} - ``` - - > [!Note] - > If you are _not_ using HTTPS, enable the **Insecure Connection** option - > under **InfluxDB Details**. - -2. Under **InfluxDB Details**: - - - **Database**: Provide a default database name to query. - - **Token**: Provide an arbitrary, non-empty string. - - **Insecure Connection**: If _not_ using HTTPS, enable this option. - -3. Click **Save & test**. - - {{< img-hd src="/img/influxdb3/influxdb3-grafana-sql.png" alt="Grafana InfluxDB data source for InfluxDB 3 that uses SQL" />}} - - -{{% /tab-content %}} -{{% tab-content %}} - - -When creating an InfluxDB data source that uses InfluxQL to query data: - -1. Under **HTTP**: - - - **URL**: Provide your {{% product-name %}} URL: - - ``` - https://{{< influxdb/host >}} - ``` - -2. Under **InfluxDB Details**: - - - **Database**: Provide a default database name to query. - - **User**: Provide an arbitrary string. - _This credential is ignored when querying {{% product-name %}}, but it cannot be empty._ - - **Password**: Provide an arbitrary string. - - **HTTP Method**: Choose one of the available HTTP request methods to use when querying data: - - - **POST** ({{< req text="Recommended" >}}) - - **GET** - -3. Click **Save & test**. - - {{< img-hd src="/img/influxdb3/influxdb3-grafana-influxql.png" alt="Grafana InfluxDB data source for InfluxDB 3 that uses InfluxQL" />}} - - -{{% /tab-content %}} -{{< /tabs-wrapper >}} - -## Query InfluxDB with Grafana - -After you [configure and save an InfluxDB datasource](#create-an-influxdb-data-source), -use Grafana to build, run, and inspect queries against {{< product-name >}}. - -{{< tabs-wrapper >}} -{{% tabs %}} -[SQL](#) -[InfluxQL](#) -{{% /tabs %}} -{{% tab-content %}} - - -> [!Note] -> {{% sql/sql-schema-intro %}} -> To learn more, see [Query Data](/influxdb3/version/query-data/sql/). - -1. Click **Explore**. -2. In the dropdown, select the saved InfluxDB data source to query. -3. Use the SQL query form to build your query: - - - **Table**: Select the measurement to query. - - **Column**: Select one or more fields and tags to return as columns in query results. - - With SQL, select the `time` column to include timestamps with the data. - Grafana relies on the `time` column to correctly graph time series data. - - - _**Optional:**_ Toggle **filter** to generate **WHERE** clause statements. - - **WHERE**: Configure condition expressions to include in the `WHERE` clause. - - - _**Optional:**_ Toggle **group** to generate **GROUP BY** clause statements. - - - **GROUP BY**: Select columns to group by. - If you include an aggregation function in the **SELECT** list, - you must group by one or more of the queried columns. - SQL returns the aggregation for each group. - - - {{< req text="Recommended" color="green" >}}: - Toggle **order** to generate **ORDER BY** clause statements. - - - **ORDER BY**: Select columns to sort by. - You can sort by time and multiple fields or tags. - To sort in descending order, select **DESC**. - -4. {{< req text="Recommended" color="green" >}}: Change format to **Time series**. - - - Use the **Format** dropdown to change the format of the query results. - For example, to visualize the query results as a time series, select **Time series**. - -5. Click **Run query** to execute the query. - - -{{% /tab-content %}} -{{% tab-content %}} - - -1. Click **Explore**. -2. In the dropdown, select the **InfluxDB** data source that you want to query. -3. Use the InfluxQL query form to build your query: - - - **FROM**: Select the measurement that you want to query. - - **WHERE**: To filter the query results, enter a conditional expression. - - **SELECT**: Select fields to query and an aggregate function to apply to each. - The aggregate function is applied to each time interval defined in the - `GROUP BY` clause. - - **GROUP BY**: By default, Grafana groups data by time to downsample results - and improve query performance. - You can also add other tags to group by. - -4. Click **Run query** to execute the query. - - -{{% /tab-content %}} -{{< /tabs-wrapper >}} - -{{< youtube "rSsouoNsNDs" >}} - -To learn about query management and inspection in Grafana, see the -[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). - -## Build visualizations with Grafana - -For a comprehensive walk-through of creating visualizations with -Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). diff --git a/content/shared/influxdb3-visualize/powerbi.md b/content/shared/influxdb3-visualize/powerbi.md new file mode 100644 index 0000000000..ca07483c0e --- /dev/null +++ b/content/shared/influxdb3-visualize/powerbi.md @@ -0,0 +1,263 @@ +Use [Microsoft Power BI Desktop](https://powerbi.microsoft.com/) with the +InfluxDB 3 custom connector to query and visualize data from {{% product-name %}}. + +Note: The Microsoft Power BI Connector for InfluxDB is currently in BETA + +> Microsoft Power BI is a collection of software services, apps, and connectors +> that work together to turn your unrelated sources of data into coherent, +> visually immersive, and interactive insights. +> +> {{% cite %}}-- [Microsoft Power BI documentation](https://learn.microsoft.com/en-us/power-bi/fundamentals/power-bi-overview){{% /cite %}} + +> [!Important] +> These instructions are for Power BI Desktop only; it uses a custom connector. + +- [Prerequisites](#prerequisites) +- [Install the Power BI connector](#install-the-power-bi-connector) +- [Install the Arrow Flight SQL ODBC Driver](#install-the-arrow-flight-sql-odbc-driver) +- [Enable the connector in Power BI](#enable-the-connector-in-power-bi) +- [Connect Power BI to InfluxDB](#connect-power-bi-to-influxdb) +- [Query and visualize data](#query-and-visualize-data) + +## Prerequisites + +- **Windows operating system**: The custom connector requires Windows + (macOS users can use virtualization software like [Parallels](https://www.parallels.com/)) +- **Power BI Desktop**: [Download and install Power BI Desktop](https://powerbi.microsoft.com/desktop/) + (A free trial is available) +- **{{% product-name %}}**: A running instance with data to query +- **Database token**: Your {{% show-in "cloud-dedicated, clustered" %}}{{% token-link "database" %}}{{% /show-in %}}{{% show-in "cloud-serverless" %}}{{% token-link %}}{{% /show-in %}}{{% show-in "core, enterprise" %}}{{% token-link "admin" "database" %}}{{% /show-in %}}{{% show-in "enterprise" %}} with query permissions for the target database{{% /show-in %}} + +The following steps guide you through downloading and installing the Arrow Flight SQL ODBC Driver and the InfluxDB 3 custom connector. + +## Install the Power BI connector + +The InfluxDB 3 custom connector for Power BI Desktop enables you to connect to +{{% product-name %}} and query data using SQL. + +### Install the Arrow Flight SQL ODBC Driver + +The custom connector requires the Arrow Flight SQL ODBC Driver. + +Download the Arrow Flight SQL ODBC driver + +Or use PowerShell to download and install: + +{{% code-placeholders "YOUR_USER" %}} +```powershell +# Download the driver +Invoke-WebRequest -Uri "https://docs.influxdata.com/downloads/arrow-flight-sql-odbc-0.9.7.1195-win64.msi" ` + -OutFile "C:\Users\YOUR_USER\Downloads\arrow-flight-sql-odbc-0.9.7.1195-win64.msi" + +# Mark as trusted +Unblock-File "C:\Users\YOUR_USER\Downloads\arrow-flight-sql-odbc-0.9.7.1195-win64.msi" + +# Install +Start-Process msiexec.exe -Wait -ArgumentList '/i "C:\Users\YOUR_USER\Downloads\arrow-flight-sql-odbc-0.9.7.1195-win64.msi"' +``` +{{% /code-placeholders %}} + +Replace the following: + +- {{% code-placeholder-key %}}`YOUR_USER`{{% /code-placeholder-key %}}: Your Windows username + +Follow the installation wizard using default settings. + +> [!Note] +> For more information about the Arrow Flight SQL ODBC Driver, see the [Dremio documentation](https://docs.dremio.com/current/client-applications/drivers/arrow-flight-sql-odbc-driver/). + +### Install the Power BI connector file + +Download the InfluxDB 3 Power BI connector + +Or use PowerShell to download: + +{{% code-placeholders "YOUR_USER" %}} +```powershell +# Download the connector +Invoke-WebRequest -Uri "https://docs.influxdata.com/downloads/InfluxDB.pqx" ` + -OutFile "C:\Users\YOUR_USER\Downloads\InfluxDB.pqx" +``` +{{% /code-placeholders %}} + +Replace the following: + +- {{% code-placeholder-key %}}`YOUR_USER`{{% /code-placeholder-key %}}: Your Windows username + +#### Install the connector + +Move the `.pqx` connector file to the Power BI custom connectors directory: + +1. Create the custom connectors folder if it doesn't exist: + + ```powershell + mkdir "$env:USERPROFILE\Documents\Power BI Desktop\Custom Connectors" + ``` + +2. Move the connector file to the custom connectors folder: + + {{% code-placeholders "YOUR_USER" %}} + ```powershell + Move-Item "C:\Users\YOUR_USER\Downloads\InfluxDB.pqx" ` + "$env:USERPROFILE\Documents\Power BI Desktop\Custom Connectors\" + ``` + {{% /code-placeholders %}} + + Replace the following: + + - {{% code-placeholder-key %}}`YOUR_USER`{{% /code-placeholder-key %}}: Your Windows username + +## Enable the connector in Power BI + +To use custom connectors, you must adjust Power BI Desktop's security settings: + +1. Open Power BI Desktop +2. Select **File** > **Options and settings** > **Options** +3. Navigate to **Security** under **GLOBAL** +4. Under **Data Extensions**, select **(Not Recommended) Allow any extension to + load without validation or warning** +5. Click **OK** +6. **Restart Power BI Desktop** for the changes to take effect + +> [!Warning] +> #### Security considerations +> Enabling uncertified extensions allows any custom connector to load. +> Only enable this setting if you trust the connectors you're installing. + +## Connect Power BI to InfluxDB + +After installing the connector and restarting Power BI Desktop: + +1. Open **Power BI Desktop** +2. Click **Get Data** > **More** +3. Search for **InfluxDB 3** and select it +4. Click **Connect** +5. In the **InfluxDB 3** connection dialog, configure the following: + + - **Server**: Your {{% product-name %}} URL without the port, (for example, + {{% show-in "cloud-serverless" %}}`https://us-west-2-1.aws.cloud2.influxdata.com`{{% /show-in %}}{{% show-in "cloud-dedicated" %}}`https://cluster-id.a.influxdb.io`{{% /show-in %}}{{% show-in "clustered" %}}`https://cluster-host.com`{{% /show-in %}}{{% show-in "enterprise,core" %}}`http://localhost`{{% /show-in %}}) + - **Database**: Your database name + - **Port**: Your server port (for example, {{% show-in "cloud-serverless,cloud-dedicated,clustered" %}}`443` (HTTPS){{% /show-in %}}{{% show-in "enterprise,core" %}}`8181` (default){{% /show-in %}}) + - **Native Query** (optional): Enter a SQL query to limit the data loaded + +6. Select **DirectQuery** as the **Data Connectivity mode** +7. Click **OK** +8. When prompted for credentials: + - Select **Basic** authentication + - **Username**: Leave blank or enter any value + - **Password**: Enter your {{% show-in "cloud-dedicated, clustered" %}}{{% token-link "database" %}}{{% /show-in %}}{{% show-in "cloud-serverless" %}}{{% token-link %}}{{% /show-in %}}{{% show-in "core, enterprise" %}}{{% token-link "admin" "database" %}}{{% /show-in %}}{{% show-in "enterprise" %}} with query permissions for the target database{{% /show-in %}} +9. Click **Connect** +10. Preview your data and click **Load** + +> [!Important] +> #### Limit query size for optimal performance +> {{% product-name %}} can handle high throughput and dimensional data. +> To ensure Power BI can successfully process data, limit query size by: +> - Using a `LIMIT` clause +> - Specifying time ranges with `WHERE time >= ...` +> - Filtering by specific columns or tags + +## Query and visualize data + +### Use Native Queries + +When connecting to InfluxDB 3, you can use the **Native Query** option to +execute custom SQL queries: + +1. In the connection dialog, enable **Native Query** +2. Enter your query in the provided field: + + ```sql + SELECT + time, + temp, + room + FROM + home + WHERE + time >= now() - INTERVAL '7 days' + ORDER BY + time DESC + LIMIT 1000 + ``` + +3. Select **DirectQuery** as the connectivity mode +4. Click **OK** to load the data + +### Create visualizations + +After loading data, Power BI displays your dataset in the **Fields** pane. + +#### View data in a table + +1. In the **Visualizations** pane, select the **Table** visualization +2. In the **Fields** pane, select the columns to display: + - **time**: Timestamp column + - **room**: Tag column + - **temp**: Field column +3. By default, Power BI summarizes numeric fields. + To display raw values: + - Select the field in the **Fields** or **Visualizations** pane + - Go to the **Modeling** tab + - Change **Default Summarization** to **Don't Summarize** + +#### Create time series visualizations + +1. In the **Visualizations** pane, select a visualization type + (for example, **Line chart**) +2. Drag fields to the appropriate areas: + - **X-axis**: `time` field + - **Y-axis**: Measurement fields (for example, `temp`) + - **Legend**: Tag fields (for example, `room`) +3. Use the **Filters** pane to apply additional filters +4. Configure visualization properties in the **Format** pane +5. Save your report + +### Time series best practices + +- Always include time range filters in your queries to limit data volume +- Use the `time` column for time-based visualizations +- Apply Power BI's date hierarchy features with the `time` column +- Select only the columns you need to improve query performance +- Use the `LIMIT` clause to restrict the number of rows returned + +## Troubleshooting + +### Connector not found + +If Power BI Desktop doesn't show the InfluxDB 3 connector: + +- Verify the `.pqx` file is in the correct location: + `Documents\Power BI Desktop\Custom Connectors\` +- Ensure you enabled custom connectors in Power BI security settings +- Restart Power BI Desktop after copying the connector file + +### Connection errors + +If you encounter connection errors: + +- Verify your {{% product-name %}} instance is accessible +- Check that the host URL and port are correct + - Local instances typically use `http://localhost:8181` + - {{% show-in "cloud-serverless,cloud-dedicated" %}}{{% product-name %}} instances use cluster-specific URLs{{% /show-in %}} +- Ensure your token has query permissions for the specified database +- Verify firewall rules allow connections on the specified port + +### Authentication errors + +If authentication fails: + +- Verify your database token is valid and not expired +- {{% show-in "enterprise" %}}Ensure the token has permissions for the target database{{% /show-in %}} +- Check that you entered the token in the **Password** field +- Leave the **Username** field blank or enter any value + +### Query performance + +For better query performance: + +- Always use `WHERE` clauses to filter data before loading +- Include time range filters to limit the data scanned +- Select only the columns you need +- Use the `LIMIT` clause to restrict result size +- Consider using **DirectQuery** mode instead of **Import** for large datasets diff --git a/content/shared/v3-process-data/visualize/grafana.md b/content/shared/v3-process-data/visualize/grafana.md index c809b1c4dd..7bf5e8e05a 100644 --- a/content/shared/v3-process-data/visualize/grafana.md +++ b/content/shared/v3-process-data/visualize/grafana.md @@ -1,5 +1,8 @@ -Use [Grafana](https://grafana.com/) to query and visualize data stored in -{{% product-name %}}. +Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/products/cloud/) +to query and visualize data from {{% product-name %}}. + +> [!Note] +> {{< influxdb-version-detector >}} > [Grafana] enables you to query, visualize, alert on, and explore your metrics, > logs, and traces wherever they are stored. @@ -8,24 +11,50 @@ Use [Grafana](https://grafana.com/) to query and visualize data stored in > > {{% cite %}}-- [Grafana documentation](https://grafana.com/docs/grafana/latest/introduction/){{% /cite %}} -- [Install Grafana or login to Grafana Cloud](#install-grafana-or-login-to-grafana-cloud) +- [Install Grafana or log in to Grafana Cloud](#install-grafana-or-log-in-to-grafana-cloud) - [InfluxDB data source](#influxdb-data-source) +- [Before you begin](#before-you-begin) - [Create an InfluxDB data source](#create-an-influxdb-data-source) -- [Query InfluxDB with Grafana](#query-influxdb-with-grafana) -- [Build visualizations with Grafana](#build-visualizations-with-grafana) +- [Query and visualize data](#query-and-visualize-data) -## Install Grafana or login to Grafana Cloud +## Install Grafana or log in to Grafana Cloud If using the open source version of **Grafana**, follow the [Grafana installation instructions](https://grafana.com/docs/grafana/latest/setup-grafana/installation/) to install Grafana for your operating system. If using **Grafana Cloud**, log in to your Grafana Cloud instance. +{{% show-in "core,enterprise" %}} +> [!Note] +> #### Using Grafana Cloud with a local InfluxDB instance +> +> If you need to keep your database local, consider running Grafana locally instead of using Grafana Cloud, +> as this avoids the need to expose your database to the internet. +> +> To use InfluxDB running on your private network with Grafana Cloud, you must +> [configure a private data source](https://grafana.com/docs/grafana-cloud/data-sources/private-data-sources/). +> See the Grafana documentation for instructions on configuring a Grafana Cloud private data source +> with {{% product-name %}} running on `http://localhost:8181`. +{{% /show-in %}} + +{{% show-in "clustered" %}} +> [!Note] +> #### Using Grafana Cloud with a local InfluxDB instance +> +> If you need to keep your database local, consider running Grafana locally instead of using Grafana Cloud, +> as this avoids the need to expose your database to the internet. +> +> To use InfluxDB running on your private network with Grafana Cloud, you must +> [configure a private data source](https://grafana.com/docs/grafana-cloud/data-sources/private-data-sources/). +> See the Grafana documentation for instructions on configuring a Grafana Cloud private data source +> with {{% product-name %}} running on `http://localhost`. +{{% /show-in %}} + ## InfluxDB data source The InfluxDB data source plugin is included in the Grafana core distribution. -Use the plugin to query and visualize data stored in {{< product-name >}} with -both InfluxQL and SQL. +Use the plugin to query and visualize data from {{< product-name >}} with +both SQL and InfluxQL. > [!Note] > #### Grafana 10.3+ @@ -33,18 +62,41 @@ both InfluxQL and SQL. > The instructions below are for **Grafana 10.3+** which introduced the newest > version of the InfluxDB core plugin. > The updated plugin includes **SQL support** for InfluxDB 3-based products such -> as {{< product-name >}}. +> as {{< product-name >}}, and the interface dynamically adapts based on your product and query language selections. + +## Before you begin + +**Prerequisites:** +- Grafana 10.3 or later +- Administrator role in Grafana +- {{% show-in "cloud-serverless" %}}An [API token](/influxdb3/version/admin/tokens/) with read access to the bucket{{% /show-in %}}{{% show-in "cloud-dedicated,clustered" %}}A [database token](/influxdb3/version/admin/tokens/#database-tokens) with read access to the database{{% /show-in %}}{{% show-in "core,enterprise" %}}Your {{% token-link "admin" "database" %}} with read access to the database{{% /show-in %}} + +### Quick reference: {{< product-name >}} configuration + +| Configuration | Value | +|:------------- |:----- | +| **Product selection** | {{% hide-in "core,enterprise" %}}**{{% product-name %}}**{{% /hide-in %}}{{% show-in "core" %}}**InfluxDB Enterprise 3.x** _(currently, no **Core** menu option)_{{% /show-in %}}{{% show-in "enterprise" %}}**InfluxDB Enterprise 3.x**{{% /show-in %}} | +| **URL** | {{% show-in "cloud-dedicated,clustered" %}} Cluster URL{{% /show-in %}}{{% show-in "cloud-serverless" %}} [Region URL](/influxdb3/cloud-serverless/reference/regions/)--for example, `https://us-west-2-1.aws.cloud2.influxdata.com`{{% /show-in %}}{{% show-in "core,enterprise" %}}Server URL{{% /show-in %}}{{% hide-in "cloud-serverless" %}}--for example, `https://{{< influxdb/host >}}`{{% /hide-in %}} | +| **Query languages** | SQL (requires HTTP/2), InfluxQL | +| **Authentication** | {{% show-in "cloud-serverless" %}}Required (API token){{% /show-in %}}{{% show-in "core" %}}Admin token (if authentication is enabled){{% /show-in %}}{{% show-in "enterprise" %}}Admin or database token (if authentication is enabled){{% /show-in %}}{{% show-in "cloud-dedicated,clustered" %}}Required (database token){{% /show-in %}} | +| **Database/Bucket** | {{% show-in "cloud-serverless" %}}Bucket name{{% /show-in %}}{{% hide-in "cloud-serverless" %}}Database name{{% /hide-in %}} | ## Create an InfluxDB data source -Which data source you create depends on which query language you want to use to -query {{% product-name %}}: +1. In your Grafana interface, click **Connections** in the left sidebar. +2. Click **Data sources**. +3. Click **Add new data source**. +4. Search for and select **InfluxDB**. The InfluxDB data source configuration page displays. +5. In the **Settings** tab, configure the following: + + - **Name**: A descriptive name for your data source + - **URL**: Your {{% product-name %}}{{% show-in "cloud-dedicated,clustered" %}} cluster URL{{% /show-in %}}{{% show-in "cloud-serverless" %}} [region URL](/influxdb3/cloud-serverless/reference/regions/)--for example, `https://us-west-2-1.aws.cloud2.influxdata.com`{{% /show-in %}}{{% show-in "core,enterprise" %}} server URL{{% /show-in %}}{{% hide-in "cloud-serverless" %}}--for example, `https://{{< influxdb/host >}}`{{% /hide-in %}} + - **Product**: From the dropdown, select {{% hide-in "core,enterprise" %}}**{{% product-name %}}**{{% /hide-in %}}{{% show-in "core" %}}**InfluxDB Enterprise 3.x** _(currently, no **Core** menu option)_{{% /show-in %}}{{% show-in "core,enterprise" %}}**InfluxDB Enterprise 3.x**{{% /show-in %}} + - **Query Language**: Select **SQL** or **InfluxQL** + +### Configure database settings -1. In your Grafana user interface (UI), navigate to **Data Sources**. -2. Click **Add new data source**. -3. Search for and select the **InfluxDB** plugin. -4. Provide a name for your data source. -5. Under **Query Language**, select either **SQL** or **InfluxQL**: +The fields in this section change based on your query language selection. {{< tabs-wrapper >}} {{% tabs %}} @@ -54,75 +106,79 @@ query {{% product-name %}}: {{% tab-content %}} -When creating an InfluxDB data source that uses SQL to query data: +#### SQL configuration -1. Under **HTTP**: +When you select **SQL** as the query language, configure the following fields: - - **URL**: Provide your {{% show-in "cloud-serverless" %}}[{{< product-name >}} region URL](/influxdb3/version/reference/regions/){{% /show-in %}} - {{% hide-in "cloud-serverless" %}}{{% product-name omit=" Clustered" %}} cluster URL{{% /hide-in %}} using the HTTPS protocol: +- **Database**: {{% show-in "cloud-serverless" %}}Your [bucket](/influxdb3/version/admin/buckets/) name. In {{< product-name >}}, buckets function as databases.{{% /show-in %}}{{% hide-in "cloud-serverless" %}}Your [database](/influxdb3/version/admin/databases/) name.{{% /hide-in %}} - ``` - https://{{< influxdb/host >}} - ``` -2. Under **InfluxDB Details**: +- **Token**: {{% show-in "cloud-serverless" %}}An [API token](/influxdb3/version/admin/tokens/) with read access to the bucket{{% /show-in %}}{{% show-in "cloud-dedicated,clustered" %}}A [database token](/influxdb3/version/admin/tokens/#database-tokens) with read access to the database{{% /show-in %}}{{% show-in "core,enterprise" %}}Your {{% token-link "admin" "database" %}} with read access to the database{{% /show-in %}} - - **Database**: Provide a default {{% show-in "cloud-serverless" %}}[bucket](/influxdb3/version/admin/buckets/) name to query. In {{< product-name >}}, a bucket functions as a database.{{% /show-in %}}{{% hide-in "cloud-serverless" %}}[database](/influxdb3/version/admin/databases/) name to query.{{% /hide-in %}} - - **Token**: Provide {{% show-in "cloud-serverless" %}}an [API token](/influxdb3/version/admin/tokens/) with read access to the buckets you want to query.{{% /show-in %}}{{% hide-in "cloud-serverless" %}}a [database token](/influxdb3/version/admin/tokens/#database-tokens) with read access to the databases you want to query.{{% /hide-in %}} -3. Click **Save & test**. +{{% show-in "cloud-serverless" %}}{{< img-hd src="/img/influxdb3/cloud-serverless-grafana-product-dropdown-sql.png" alt="SQL configuration for {{% product-name %}}" />}}{{% /show-in %}} +{{% show-in "cloud-dedicated" %}}{{< img-hd src="/img/influxdb3/cloud-dedicated-grafana-product-dropdown-sql.png" alt="SQL configuration for {{% product-name %}}" />}}{{% /show-in %}} +{{% show-in "clustered" %}}{{< img-hd src="/img/influxdb3/cluster-grafana-product-dropdown-sql.png" alt="SQL configuration for {{% product-name %}}" />}}{{% /show-in %}} +{{% show-in "core, enterprise" %}}{{< img-hd src="/img/influxdb3/enterprise-v3-grafana-product-dropdown-sql.png" alt="SQL configuration for {{% product-name %}}" />}}{{% /show-in %}} -{{% show-in "cloud-serverless" %}}{{< img-hd src="/img/influxdb3/cloud-serverless-grafana-influxdb-data-source-sql.png" alt="Grafana InfluxDB data source for InfluxDB Cloud Serverless that uses SQL" />}}{{% /show-in %}} -{{% show-in "cloud-dedicated" %}}{{< img-hd src="/img/influxdb/cloud-dedicated-grafana-influxdb-data-source-sql.png" alt="Grafana InfluxDB data source for InfluxDB Cloud Dedicated that uses SQL" />}}{{% /show-in %}} -{{% show-in "clustered" %}}{{< img-hd src="/img/influxdb3/clustered-grafana-influxdb-data-source-sql.png" alt="Grafana InfluxDB data source for InfluxDB Clustered that uses SQL" />}}{{% /show-in %}} +> [!Important] +> #### Grafana queries through a proxy require HTTP/2 +> +> For SQL queries, Grafana uses the Flight SQL protocol (gRPC) to query {{% product-name %}}, which requires **HTTP/2**. +> If you query {{% product-name %}} through a proxy (such as HAProxy, nginx, or a load balancer), +> verify that your proxy is configured to support HTTP/2. +> Without HTTP/2 support, SQL queries through Grafana will fail to connect. +> +> InfluxQL queries use HTTP/1.1 and are not affected by this requirement. + +Click **Save & test**. Grafana attempts to connect to {{% product-name %}} and returns the result of the test. {{% /tab-content %}} {{% tab-content %}} -When creating an InfluxDB data source that uses InfluxQL to query data: +#### InfluxQL configuration {{% show-in "cloud-serverless" %}} -> [!Note] -> #### Map databases and retention policies to buckets -> -> To query {{% product-name %}} with InfluxQL, first map database and retention policy -> (DBRP) combinations to your InfluxDB Cloud buckets. For more information, see -> [Map databases and retention policies to buckets](/influxdb3/version/query-data/influxql/dbrp/). +> [!Important] +> #### DBRP mapping required +> +> To query {{% product-name %}} with InfluxQL, you must first map database and +> retention policy (DBRP) combinations to your InfluxDB Cloud buckets. The +> configuration form displays a warning if DBRP mapping is not configured. +> +> For more information, see [Map databases and retention policies to buckets](/influxdb3/version/query-data/influxql/dbrp/). {{% /show-in %}} -1. Under **HTTP**: +When you select **InfluxQL** as the query language, configure the following fields: - - **URL**: Provide your {{% show-in "cloud-serverless" %}}[{{< product-name >}} region URL](/influxdb3/version/reference/regions/){{% /show-in %}}{{% hide-in "cloud-serverless" %}}{{% product-name omit=" Clustered" %}} cluster URL{{% /hide-in %}} - using the HTTPS protocol: +- **Database**: {{% show-in "cloud-serverless" %}}The database name mapped to your InfluxDB bucket.{{% /show-in %}}{{% hide-in "cloud-serverless" %}}Your [database](/influxdb3/version/admin/databases/) name.{{% /hide-in %}} - ``` - https://{{< influxdb/host >}} - ``` -2. Under **InfluxDB Details**: +- **User**: A username (can be any non-empty value). - - **Database**: Provide a {{% show-in "cloud-serverless" %}}database name to query. - Use the database name that is mapped to your InfluxDB bucket{{% /show-in %}}{{% hide-in "cloud-serverless" %}}default [database](/influxdb3/version/admin/databases/) name to query{{% /hide-in %}}. - - **User**: Provide an arbitrary string. - _This credential is ignored when querying {{% product-name %}}, but it cannot be empty._ - - **Password**: Provide {{% show-in "cloud-serverless" %}}an [API token](/influxdb3/version/admin/tokens/) with read access to the buckets you want to query{{% /show-in %}}{{% hide-in "cloud-serverless" %}}a [database token](/influxdb3/version/admin/tokens/#database-tokens) with read access to the databases you want to query{{% /hide-in %}}. - - **HTTP Method**: Choose one of the available HTTP request methods to use when querying data: +- **Password**: {{% show-in "cloud-serverless" %}}Your [API token](/influxdb3/version/admin/tokens/) with read access to the bucket.{{% /show-in %}}{{% hide-in "cloud-serverless" %}}Your [database token](/influxdb3/version/admin/tokens/#database-tokens) with read access to the database.{{% /hide-in %}} - - **POST** ({{< req text="Recommended" >}}) - - **GET** -3. Click **Save & test**. +- **HTTP Method**: Select **POST** (recommended) or **GET** -{{% show-in "cloud-dedicated" %}}{{< img-hd src="/img/influxdb/cloud-dedicated-grafana-influxdb-data-source-influxql.png" alt="Grafana InfluxDB data source for InfluxDB Cloud Dedicated using InfluxQL" />}}{{% /show-in %}} -{{% show-in "cloud-serverless" %}}{{< img-hd src="/img/influxdb3/cloud-serverless-grafana-influxdb-data-source-influxql.png" alt="Grafana InfluxDB data source for InfluxDB Cloud Serverless using InfluxQL" />}}{{% /show-in %}} -{{% show-in "clustered" %}}{{< img-hd src="/img/influxdb3/clustered-grafana-influxdb-data-source-influxql.png" alt="Grafana InfluxDB data source for InfluxDB Clustered using InfluxQL" />}}{{% /show-in %}} +{{% show-in "cloud-serverless" %}}{{< img-hd src="/img/influxdb3/cloud-serverless-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for {{% product-name %}} with DBRP warning" />}}{{% /show-in %}} +{{% show-in "cloud-dedicated" %}}{{< img-hd src="/img/influxdb3/cloud-dedicated-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for {{% product-name %}} with DBRP warning" />}}{{% /show-in %}} +{{% show-in "clustered" %}}{{< img-hd src="/img/influxdb3/cluster-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for {{% product-name %}} with DBRP warning" />}}{{% /show-in %}} + +{{% show-in "enterprise" %}}{{< img-hd src="/img/influxdb3/enterprise-v3-grafana-product-dropdown-influxql.png" alt="InfluxQL configuration for {{% product-name %}} with DBRP warning" />}}{{% /show-in %}} + +Click **Save & test**. Grafana attempts to connect to {{% product-name %}} and returns the result of the test. {{% /tab-content %}} {{< /tabs-wrapper >}} -## Query InfluxDB with Grafana +## Query and visualize data + +With your InfluxDB connection configured, use Grafana to query and visualize time series data. + +### Query InfluxDB with Grafana After you [configure and save an InfluxDB datasource](#create-an-influxdb-data-source), -use Grafana to build, run, and inspect queries against your InfluxDB {{% show-in "cloud-serverless" %}}bucket{{% /show-in %}}{{% hide-in "cloud-serverless" %}}database{{% /hide-in %}}. +use Grafana to build, run, and inspect queries against {{% show-in "cloud-serverless" %}}your InfluxDB bucket{{% /show-in %}}{{% hide-in "cloud-serverless" %}}{{% product-name %}}{{% /hide-in %}}. {{< tabs-wrapper >}} {{% tabs %}} @@ -139,12 +195,12 @@ use Grafana to build, run, and inspect queries against your InfluxDB {{% show-in 1. Click **Explore**. 2. In the dropdown, select the saved InfluxDB data source to query. 3. Use the SQL query form to build your query: - - **Table**: Select the measurement to query. + - **Table**: Select the table (measurement) to query. - **Column**: Select one or more fields and tags to return as columns in query results. - + With SQL, select the `time` column to include timestamps with the data. Grafana relies on the `time` column to correctly graph time series data. - + - _**Optional:**_ Toggle **filter** to generate **WHERE** clause statements. - **WHERE**: Configure condition expressions to include in the `WHERE` clause. @@ -190,12 +246,14 @@ use Grafana to build, run, and inspect queries against your InfluxDB {{% show-in {{% /tab-content %}} {{< /tabs-wrapper >}} -{{< youtube "rSsouoNsNDs" >}} - -To learn about query management and inspection in Grafana, see the -[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). +With your InfluxDB connection configured, use Grafana to query and visualize time series data. -## Build visualizations with Grafana +### Build visualizations with Grafana For a comprehensive walk-through of creating visualizations with -Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). \ No newline at end of file +Grafana, see the [Grafana documentation](https://grafana.com/docs/grafana/latest/). + +### Query inspection in Grafana + +To learn more about query management and inspection in Grafana, see the +[Grafana Explore documentation](https://grafana.com/docs/grafana/latest/explore/). diff --git a/content/test-version-detector.md b/content/test-version-detector.md new file mode 100644 index 0000000000..452c008721 --- /dev/null +++ b/content/test-version-detector.md @@ -0,0 +1,18 @@ +--- +title: Test InfluxDB Version Detector +description: Test page for the InfluxDB version detector component +weight: 1000 +test_only: true # Custom parameter to indicate test-only content +--- + +This is a test page for the InfluxDB version detector component. + +{{< influxdb-version-detector >}} + +## About this component + +This interactive component helps users identify which InfluxDB product they're using by: +- Checking URL patterns +- Analyzing ping response headers +- Asking guided questions about their setup +- Providing ranked results based on their answers \ No newline at end of file diff --git a/cypress/e2e/content/influxdb-version-detector.cy.js b/cypress/e2e/content/influxdb-version-detector.cy.js new file mode 100644 index 0000000000..937dea6775 --- /dev/null +++ b/cypress/e2e/content/influxdb-version-detector.cy.js @@ -0,0 +1,861 @@ +/// + +/** + * InfluxDB Version Detector E2E Test Suite + * + * COMPREHENSIVE TEST SCENARIOS CHECKLIST: + * + * URL Detection Scenarios: + * ------------------------- + * Cloud URLs (Definitive Detection): + * - [ ] Dedicated: https://cluster-id.influxdb.io → InfluxDB Cloud Dedicated (confidence 1.0) + * - [ ] Serverless US: https://us-east-1-1.aws.cloud2.influxdata.com → InfluxDB Cloud Serverless + * - [ ] Serverless EU: https://eu-central-1-1.aws.cloud2.influxdata.com → InfluxDB Cloud Serverless + * - [ ] Cloud TSM: https://us-west-2-1.aws.cloud2.influxdata.com → InfluxDB Cloud v2 (TSM) + * - [ ] Cloud v1: https://us-west-1-1.influxcloud.net → InfluxDB Cloud v1 + * + * Localhost URLs (Port-based Detection): + * - [ ] Core/Enterprise Port: http://localhost:8181 → Should suggest ping test + * - [ ] OSS Port: http://localhost:8086 → Should suggest version check + * - [ ] Custom Port: http://localhost:9999 → Should fall back to questionnaire + * + * Edge Cases: + * - [ ] Empty URL: Submit without entering URL → Should show error + * - [ ] Invalid URL: "not-a-url" → Should fall back to questionnaire + * - [ ] Cloud keyword: "cloud 2" → Should start questionnaire with cloud context + * - [ ] Mixed case: HTTP://LOCALHOST:8181 → Should detect port correctly + * + * Airgapped/Manual Analysis Scenarios: + * ------------------------------------- + * Ping Headers Analysis: + * - [ ] v3 Core headers: x-influxdb-build: core → InfluxDB 3 Core + * - [ ] v3 Enterprise headers: x-influxdb-build: enterprise → InfluxDB 3 Enterprise + * - [ ] v2 OSS headers: X-Influxdb-Version: 2.7.8 → InfluxDB OSS 2.x + * - [ ] v1 headers: X-Influxdb-Version: 1.8.10 → InfluxDB OSS 1.x + * - [ ] 401 Response: Headers showing 401/403 → Should show auth required message + * - [ ] Empty headers: Submit without text → Should show error + * - [ ] Example content: Submit with placeholder text → Should show error + * + * Docker Output Analysis: + * - [ ] Explicit v3 Core: "InfluxDB 3 Core" in output → InfluxDB 3 Core + * - [ ] Explicit v3 Enterprise: "InfluxDB 3 Enterprise" in output → InfluxDB 3 Enterprise + * - [ ] Generic v3: x-influxdb-version: 3.1.0 but no build header → Core or Enterprise + * - [ ] v2 version: "InfluxDB v2.7.8" in output → InfluxDB OSS 2.x + * - [ ] v1 OSS: "InfluxDB v1.8.10" in output → InfluxDB OSS 1.x + * - [ ] v1 Enterprise: "InfluxDB 1.8.10" + "Enterprise" → InfluxDB Enterprise + * - [ ] Empty output: Submit without text → Should show error + * - [ ] Example content: Submit with placeholder text → Should show error + * + * Questionnaire Flow Scenarios: + * ------------------------------ + * License-based Paths: + * - [ ] Free → Self-hosted → Recent → SQL → Should rank Core/OSS highly + * - [ ] Paid → Self-hosted → Recent → SQL → Should rank Enterprise highly + * - [ ] Free → Cloud → Recent → Flux → Should rank Cloud Serverless/TSM + * - [ ] Paid → Cloud → Recent → SQL → Should rank Dedicated highly + * - [ ] Unknown license → Should not eliminate products + * + * Age-based Scoring: + * - [ ] Recent (< 1 year) → Should favor v3 products + * - [ ] 1-5 years → Should favor v2 era products + * - [ ] 5+ years → Should favor v1 products only + * - [ ] Unknown age → Should not affect scoring + * + * Language-based Elimination: + * - [ ] SQL only → Should eliminate v1, v2, Cloud TSM + * - [ ] Flux only → Should eliminate v1, all v3 products + * - [ ] InfluxQL only → Should favor v1, but not eliminate others + * - [ ] Multiple languages → Should not eliminate products + * - [ ] Unknown language → Should not affect scoring + * + * Combined Detection Scenarios: + * ----------------------------- + * URL + Questionnaire: + * - [ ] Port 8181 + Free license → Should show Core as high confidence + * - [ ] Port 8181 + Paid license → Should show Enterprise as high confidence + * - [ ] Port 8086 + Free + Recent + SQL → Mixed signals, show ranked results + * - [ ] Cloud URL pattern + Paid → Should favor Dedicated/Serverless + * + * UI/UX Scenarios: + * ---------------- + * Navigation: + * - [ ] Back button: From URL input → Should return to "URL known" question + * - [ ] Back button: From questionnaire Q2 → Should return to Q1 + * - [ ] Back button: From first question → Should stay at first question + * - [ ] Progress bar: Should update with each question + * + * Results Display: + * - [ ] High confidence (score > 60): Should show "Most Likely" label + * - [ ] Medium confidence (30-60): Should show confidence rating + * - [ ] Low confidence (< 30): Should show multiple candidates + * - [ ] Score gap ≥ 15: Top result should stand out + * - [ ] Score gap < 15: Should show multiple options + * + * Interactive Elements: + * - [ ] Start questionnaire button: From detection results → Should hide results and start questions + * - [ ] Restart button: Should clear all answers and return to start + * - [ ] Grafana links: Should display for detected products + * - [ ] Configuration guidance: Should display for top results + * - [ ] Quick reference table: Should expand/collapse + * + * Pre-filled Values: + * - [ ] Stored URL: Should pre-fill URL input from localStorage + * - [ ] URL indicator: Should show when URL is pre-filled + * - [ ] Clear indicator: Should hide when user edits URL + * + * Analytics Tracking Scenarios: + * ----------------------------- + * - [ ] Modal opened: Track when component initializes + * - [ ] Question answered: Track each answer with question_id and value + * - [ ] URL detection: Track with detection_method: "url_analysis" + * - [ ] Product detected: Track with detected_product and completion_status + * - [ ] Restart: Track restart action + * + * Accessibility Scenarios: + * ------------------------ + * - [ ] Keyboard navigation: Tab through buttons and inputs + * - [ ] Focus management: Should focus on heading after showing result + * - [ ] Screen reader: Labels and ARIA attributes present + * - [ ] Color contrast: Results visible in different themes + * + * Error Handling: + * --------------- + * - [ ] Missing products data: Component should handle gracefully + * - [ ] Missing influxdb_urls data: Should use fallback values + * - [ ] Invalid JSON in data attributes: Should log warning and continue + * + * Edge Cases: + * ----------- + * - [ ] Modal initialization: Component in modal should wait for modal to open + * - [ ] Multiple instances: Each instance should work independently + * - [ ] Page navigation: State should persist if using back button + * - [ ] URL query params: Should update with detection results + */ + +const modalTriggerSelector = 'a.btn.influxdb-detector-trigger'; + +describe('InfluxDB Version Detector Component', function () { + // Remove the global beforeEach to optimize for efficient running + // Each describe block will visit the page once + + describe('Component Data Attributes', function () { + beforeEach(() => { + cy.visit('/test-version-detector/'); + // The trigger is an anchor element with .btn class, not a button + cy.contains(modalTriggerSelector, 'Detect my InfluxDB version').click(); + }); + + it('should not throw JavaScript console errors', function () { + cy.window().then((win) => { + const logs = []; + const originalError = win.console.error; + + win.console.error = (...args) => { + logs.push(args.join(' ')); + originalError.apply(win.console, args); + }; + + cy.wait(2000); + + cy.then(() => { + const relevantErrors = logs.filter( + (log) => + log.includes('influxdb-version-detector') || + log.includes('detectContext is not a function') || + log.includes('Failed to parse influxdb_urls data') + ); + expect(relevantErrors).to.have.length(0); + }); + }); + }); + }); + + describe('URL with port 8086', function () { + before(() => { + cy.visit('/test-version-detector/'); + cy.contains(modalTriggerSelector, 'Detect my InfluxDB version').click(); + cy.get('[data-component="influxdb-version-detector"]') + .eq(0) + .within(() => { + cy.get('.option-button').contains('Yes, I know the URL').click(); + it('should suggest legacy editions for custom URL or hostname', function () { + cy.get('#url-input', { timeout: 10000 }) + .clear() + .type('http://willieshotchicken.com:8086'); + cy.get('.submit-button').click(); + + cy.get('.result') + .invoke('text') + .then((text) => { + // Should mention multiple products for port 8086 + const mentionsLegacyEditions = + text.includes('OSS 1.x') || + text.includes('OSS 2.x') || + text.includes('Enterprise'); + expect(mentionsLegacyEditions).to.be.true; + }); + cy.get('#url-input', { timeout: 10000 }) + .should('be.visible') + .clear() + .type('willieshotchicken.com:8086'); + cy.get('.submit-button').click(); + + cy.get('.result') + .invoke('text') + .then((text) => { + // Should mention multiple products + const mentionsLegacyEditions = + text.includes('OSS 1.x') || + text.includes('OSS 2.x') || + text.includes('Enterprise'); + expect(mentionsLegacyEditions).to.be.true; + }); + }); + it('should suggest OSS for localhost', function () { + cy.get('#url-input', { timeout: 10000 }) + .should('be.visible') + .clear() + .type('http://localhost:8086'); + cy.get('.submit-button').click(); + + cy.get('.result') + .invoke('text') + .then((text) => { + // Should mention multiple editions + const mentionsLegacyOSS = + text.includes('OSS 1.x') || text.includes('OSS 2.x'); + expect(mentionsLegacyOSS).to.be.true; + }); + }); + }); + }); + + describe.skip('URL with port 8181', function () { + const port8181UrlTests = [ + // InfluxDB 3 Core/Enterprise URLs + { + url: 'http://localhost:8181', + }, + { + url: 'https://my-server.com:8181', + }, + ]; + port8181UrlTests.forEach(({ url }) => { + it(`should detect Core and Enterprise 3 for ${url}`, function () { + cy.visit('/test-version-detector/'); + cy.get('body').then(($body) => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + cy.get('.result') + .invoke('text') + .then((text) => { + // Should mention multiple editions + const mentionsCoreAndEnterprise = + text.includes('InfluxDB 3 Core') && + text.includes('InfluxDB 3 Enterprise'); + expect(mentionsCoreAndEnterprise).to.be.true; + }); + }); + }); + }); + }); + + describe.skip('Cloud URLs', function () { + const cloudUrlTests = [ + { + url: 'https://us-west-2-1.aws.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + { + url: 'https://us-east-1-1.aws.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + { + url: 'https://eu-central-1-1.aws.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + { + url: 'https://us-central1-1.gcp.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + { + url: 'https://westeurope-1.azure.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + { + url: 'https://eastus-1.azure.cloud2.influxdata.com', + expectedText: 'Cloud', + }, + ]; + + cloudUrlTests.forEach(({ url, expectedText }) => { + it(`should detect ${expectedText} for ${url}`, function () { + cy.visit('/test-version-detector/'); + cy.get('body').then(($body) => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + + cy.get('.result').should('be.visible').and('contain', expectedText); + }); + }); + }); + }); + + describe.skip('Cloud Dedicated and Clustered URLs', function () { + const clusterUrlTests = [ + // v3 Cloud Dedicated + { + url: 'https://cluster-id.a.influxdb.io', + expectedText: 'Cloud Dedicated', + }, + { + url: 'https://my-cluster.a.influxdb.io', + expectedText: 'Cloud Dedicated', + }, + + // v1 Enterprise/v3 Clustered + { url: 'https://cluster-host.com', expectedText: 'Clustered' }, + ]; + clusterUrlTests.forEach(({ url, expectedText }) => { + it(`should detect ${expectedText} for ${url}`, function () { + cy.visit('/test-version-detector/'); + cy.get('body').then(($body) => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + + cy.get('.result').should('be.visible').and('contain', expectedText); + }); + }); + }); + }); + + describe.skip('Cloud Dedicated and Clustered URLs', function () { + const clusterUrlTests = [ + // v3 Cloud Dedicated + { + url: 'https://cluster-id.a.influxdb.io', + expectedText: 'Cloud Dedicated', + }, + { + url: 'https://my-cluster.a.influxdb.io', + expectedText: 'Cloud Dedicated', + }, + + // v1 Enterprise/v3 Clustered + { url: 'https://cluster-host.com', expectedText: 'Clustered' }, + ]; + clusterUrlTests.forEach(({ url, expectedText }) => { + it(`should detect ${expectedText} for ${url}`, function () { + cy.visit('/test-version-detector/'); + cy.get('body').then(($body) => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + + cy.get('.result').should('be.visible').and('contain', expectedText); + }); + }); + }); + }); + + it('should handle cloud context detection', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Wait for URL input question to appear and then enter cloud context + cy.get('#q-url-input', { timeout: 10000 }).should('be.visible'); + cy.get('#url-input', { timeout: 10000 }) + .should('be.visible') + .clear() + .type('cloud 2'); + cy.get('.submit-button').click(); + + // Should proceed to next step - either show result or start questionnaire + // Don't be too specific about what happens next, just verify it progresses + cy.get('body').then(($body) => { + if ($body.find('.result').length > 0) { + cy.get('.result').should('be.visible'); + } else { + cy.get('.question.active', { timeout: 15000 }).should('be.visible'); + } + }); + }); + + it('should handle v3 port detection', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Wait for URL input question to appear and then test v3 port detection (8181) + cy.get('#q-url-input', { timeout: 10000 }).should('be.visible'); + cy.get('#url-input', { timeout: 10000 }) + .should('be.visible') + .clear() + .type('http://localhost:8181'); + cy.get('.submit-button').click(); + + // Should progress to either result or questionnaire + cy.get('body', { timeout: 15000 }).then(($body) => { + if ($body.find('.result').length > 0) { + cy.get('.result').should('be.visible'); + } else { + cy.get('.question.active').should('be.visible'); + } + }); + }); + }); + + describe.skip('Questionnaire Flow', function () { + beforeEach(() => { + cy.visit('/test-version-detector/'); + // The trigger is an anchor element with .btn class, not a button + cy.contains(modalTriggerSelector, 'Detect my InfluxDB version').click(); + }); + it('should start questionnaire for unknown URL', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + + cy.get('.question.active').should('be.visible'); + cy.get('.option-button').should('have.length.greaterThan', 0); + }); + + it('should complete basic questionnaire flow', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Start questionnaire + cy.get('#url-input') + .should('be.visible') + .clear() + .type('https://test.com'); + cy.get('.submit-button').click(); + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + + // Answer questions with proper waiting for DOM updates + const answers = ['Self-hosted', 'Free', '2-5 years', 'SQL']; + + answers.forEach((answer, index) => { + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + cy.get('.back-button').should('be.visible'); + cy.get('.option-button').contains(answer).should('be.visible').click(); + + // Wait for the next question or final result + if (index < answers.length - 1) { + cy.get('.question.active', { timeout: 5000 }).should('be.visible'); + } + }); + + // Should show results + cy.get('.result', { timeout: 10000 }).should('be.visible'); + }); + + it('should show all products when answering "I\'m not sure" to all questions', function () { + // Test fix for: Core/Enterprise disappearing with all "unknown" answers + cy.get('.option-button').contains("No, I don't know the URL").click(); + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + + // Answer "I'm not sure" to all questions + for (let i = 0; i < 4; i++) { + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + cy.get('.option-button').contains("I'm not sure").click(); + cy.wait(500); + } + + cy.get('.result', { timeout: 10000 }).should('be.visible'); + // Should show multiple products, not empty or filtered list + cy.get('.result').invoke('text').should('have.length.greaterThan', 100); + }); + + it('should NOT recommend InfluxDB 3 for Flux users (regression test)', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').should('be.visible').clear().type('cloud 2'); + cy.get('.submit-button').click(); + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + + // Complete problematic scenario that was fixed + const answers = ['Paid', '2-5 years', 'Flux']; + answers.forEach((answer, index) => { + cy.get('.question.active', { timeout: 10000 }).should('be.visible'); + cy.get('.option-button').contains(answer).should('be.visible').click(); + + // Wait for the next question or final result + if (index < answers.length - 1) { + cy.get('.question.active', { timeout: 5000 }).should('be.visible'); + } + }); + + cy.get('.result', { timeout: 10000 }).should('be.visible'); + + // Should NOT recommend InfluxDB 3 products for Flux + cy.get('.result').should('not.contain', 'InfluxDB 3 Core'); + cy.get('.result').should('not.contain', 'InfluxDB 3 Enterprise'); + }); + + // Comprehensive questionnaire scenarios covering all decision tree paths + const questionnaireScenarios = [ + { + name: 'SQL Filtering Test - Only InfluxDB 3 products for SQL (Free)', + answers: ['Self-hosted', 'Free', 'Less than 6 months', 'SQL'], + shouldContain: ['InfluxDB 3'], + shouldNotContain: [ + 'InfluxDB OSS 1.x', + 'InfluxDB OSS 2.x', + 'InfluxDB Enterprise v1.x', + 'InfluxDB Cloud (TSM)', + ], + }, + { + name: 'SQL Filtering Test - Only InfluxDB 3 products for SQL (Paid)', + answers: ['Self-hosted', 'Paid', 'Less than 6 months', 'SQL'], + shouldContain: ['InfluxDB 3'], + shouldNotContain: [ + 'InfluxDB OSS 1.x', + 'InfluxDB OSS 2.x', + 'InfluxDB Enterprise v1.x', + 'InfluxDB Cloud (TSM)', + ], + }, + { + name: 'SQL Filtering Test - Only InfluxDB 3 Cloud products for SQL', + answers: [ + 'Cloud (managed service)', + 'Paid', + 'Less than 6 months', + 'SQL', + ], + shouldContain: ['Cloud'], + shouldNotContain: [ + 'InfluxDB OSS', + 'InfluxDB Enterprise v1.x', + 'InfluxDB Cloud (TSM)', + ], + }, + { + name: 'OSS Free User - SQL (recent)', + answers: ['Self-hosted', 'Free', 'Less than 6 months', 'SQL'], + shouldContain: ['InfluxDB 3 Core'], + shouldNotContain: ['InfluxDB 3 Enterprise'], + }, + { + name: 'OSS Free User - SQL (experienced)', + answers: ['Self-hosted', 'Free', '2-5 years', 'SQL'], + shouldContain: ['InfluxDB 3 Core'], + shouldNotContain: ['InfluxDB 3 Enterprise'], + }, + { + name: 'Cloud Flux User', + answers: [ + 'Cloud (managed service)', + 'Paid', + 'Less than 6 months', + 'Flux', + ], + shouldContain: ['InfluxDB v2', 'Cloud'], + shouldNotContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + }, + { + name: 'Cloud SQL User (recent)', + answers: [ + 'Cloud (managed service)', + 'Paid', + 'Less than 6 months', + 'SQL', + ], + shouldContain: ['Cloud Serverless', 'Cloud Dedicated'], + shouldNotContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + }, + { + name: 'Modern Self-hosted SQL User', + answers: ['Self-hosted', 'Paid', 'Less than 6 months', 'SQL'], + shouldContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + shouldNotContain: ['Cloud'], + }, + { + name: 'High Volume Enterprise User', + answers: ['Self-hosted', 'Paid', 'Less than 6 months', 'SQL', 'Yes'], + shouldContain: ['InfluxDB 3 Enterprise'], + shouldNotContain: ['Cloud'], + }, + { + name: 'Legacy Self-hosted User (InfluxQL)', + answers: ['Self-hosted', 'Free', '5+ years', 'InfluxQL'], + shouldContain: ['InfluxDB v1', 'OSS'], + shouldNotContain: ['InfluxDB 3'], + }, + { + name: 'Legacy Enterprise User', + answers: ['Self-hosted', 'Paid', '5+ years', 'InfluxQL'], + shouldContain: ['InfluxDB Enterprise', 'InfluxDB v1'], + shouldNotContain: ['InfluxDB 3'], + }, + { + name: 'Experienced OSS User (Flux)', + answers: ['Self-hosted', 'Free', '2-5 years', 'Flux'], + shouldContain: ['InfluxDB v2', 'OSS'], + shouldNotContain: ['InfluxDB 3', 'Enterprise'], + }, + { + name: 'Cloud Free User (recent)', + answers: [ + 'Cloud (managed service)', + 'Free', + 'Less than 6 months', + 'SQL', + ], + shouldContain: ['Cloud Serverless'], + shouldNotContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + }, + { + name: 'SQL Cloud User - Only InfluxDB 3 Cloud products', + answers: [ + 'Cloud (managed service)', + 'Paid', + 'Less than 6 months', + 'SQL', + ], + shouldContain: ['Cloud Serverless', 'Cloud Dedicated'], + shouldNotContain: [ + 'InfluxDB OSS', + 'InfluxDB Enterprise v1', + 'InfluxDB Cloud (TSM)', + ], + }, + { + name: 'Uncertain User', + answers: [ + "I'm not sure", + "I'm not sure", + "I'm not sure", + "I'm not sure", + ], + shouldContain: [], // Should still provide some recommendations + shouldNotContain: [], + }, + ]; + + questionnaireScenarios.forEach((scenario) => { + it(`should handle questionnaire scenario: ${scenario.name}`, function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Start questionnaire + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + // Answer questions + scenario.answers.forEach((answer) => { + cy.get('.question.active').should('be.visible'); + cy.get('.option-button').contains(answer).click(); + cy.wait(500); + }); + + // Verify results + cy.get('.result').should('be.visible'); + + // Check expected content + scenario.shouldContain.forEach((product) => { + cy.get('.result').should('contain', product); + }); + + // Check content that should NOT be present + scenario.shouldNotContain.forEach((product) => { + cy.get('.result').should('not.contain', product); + }); + }); + + it('should NOT recommend InfluxDB 3 for 5+ year installations (time-aware)', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + // Test that v3 products are excluded for 5+ years + const answers = ['Free', 'Self-hosted', 'More than 5 years', 'SQL']; + answers.forEach((answer) => { + cy.get('.question.active').should('be.visible'); + cy.get('.option-button').contains(answer).click(); + cy.wait(500); + }); + + cy.get('.result').should('be.visible'); + cy.get('.result').should('not.contain', 'InfluxDB 3 Core'); + cy.get('.result').should('not.contain', 'InfluxDB 3 Enterprise'); + // Should recommend legacy products instead + cy.get('.result').should('contain', 'InfluxDB'); + }); + }); + + it('should apply -100 Flux penalty to InfluxDB 3 products', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + // Even for recent, paid, self-hosted users, Flux should eliminate v3 products + const answers = ['Self-hosted', 'Paid', 'Less than 6 months', 'Flux']; + answers.forEach((answer) => { + cy.get('.question.active').should('be.visible'); + cy.get('.option-button').contains(answer).click(); + cy.wait(500); + }); + + cy.get('.result').should('be.visible'); + cy.get('.result').should('not.contain', 'InfluxDB 3 Core'); + cy.get('.result').should('not.contain', 'InfluxDB 3 Enterprise'); + }); + + it('should detect cloud context correctly with regex patterns', function () { + const cloudPatterns = ['cloud 2', 'cloud v2', 'influxdb cloud 2']; + + // Test first pattern in current session + cy.get('.option-button').contains('Yes, I know the URL').click(); + cy.get('#url-input').clear().type(cloudPatterns[0]); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + }); + + // Navigation and interaction tests + it('should allow going back through questionnaire questions', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Start questionnaire + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + // Answer first question + cy.get('.option-button').first().click(); + cy.wait(500); + + // Check if back button exists and is clickable + cy.get('body').then(($body) => { + if ($body.find('.back-button').length > 0) { + cy.get('.back-button').should('be.visible').click(); + cy.get('.question.active').should('be.visible'); + } + }); + }); + + it('should allow restarting questionnaire from results', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Complete a questionnaire + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + const answers = ['Self-hosted', 'Free', '2-5 years', 'SQL']; + answers.forEach((answer) => { + cy.get('.question.active').should('be.visible'); + cy.get('.back-button').should('be.visible'); + cy.get('.option-button').contains(answer).click(); + cy.wait(500); + }); + + cy.get('.result').should('be.visible'); + + // Check if restart button exists and works + cy.get('body').then(($body) => { + if ($body.find('.restart-button').length > 0) { + cy.get('.restart-button').should('be.visible').click(); + cy.get('.question.active').should('be.visible'); + } + }); + }); + }); + + describe.skip('Basic Error Handling', function () { + beforeEach(() => { + cy.visit('/test-version-detector/'); + // The trigger is an anchor element with .btn class, not a button + cy.contains(modalTriggerSelector, 'Detect my InfluxDB version').click(); + }); + + it('should handle empty URL input gracefully', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').clear(); + cy.get('.submit-button').click(); + + // Should start questionnaire or show guidance + cy.get('.question.active, .result').should('be.visible'); + }); + + it('should handle invalid URL format gracefully', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + cy.get('#url-input').clear().type('not-a-valid-url'); + cy.get('.submit-button').click(); + + // Should handle gracefully + cy.get('.question.active, .result').should('be.visible'); + }); + }); + + describe.skip('SQL Language Filtering', function () { + beforeEach(() => { + cy.visit('/test-version-detector/'); + // The trigger is an anchor element with .btn class, not a button + cy.contains(modalTriggerSelector, 'Detect my InfluxDB version').click(); + }); + + it('should only show InfluxDB 3 products when SQL is selected', function () { + // Click "Yes, I know the URL" first + cy.get('.option-button').contains('Yes, I know the URL').click(); + + // Start questionnaire with unknown URL + cy.get('#url-input').clear().type('https://unknown-server.com:9999'); + cy.get('.submit-button').click(); + cy.get('.question.active').should('be.visible'); + + // Answer questions leading to SQL selection + const answers = ['Self-hosted', 'Free', 'Less than 6 months', 'SQL']; + answers.forEach((answer) => { + cy.get('.question.active').should('be.visible'); + cy.get('.option-button').contains(answer).click(); + cy.wait(500); + }); + + cy.get('.result').should('be.visible'); + + // Get the full result text to verify filtering + cy.get('.result') + .invoke('text') + .then((resultText) => { + // Verify that ONLY InfluxDB 3 products are shown + const shouldNotContain = [ + 'InfluxDB Enterprise v1.x', + 'InfluxDB OSS v2.x', + 'InfluxDB OSS 1.x', + 'InfluxDB Cloud (TSM)', + ]; + + // Check that forbidden products are NOT in results + shouldNotContain.forEach((forbiddenProduct) => { + expect(resultText).to.not.contain(forbiddenProduct); + }); + + // Verify at least one InfluxDB 3 product is shown + const hasValidProduct = + resultText.includes('InfluxDB 3 Core') || + resultText.includes('Cloud Dedicated') || + resultText.includes('Cloud Serverless') || + resultText.includes('InfluxDB Clustered'); + + expect(hasValidProduct).to.be.true; + }); + }); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 66ea16ef0e..96e2b2f3c3 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -22,4 +22,7 @@ // // // -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +// Import custom commands for InfluxDB Version Detector +import './influxdb-version-detector-commands.js'; diff --git a/cypress/support/influxdb-version-detector-commands.js b/cypress/support/influxdb-version-detector-commands.js new file mode 100644 index 0000000000..4cb0ebde84 --- /dev/null +++ b/cypress/support/influxdb-version-detector-commands.js @@ -0,0 +1,299 @@ +// Custom Cypress commands for InfluxDB Version Detector testing + +/** + * Navigate to a page with the version detector component + * @param {string} [path='/influxdb3/core/visualize-data/grafana/'] - Path to a page with the component + */ +Cypress.Commands.add( + 'visitVersionDetector', + (path = '/influxdb3/core/visualize-data/grafana/') => { + cy.visit(path); + cy.get('[data-component="influxdb-version-detector"]', { + timeout: 10000, + }).should('be.visible'); + } +); + +/** + * Test URL detection for a specific URL + * @param {string} url - The URL to test + * @param {string} expectedProduct - Expected product name in the result + */ +Cypress.Commands.add('testUrlDetection', (url, expectedProduct) => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + + cy.get('.result.show', { timeout: 5000 }).should('be.visible'); + cy.get('.detected-version').should('contain', expectedProduct); +}); + +/** + * Complete a questionnaire with given answers + * @param {string[]} answers - Array of answers to select in order + */ +Cypress.Commands.add('completeQuestionnaire', (answers) => { + answers.forEach((answer, index) => { + cy.get('.question.active', { timeout: 3000 }).should('be.visible'); + cy.get('.option-button').contains(answer).should('be.visible').click(); + + // Wait for transition between questions + if (index < answers.length - 1) { + cy.wait(500); + } + }); + + // Wait for final results + cy.get('.result.show', { timeout: 5000 }).should('be.visible'); +}); + +/** + * Start questionnaire with unknown URL + * @param {string} [url='https://unknown-server.com:9999'] - URL to trigger questionnaire + */ +Cypress.Commands.add( + 'startQuestionnaire', + (url = 'https://unknown-server.com:9999') => { + cy.get('#influxdb-url').clear().type(url); + cy.get('.submit-button').click(); + cy.get('.question.active', { timeout: 5000 }).should('be.visible'); + } +); + +/** + * Verify questionnaire results contain/don't contain specific products + * @param {Object} options - Configuration object + * @param {string[]} [options.shouldContain] - Products that should be in results + * @param {string[]} [options.shouldNotContain] - Products that should NOT be in results + */ +Cypress.Commands.add( + 'verifyQuestionnaireResults', + ({ shouldContain = [], shouldNotContain = [] }) => { + cy.get('.result.show', { timeout: 5000 }).should('be.visible'); + + shouldContain.forEach((product) => { + cy.get('.result').should('contain', product); + }); + + shouldNotContain.forEach((product) => { + cy.get('.result').should('not.contain', product); + }); + } +); + +/** + * Test navigation through questionnaire (back/restart functionality) + */ +Cypress.Commands.add('testQuestionnaireNavigation', () => { + // Answer first question + cy.get('.option-button').first().click(); + cy.wait(500); + + // Test back button + cy.get('.back-button').should('be.visible').click(); + cy.get('.question.active').should('be.visible'); + cy.get('.progress .progress-bar').should('have.css', 'width', '0px'); + + // Complete questionnaire to test restart + const quickAnswers = ['Self-hosted', 'Free', '2-5 years', 'SQL']; + cy.completeQuestionnaire(quickAnswers); + + // Test restart button + cy.get('.restart-button', { timeout: 3000 }).should('be.visible').click(); + cy.get('.question.active').should('be.visible'); + cy.get('.progress .progress-bar').should('have.css', 'width', '0px'); +}); + +/** + * Check for JavaScript console errors related to the component + */ +Cypress.Commands.add('checkForConsoleErrors', () => { + cy.window().then((win) => { + const logs = []; + const originalConsoleError = win.console.error; + + win.console.error = (...args) => { + logs.push(args.join(' ')); + originalConsoleError.apply(win.console, args); + }; + + // Wait for any potential errors to surface + cy.wait(1000); + + cy.then(() => { + const relevantErrors = logs.filter( + (log) => + log.includes('influxdb-version-detector') || + log.includes('Failed to parse influxdb_urls data') || + log.includes('SyntaxError') || + log.includes('#ZgotmplZ') || + log.includes('detectContext is not a function') + ); + + if (relevantErrors.length > 0) { + throw new Error( + `Console errors detected: ${relevantErrors.join('; ')}` + ); + } + }); + }); +}); + +/** + * Test URL scenarios from the influxdb_urls.yml data + */ +Cypress.Commands.add('testAllKnownUrls', () => { + const urlTestCases = [ + // OSS URLs + { url: 'http://localhost:8086', product: 'InfluxDB OSS' }, + { url: 'https://my-server.com:8086', product: 'InfluxDB OSS' }, + + // InfluxDB 3 URLs + { url: 'http://localhost:8181', product: 'InfluxDB 3' }, + { url: 'https://my-server.com:8181', product: 'InfluxDB 3' }, + + // Cloud URLs + { + url: 'https://us-west-2-1.aws.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + { + url: 'https://us-east-1-1.aws.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + { + url: 'https://eu-central-1-1.aws.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + { + url: 'https://us-central1-1.gcp.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + { + url: 'https://westeurope-1.azure.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + { + url: 'https://eastus-1.azure.cloud2.influxdata.com', + product: 'InfluxDB Cloud', + }, + + // Cloud Dedicated + { + url: 'https://cluster-id.a.influxdb.io', + product: 'InfluxDB Cloud Dedicated', + }, + { + url: 'https://my-cluster.a.influxdb.io', + product: 'InfluxDB Cloud Dedicated', + }, + + // Clustered + { url: 'https://cluster-host.com', product: 'InfluxDB Clustered' }, + ]; + + urlTestCases.forEach(({ url, product }) => { + cy.visitVersionDetector(); + cy.testUrlDetection(url, product); + }); +}); + +/** + * Test comprehensive questionnaire scenarios + */ +Cypress.Commands.add('testQuestionnaireScenarios', () => { + const scenarios = [ + { + name: 'OSS Free User', + answers: ['Self-hosted', 'Free', '2-5 years', 'SQL'], + shouldContain: ['InfluxDB OSS', 'InfluxDB v2'], + shouldNotContain: ['InfluxDB 3 Enterprise'], + }, + { + name: 'Cloud Flux User', + answers: [ + 'Cloud (managed service)', + 'Paid', + 'Less than 6 months', + 'Flux', + ], + shouldContain: ['InfluxDB v2'], + shouldNotContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + }, + { + name: 'Modern Self-hosted SQL User', + answers: ['Self-hosted', 'Paid', 'Less than 6 months', 'SQL'], + shouldContain: ['InfluxDB 3 Core', 'InfluxDB 3 Enterprise'], + shouldNotContain: [], + }, + { + name: 'High Volume Enterprise User', + answers: ['Self-hosted', 'Paid', 'Less than 6 months', 'SQL', 'Yes'], + shouldContain: ['InfluxDB 3 Enterprise'], + shouldNotContain: [], + }, + { + name: 'Uncertain User', + answers: ["I'm not sure", "I'm not sure", "I'm not sure", "I'm not sure"], + shouldContain: [], // Should still provide some recommendations + shouldNotContain: [], + }, + ]; + + scenarios.forEach((scenario) => { + cy.visitVersionDetector(); + cy.startQuestionnaire(); + cy.completeQuestionnaire(scenario.answers); + cy.verifyQuestionnaireResults({ + shouldContain: scenario.shouldContain, + shouldNotContain: scenario.shouldNotContain, + }); + }); +}); + +/** + * Test accessibility features + */ +Cypress.Commands.add('testAccessibility', () => { + // Test keyboard navigation + cy.get('body').tab(); + cy.focused().should('have.id', 'influxdb-url'); + + cy.focused().type('https://test.com'); + cy.focused().tab(); + cy.focused().should('have.class', 'submit-button'); + + cy.focused().type('{enter}'); + cy.get('.question.active', { timeout: 3000 }).should('be.visible'); + + // Test that buttons are focusable + cy.get('.option-button') + .first() + .should('be.visible') + .focus() + .should('be.focused'); +}); + +/** + * Test theme integration + */ +Cypress.Commands.add('testThemeIntegration', () => { + // Test light theme (default) + cy.get('[data-component="influxdb-version-detector"]') + .should('have.css', 'background-color') + .and('not.equal', 'transparent'); + + cy.get('.detector-title') + .should('have.css', 'color') + .and('not.equal', 'rgb(0, 0, 0)'); + + // Test dark theme if theme switcher exists + cy.get('body').then(($body) => { + if ($body.find('[data-theme-toggle]').length > 0) { + cy.get('[data-theme-toggle]').click(); + + cy.get('[data-component="influxdb-version-detector"]') + .should('have.css', 'background-color') + .and('not.equal', 'rgb(255, 255, 255)'); + } + }); +}); diff --git a/cypress/support/run-e2e-specs.js b/cypress/support/run-e2e-specs.js index 71f1616fa6..1a3d3934e9 100644 --- a/cypress/support/run-e2e-specs.js +++ b/cypress/support/run-e2e-specs.js @@ -2,8 +2,7 @@ * InfluxData Documentation E2E Test Runner * * This script automates running Cypress end-to-end tests for the InfluxData documentation site. - * It handles starting a local Hugo server, mapping content files to their URLs, and running Cypress tests, - * and reporting broken links. + * It handles starting a local Hugo server, mapping content files to their URLs, and running Cypress tests. * * Usage: node run-e2e-specs.js [file paths...] [--spec test specs...] */ @@ -303,7 +302,7 @@ async function main() { try { const screenshotsDir = path.resolve('cypress/screenshots'); const videosDir = path.resolve('cypress/videos'); - const specScreenshotDir = path.join(screenshotsDir, 'article-links.cy.js'); + const specScreenshotDir = path.join(screenshotsDir, 'content'); // Ensure base directories exist ensureDirectoryExists(screenshotsDir); @@ -402,7 +401,7 @@ async function main() { if (testFailureCount > 0) { console.warn( - `ℹ️ Note: ${testFailureCount} test(s) failed but no broken links were detected in the report.` + `ℹ️ Note: ${testFailureCount} test(s) failed.` ); // Provide detailed failure analysis diff --git a/data/products.yml b/data/products.yml index 2add87d674..cb44430527 100644 --- a/data/products.yml +++ b/data/products.yml @@ -8,6 +8,20 @@ influxdb3_core: latest: core latest_patch: 3.5.0 placeholder_host: localhost:8181 + detector_config: + query_languages: + SQL: + required_params: ['Host', 'Database'] + optional_params: [] + InfluxQL: + required_params: ['Host', 'Database'] + optional_params: [] + characteristics: ['Free', 'Self-hosted', 'SQL/InfluxQL', 'No auth required', 'Databases'] + detection: + ping_headers: + x-influxdb-version: '^3\.' + x-influxdb-build: 'Core' + url_contains: ['localhost:8181'] ai_sample_questions: - How do I install and run InfluxDB 3 Core? - How do I write a plugin for the Python Processing engine? @@ -23,6 +37,20 @@ influxdb3_enterprise: latest: enterprise latest_patch: 3.5.0 placeholder_host: localhost:8181 + detector_config: + query_languages: + SQL: + required_params: ['Host', 'Database', 'Token'] + optional_params: [] + InfluxQL: + required_params: ['Host', 'Database', 'Token'] + optional_params: [] + characteristics: ['Paid', 'Self-hosted', 'SQL/InfluxQL', 'Token', 'Databases'] + detection: + ping_headers: + x-influxdb-version: '^3\.' + x-influxdb-build: 'Enterprise' + url_contains: ['localhost:8181'] ai_sample_questions: - How do I install and run InfluxDB 3 Enterprise? - Help me write a plugin for the Python Processing engine? @@ -51,6 +79,20 @@ influxdb3_cloud_serverless: list_order: 2 latest: cloud-serverless placeholder_host: cloud2.influxdata.com + detector_config: + query_languages: + SQL: + required_params: ['Host', 'Bucket', 'Token'] + optional_params: [] + InfluxQL: + required_params: ['Host', 'Bucket', 'Token'] + optional_params: [] + Flux: + required_params: ['Host', 'Organization', 'Token', 'Default bucket'] + optional_params: [] + characteristics: ['Paid/Free', 'Cloud', 'All languages', 'Token', 'Buckets'] + detection: + url_contains: ['us-east-1-1.aws.cloud2.influxdata.com', 'eu-central-1-1.aws.cloud2.influxdata.com'] ai_sample_questions: - How do I migrate from InfluxDB Cloud 2 to InfluxDB Cloud Serverless? - What tools can I use to write data to InfluxDB Cloud Serverless? @@ -66,6 +108,17 @@ influxdb3_cloud_dedicated: link: "https://www.influxdata.com/contact-sales-cloud-dedicated/" latest_cli: 2.10.5 placeholder_host: cluster-id.a.influxdb.io + detector_config: + query_languages: + SQL: + required_params: ['Host', 'Database', 'Token'] + optional_params: [] + InfluxQL: + required_params: ['Host', 'Database', 'Token'] + optional_params: [] + characteristics: ['Paid', 'Cloud', 'SQL/InfluxQL', 'Token', 'Databases'] + detection: + url_contains: ['influxdb.io'] ai_sample_questions: - How do I migrate from InfluxDB v1 to InfluxDB Cloud Dedicated? - What tools can I use to write data to Cloud Dedicated? @@ -81,6 +134,18 @@ influxdb3_clustered: latest: clustered link: "https://www.influxdata.com/contact-sales-influxdb-clustered/" placeholder_host: cluster-host.com + detector_config: + query_languages: + SQL: + required_params: ['Host', 'Database', 'Token'] + optional_params: [] + InfluxQL: + required_params: ['URL', 'Database', 'Token'] + optional_params: [] + characteristics: ['Paid', 'Self-hosted', 'SQL/InfluxQL', 'Token', 'Databases'] + detection: + ping_headers: + x-influxdb-version: 'influxqlbridged-development' ai_sample_questions: - How do I use a Helm chart to configure Clustered? - What tools can I use to write data to Clustered? @@ -103,6 +168,20 @@ influxdb: v1: 1.12.2 latest_cli: v2: 2.7.5 + detector_config: + query_languages: + InfluxQL: + required_params: ['URL', 'Database', 'Auth Type (Basic or Token)'] + optional_params: [] + Flux: + required_params: ['URL', 'Token', 'Default bucket'] + optional_params: [] + characteristics: ['Free', 'Self-hosted', 'InfluxQL/Flux', 'Token or Username/Password', 'Buckets'] + detection: + ping_headers: + x-influxdb-build: 'OSS' + x-influxdb-version: '^(1|2)\.' + url_contains: ['localhost:8086'] ai_sample_questions: - How do I write and query data with InfluxDB v2 OSS? - How can I migrate from InfluxDB v2 OSS to InfluxDB 3 Core? @@ -117,6 +196,17 @@ influxdb_cloud: list_order: 1 latest: cloud placeholder_host: cloud2.influxdata.com + detector_config: + query_languages: + InfluxQL: + required_params: ['URL', 'Database', 'Token'] + optional_params: [] + Flux: + required_params: ['URL', 'Organization', 'Token', 'Default bucket'] + optional_params: [] + characteristics: ['Paid/Free', 'Cloud', 'InfluxQL/Flux', 'Token', 'Databases/Buckets'] + detection: + url_contains: ['us-west-2-1.aws.cloud2.influxdata.com', 'us-west-2-2.aws.cloud2.influxdata.com', 'us-east-1-1.aws.cloud2.influxdata.com', 'eu-central-1-1.aws.cloud2.influxdata.com', 'us-central1-1.gcp.cloud2.influxdata.com', 'westeurope-1.azure.cloud2.influxdata.com', 'eastus-1.azure.cloud2.influxdata.com'] ai_sample_questions: - How do I write and query data with InfluxDB Cloud 2? - How is Cloud 2 different from Cloud Serverless? @@ -186,6 +276,18 @@ enterprise_influxdb: latest: v1.12 latest_patches: v1: 1.12.2 + detector_config: + query_languages: + InfluxQL: + required_params: ['URL', 'Database', 'User', 'Password'] + optional_params: [] + Flux: + required_params: ['URL', 'User', 'Password', 'Default database'] + optional_params: [] + characteristics: ['Paid', 'Self-hosted', 'InfluxQL/Flux', 'Username/Password', 'Databases'] + detection: + ping_headers: + x-influxdb-build: 'Enterprise' ai_sample_questions: - How can I configure my InfluxDB v1 Enterprise server? - How do I replicate data between InfluxDB v1 Enterprise and OSS? diff --git a/eslint.config.js b/eslint.config.js index 18764ab0ef..bd99c171fb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -150,8 +150,19 @@ export default [ }, { files: ['**/*.ts'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + project: './tsconfig.json', + }, + }, rules: { - // Rules specific to TypeScript files + // TypeScript-specific rules + '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'warn', }, }, { @@ -160,6 +171,7 @@ export default [ '**/node_modules/**', '**/public/**', '**/resources/**', + '**/dist/**', '**/.hugo_build.lock', ], }, diff --git a/layouts/partials/footer/modals.html b/layouts/partials/footer/modals.html index 4f6fc5b5b4..9830135dde 100644 --- a/layouts/partials/footer/modals.html +++ b/layouts/partials/footer/modals.html @@ -6,6 +6,7 @@ {{ partial "footer/modals/influxdb-url.html" . }} + {{ partial "footer/modals/influxdb-version-detector.html" . }} {{ partial "footer/modals/page-feedback.html" . }} {{ if or (.Page.HasShortcode "influxdb/custom-timestamps") (.Page.HasShortcode "influxdb/custom-timestamps-span") }} {{ partial "footer/modals/influxdb-gs-date-select.html" . }} diff --git a/layouts/partials/footer/modals/influxdb-version-detector.html b/layouts/partials/footer/modals/influxdb-version-detector.html new file mode 100644 index 0000000000..29f92e12e9 --- /dev/null +++ b/layouts/partials/footer/modals/influxdb-version-detector.html @@ -0,0 +1,28 @@ +{{/* + InfluxDB Version Detector Modal Template + + This modal contains the interactive version detector component that helps users + identify which InfluxDB product they're using through a guided questionnaire. +*/}} + +{{/* Process products data into detector format */}} +{{ $detectorProducts := dict }} +{{ range $key, $product := site.Data.products }} + {{ if $product.detector_config }} + {{/* Include detector_config plus name and placeholder_host for configuration guidance */}} + {{ $productData := $product.detector_config }} + {{ if $product.name }} + {{ $productData = merge $productData (dict "name" $product.name) }} + {{ end }} + {{ if $product.placeholder_host }} + {{ $productData = merge $productData (dict "placeholder_host" $product.placeholder_host) }} + {{ end }} + {{ $detectorProducts = merge $detectorProducts (dict $key $productData) }} + {{ end }} +{{ end }} + + \ No newline at end of file diff --git a/layouts/shortcodes/influxdb-version-detector.html b/layouts/shortcodes/influxdb-version-detector.html new file mode 100644 index 0000000000..2b9c903210 --- /dev/null +++ b/layouts/shortcodes/influxdb-version-detector.html @@ -0,0 +1,61 @@ +{{/* + InfluxDB Version Detector Modal Trigger Shortcode + Usage: {{< influxdb-version-detector >}} + Usage with Ask AI integration: {{< influxdb-version-detector context="grafana" ai_instruction="Help me use [context] with [influxdb_version]" >}} + + This shortcode creates a button that opens a modal with the InfluxDB version detector component. + The component helps users identify which InfluxDB product they're using through a guided questionnaire. + + Parameters: + - context: Optional context for Ask AI integration (e.g., "grafana") + - ai_instruction: Optional instruction template for Ask AI with placeholders [context] and [influxdb_version] +*/}} + +{{ $context := .Get "context" }} +{{ $aiInstruction := .Get "ai_instruction" }} + +

Identify your InfluxDB version

+ +

+If you are unsure which InfluxDB product you are using, use our interactive version detector to help identify it:

+ +

+ {{ if $context }} + + Detect my InfluxDB version + + {{ else }} + + Detect my InfluxDB version + + {{ end }} +

+ +{{ if and $context $aiInstruction }} + +{{ end }} \ No newline at end of file diff --git a/lefthook.yml b/lefthook.yml index 67db3a7717..202152a37e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -82,6 +82,10 @@ pre-commit: run: '.ci/vale/vale.sh --config=content/influxdb/v2/.vale.ini --minAlertLevel=error {staged_files}' + build-typescript: + glob: "assets/js/*.ts" + run: yarn build:ts + stage_fixed: true prettier: tags: [frontend, style] glob: '*.{css,js,ts,jsx,tsx}' @@ -108,7 +112,7 @@ pre-push: - content/example.md run: | echo "Running shortcode examples test due to changes in: {staged_files}" - node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/article-links.cy.js" content/example.md + node cypress/support/run-e2e-specs.js --spec "cypress/e2e/content/index.cy.js" content/example.md exit $? # Manage Docker containers diff --git a/package.json b/package.json index 39c97e11e2..118893cd31 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "scripts": { "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", + "build:ts:watch": "tsc --project tsconfig.json --outDir dist --watch", "lint": "LEFTHOOK_EXCLUDE=test lefthook run pre-commit && lefthook run pre-push", "pre-commit": "lefthook run pre-commit", "test": "echo \"Run 'yarn test:e2e', 'yarn test:links', 'yarn test:codeblocks:all' or a specific test command. e2e and links test commands can take a glob of file paths to test. Some commands run automatically during the git pre-commit and pre-push hooks.\" && exit 0", @@ -55,7 +57,7 @@ "test:codeblocks:v2": "docker compose run --rm --name v2-pytest v2-pytest", "test:codeblocks:stop-monitors": "./test/scripts/monitor-tests.sh stop cloud-dedicated-pytest && ./test/scripts/monitor-tests.sh stop clustered-pytest", "test:e2e": "node cypress/support/run-e2e-specs.js", - "test:shortcode-examples": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/article-links.cy.js\" content/example.md" + "test:shortcode-examples": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/index.cy.js\" content/example.md" }, "type": "module", "browserslist": [ diff --git a/static/downloads/InfluxDB.pqx b/static/downloads/InfluxDB.pqx new file mode 100644 index 0000000000..3e102cc017 Binary files /dev/null and b/static/downloads/InfluxDB.pqx differ diff --git a/static/downloads/arrow-flight-sql-odbc-0.9.7.1195-win64.msi b/static/downloads/arrow-flight-sql-odbc-0.9.7.1195-win64.msi new file mode 100644 index 0000000000..de4670b4b0 Binary files /dev/null and b/static/downloads/arrow-flight-sql-odbc-0.9.7.1195-win64.msi differ diff --git a/static/img/influxdb/OSS-v1-grafana-product-dropdown-flux.png b/static/img/influxdb/OSS-v1-grafana-product-dropdown-flux.png new file mode 100644 index 0000000000..3b20ffbaeb Binary files /dev/null and b/static/img/influxdb/OSS-v1-grafana-product-dropdown-flux.png differ diff --git a/static/img/influxdb/OSS-v1-grafana-product-dropdown-influxql.png b/static/img/influxdb/OSS-v1-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..87b3de6d4f Binary files /dev/null and b/static/img/influxdb/OSS-v1-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb/OSS-v2-grafana-product-dropdown-flux.png b/static/img/influxdb/OSS-v2-grafana-product-dropdown-flux.png new file mode 100644 index 0000000000..5560b95577 Binary files /dev/null and b/static/img/influxdb/OSS-v2-grafana-product-dropdown-flux.png differ diff --git a/static/img/influxdb/OSS-v2-grafana-product-dropdown-influxql.png b/static/img/influxdb/OSS-v2-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..f4495facac Binary files /dev/null and b/static/img/influxdb/OSS-v2-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb/cloud-tools-grafana-flux.png b/static/img/influxdb/cloud-tools-grafana-flux.png deleted file mode 100644 index ac6e63de0a..0000000000 Binary files a/static/img/influxdb/cloud-tools-grafana-flux.png and /dev/null differ diff --git a/static/img/influxdb/cloud-tools-grafana-influxql.png b/static/img/influxdb/cloud-tools-grafana-influxql.png deleted file mode 100644 index f3e8f74225..0000000000 Binary files a/static/img/influxdb/cloud-tools-grafana-influxql.png and /dev/null differ diff --git a/static/img/influxdb/enterprise-v1-grafana-product-dropdown-flux.png b/static/img/influxdb/enterprise-v1-grafana-product-dropdown-flux.png new file mode 100644 index 0000000000..87fa395013 Binary files /dev/null and b/static/img/influxdb/enterprise-v1-grafana-product-dropdown-flux.png differ diff --git a/static/img/influxdb/enterprise-v1-grafana-product-dropdown-influxql.png b/static/img/influxdb/enterprise-v1-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..c48ccafd1e Binary files /dev/null and b/static/img/influxdb/enterprise-v1-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb/influxdb-v2-cloud-flux.png b/static/img/influxdb/influxdb-v2-cloud-flux.png new file mode 100644 index 0000000000..90866f33cf Binary files /dev/null and b/static/img/influxdb/influxdb-v2-cloud-flux.png differ diff --git a/static/img/influxdb/influxdb-v2-cloud-influxql.png b/static/img/influxdb/influxdb-v2-cloud-influxql.png new file mode 100644 index 0000000000..5ec83445f4 Binary files /dev/null and b/static/img/influxdb/influxdb-v2-cloud-influxql.png differ diff --git a/static/img/influxdb/v1-tools-grafana-flux.png b/static/img/influxdb/v1-tools-grafana-flux.png deleted file mode 100644 index 1ad98c315f..0000000000 Binary files a/static/img/influxdb/v1-tools-grafana-flux.png and /dev/null differ diff --git a/static/img/influxdb/v1-tools-grafana-influxql.png b/static/img/influxdb/v1-tools-grafana-influxql.png deleted file mode 100644 index 80f2372f47..0000000000 Binary files a/static/img/influxdb/v1-tools-grafana-influxql.png and /dev/null differ diff --git a/static/img/influxdb/v2-tools-grafana-flux.png b/static/img/influxdb/v2-tools-grafana-flux.png deleted file mode 100644 index 276a5e5164..0000000000 Binary files a/static/img/influxdb/v2-tools-grafana-flux.png and /dev/null differ diff --git a/static/img/influxdb/v2-tools-grafana-influxql.png b/static/img/influxdb/v2-tools-grafana-influxql.png deleted file mode 100644 index d51a3fee0b..0000000000 Binary files a/static/img/influxdb/v2-tools-grafana-influxql.png and /dev/null differ diff --git a/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-influxql.png b/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..4114e4c1fa Binary files /dev/null and b/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-sql.png b/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-sql.png new file mode 100644 index 0000000000..4460ee1271 Binary files /dev/null and b/static/img/influxdb3/cloud-dedicated-grafana-product-dropdown-sql.png differ diff --git a/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-flux.png b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-flux.png new file mode 100644 index 0000000000..ecd8e3776e Binary files /dev/null and b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-flux.png differ diff --git a/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-influxql.png b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..63a0fa7439 Binary files /dev/null and b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-sql.png b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-sql.png new file mode 100644 index 0000000000..9ed3c02e8f Binary files /dev/null and b/static/img/influxdb3/cloud-serverless-grafana-product-dropdown-sql.png differ diff --git a/static/img/influxdb3/cluster-grafana-product-dropdown-influxql.png b/static/img/influxdb3/cluster-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..b7b80cc3f4 Binary files /dev/null and b/static/img/influxdb3/cluster-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb3/cluster-grafana-product-dropdown-sql.png b/static/img/influxdb3/cluster-grafana-product-dropdown-sql.png new file mode 100644 index 0000000000..e87bb3a6d0 Binary files /dev/null and b/static/img/influxdb3/cluster-grafana-product-dropdown-sql.png differ diff --git a/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-influxql.png b/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-influxql.png new file mode 100644 index 0000000000..37067f201f Binary files /dev/null and b/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-influxql.png differ diff --git a/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-sql.png b/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-sql.png new file mode 100644 index 0000000000..5f5a2b60c3 Binary files /dev/null and b/static/img/influxdb3/enterprise-v3-grafana-product-dropdown-sql.png differ diff --git a/static/img/influxdb3/general-grafana-add-datasource-selection.png b/static/img/influxdb3/general-grafana-add-datasource-selection.png new file mode 100644 index 0000000000..43a1c18b7f Binary files /dev/null and b/static/img/influxdb3/general-grafana-add-datasource-selection.png differ diff --git a/static/img/influxdb3/general-grafana-configuration-connection.png b/static/img/influxdb3/general-grafana-configuration-connection.png new file mode 100644 index 0000000000..f66f09f4e9 Binary files /dev/null and b/static/img/influxdb3/general-grafana-configuration-connection.png differ diff --git a/static/img/influxdb3/general-grafana-product-dropdown.png b/static/img/influxdb3/general-grafana-product-dropdown.png new file mode 100644 index 0000000000..8fc1e5ee32 Binary files /dev/null and b/static/img/influxdb3/general-grafana-product-dropdown.png differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..cdc23e314f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ES2020", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./assets/js", + "allowJs": true, + "checkJs": false, + "noEmit": false, + "isolatedModules": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "include": [ + "assets/js/**/*.ts" + ], + "exclude": [ + "node_modules", + "public", + "resources", + "dist" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f291dcef63..cb8034ef88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1126,9 +1126,9 @@ callsites@^3.0.0: integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001737: - version "1.0.30001739" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz#b34ce2d56bfc22f4352b2af0144102d623a124f4" - integrity sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA== + version "1.0.30001745" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz" + integrity sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ== careful-downloader@^3.0.0: version "3.0.0"