Compare commits

...

3 Commits

Author SHA1 Message Date
Ryan McKinley
ac8ba8aa16 frontend lint 2025-11-26 18:02:16 +00:00
Ryan McKinley
8d747ecf00 frontend fixes 2025-11-26 18:02:16 +00:00
Ryan McKinley
88100b0037 remove unused feature flag 2025-11-26 18:02:15 +00:00
10 changed files with 6 additions and 385 deletions

View File

@@ -30,7 +30,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes | | `cloudWatchCrossAccountQuerying` | Enables cross-account querying in CloudWatch datasources | Yes |
| `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes | | `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes |
| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes | | `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes |
| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes |
| `unifiedRequestLog` | Writes error logs to the request logger | Yes | | `unifiedRequestLog` | Writes error logs to the request logger | Yes |
| `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes | | `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes |
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes |

View File

@@ -97,11 +97,6 @@ export interface FeatureToggles {
*/ */
individualCookiePreferences?: boolean; individualCookiePreferences?: boolean;
/** /**
* Query InfluxDB InfluxQL without the proxy
* @default true
*/
influxdbBackendMigration?: boolean;
/**
* populate star status from apiserver * populate star status from apiserver
*/ */
starsFromAPIServer?: boolean; starsFromAPIServer?: boolean;

View File

@@ -137,14 +137,6 @@ var (
Stage: FeatureStageExperimental, Stage: FeatureStageExperimental,
Owner: grafanaBackendGroup, Owner: grafanaBackendGroup,
}, },
{
Name: "influxdbBackendMigration",
Description: "Query InfluxDB InfluxQL without the proxy",
Stage: FeatureStageGeneralAvailability,
FrontendOnly: true,
Owner: grafanaPartnerPluginsSquad,
Expression: "true", // enabled by default
},
{ {
Name: "starsFromAPIServer", Name: "starsFromAPIServer",
Description: "populate star status from apiserver", Description: "populate star status from apiserver",

View File

@@ -17,7 +17,6 @@ logsContextDatasourceUi,GA,@grafana/observability-logs,false,false,true
lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false
influxdbBackendMigration,GA,@grafana/partner-datasources,false,false,true
starsFromAPIServer,experimental,@grafana/grafana-search-navigate-organise,false,false,true starsFromAPIServer,experimental,@grafana/grafana-search-navigate-organise,false,false,true
kubernetesStars,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesStars,experimental,@grafana/grafana-app-platform-squad,false,true,false
influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,false influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
17 lokiShardSplitting experimental @grafana/observability-logs false false true
18 lokiQuerySplitting GA @grafana/observability-logs false false true
19 individualCookiePreferences experimental @grafana/grafana-backend-group false false false
influxdbBackendMigration GA @grafana/partner-datasources false false true
20 starsFromAPIServer experimental @grafana/grafana-search-navigate-organise false false true
21 kubernetesStars experimental @grafana/grafana-app-platform-squad false true false
22 influxqlStreamingParser experimental @grafana/partner-datasources false false false

View File

@@ -1,14 +1,11 @@
import { lastValueFrom, of } from 'rxjs'; import { of } from 'rxjs';
import { AdHocVariableFilter } from '@grafana/data'; import { AdHocVariableFilter } from '@grafana/data';
import { BackendSrvRequest, TemplateSrv } from '@grafana/runtime'; import { TemplateSrv } from '@grafana/runtime';
import config from 'app/core/config';
import { queryBuilder } from '../../../features/variables/shared/testing/builders'; import { queryBuilder } from '../../../features/variables/shared/testing/builders';
import { BROWSER_MODE_DISABLED_MESSAGE } from './constants'; import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService } from './mocks/datasource';
import InfluxDatasource from './datasource';
import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService, replaceMock } from './mocks/datasource';
import { mockInfluxQueryRequest } from './mocks/request'; import { mockInfluxQueryRequest } from './mocks/request';
import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './mocks/response'; import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './mocks/response';
import { InfluxQuery, InfluxVersion } from './types'; import { InfluxQuery, InfluxVersion } from './types';
@@ -24,231 +21,8 @@ describe('datasource initialization', () => {
}); });
}); });
// Remove this suite when influxdbBackendMigration feature toggle removed describe('InfluxDataSource Backend Mode', () => {
describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () => {
beforeEach(() => { beforeEach(() => {
// we want only frontend mode in this suite
config.featureToggles.influxdbBackendMigration = false;
jest.clearAllMocks();
});
describe('general checks', () => {
it('should throw an error if there is 200 response with error', async () => {
const ds = getMockInfluxDS();
fetchMock.mockImplementation(() => {
return of({
data: {
results: [
{
error: 'Query timeout',
},
],
},
});
});
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe('InfluxDB Error: Query timeout');
}
}
});
it('should throw an error when querying data when deprecated access mode', async () => {
expect.assertions(1);
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.access = 'direct';
const ds = getMockInfluxDS(instanceSettings);
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe(BROWSER_MODE_DISABLED_MESSAGE);
}
}
});
});
describe('metricFindQuery', () => {
let ds: InfluxDatasource;
const query = 'SELECT max(value) FROM measurement WHERE $timeFilter';
const queryOptions = {
range: {
from: '2018-01-01T00:00:00Z',
to: '2018-01-02T00:00:00Z',
},
};
const fetchMockImpl = (req: BackendSrvRequest) => {
return of({
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
};
beforeEach(async () => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should replace $timefilter', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].params?.q).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].params?.q).toBeFalsy();
expect(fetchMock.mock.lastCall[0].data).toMatch(
'time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms'
);
});
it('should not have any data in request body if http mode is GET', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].data).toBeNull();
});
it('should have data in request body if http mode is POST', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].data).not.toBeNull();
expect(fetchMock.mock.lastCall[0].data).toMatch('q=SELECT');
});
it('parse response correctly', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
let responseGet = await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(responseGet).toEqual([{ text: 'cpu' }]);
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
let responsePost = await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(responsePost).toEqual([{ text: 'cpu' }]);
});
});
// Update this after starting to use TemplateSrv from @grafana/runtime package
describe('adhoc variables', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('query should contain the ad-hoc variable', () => {
ds.query(mockInfluxQueryRequest());
expect(replaceMock.mock.calls[0][0]).toBe('adhoc_val');
});
it('should make the fetch call for adhoc filter keys', () => {
fetchMock.mockReturnValue(
of({
results: [
{
statement_id: 0,
series: [
{
name: 'cpu',
columns: ['tagKey'],
values: [['datacenter'], ['geohash'], ['source']],
},
],
},
],
})
);
ds.getTagKeys();
expect(fetchMock).toHaveBeenCalled();
const fetchReq = fetchMock.mock.calls[0][0];
expect(fetchReq).not.toBeNull();
expect(fetchReq.data).toMatch(encodeURIComponent(`SHOW TAG KEYS`));
});
it('should make the fetch call for adhoc filter values', () => {
fetchMock.mockReturnValue(
of({
results: [
{
statement_id: 0,
series: [
{
name: 'mykey',
columns: ['key', 'value'],
values: [['mykey', 'value']],
},
],
},
],
})
);
ds.getTagValues({ key: 'mykey', filters: [] });
expect(fetchMock).toHaveBeenCalled();
const fetchReq = fetchMock.mock.calls[0][0];
expect(fetchReq).not.toBeNull();
expect(fetchReq.data).toMatch(encodeURIComponent(`SHOW TAG VALUES WITH KEY = "mykey"`));
});
});
describe('datasource contract', () => {
let ds: InfluxDatasource;
const metricFindQueryMock = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
ds = getMockInfluxDS();
ds.metricFindQuery = metricFindQueryMock;
});
afterEach(() => {
jest.clearAllMocks();
});
it('should check the datasource has "getTagKeys" function defined', () => {
expect(Object.getOwnPropertyNames(Object.getPrototypeOf(ds))).toContain('getTagKeys');
});
it('should check the datasource has "getTagValues" function defined', () => {
expect(Object.getOwnPropertyNames(Object.getPrototypeOf(ds))).toContain('getTagValues');
});
it('should be able to call getTagKeys without specifying any parameter', () => {
ds.getTagKeys();
expect(metricFindQueryMock).toHaveBeenCalled();
});
it('should be able to call getTagValues without specifying anything but key', () => {
ds.getTagValues({ key: 'test', filters: [] });
expect(metricFindQueryMock).toHaveBeenCalled();
});
it('should use dbName instead of database', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'should_not_be_used';
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('site');
});
it('should fallback to use use database is dbName is not exist', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'fallback';
instanceSettings.jsonData.dbName = undefined;
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('fallback');
});
});
});
describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () => {
beforeEach(() => {
// we want only backend mode in this suite
config.featureToggles.influxdbBackendMigration = true;
jest.clearAllMocks(); jest.clearAllMocks();
}); });

View File

@@ -35,7 +35,6 @@ import {
TemplateSrv, TemplateSrv,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { QueryFormat, SQLQuery } from '@grafana/sql'; import { QueryFormat, SQLQuery } from '@grafana/sql';
import config from 'app/core/config';
import { AnnotationEditor } from './components/editor/annotation/AnnotationEditor'; import { AnnotationEditor } from './components/editor/annotation/AnnotationEditor';
import { FluxQueryEditor } from './components/editor/query/flux/FluxQueryEditor'; import { FluxQueryEditor } from './components/editor/query/flux/FluxQueryEditor';
@@ -665,7 +664,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
// ------------------------ Legacy Code - Before Backend Migration --------------- // ------------------------ Legacy Code - Before Backend Migration ---------------
isMigrationToggleOnAndIsAccessProxy() { isMigrationToggleOnAndIsAccessProxy() {
return config.featureToggles.influxdbBackendMigration && this.access === 'proxy'; return this.access === 'proxy';
} }
/** /**

View File

@@ -1,7 +1,6 @@
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { SQLQuery } from '@grafana/sql'; import { SQLQuery } from '@grafana/sql';
import config from 'app/core/config';
import InfluxDatasource from './datasource'; import InfluxDatasource from './datasource';
import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './mocks/datasource'; import { getMockDSInstanceSettings, mockBackendService, mockTemplateSrv } from './mocks/datasource';
@@ -9,7 +8,6 @@ import { mockInfluxQueryRequest } from './mocks/request';
import { mockInfluxSQLFetchResponse } from './mocks/response'; import { mockInfluxSQLFetchResponse } from './mocks/response';
import { InfluxVersion } from './types'; import { InfluxVersion } from './types';
config.featureToggles.influxdbBackendMigration = true;
mockBackendService(mockInfluxSQLFetchResponse); mockBackendService(mockInfluxSQLFetchResponse);
describe('InfluxDB SQL Support', () => { describe('InfluxDB SQL Support', () => {

View File

@@ -1,5 +1,3 @@
import config from 'app/core/config';
import { getAllMeasurements, getAllPolicies, getFieldKeys, getTagKeys, getTagValues } from './influxql_metadata_query'; import { getAllMeasurements, getAllPolicies, getFieldKeys, getTagKeys, getTagValues } from './influxql_metadata_query';
import { getMockInfluxDS } from './mocks/datasource'; import { getMockInfluxDS } from './mocks/datasource';
import { InfluxQuery, InfluxVariableQuery } from './types'; import { InfluxQuery, InfluxVariableQuery } from './types';
@@ -27,132 +25,7 @@ describe('influx_metadata_query', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
// This should be removed when backend mode is default
describe('backend mode disabled', () => {
beforeEach(() => {
config.featureToggles.influxdbBackendMigration = false;
});
function frontendModeChecks() {
expect(mockRunMetadataQuery).not.toHaveBeenCalled();
expect(mockMetricFindQuery).toHaveBeenCalled();
}
describe('getAllPolicies', () => {
it('should call metricFindQuery with SHOW RETENTION POLICIES', () => {
getAllPolicies(ds);
frontendModeChecks();
expect(query.query).toMatch('SHOW RETENTION POLICIES');
});
});
describe('getAllMeasurements', () => {
it('no tags specified', () => {
getAllMeasurements(ds, []);
frontendModeChecks();
expect(query.query).toBe('SHOW MEASUREMENTS LIMIT 100');
});
it('with tags', () => {
getAllMeasurements(ds, [{ key: 'key', value: 'val' }]);
frontendModeChecks();
expect(query.query).toMatch('SHOW MEASUREMENTS WHERE "key"');
});
it('with measurement filter', () => {
getAllMeasurements(ds, [{ key: 'key', value: 'val' }], 'measurementFilter');
frontendModeChecks();
expect(query.query).toMatch('SHOW MEASUREMENTS WITH MEASUREMENT =~ /(?i)measurementFilter/ WHERE "key"');
});
});
describe('getTagKeys', () => {
it('no tags specified', () => {
getTagKeys(ds);
frontendModeChecks();
expect(query.query).toBe('SHOW TAG KEYS');
});
it('with measurement', () => {
getTagKeys(ds, 'test_measurement');
frontendModeChecks();
expect(query.query).toBe('SHOW TAG KEYS FROM "test_measurement"');
});
it('with retention policy', () => {
getTagKeys(ds, 'test_measurement', 'rp');
frontendModeChecks();
expect(query.query).toBe('SHOW TAG KEYS FROM "rp"."test_measurement"');
});
});
describe('getTagValues', () => {
it('with key', () => {
getTagValues(ds, [], 'test_key');
frontendModeChecks();
expect(query.query).toBe('SHOW TAG VALUES WITH KEY = "test_key"');
});
it('with key ends with ::tag', () => {
getTagValues(ds, [], 'test_key::tag');
frontendModeChecks();
expect(query.query).toBe('SHOW TAG VALUES WITH KEY = "test_key"');
});
it('with key ends with ::field', async () => {
const result = await getTagValues(ds, [], 'test_key::field');
expect(result.length).toBe(0);
});
it('with tags', () => {
getTagValues(ds, [{ key: 'tagKey', value: 'tag_val' }], 'test_key');
frontendModeChecks();
expect(query.query).toBe('SHOW TAG VALUES WITH KEY = "test_key" WHERE "tagKey" = \'tag_val\'');
});
it('with measurement', () => {
getTagValues(ds, [{ key: 'tagKey', value: 'tag_val' }], 'test_key', 'test_measurement');
frontendModeChecks();
expect(query.query).toBe(
'SHOW TAG VALUES FROM "test_measurement" WITH KEY = "test_key" WHERE "tagKey" = \'tag_val\''
);
});
it('with retention policy', () => {
getTagValues(ds, [{ key: 'tagKey', value: 'tag_val' }], 'test_key', 'test_measurement', 'rp');
frontendModeChecks();
expect(query.query).toBe(
'SHOW TAG VALUES FROM "rp"."test_measurement" WITH KEY = "test_key" WHERE "tagKey" = \'tag_val\''
);
});
});
describe('getFieldKeys', () => {
it('with no retention policy', () => {
getFieldKeys(ds, 'test_measurement');
frontendModeChecks();
expect(query.query).toBe('SHOW FIELD KEYS FROM "test_measurement"');
});
it('with empty measurement', () => {
getFieldKeys(ds, '');
frontendModeChecks();
expect(query.query).toBe('SHOW FIELD KEYS');
});
it('with retention policy', () => {
getFieldKeys(ds, 'test_measurement', 'rp');
frontendModeChecks();
expect(query.query).toBe('SHOW FIELD KEYS FROM "rp"."test_measurement"');
});
});
});
describe('backend mode enabled', () => { describe('backend mode enabled', () => {
beforeEach(() => {
config.featureToggles.influxdbBackendMigration = true;
});
function backendModeChecks() { function backendModeChecks() {
expect(mockMetricFindQuery).not.toHaveBeenCalled(); expect(mockMetricFindQuery).not.toHaveBeenCalled();
expect(mockRunMetadataQuery).toHaveBeenCalled(); expect(mockRunMetadataQuery).toHaveBeenCalled();

View File

@@ -1,5 +1,4 @@
import { ScopedVars } from '@grafana/data'; import { ScopedVars } from '@grafana/data';
import config from 'app/core/config';
import InfluxDatasource from './datasource'; import InfluxDatasource from './datasource';
import { buildMetadataQuery } from './influxql_query_builder'; import { buildMetadataQuery } from './influxql_query_builder';
@@ -38,12 +37,7 @@ const runExploreQuery = async (options: MetadataQueryOptions): Promise<Array<{ t
rawQuery: true, rawQuery: true,
refId: 'metadataQuery', refId: 'metadataQuery',
}; };
if (config.featureToggles.influxdbBackendMigration) { return datasource.runMetadataQuery(target);
return datasource.runMetadataQuery(target);
} else {
const options = { policy: target.policy };
return datasource.metricFindQuery({ refId: 'run-explore-query', query }, options);
}
}; };
export async function getAllPolicies(datasource: InfluxDatasource): Promise<string[]> { export async function getAllPolicies(datasource: InfluxDatasource): Promise<string[]> {

View File

@@ -3,7 +3,6 @@ import { of } from 'rxjs';
import { AnnotationEvent, DataFrame, DataQueryRequest, dateTime, FieldType, MutableDataFrame } from '@grafana/data'; import { AnnotationEvent, DataFrame, DataQueryRequest, dateTime, FieldType, MutableDataFrame } from '@grafana/data';
import { FetchResponse } from '@grafana/runtime'; import { FetchResponse } from '@grafana/runtime';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__ import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import InfluxQueryModel from './influx_query_model'; import InfluxQueryModel from './influx_query_model';
@@ -355,7 +354,6 @@ describe('influxdb response parser', () => {
return of(annotationMockResponse); return of(annotationMockResponse);
}); });
config.featureToggles.influxdbBackendMigration = true;
response = await ctx.ds.annotationEvents(queryOptions, annotation); response = await ctx.ds.annotationEvents(queryOptions, annotation);
}); });