[InfluxDB]: Update product selection and UI (#112074)
This commit is contained in:
@@ -15,7 +15,7 @@ export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }: Prop
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<Stack justifyContent="space-between">
|
||||
<div className={styles.hideOnSmallScreen}>
|
||||
<div className={`${styles.hideOnSmallScreen} ${styles.leftSticky}`}>
|
||||
<Box width="100%" flex="1 1 auto">
|
||||
<LeftSideBar pdcInjected={options?.jsonData?.pdcInjected!!} />
|
||||
</Box>
|
||||
@@ -38,7 +38,7 @@ export const ConfigEditor: React.FC<Props> = ({ onOptionsChange, options }: Prop
|
||||
to help us make it even better.
|
||||
</>
|
||||
</Alert>
|
||||
<Text color="secondary" element="p" italic>
|
||||
<Text variant="bodySmall" color="secondary">
|
||||
Fields marked with * are required
|
||||
</Text>
|
||||
<UrlAndAuthenticationSection options={options} onOptionsChange={onOptionsChange} />
|
||||
@@ -61,6 +61,13 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
leftSticky: css({
|
||||
position: 'sticky',
|
||||
top: '100px',
|
||||
alignSelf: 'flex-start',
|
||||
maxHeight: 'calc(100vh - 100px)',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
alertHeight: css({
|
||||
height: '100px',
|
||||
}),
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ export const DatabaseConnectionSection = ({ options, onOptionsChange }: Props) =
|
||||
minWidth={CONTAINER_MIN_WIDTH}
|
||||
>
|
||||
<CollapsableSection
|
||||
label={<Text element="h3">2. {CONFIG_SECTION_HEADERS[1].label}</Text>}
|
||||
label={<Text element="h3">{CONFIG_SECTION_HEADERS[1].label}</Text>}
|
||||
isOpen={CONFIG_SECTION_HEADERS[1].isOpen}
|
||||
>
|
||||
{!options.jsonData.version && (
|
||||
|
||||
+6
-3
@@ -28,14 +28,17 @@ describe('InfluxInfluxQLDBConnection', () => {
|
||||
|
||||
it('renders dbName, user and password fields', () => {
|
||||
render(<InfluxInfluxQLDBConnection {...defaultProps} />);
|
||||
expect(screen.getByLabelText(/Database/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/User/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/Password/i)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByLabelText(/^Database\b/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/^User\b/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/^Password\b/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onOptionsChange on input changes', () => {
|
||||
render(<InfluxInfluxQLDBConnection {...defaultProps} />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/User/i), { target: { value: 'newuser' } });
|
||||
|
||||
expect(onOptionsChangeMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
+56
-19
@@ -1,4 +1,6 @@
|
||||
import { Box, InlineField, LinkButton, Space, Stack, Text } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { Box, Icon, LinkButton, Space, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { CONFIG_SECTION_HEADERS, CONFIG_SECTION_HEADERS_WITH_PDC } from './constants';
|
||||
|
||||
@@ -8,29 +10,40 @@ interface LeftSideBarProps {
|
||||
|
||||
export const LeftSideBar = ({ pdcInjected }: LeftSideBarProps) => {
|
||||
const headers = pdcInjected ? CONFIG_SECTION_HEADERS_WITH_PDC : CONFIG_SECTION_HEADERS;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Box flex={1} marginY={10}>
|
||||
<Box height="75px"></Box>
|
||||
<Text element="h4">InfluxDB</Text>
|
||||
<Box flex={1} marginY={1}>
|
||||
<Text element="h4">Connect data source</Text>
|
||||
<Box paddingTop={2}>
|
||||
{headers.map((header, index) => (
|
||||
<div key={index} data-testid={`${header.label}-sidebar`}>
|
||||
<InlineField label={`${index + 1}`} style={{ display: 'flex', alignItems: 'center' }} grow>
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const target = document.getElementById(header.id);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{header.label}
|
||||
</LinkButton>
|
||||
</InlineField>
|
||||
<Icon name="circle" size="xs" />
|
||||
<LinkButton
|
||||
style={header.isOptional ? { padding: '5px 15px', height: '50px', width: '225px' } : {}}
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
const target = document.getElementById(header.id);
|
||||
if (target) {
|
||||
const y = target.getBoundingClientRect().top + window.scrollY - 60;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.sidebarText}>
|
||||
<div className={styles.sidebarLabel}>{header.label}</div>
|
||||
{header.isOptional && (
|
||||
<div className={styles.sidebarOptional}>
|
||||
<Text color="secondary" variant="bodySmall">
|
||||
optional
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</LinkButton>
|
||||
<Space v={1} />
|
||||
</div>
|
||||
))}
|
||||
@@ -39,3 +52,27 @@ export const LeftSideBar = ({ pdcInjected }: LeftSideBarProps) => {
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
inlineField: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}),
|
||||
sidebarText: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}),
|
||||
sidebarLabel: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 0,
|
||||
lineHeight: 1,
|
||||
}),
|
||||
sidebarOptional: css({
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
lineHeight: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
+39
-23
@@ -1,4 +1,16 @@
|
||||
const backendSrv = {
|
||||
fetch: jest.fn(),
|
||||
} as unknown as BackendSrv;
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getBackendSrv: () => backendSrv,
|
||||
}));
|
||||
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { BackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { InfluxVersion } from '../../../types';
|
||||
|
||||
@@ -11,6 +23,7 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
|
||||
const defaultProps = createTestProps({
|
||||
options: {
|
||||
id: 1234,
|
||||
jsonData: {
|
||||
url: 'http://localhost:8086',
|
||||
product: '',
|
||||
@@ -24,6 +37,29 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const mockFetchPing = ({ build, version, status = 204 }: { build?: string; version?: string; status?: number }) => {
|
||||
backendSrv.fetch = jest.fn().mockReturnValue(
|
||||
of({
|
||||
status,
|
||||
ok: status >= 200 && status < 300,
|
||||
data: status === 204 ? '' : {},
|
||||
headers: {
|
||||
get: (k: string) => {
|
||||
const key = k.toLowerCase();
|
||||
if (key === 'x-influxdb-build') {
|
||||
return build ?? null;
|
||||
}
|
||||
if (key === 'x-influxdb-version') {
|
||||
return version ?? null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
url: '/api/datasources/proxy/1234/ping',
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock console.error to suppress React act() warnings
|
||||
consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
@@ -239,7 +275,7 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockFetchPing({ ok: true, build: 'OSS', version: '1.8.10' });
|
||||
mockFetchPing({ build: 'OSS', version: '1.8.10' });
|
||||
|
||||
render(<UrlAndAuthenticationSection {...props} />);
|
||||
const input = screen.getByTestId('influxdb-v2-config-url-input');
|
||||
@@ -268,7 +304,7 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockFetchPing({ ok: true, build: 'OSS', version: '2.7.1' });
|
||||
mockFetchPing({ build: 'OSS', version: '2.7.1' });
|
||||
|
||||
render(<UrlAndAuthenticationSection {...props} />);
|
||||
const input = screen.getByTestId('influxdb-v2-config-url-input');
|
||||
@@ -297,7 +333,7 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockFetchPing({ ok: true, build: undefined, version: undefined });
|
||||
mockFetchPing({ build: undefined, version: undefined });
|
||||
|
||||
render(<UrlAndAuthenticationSection {...props} />);
|
||||
const input = screen.getByTestId('influxdb-v2-config-url-input');
|
||||
@@ -358,23 +394,3 @@ describe('UrlAndAuthenticationSection', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export function mockFetchPing(resp: { ok?: boolean; build?: string; version?: string } = {}) {
|
||||
const { ok = true, build, version } = resp;
|
||||
|
||||
global.fetch = jest.fn().mockResolvedValue({
|
||||
ok,
|
||||
headers: {
|
||||
get: (key: string) => {
|
||||
const normalized = key.toLowerCase();
|
||||
if (normalized === 'x-influxdb-build') {
|
||||
return build ?? null;
|
||||
}
|
||||
if (normalized === 'x-influxdb-version') {
|
||||
return version ?? null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+96
-34
@@ -1,4 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { onUpdateDatasourceJsonDataOptionSelect, onUpdateDatasourceOption } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import {
|
||||
Box,
|
||||
CollapsableSection,
|
||||
@@ -11,6 +15,7 @@ import {
|
||||
Text,
|
||||
ComboboxOption,
|
||||
Alert,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { InfluxVersion } from '../../../types';
|
||||
@@ -33,6 +38,7 @@ const getQueryLanguageOptions = (productName: string): Array<{ value: string }>
|
||||
|
||||
export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
const { options, onOptionsChange } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const isInfluxVersion = (v: string): v is InfluxVersion =>
|
||||
typeof v === 'string' && (v === InfluxVersion.Flux || v === InfluxVersion.InfluxQL || v === InfluxVersion.SQL);
|
||||
@@ -68,13 +74,30 @@ export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
};
|
||||
|
||||
const pingInfluxForProductDetection = async (urlValue: string) => {
|
||||
const base = urlValue.replace(/\/$/, '');
|
||||
const dsId = options.id;
|
||||
if (!dsId) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${base}/ping`);
|
||||
const res = await firstValueFrom(
|
||||
getBackendSrv().fetch({
|
||||
method: 'GET',
|
||||
url: `/api/datasources/proxy/${dsId}/ping`,
|
||||
headers: { Accept: 'application/json' },
|
||||
responseType: 'text',
|
||||
showErrorAlert: false,
|
||||
showSuccessAlert: false,
|
||||
})
|
||||
);
|
||||
if (res.ok) {
|
||||
const product = res.headers.get('x-influxdb-build') ?? undefined;
|
||||
const version = res.headers.get('x-influxdb-version') ?? undefined;
|
||||
let product: string | undefined;
|
||||
let version: string | undefined;
|
||||
|
||||
if (res.headers && typeof res.headers.get === 'function') {
|
||||
product = res.headers.get('x-influxdb-build') ?? undefined;
|
||||
version = res.headers.get('x-influxdb-version') ?? undefined;
|
||||
}
|
||||
|
||||
if (product || version) {
|
||||
return { product, version };
|
||||
@@ -136,21 +159,19 @@ export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
borderStyle="solid"
|
||||
borderColor="weak"
|
||||
padding={2}
|
||||
marginBottom={4}
|
||||
id={`${CONFIG_SECTION_HEADERS[0].id}`}
|
||||
minWidth={CONTAINER_MIN_WIDTH}
|
||||
>
|
||||
<CollapsableSection
|
||||
label={<Text element="h3">1. {CONFIG_SECTION_HEADERS[0].label}</Text>}
|
||||
label={<Text element="h3">{CONFIG_SECTION_HEADERS[0].label}</Text>}
|
||||
isOpen={CONFIG_SECTION_HEADERS[0].isOpen}
|
||||
>
|
||||
<Text color="secondary">
|
||||
Enter the URL of your InfluxDB instance, then select your product and query language. This will determine the
|
||||
available settings and authentication methods in the next steps.
|
||||
</Text>
|
||||
|
||||
<Box direction="column" gap={2} marginTop={3}>
|
||||
<Field label={<div style={{ marginBottom: '5px' }}>URL *</div>} noMargin required>
|
||||
<Box direction="column" marginTop={3}>
|
||||
<Field label="URL" noMargin required>
|
||||
<Input
|
||||
data-testid="influxdb-v2-config-url-input"
|
||||
placeholder="example: http://localhost:8086/"
|
||||
@@ -162,34 +183,59 @@ export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Box marginTop={2}>
|
||||
<Stack direction="row" gap={2}>
|
||||
<Box flex={1}>
|
||||
<Field label={<div style={{ marginBottom: '5px' }}>Product *</div>} noMargin required>
|
||||
<Combobox
|
||||
data-testid="influxdb-v2-config-product-select"
|
||||
value={options.jsonData.product}
|
||||
options={INFLUXDB_VERSION_MAP.map(({ name }) => ({ value: name }))}
|
||||
onChange={onProductChange}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<Field label={<div style={{ marginBottom: '5px' }}>Query language *</div>} noMargin>
|
||||
<Combobox
|
||||
data-testid="influxdb-v2-config-query-language-select"
|
||||
value={options.jsonData.product !== '' ? options.jsonData.version : ''}
|
||||
options={getQueryLanguageOptions(options.jsonData.product || '')}
|
||||
onChange={onQueryLanguageChange}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
<Stack direction="row" wrap="wrap" justifyContent="space-between">
|
||||
<div className={styles.col}>
|
||||
<Box width="100%" minWidth={37}>
|
||||
<Field
|
||||
label="Product"
|
||||
description={
|
||||
<div className={styles.dropdown}>
|
||||
<Text color="secondary">
|
||||
Use{' '}
|
||||
<TextLink
|
||||
href="https://docs.influxdata.com/influxdb3/enterprise/visualize-data/grafana/?section=influxdb3%252Fenterprise%252Fvisualize-data&detection_method=url_analysis"
|
||||
variant="bodySmall"
|
||||
external
|
||||
>
|
||||
InfluxDB detection
|
||||
</TextLink>{' '}
|
||||
to identify the product
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
noMargin
|
||||
required
|
||||
>
|
||||
<Combobox
|
||||
data-testid="influxdb-v2-config-product-select"
|
||||
value={options.jsonData.product}
|
||||
options={INFLUXDB_VERSION_MAP.map(({ name }) => ({ value: name }))}
|
||||
onChange={onProductChange}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
</div>
|
||||
<div className={styles.col}>
|
||||
<Box width="100%" minWidth={37}>
|
||||
<Field
|
||||
label="Query language"
|
||||
description={<div className={styles.dropdown}>The query language depends on product selection</div>}
|
||||
noMargin
|
||||
required
|
||||
>
|
||||
<Combobox
|
||||
data-testid="influxdb-v2-config-query-language-select"
|
||||
value={options.jsonData.product !== '' ? options.jsonData.version : ''}
|
||||
options={getQueryLanguageOptions(options.jsonData.product || '')}
|
||||
onChange={onQueryLanguageChange}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Space v={2} />
|
||||
|
||||
{requiresDbrpMapping && (
|
||||
<Alert severity="warning" title="InfluxQL requires DBRP mapping">
|
||||
{`${options.jsonData.product} requires a Database + Retention Policy (DBRP) mapping via the CLI or
|
||||
@@ -199,7 +245,6 @@ export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
</TextLink>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<AdvancedHttpSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
<AuthSettings options={options} onOptionsChange={onOptionsChange} />
|
||||
</Box>
|
||||
@@ -207,3 +252,20 @@ export const UrlAndAuthenticationSection = (props: Props) => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => {
|
||||
return {
|
||||
dropdown: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '18px',
|
||||
}),
|
||||
col: css({
|
||||
flex: '1 1 48%',
|
||||
minWidth: '320px',
|
||||
}),
|
||||
'@media (max-width: 768px)': {
|
||||
flexBasis: '100%',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,16 +17,16 @@ export const AUTH_RADIO_BUTTON_OPTIONS = [
|
||||
];
|
||||
|
||||
export const CONFIG_SECTION_HEADERS = [
|
||||
{ label: 'URL and authentication', id: 'url', isOpen: true },
|
||||
{ label: 'Database settings', id: 'tls', isOpen: true },
|
||||
{ label: 'Save & test', id: `${selectors.pages.DataSource.saveAndTest}`, isOpen: true },
|
||||
{ label: 'URL and authentication', id: 'url', isOpen: true, isOptional: false },
|
||||
{ label: 'Database settings', id: 'db', isOpen: true, isOptional: false },
|
||||
{ label: 'Save & test', id: `${selectors.pages.DataSource.saveAndTest}`, isOpen: true, isOptional: null },
|
||||
];
|
||||
|
||||
export const CONFIG_SECTION_HEADERS_WITH_PDC = [
|
||||
{ label: 'URL and authentication', id: 'url', isOpen: true },
|
||||
{ label: 'Database settings', id: 'tls', isOpen: true },
|
||||
{ label: 'Private data source connect', id: 'pdc', isOpen: true },
|
||||
{ label: 'Save & test', id: `${selectors.pages.DataSource.saveAndTest}`, isOpen: true },
|
||||
{ label: 'URL and authentication', id: 'url', isOpen: true, isOptional: false },
|
||||
{ label: 'Database settings', id: 'db', isOpen: true, isOptional: false },
|
||||
{ label: 'Private data source connect', id: 'pdc', isOpen: false, isOptional: true },
|
||||
{ label: 'Save & test', id: `${selectors.pages.DataSource.saveAndTest}`, isOpen: true, isOptional: null },
|
||||
];
|
||||
|
||||
export const HTTP_MODES: ComboboxOption[] = [
|
||||
@@ -34,7 +34,7 @@ export const HTTP_MODES: ComboboxOption[] = [
|
||||
{ label: 'GET', value: 'GET' },
|
||||
];
|
||||
|
||||
export const getInlineLabelStyles = (theme: GrafanaTheme2, transparent = false, width?: number | 'auto') => {
|
||||
export const getInlineLabelStyles = (theme: GrafanaTheme2, transparent = false) => {
|
||||
return {
|
||||
label: css({
|
||||
display: 'flex',
|
||||
@@ -56,5 +56,5 @@ export const getInlineLabelStyles = (theme: GrafanaTheme2, transparent = false,
|
||||
};
|
||||
};
|
||||
|
||||
export const DB_SETTINGS_LABEL_WIDTH = 18;
|
||||
export const CONTAINER_MIN_WIDTH = '450px';
|
||||
export const DB_SETTINGS_LABEL_WIDTH = 22;
|
||||
|
||||
@@ -63,7 +63,8 @@ export const INFLUXDB_VERSION_MAP: InfluxDBProduct[] = [
|
||||
],
|
||||
detectionMethod: {
|
||||
pingHeaderResponse: {
|
||||
'x-influxdb-build': 'Enterprise (needs confirmation)',
|
||||
'x-influxdb-version': '^v?1\\.',
|
||||
'x-influxdb-build': 'Enterprise',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -75,7 +76,8 @@ export const INFLUXDB_VERSION_MAP: InfluxDBProduct[] = [
|
||||
],
|
||||
detectionMethod: {
|
||||
pingHeaderResponse: {
|
||||
'x-influxdb-build': 'TBD',
|
||||
'x-influxdb-version': '^v?3\\.',
|
||||
'x-influxdb-build': 'Enterprise',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -113,7 +115,7 @@ export const INFLUXDB_VERSION_MAP: InfluxDBProduct[] = [
|
||||
detectionMethod: {
|
||||
pingHeaderResponse: {
|
||||
'x-influxdb-build': 'OSS',
|
||||
'x-influxdb-version': '^1\\.',
|
||||
'x-influxdb-version': '^v?1\\.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -134,7 +136,20 @@ export const INFLUXDB_VERSION_MAP: InfluxDBProduct[] = [
|
||||
detectionMethod: {
|
||||
pingHeaderResponse: {
|
||||
'x-influxdb-build': 'OSS',
|
||||
'x-influxdb-version': '^2\\.',
|
||||
'x-influxdb-version': '^v?2\\.',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'InfluxDB OSS 3.x',
|
||||
queryLanguages: [
|
||||
{ name: InfluxVersion.SQL, fields: ['URL', 'Token'] },
|
||||
{ name: InfluxVersion.InfluxQL, fields: ['URL', 'Token'] },
|
||||
],
|
||||
detectionMethod: {
|
||||
pingHeaderResponse: {
|
||||
'x-influxdb-build': 'OSS',
|
||||
'x-influxdb-version': '^v?3\\.',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user