fix(version-detector): centralize Grafana links and DRY up host examples (#6693)
* fix(version-detector): use centralized getGrafanaLink for all Grafana URLs Refactor handleAuthorizationHelp to use getGrafanaLink() instead of hardcoded URLs, ensuring all Grafana links come from a single source. Also fix incorrect URLs in getGrafanaLink mapping: - InfluxDB OSS 2.x: /visualize-data/ → /tools/ - InfluxDB Enterprise: /influxdb/enterprise/ → /enterprise_influxdb/v1/ - InfluxDB Cloud (TSM): /visualize-data/ → /tools/ - InfluxDB Cloud v1: now links to Enterprise v1 docs (Cloud v1 is Enterprise under the hood) * refactor(version-detector): DRY up localhost:8086 references Extract HOST_EXAMPLES to a class-level constant and add DEFAULT_HOST and DEFAULT_HOST_PORT constants to eliminate duplicate localhost:8086 strings throughout the code. - Move hostExamples from local variable to class constant - Use DEFAULT_HOST for URL placeholder and comparison checks - Use DEFAULT_HOST_PORT for docker curl command examples * feat(ask-ai): Support source group IDs in Ask AI trigger links * feat(version-detector): Present context-aware links - Add ai_source_group_ids fields to ProductConfig interface - Improve SCSS for doc and Ask AI links - Update Grafana docs to add aliases and context param for detector - Update modal partial to include AI source group IDs in config - Remove custom Cypress commands for version detector - Update E2E tests to use direct Cypress commandspull/6758/head^2 link-checker-v1.3.1
parent
fc8c9bbe29
commit
68f00e6805
|
|
@ -1,10 +1,10 @@
|
|||
extends: substitution
|
||||
message: Use '%s' instead of '%s'
|
||||
level: warning
|
||||
ignorecase: false
|
||||
ignorecase: false
|
||||
# swap maps tokens in form of bad: good
|
||||
# NOTE: The left-hand (bad) side can match the right-hand (good) side;
|
||||
# Vale ignores alerts that match the intended form.
|
||||
# NOTE: The left-hand (bad) side can match the right-hand (good) side;
|
||||
# Vale ignores alerts that match the intended form.
|
||||
swap:
|
||||
'the compactor': the Compactor
|
||||
'dedupe': deduplicate
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ ExecutionPlan
|
|||
Flight SQL
|
||||
FlightQuery
|
||||
GBs?
|
||||
Grafana|\{\{.*grafana.*\}\}
|
||||
(?i)Grafana|\{\{.*grafana.*\}\}
|
||||
HostURL
|
||||
[Hh]ardcod(e|ed|es|ing)
|
||||
InfluxDB Cloud
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ function handleAskAILinks() {
|
|||
if (!link) return;
|
||||
|
||||
const query = link.getAttribute('data-query');
|
||||
const sourceGroupIds = link.getAttribute('data-source-group-ids');
|
||||
|
||||
// Initialize Kapa if not already done
|
||||
if (!state.kapaInitialized) {
|
||||
|
|
@ -88,20 +89,30 @@ function handleAskAILinks() {
|
|||
// Give Kapa a moment to initialize
|
||||
setTimeout(() => {
|
||||
if (window.Kapa?.open) {
|
||||
window.Kapa.open({
|
||||
const openOptions = {
|
||||
mode: 'ai',
|
||||
query: query,
|
||||
});
|
||||
};
|
||||
// Add source group IDs if provided
|
||||
if (sourceGroupIds) {
|
||||
openOptions.sourceGroupIdsInclude = sourceGroupIds;
|
||||
}
|
||||
window.Kapa.open(openOptions);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
// Kapa is already initialized - open with query if provided
|
||||
if (query && window.Kapa?.open) {
|
||||
window.Kapa.open({
|
||||
const openOptions = {
|
||||
mode: 'ai',
|
||||
query: query,
|
||||
});
|
||||
};
|
||||
// Add source group IDs if provided
|
||||
if (sourceGroupIds) {
|
||||
openOptions.sourceGroupIdsInclude = sourceGroupIds;
|
||||
}
|
||||
window.Kapa.open(openOptions);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -122,6 +122,9 @@ interface ProductConfig {
|
|||
url_contains?: string[];
|
||||
ping_headers?: Record<string, string>;
|
||||
};
|
||||
ai_source_group_ids?: string;
|
||||
ai_source_group_ids__v1?: string;
|
||||
[key: string]: unknown; // Allow additional properties
|
||||
}
|
||||
|
||||
interface Products {
|
||||
|
|
@ -161,9 +164,9 @@ interface AnalyticsEventData {
|
|||
declare global {
|
||||
interface Window {
|
||||
gtag?: (
|
||||
_event: string,
|
||||
_action: string,
|
||||
_parameters?: Record<string, unknown>
|
||||
_command: 'event' | 'config' | 'set',
|
||||
_targetId: string,
|
||||
_config?: Record<string, unknown>
|
||||
) => void;
|
||||
}
|
||||
}
|
||||
|
|
@ -181,6 +184,25 @@ class InfluxDBVersionDetector {
|
|||
private resultDiv: HTMLElement | null = null;
|
||||
private restartBtn: HTMLElement | null = null;
|
||||
private currentContext: 'questionnaire' | 'result' = 'questionnaire';
|
||||
private pageContext: string | null = null; // Context from page (e.g., "grafana")
|
||||
|
||||
/** Example host URLs for each product type */
|
||||
private static readonly HOST_EXAMPLES: Record<string, string> = {
|
||||
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',
|
||||
};
|
||||
|
||||
/** Default host URL (InfluxDB v2 localhost) */
|
||||
private static readonly DEFAULT_HOST =
|
||||
InfluxDBVersionDetector.HOST_EXAMPLES.influxdb_v2;
|
||||
|
||||
/** Default host:port without protocol (for curl examples) */
|
||||
private static readonly DEFAULT_HOST_PORT = 'localhost:8086';
|
||||
|
||||
constructor(options: ComponentOptions) {
|
||||
this.container = options.component;
|
||||
|
|
@ -191,6 +213,9 @@ class InfluxDBVersionDetector {
|
|||
this.products = products;
|
||||
this.influxdbUrls = influxdbUrls;
|
||||
|
||||
// Check for context from modal trigger button
|
||||
this.parsePageContext();
|
||||
|
||||
// Check if component is in a modal
|
||||
const modal = this.container.closest('.modal-content');
|
||||
if (modal) {
|
||||
|
|
@ -202,6 +227,19 @@ class InfluxDBVersionDetector {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse page context from modal trigger button
|
||||
*/
|
||||
private parsePageContext(): void {
|
||||
// Look for the modal trigger button with data-context attribute
|
||||
const trigger = document.querySelector(
|
||||
'.influxdb-detector-trigger[data-context]'
|
||||
);
|
||||
if (trigger) {
|
||||
this.pageContext = trigger.getAttribute('data-context');
|
||||
}
|
||||
}
|
||||
|
||||
private parseComponentData(): {
|
||||
products: Products;
|
||||
influxdbUrls: Record<string, unknown>;
|
||||
|
|
@ -627,17 +665,10 @@ class InfluxDBVersionDetector {
|
|||
}
|
||||
|
||||
// Fallback based on product type
|
||||
const hostExamples: Record<string, string> = {
|
||||
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';
|
||||
return (
|
||||
InfluxDBVersionDetector.HOST_EXAMPLES[productDataKey] ||
|
||||
InfluxDBVersionDetector.DEFAULT_HOST
|
||||
);
|
||||
}
|
||||
|
||||
private usesDatabaseTerminology(productConfig: ProductConfig): boolean {
|
||||
|
|
@ -888,7 +919,7 @@ class InfluxDBVersionDetector {
|
|||
</div>
|
||||
<div class="input-group">
|
||||
<input type="url" id="url-input"
|
||||
placeholder="for example, https://us-east-1-1.aws.cloud2.influxdata.com or http://localhost:8086">
|
||||
placeholder="for example, https://us-east-1-1.aws.cloud2.influxdata.com or ${InfluxDBVersionDetector.DEFAULT_HOST}">
|
||||
</div>
|
||||
<button class="back-button" data-action="go-back">Back</button>
|
||||
<button class="submit-button"
|
||||
|
|
@ -930,7 +961,7 @@ class InfluxDBVersionDetector {
|
|||
docker exec <container> influxd version
|
||||
|
||||
# Get ping headers:
|
||||
docker exec <container> curl -I localhost:8086/ping
|
||||
docker exec <container> curl -I ${InfluxDBVersionDetector.DEFAULT_HOST_PORT}/ping
|
||||
|
||||
# Or check startup logs:
|
||||
docker logs <container> 2>&1 | head -20</div>
|
||||
|
|
@ -1207,7 +1238,7 @@ docker logs <container> 2>&1 | head -20</div>
|
|||
const currentProduct = this.getCurrentProduct();
|
||||
const storedUrl = storedUrls[currentProduct] || storedUrls.custom;
|
||||
|
||||
if (storedUrl && storedUrl !== 'http://localhost:8086') {
|
||||
if (storedUrl && storedUrl !== InfluxDBVersionDetector.DEFAULT_HOST) {
|
||||
urlInput.value = storedUrl;
|
||||
// Add indicator that URL was pre-filled (only if one doesn't already exist)
|
||||
const existingIndicator = urlInput.parentElement?.querySelector(
|
||||
|
|
@ -1491,29 +1522,75 @@ docker logs <container> 2>&1 | head -20</div>
|
|||
licenseGuidance.style.borderRadius = '4px';
|
||||
|
||||
if (answer === 'free') {
|
||||
const freeProducts = [
|
||||
'InfluxDB 3 Core',
|
||||
'InfluxDB OSS 2.x',
|
||||
'InfluxDB OSS 1.x',
|
||||
];
|
||||
let freeLinks = '';
|
||||
if (this.pageContext === 'grafana') {
|
||||
freeLinks = freeProducts
|
||||
.map((product) => {
|
||||
const link = this.getGrafanaLink(product);
|
||||
return link
|
||||
? `<li><a href="${link}" target="_blank" class="doc-link">Configure Grafana for ${product}</a></li>`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n ');
|
||||
} else {
|
||||
freeLinks = freeProducts
|
||||
.map((product) => {
|
||||
const link = this.getDocumentationUrl(product);
|
||||
return link
|
||||
? `<li><a href="${link}" target="_blank" class="doc-link">View ${product} documentation</a></li>`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n ');
|
||||
}
|
||||
|
||||
licenseGuidance.innerHTML = `
|
||||
<strong>Free/Open Source License:</strong>
|
||||
<p>This suggests you're using InfluxDB 3 Core or InfluxDB OSS.</p>
|
||||
<ul>
|
||||
<li><a href="/influxdb3/core/visualize-data/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB 3 Core</a></li>
|
||||
<li><a href="/influxdb/v2/visualize-data/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB OSS v2</a></li>
|
||||
<li><a href="/influxdb/v1/tools/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB OSS v1</a></li>
|
||||
${freeLinks}
|
||||
</ul>
|
||||
`;
|
||||
} else if (answer === 'paid') {
|
||||
const paidProducts = [
|
||||
'InfluxDB 3 Enterprise',
|
||||
'InfluxDB Cloud Dedicated',
|
||||
'InfluxDB Cloud Serverless',
|
||||
];
|
||||
let paidLinks = '';
|
||||
if (this.pageContext === 'grafana') {
|
||||
paidLinks = paidProducts
|
||||
.map((product) => {
|
||||
const link = this.getGrafanaLink(product);
|
||||
return link
|
||||
? `<li><a href="${link}" target="_blank" class="doc-link">Configure Grafana for ${product}</a></li>`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n ');
|
||||
} else {
|
||||
paidLinks = paidProducts
|
||||
.map((product) => {
|
||||
const link = this.getDocumentationUrl(product);
|
||||
return link
|
||||
? `<li><a href="${link}" target="_blank" class="doc-link">View ${product} documentation</a></li>`
|
||||
: '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n ');
|
||||
}
|
||||
|
||||
licenseGuidance.innerHTML = `
|
||||
<strong>Paid/Commercial License:</strong>
|
||||
<p>This suggests you're using InfluxDB 3 Enterprise or a paid cloud service.</p>
|
||||
<ul>
|
||||
<li><a href="/influxdb3/enterprise/visualize-data/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB 3 Enterprise</a></li>
|
||||
<li><a href="/influxdb3/cloud-dedicated/visualize-data/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB Cloud Dedicated</a></li>
|
||||
<li><a href="/influxdb3/cloud-serverless/visualize-data/grafana/"
|
||||
target="_blank" class="grafana-link">Configure Grafana for InfluxDB Cloud Serverless</a></li>
|
||||
${paidLinks}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
|
@ -1571,24 +1648,94 @@ docker logs <container> 2>&1 | head -20</div>
|
|||
|
||||
/**
|
||||
* Gets the Grafana documentation link for a given product
|
||||
* Builds on the documentation URL by appending the visualize-data/grafana path
|
||||
*/
|
||||
private getGrafanaLink(productName: string): string | null {
|
||||
const GRAFANA_LINKS: Record<string, string> = {
|
||||
'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/',
|
||||
const docUrl = this.getDocumentationUrl(productName);
|
||||
if (!docUrl) return null;
|
||||
|
||||
return `${docUrl}visualize-data/grafana/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the documentation URL for a given product
|
||||
*/
|
||||
private getDocumentationUrl(productName: string): string | null {
|
||||
const DOC_LINKS: Record<string, string> = {
|
||||
'InfluxDB 3 Core': '/influxdb3/core/',
|
||||
'InfluxDB 3 Enterprise': '/influxdb3/enterprise/',
|
||||
'InfluxDB Cloud Dedicated': '/influxdb3/cloud-dedicated/',
|
||||
'InfluxDB Cloud Serverless': '/influxdb3/cloud-serverless/',
|
||||
'InfluxDB OSS 1.x': '/influxdb/v1/',
|
||||
'InfluxDB OSS 2.x': '/influxdb/v2/',
|
||||
'InfluxDB Enterprise': '/enterprise_influxdb/v1/',
|
||||
'InfluxDB Clustered': '/influxdb3/clustered/',
|
||||
'InfluxDB Cloud (TSM)': '/influxdb/cloud/',
|
||||
'InfluxDB Cloud v1': '/enterprise_influxdb/v1/',
|
||||
};
|
||||
|
||||
return GRAFANA_LINKS[productName] || null;
|
||||
return DOC_LINKS[productName] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Ask AI context/product identifier for a given product
|
||||
*/
|
||||
private getAskAIContext(productName: string): string | null {
|
||||
const AI_CONTEXTS: Record<string, string> = {
|
||||
'InfluxDB 3 Core': 'InfluxDB 3 Core',
|
||||
'InfluxDB 3 Enterprise': 'InfluxDB 3 Enterprise',
|
||||
'InfluxDB Cloud Dedicated': 'InfluxDB Cloud Dedicated',
|
||||
'InfluxDB Cloud Serverless': 'InfluxDB Cloud Serverless',
|
||||
'InfluxDB OSS 1.x': 'InfluxDB OSS v1',
|
||||
'InfluxDB OSS 2.x': 'InfluxDB OSS v2',
|
||||
'InfluxDB Enterprise': 'InfluxDB Enterprise v1',
|
||||
'InfluxDB Clustered': 'InfluxDB Clustered',
|
||||
'InfluxDB Cloud (TSM)': 'InfluxDB Cloud (TSM)',
|
||||
'InfluxDB Cloud v1': 'InfluxDB Cloud v1',
|
||||
};
|
||||
|
||||
return AI_CONTEXTS[productName] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AI source group IDs for a given product
|
||||
* Maps display names to product keys to look up source group IDs
|
||||
*/
|
||||
private getAISourceGroupIds(productName: string): string | null {
|
||||
// Map display names to product keys in products.yml
|
||||
const PRODUCT_KEY_MAP: Record<string, string> = {
|
||||
'InfluxDB 3 Core': 'influxdb3_core',
|
||||
'InfluxDB 3 Enterprise': 'influxdb3_enterprise',
|
||||
'InfluxDB Cloud Dedicated': 'influxdb3_cloud_dedicated',
|
||||
'InfluxDB Cloud Serverless': 'influxdb3_cloud_serverless',
|
||||
'InfluxDB OSS 1.x': 'influxdb',
|
||||
'InfluxDB OSS 2.x': 'influxdb',
|
||||
'InfluxDB Enterprise': 'enterprise_influxdb',
|
||||
'InfluxDB Clustered': 'influxdb3_clustered',
|
||||
'InfluxDB Cloud (TSM)': 'influxdb_cloud',
|
||||
'InfluxDB Cloud v1': 'enterprise_influxdb',
|
||||
};
|
||||
|
||||
const productKey = PRODUCT_KEY_MAP[productName];
|
||||
if (!productKey || !this.products[productKey]) return null;
|
||||
|
||||
// Handle version-specific source group IDs first
|
||||
// InfluxDB OSS has different source groups for v1 and v2
|
||||
if (productName === 'InfluxDB OSS 1.x') {
|
||||
const v1SourceGroupIds =
|
||||
this.products[productKey].ai_source_group_ids__v1;
|
||||
if (typeof v1SourceGroupIds === 'string') {
|
||||
return v1SourceGroupIds;
|
||||
}
|
||||
}
|
||||
|
||||
// Get general source group IDs from products data
|
||||
const sourceGroupIds = this.products[productKey].ai_source_group_ids;
|
||||
if (typeof sourceGroupIds === 'string') {
|
||||
return sourceGroupIds;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1601,7 +1748,6 @@ docker logs <container> 2>&1 | head -20</div>
|
|||
showRanking?: boolean
|
||||
): string {
|
||||
const displayName = this.getProductDisplayName(productName) || productName;
|
||||
const grafanaLink = this.getGrafanaLink(displayName);
|
||||
const resultClass = isTopResult
|
||||
? 'product-ranking top-result'
|
||||
: 'product-ranking';
|
||||
|
|
@ -1634,15 +1780,46 @@ docker logs <container> 2>&1 | head -20</div>
|
|||
html += `<div class="product-details">${details.join(' • ')}</div>`;
|
||||
}
|
||||
|
||||
// Add Grafana link if available
|
||||
if (grafanaLink) {
|
||||
html += `
|
||||
<div class="product-details" style="margin-top: 0.5rem;">
|
||||
<a href="${grafanaLink}" target="_blank" class="grafana-link">
|
||||
Configure Grafana for ${displayName}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
// Add context-aware links
|
||||
if (this.pageContext === 'grafana') {
|
||||
// Show Grafana-specific link
|
||||
const grafanaLink = this.getGrafanaLink(displayName);
|
||||
if (grafanaLink) {
|
||||
html += `
|
||||
<div class="product-details" style="margin-top: 0.5rem;">
|
||||
<a href="${grafanaLink}" target="_blank" class="doc-link">
|
||||
Configure Grafana for ${displayName} →
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
// Show general documentation and Ask AI links
|
||||
const docLink = this.getDocumentationUrl(displayName);
|
||||
const aiContext = this.getAskAIContext(displayName);
|
||||
|
||||
if (docLink || aiContext) {
|
||||
html += '<div class="product-details" style="margin-top: 0.5rem;">';
|
||||
|
||||
if (docLink) {
|
||||
html += `
|
||||
<a href="${docLink}" target="_blank" class="doc-link" style="margin-right: 1rem;">
|
||||
View ${displayName} documentation →
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
if (aiContext) {
|
||||
const sourceGroupIds = this.getAISourceGroupIds(displayName);
|
||||
html += `
|
||||
<a href="#" class="ask-ai-open" data-query="Help me with ${aiContext}"${sourceGroupIds ? ` data-source-group-ids="${sourceGroupIds}"` : ''}>
|
||||
Ask AI about ${displayName} →
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
|
@ -2213,10 +2390,10 @@ docker exec <container> curl -I localhost:8181/ping
|
|||
</details>
|
||||
|
||||
<div class="expected-results">
|
||||
<div class="results-title">Expected results:</div>
|
||||
• <strong>X-Influxdb-Build: Enterprise</strong> → InfluxDB 3 Enterprise (definitive)<br>
|
||||
• <strong>X-Influxdb-Build: Core</strong> → InfluxDB 3 Core (definitive)<br>
|
||||
• <strong>401 Unauthorized</strong> → Use the license information below
|
||||
<div class="results-title">Expected results from command:</div>
|
||||
• Response header <strong>X-Influxdb-Build: Enterprise</strong> → InfluxDB 3 Enterprise (definitive)<br>
|
||||
• Response header <strong>X-Influxdb-Build: Core</strong> → InfluxDB 3 Core (definitive)<br>
|
||||
• Status code <strong>401 Unauthorized</strong> → Use the license information below
|
||||
</div>
|
||||
|
||||
<div class="authorization-help">
|
||||
|
|
@ -2261,9 +2438,12 @@ curl -I ${url}/ping
|
|||
</div>
|
||||
|
||||
<div class="expected-results">
|
||||
<div class="results-title">Expected version patterns:</div>
|
||||
• <strong>v1.x.x</strong> → ${this.getProductDisplayName('oss-v1')}<br>
|
||||
• <strong>v2.x.x</strong> → ${this.getProductDisplayName('oss-v2')}<br>
|
||||
<div class="results-title">Look for version pattern:</div>
|
||||
• <strong>v1.x.x</strong> (for example, v1.8.10) → ${this.getProductDisplayName('oss-v1')}<br>
|
||||
• <strong>v2.x.x</strong> (for example, v2.7.4) → ${this.getProductDisplayName('oss-v2')}<br>
|
||||
<p style="font-size: 0.9em; margin-top: 0.5rem; opacity: 0.8;">
|
||||
From <code>influxd version</code> command output or <code>X-Influxdb-Version</code> response header
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<details style="margin: 1rem 0;">
|
||||
|
|
@ -2275,7 +2455,7 @@ curl -I ${url}/ping
|
|||
docker exec <container> influxd version
|
||||
|
||||
# Get ping headers:
|
||||
docker exec <container> curl -I localhost:8086/ping
|
||||
docker exec <container> curl -I ${InfluxDBVersionDetector.DEFAULT_HOST_PORT}/ping
|
||||
|
||||
# Or check startup logs:
|
||||
docker logs <container> 2>&1 | head -20
|
||||
|
|
|
|||
|
|
@ -506,16 +506,22 @@
|
|||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
// Grafana links styling
|
||||
.grafana-link {
|
||||
// Documentation and Ask AI links styling
|
||||
.doc-link,
|
||||
.ask-ai-open {
|
||||
color: $article-link;
|
||||
text-decoration: underline;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
color: $article-link-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.ask-ai-open {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
// Manual command output
|
||||
.manual-output {
|
||||
margin: 1rem 0;
|
||||
|
|
@ -644,4 +650,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ menu:
|
|||
related:
|
||||
- /flux/v0/get-started/, Get started with Flux
|
||||
- https://grafana.com/docs/, Grafana documentation
|
||||
aliases:
|
||||
- /enterprise_influxdb/v1/visualize-data/grafana/
|
||||
alt_links:
|
||||
core: /influxdb3/core/visualize-data/grafana/
|
||||
enterprise: /influxdb3/enterprise/visualize-data/grafana/
|
||||
|
|
@ -23,7 +25,7 @@ Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/produ
|
|||
to visualize data from your **InfluxDB Enterprise** cluster.
|
||||
|
||||
> [!Note]
|
||||
> {{< influxdb-version-detector >}}
|
||||
> {{< influxdb-version-detector context="grafana" >}}
|
||||
|
||||
> [!Note]
|
||||
> #### Required
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ menu:
|
|||
parent: Tools
|
||||
related:
|
||||
- /flux/v0/get-started/, Get started with Flux
|
||||
aliases:
|
||||
- /influxdb/v1/visualize-data/grafana/
|
||||
alt_links:
|
||||
v2: /influxdb/v2/tools/grafana/
|
||||
core: /influxdb3/core/visualize-data/grafana/
|
||||
|
|
@ -24,7 +26,7 @@ Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/produ
|
|||
to visualize data from your {{% product-name %}} instance.
|
||||
|
||||
> [!Note]
|
||||
> {{< influxdb-version-detector >}}
|
||||
> {{< influxdb-version-detector context="grafana" >}}
|
||||
|
||||
> [!Note]
|
||||
> #### Grafana 12.2+
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/produ
|
|||
to visualize data from your **InfluxDB {{< current-version >}}** instance.
|
||||
|
||||
> [!Note]
|
||||
> {{< influxdb-version-detector >}}
|
||||
> {{< influxdb-version-detector context="grafana" >}}
|
||||
|
||||
> [!Note]
|
||||
> #### Grafana 12.2+
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Use [Grafana](https://grafana.com/) or [Grafana Cloud](https://grafana.com/produ
|
|||
to query and visualize data from {{% product-name %}}.
|
||||
|
||||
> [!Note]
|
||||
> {{< influxdb-version-detector >}}
|
||||
> {{< influxdb-version-detector context="grafana" >}}
|
||||
|
||||
> [Grafana] enables you to query, visualize, alert on, and explore your metrics,
|
||||
> logs, and traces wherever they are stored.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,5 +24,5 @@
|
|||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
// Import custom commands for InfluxDB Version Detector
|
||||
import './influxdb-version-detector-commands.js';
|
||||
// Custom commands for InfluxDB Version Detector have been removed
|
||||
// Tests now use direct Cypress commands for better clarity and maintainability
|
||||
|
|
|
|||
|
|
@ -1,299 +0,0 @@
|
|||
// 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)');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -84,7 +84,15 @@ export default [
|
|||
'import/no-unresolved': 'off', // Hugo handles module resolution differently
|
||||
|
||||
// Code formatting
|
||||
'max-len': ['warn', { code: 80, ignoreUrls: true, ignoreStrings: true }],
|
||||
'max-len': [
|
||||
'warn',
|
||||
{
|
||||
code: 80,
|
||||
ignoreUrls: true,
|
||||
ignoreStrings: true,
|
||||
ignoreComments: true,
|
||||
},
|
||||
],
|
||||
quotes: ['error', 'single', { avoidEscape: true }],
|
||||
|
||||
// Hugo template string linting (custom rule)
|
||||
|
|
@ -162,7 +170,14 @@ export default [
|
|||
},
|
||||
rules: {
|
||||
// TypeScript-specific rules
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'no-unused-vars': 'off', // Disable base rule for TS files
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{{ $detectorProducts := dict }}
|
||||
{{ range $key, $product := site.Data.products }}
|
||||
{{ if $product.detector_config }}
|
||||
{{/* Include detector_config plus name and placeholder_host for configuration guidance */}}
|
||||
{{/* Include detector_config plus name, placeholder_host, and AI source group IDs */}}
|
||||
{{ $productData := $product.detector_config }}
|
||||
{{ if $product.name }}
|
||||
{{ $productData = merge $productData (dict "name" $product.name) }}
|
||||
|
|
@ -17,6 +17,12 @@
|
|||
{{ if $product.placeholder_host }}
|
||||
{{ $productData = merge $productData (dict "placeholder_host" $product.placeholder_host) }}
|
||||
{{ end }}
|
||||
{{ if $product.ai_source_group_ids }}
|
||||
{{ $productData = merge $productData (dict "ai_source_group_ids" $product.ai_source_group_ids) }}
|
||||
{{ end }}
|
||||
{{ if $product.ai_source_group_ids__v1 }}
|
||||
{{ $productData = merge $productData (dict "ai_source_group_ids__v1" $product.ai_source_group_ids__v1) }}
|
||||
{{ end }}
|
||||
{{ $detectorProducts = merge $detectorProducts (dict $key $productData) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
@ -25,4 +31,4 @@
|
|||
<div data-component="influxdb-version-detector"
|
||||
data-products='{{ $detectorProducts | jsonify }}'
|
||||
data-influxdb-urls='{{ site.Data.influxdb_urls | jsonify }}'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue