diff --git a/.betterer.results b/.betterer.results index 0ac3ad0c701..7c38c251319 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3606,13 +3606,6 @@ exports[`better eslint`] = { [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/plugins/datasource/influxdb/components/editor/config/InfluxInfluxQLConfig.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "4"] - ], "public/app/plugins/datasource/influxdb/components/editor/config/InfluxSQLConfig.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], diff --git a/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md b/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md index 88aa4a4931a..d50db2e9d43 100644 --- a/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md +++ b/docs/sources/datasources/influxdb/configure-influxdb-data-source/_index.md @@ -126,6 +126,7 @@ The following settings are specific to the InfluxQL query language option. - **Password** - Defines the token used to query the bucket defined in **Database**. Retrieve the password from the [Tokens page](https://docs.influxdata.com/influxdb/v2.0/security/tokens/view-tokens/) of the InfluxDB UI. - **HTTP method** - Sets the HTTP method used to query your data source. The POST method allows for larger queries that would return an error using the GET method. The default method is `POST`. - **Min time interval** - _(Optional)_ Sets the minimum time interval for auto group-by. Grafana recommends setting this to match the data write frequency. For example, if your data is written every minute, it’s recommended to set this interval to 1 minute, so that each group contains data from each new write. The default is `10s`. Refer to [Min time interval](#min-time-interval) for format examples. +- **Autocomplete range** - _(Optional)_ Sets a time range limit for the query editor's autocomplete to reduce the execution time of tag filter queries. As a result, any tags not present within the defined time range will be filtered out. For example, setting the value to 12h will include only tag keys/values from the past 12 hours. This feature is recommended for use with very large databases, where significant performance improvements can be observed. - **Max series** - _(Optional)_ Sets a limit on the maximum number of series or tables that Grafana processes. Set a lower limit to prevent system overload, or increase it if you have many small time series and need to display more of them. The default is `1000`. ### SQL-specific configuration section diff --git a/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.test.tsx b/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.test.tsx index 5964465b0c9..a0c61ce4313 100644 --- a/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.test.tsx +++ b/public/app/plugins/datasource/influxdb/components/editor/config/ConfigEditor.test.tsx @@ -36,6 +36,7 @@ const setup = (optionOverrides?: object) => { jsonData: { httpMode: 'POST', timeInterval: '4', + showTagTime: '3h', }, secureJsonFields: {}, version: 1, diff --git a/public/app/plugins/datasource/influxdb/components/editor/config/InfluxInfluxQLConfig.tsx b/public/app/plugins/datasource/influxdb/components/editor/config/InfluxInfluxQLConfig.tsx index bdde3be2533..ae5f83dbf9d 100644 --- a/public/app/plugins/datasource/influxdb/components/editor/config/InfluxInfluxQLConfig.tsx +++ b/public/app/plugins/datasource/influxdb/components/editor/config/InfluxInfluxQLConfig.tsx @@ -50,6 +50,7 @@ export const InfluxInfluxQLConfig = (props: Props) => { label={Database} className={styles.horizontalField} htmlFor={`${htmlPrefix}-db`} + noMargin > { label={User} className={styles.horizontalField} htmlFor={`${htmlPrefix}-user`} + noMargin > { horizontal label={Password} className={styles.horizontalField} + noMargin > { } htmlFor={`${htmlPrefix}-http-method`} className={styles.horizontalField} + noMargin > { onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')} /> + + + Autocomplete range + + } + className={styles.horizontalField} + noMargin + > + + ); }; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index f24d9b1b4b8..a21031f20ab 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -64,6 +64,7 @@ export default class InfluxDatasource extends DataSourceWithBackend, @@ -85,6 +86,7 @@ export default class InfluxDatasource extends DataSourceWithBackend> => { @@ -26,6 +27,7 @@ const runExploreQuery = async (options: MetadataQueryOptions): Promise { }); expect(query).toBe(`SHOW TAG KEYS WHERE "app" == ''`); }); + + it('should have where condition in tag keys query with time filter', () => { + const query = buildMetadataQuery({ + type: 'TAG_KEYS', + templateService, + measurement: '', + tags: [], + withTimeFilter: '3h', + }); + expect(query).toBe('SHOW TAG KEYS WHERE time > now() - 3h'); + }); + + it('should have and in where condition in tag keys query with tags and time filter', () => { + const query = buildMetadataQuery({ + type: 'TAG_KEYS', + templateService, + measurement: '', + tags: [{ key: 'host', value: 'se1' }], + withTimeFilter: '3h', + }); + expect(query).toBe('SHOW TAG KEYS WHERE "host" = \'se1\' AND time > now() - 3h'); + }); }); describe('TAG_VALUES', () => { @@ -184,6 +206,37 @@ describe('influxql-query-builder', () => { }); expect(query).toBe('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" =~ /server.*/'); }); + + it('should add time filter with and if with time filter', () => { + const query = buildMetadataQuery({ + type: 'TAG_VALUES', + templateService, + withKey: 'app', + measurement: 'cpu', + retentionPolicy: 'one_week', + tags: [], + withTimeFilter: '2h', + }); + expect(query).toBe('SHOW TAG VALUES FROM "one_week"."cpu" WITH KEY = "app" WHERE time > now() - 2h'); + }); + + it('should add time filter if with time filter and tags', () => { + const query = buildMetadataQuery({ + type: 'TAG_VALUES', + templateService, + withKey: 'app', + measurement: 'cpu', + retentionPolicy: 'one_week', + tags: [ + { key: 'app', value: 'email' }, + { key: 'host', value: 'server1' }, + ], + withTimeFilter: '2h', + }); + expect(query).toBe( + 'SHOW TAG VALUES FROM "one_week"."cpu" WITH KEY = "app" WHERE "host" = \'server1\' AND time > now() - 2h' + ); + }); }); describe('MEASUREMENTS', () => { @@ -271,6 +324,18 @@ describe('influxql-query-builder', () => { }); expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '' LIMIT 100`); }); + + it('should not add time filter when getting measurements', () => { + const query = buildMetadataQuery({ + type: 'MEASUREMENTS', + templateService, + database: undefined, + measurement: undefined, + tags: [{ key: 'app', value: '', operator: '==' }], + withTimeFilter: '2h', + }); + expect(query).toBe(`SHOW MEASUREMENTS WHERE "app" == '' LIMIT 100`); + }); }); it('should not add FROM statement if the measurement empty', () => { diff --git a/public/app/plugins/datasource/influxdb/influxql_query_builder.ts b/public/app/plugins/datasource/influxdb/influxql_query_builder.ts index 1ca19b15b9a..f7057e329c7 100644 --- a/public/app/plugins/datasource/influxdb/influxql_query_builder.ts +++ b/public/app/plugins/datasource/influxdb/influxql_query_builder.ts @@ -15,6 +15,7 @@ export const buildMetadataQuery = (params: { tags?: InfluxQueryTag[]; withKey?: string; withMeasurementFilter?: string; + withTimeFilter?: string; }): string => { let query = ''; let { @@ -27,6 +28,7 @@ export const buildMetadataQuery = (params: { tags, withKey, withMeasurementFilter, + withTimeFilter, } = params; switch (type) { @@ -89,8 +91,9 @@ export const buildMetadataQuery = (params: { query += ' WITH KEY = "' + keyIdentifier + '"'; } + let whereConditions: string[] = []; if (tags && tags.length > 0) { - const whereConditions = reduce( + whereConditions = reduce( tags, (memo, tag) => { // do not add a condition for the key we want to explore for @@ -108,10 +111,13 @@ export const buildMetadataQuery = (params: { }, [] ); + } - if (whereConditions.length > 0) { - query += ' WHERE ' + whereConditions.join(' '); - } + let shouldUseTime = isValidTimeFilter(withTimeFilter) && type !== 'MEASUREMENTS'; + if (whereConditions.length > 0) { + query += ' WHERE ' + whereConditions.join(' ') + (shouldUseTime ? ' AND time > now() - ' + withTimeFilter : ''); + } else { + query += shouldUseTime ? ' WHERE time > now() - ' + withTimeFilter : ''; } if (type === 'MEASUREMENTS') { @@ -124,6 +130,34 @@ export const buildMetadataQuery = (params: { return query; }; +// Function to validate the provided time filter value +export function isValidTimeFilter(value?: string): boolean { + if (!value || typeof value !== 'string') { + return false; + } + + // Normalize microseconds unit + const normalizedValue = value.replace(/µ/g, 'u'); + + const validUnits = new Set(['ns', 'u', 'ms', 's', 'm', 'h', 'd', 'w']); + const pattern = /(\d+)(ns|u|ms|s|m|h|d|w)/g; + const usedUnits = new Set(); + + let match: RegExpExecArray | null; + let totalLength = 0; + + while ((match = pattern.exec(normalizedValue)) !== null) { + const [fullMatch, numberPart, unit] = match; + totalLength += fullMatch.length; + + if (!validUnits.has(unit) || usedUnits.has(unit) || numberPart.startsWith('0')) { + return false; + } + usedUnits.add(unit); + } + return totalLength === normalizedValue.length; +} + // A merge of query_builder/renderTagCondition and influx_query_model/renderTagCondition export function renderTagCondition( tag: InfluxQueryTag, diff --git a/public/app/plugins/datasource/influxdb/types.ts b/public/app/plugins/datasource/influxdb/types.ts index 3f0b715b421..623e2acf531 100644 --- a/public/app/plugins/datasource/influxdb/types.ts +++ b/public/app/plugins/datasource/influxdb/types.ts @@ -13,6 +13,7 @@ export interface InfluxOptions extends DataSourceJsonData { timeInterval?: string; httpMode?: string; + showTagTime?: string; dbName?: string; product?: string;