From 84ef5bc744c01b864c9daa8d89ce9c30cbd381b2 Mon Sep 17 00:00:00 2001 From: Gareth Date: Thu, 10 Jul 2025 15:54:16 +0100 Subject: [PATCH] Remove jaegerBackendMigration feature toggle (#107702) * remove feature toggle if statements * remove unused impoerts * remove unused private functions * prettier * official ft removal * fix some failing tests in datasource.test.ts * clean up test file * update test names * remove tests for testDatasource * remove describe * tests * fix import order * betterer --- .betterer.results | 3 +- .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 5 - pkg/services/featuremgmt/registry.go | 7 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 - pkg/services/featuremgmt/toggles_gen.json | 1 + .../datasource/jaeger/datasource.test.ts | 532 +++--------------- .../plugins/datasource/jaeger/datasource.ts | 179 +----- .../datasource/jaeger/mockSearchResponse.json | 52 ++ .../datasource/jaeger/mockTraceResponse.json | 21 + 11 files changed, 174 insertions(+), 632 deletions(-) create mode 100644 public/app/plugins/datasource/jaeger/mockSearchResponse.json create mode 100644 public/app/plugins/datasource/jaeger/mockTraceResponse.json diff --git a/.betterer.results b/.betterer.results index cfb0ebda934..8a98e82ff57 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3462,8 +3462,7 @@ exports[`better eslint`] = { [0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"] ], "public/app/plugins/datasource/jaeger/datasource.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] + [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/datasource/loki/LanguageProvider.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 4015e51df88..060345d124a 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -72,7 +72,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `pluginsSriChecks` | Enables SRI checks for plugin assets | | | `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | | | `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes | -| `jaegerBackendMigration` | Enables querying the Jaeger data source without the proxy | Yes | | `alertingUIOptimizeReducer` | Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query | Yes | | `azureMonitorEnableUserAuth` | Enables user auth for Azure Monitor datasource only | Yes | | `alertingNotificationsStepMode` | Enables simplified step mode in the notifications section | Yes | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index c1099124dc5..81468c40d02 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -738,11 +738,6 @@ export interface FeatureToggles { */ crashDetection?: boolean; /** - * Enables querying the Jaeger data source without the proxy - * @default true - */ - jaegerBackendMigration?: boolean; - /** * Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query * @default true */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index c099d584788..7feb6c5dbda 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1261,13 +1261,6 @@ var ( Owner: grafanaObservabilityTracesAndProfilingSquad, FrontendOnly: true, }, - { - Name: "jaegerBackendMigration", - Description: "Enables querying the Jaeger data source without the proxy", - Stage: FeatureStageGeneralAvailability, - Owner: grafanaOSSBigTent, - Expression: "true", - }, { Name: "alertingUIOptimizeReducer", Description: "Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index dffcd8bcad4..0fe72290ac6 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -165,7 +165,6 @@ prometheusSpecialCharsInLabelValues,experimental,@grafana/oss-big-tent,false,fal enableExtensionsAdminPage,experimental,@grafana/plugins-platform-backend,false,true,false enableSCIM,preview,@grafana/identity-access-team,false,false,false crashDetection,experimental,@grafana/observability-traces-and-profiling,false,false,true -jaegerBackendMigration,GA,@grafana/oss-big-tent,false,false,false alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index e105464df6a..981a2ad0db2 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -671,10 +671,6 @@ const ( // Enables browser crash detection reporting to Faro. FlagCrashDetection = "crashDetection" - // FlagJaegerBackendMigration - // Enables querying the Jaeger data source without the proxy - FlagJaegerBackendMigration = "jaegerBackendMigration" - // FlagAlertingUIOptimizeReducer // Enables removing the reducer from the alerting UI when creating a new alert rule and using instant query FlagAlertingUIOptimizeReducer = "alertingUIOptimizeReducer" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 1735b3c01b0..4cb7929a9c9 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1542,6 +1542,7 @@ "name": "jaegerBackendMigration", "resourceVersion": "1751465665226", "creationTimestamp": "2024-11-15T14:40:20Z", + "deletionTimestamp": "2025-07-07T14:12:35Z", "annotations": { "grafana.app/updatedTimestamp": "2025-07-02 14:14:25.226989 +0000 UTC" } diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 2fa37357816..5c0961d4ac7 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -1,4 +1,4 @@ -import { lastValueFrom, of, throwError } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { DataQueryRequest, @@ -9,18 +9,12 @@ import { PluginType, ScopedVars, } from '@grafana/data'; -import { BackendSrv, config, DataSourceWithBackend } from '@grafana/runtime'; +import { BackendSrv, DataSourceWithBackend } from '@grafana/runtime'; -import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { JaegerDatasource, JaegerJsonData } from './datasource'; -import { createFetchResponse } from './helpers/createFetchResponse'; import mockJson from './mockJsonResponse.json'; -import { - testResponse, - testResponseDataFrameFields, - testResponseEdgesFields, - testResponseNodesFields, -} from './testResponse'; +import mockSearchResponse from './mockSearchResponse.json'; +import mockTraceResponse from './mockTraceResponse.json'; import { JaegerQuery } from './types'; export const backendSrv = { fetch: jest.fn() } as unknown as BackendSrv; @@ -38,128 +32,29 @@ jest.mock('@grafana/runtime', () => ({ }), })); -const defaultQuery: DataQueryRequest = { - requestId: '1', - interval: '0', - intervalMs: 10, - panelId: 0, - scopedVars: {}, - range: { - from: dateTime().subtract(1, 'h'), - to: dateTime(), - raw: { from: '1h', to: 'now' }, - }, - timezone: 'browser', - app: 'explore', - startTime: 0, - targets: [ - { - query: '12345', - refId: '1', - }, - ], -}; - -describe('JaegerDatasource', () => { - const defaultSearchRangeParams = `start=${Number(defaultQuery.range.from) * 1000}&end=${Number(defaultQuery.range.to) * 1000}`; - - beforeEach(() => { - jest.clearAllMocks(); - - const fetchMock = jest.spyOn(Date, 'now'); - fetchMock.mockImplementation(() => 1704106800000); // milliseconds for 2024-01-01 at 11:00am UTC - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('returns trace and graph when queried', async () => { - setupFetchMock({ data: [testResponse] }); - - const ds = new JaegerDatasource(defaultSettings); - const response = await lastValueFrom(ds.query(defaultQuery)); - expect(response.data.length).toBe(3); - expect(response.data[0].fields).toMatchObject(testResponseDataFrameFields); - expect(response.data[1].fields).toMatchObject(testResponseNodesFields); - expect(response.data[2].fields).toMatchObject(testResponseEdgesFields); - }); - - it('returns trace when traceId with special characters is queried', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - const query = { - ...defaultQuery, - targets: [ - { - query: 'a/b', - refId: '1', - }, - ], - }; - await lastValueFrom(ds.query(query)); - expect(mock).toHaveBeenCalledWith({ url: `${defaultSettings.url}/api/traces/a%2Fb` }); - }); - - it('should trim whitespace from traceid', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - const query = { - ...defaultQuery, - targets: [ - { - query: 'a/b ', - refId: '1', - }, - ], - }; - await lastValueFrom(ds.query(query)); - expect(mock).toHaveBeenCalledWith({ url: `${defaultSettings.url}/api/traces/a%2Fb` }); - }); - - it('returns empty response if trace id is not specified', async () => { - const ds = new JaegerDatasource(defaultSettings); - const response = await lastValueFrom( - ds.query({ - ...defaultQuery, - targets: [], - }) - ); - const field = response.data[0].fields[0]; - expect(field.name).toBe('trace'); - expect(field.type).toBe(FieldType.trace); - expect(field.values.length).toBe(0); - }); - - it('should handle json file upload', async () => { +describe('upload, search and trace query types', () => { + it('should process valid JSON file uploads', async () => { const ds = new JaegerDatasource(defaultSettings); ds.uploadedJson = JSON.stringify(mockJson); - const response = await lastValueFrom( - ds.query({ - ...defaultQuery, - targets: [{ queryType: 'upload', refId: 'A' }], - }) - ); + const response = await lastValueFrom(ds.query({ ...defaultQuery, targets: [{ queryType: 'upload', refId: 'A' }] })); const field = response.data[0].fields[0]; expect(field.name).toBe('traceID'); expect(field.type).toBe(FieldType.string); expect(field.values.length).toBe(2); }); - it('should fail on invalid json file upload', async () => { + it('should reject invalid JSON file uploads', async () => { const ds = new JaegerDatasource(defaultSettings); ds.uploadedJson = JSON.stringify({ key: 'value', arr: [] }); const response = await lastValueFrom( - ds.query({ - targets: [{ queryType: 'upload', refId: 'A' }], - } as DataQueryRequest) + ds.query({ targets: [{ queryType: 'upload', refId: 'A' }] } as DataQueryRequest) ); expect(response.error?.message).toBe('The JSON file uploaded is not in a valid Jaeger format'); expect(response.data.length).toBe(0); }); - it('should return search results when the query type is search', async () => { - const mock = setupFetchMock({ data: [testResponse] }); + it('should return search results when query type is search', async () => { + setupQueryMock('search'); const ds = new JaegerDatasource(defaultSettings); const response = await lastValueFrom( ds.query({ @@ -167,185 +62,66 @@ describe('JaegerDatasource', () => { targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', operation: '/api/services' }], }) ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces?service=jaeger-query&operation=%2Fapi%2Fservices&${defaultSearchRangeParams}&lookback=custom`, - }); + expect(response.data[0].meta.preferredVisualisationType).toBe('table'); - // Make sure that traceID field has data link configured expect(response.data[0].fields[0].config.links).toHaveLength(1); expect(response.data[0].fields[0].name).toBe('traceID'); }); - it('uses default range when no range is provided for search query,', async () => { - const mock = setupFetchMock({ data: [testResponse] }); + it('should return trace results when query type is trace', async () => { + setupQueryMock('trace'); const ds = new JaegerDatasource(defaultSettings); - const query = { - ...defaultQuery, - targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', operation: ALL_OPERATIONS_KEY }], - // set range to undefined to test default range - range: undefined, - } as unknown as DataQueryRequest; + const response = await lastValueFrom( + ds.query({ ...defaultQuery, targets: [{ queryType: undefined, refId: 'a', query: '12345' }] }) + ); - ds.query(query); - expect(mock).toHaveBeenCalledWith({ - // Check that query has time range from 6 hours ago to now (default range) - url: `${defaultSettings.url}/api/traces?service=jaeger-query&start=1704085200000000&end=1704106800000000&lookback=custom`, - }); + expect(response.data[0].meta.preferredVisualisationType).toBe('trace'); + expect(response.data[0].fields.length).toBe(7); }); +}); + +describe('node graph functionality', () => { + it('should include node graph frames when nodeGraph is enabled for trace queries', async () => { + const settingsWithNodeGraph = { + ...defaultSettings, + jsonData: { + ...defaultSettings.jsonData, + nodeGraph: { enabled: true }, + }, + }; + + const ds = new JaegerDatasource(settingsWithNodeGraph); + setupQueryMock('trace'); - it('should show the correct error message if no service name is selected', async () => { - const ds = new JaegerDatasource(defaultSettings); const response = await lastValueFrom( ds.query({ ...defaultQuery, - targets: [{ queryType: 'search', refId: 'a', service: undefined, operation: '/api/services' }], - }) - ); - expect(response.error?.message).toBe('You must select a service.'); - }); - - it('should remove operation from the query when all is selected', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - await lastValueFrom( - ds.query({ - ...defaultQuery, - targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', operation: ALL_OPERATIONS_KEY }], - }) - ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces?service=jaeger-query&${defaultSearchRangeParams}&lookback=custom`, - }); - }); - - it('should convert tags from logfmt format to an object', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - await lastValueFrom( - ds.query({ - ...defaultQuery, - targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', tags: 'error=true' }], - }) - ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces?service=jaeger-query&tags=%7B%22error%22%3A%22true%22%7D&${defaultSearchRangeParams}&lookback=custom`, - }); - }); - - it('should resolve templates in traceID', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - - await lastValueFrom( - ds.query({ - ...defaultQuery, - scopedVars: { - $traceid: { - text: 'traceid', - value: '5311b0dd0ca8df3463df93c99cb805a6', - }, - }, targets: [ { - query: '$traceid', + query: '12345', refId: '1', }, ], }) ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces/5311b0dd0ca8df3463df93c99cb805a6`, - }); + + expect(response.data.length).toBe(3); }); - it('should resolve templates in tags', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - await lastValueFrom( + it('should exclude node graph frames when nodeGraph is disabled for trace queries', async () => { + const settingsWithoutNodeGraph = { + ...defaultSettings, + jsonData: { + ...defaultSettings.jsonData, + nodeGraph: { enabled: false }, + }, + }; + + const ds = new JaegerDatasource(settingsWithoutNodeGraph); + setupQueryMock('trace'); + + const response = await lastValueFrom( ds.query({ - ...defaultQuery, - scopedVars: { - 'error=$error': { - text: 'error', - value: 'error=true', - }, - }, - targets: [{ queryType: 'search', refId: 'a', service: 'jaeger-query', tags: 'error=$error' }], - }) - ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces?service=jaeger-query&tags=%7B%22error%22%3A%22true%22%7D&${defaultSearchRangeParams}&lookback=custom`, - }); - }); - - it('should interpolate variables correctly', async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - const text = 'interpolationText'; - await lastValueFrom( - ds.query({ - ...defaultQuery, - scopedVars: { - $interpolationVar: { - text: text, - value: text, - }, - }, - targets: [ - { - queryType: 'search', - refId: 'a', - service: '$interpolationVar', - operation: '$interpolationVar', - minDuration: '$interpolationVar', - maxDuration: '$interpolationVar', - }, - ], - }) - ); - expect(mock).toHaveBeenCalledWith({ - url: `${defaultSettings.url}/api/traces?service=interpolationText&operation=interpolationText&minDuration=interpolationText&maxDuration=interpolationText&${defaultSearchRangeParams}&lookback=custom`, - }); - }); - - describe('when jaegerBackendMigration feature toggle is enabled', () => { - let originalFeatureToggleValue: boolean | undefined; - - beforeEach(() => { - originalFeatureToggleValue = config.featureToggles.jaegerBackendMigration; - config.featureToggles.jaegerBackendMigration = true; - }); - - afterEach(() => { - config.featureToggles.jaegerBackendMigration = originalFeatureToggleValue; - }); - - it('should add node graph frames to response when nodeGraph is enabled and query is a trace ID query', async () => { - // Create a datasource with nodeGraph enabled - const settings = { - ...defaultSettings, - jsonData: { - ...defaultSettings.jsonData, - nodeGraph: { enabled: true }, - }, - }; - - const ds = new JaegerDatasource(settings); - - // Mock the super.query method to return our mock response - jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { - return of({ - data: [ - { - fields: testResponseDataFrameFields, - values: testResponseDataFrameFields.values, - }, - ], - }); - }); - - // Create a query without queryType (trace ID query) - const query = { ...defaultQuery, targets: [ { @@ -353,189 +129,31 @@ describe('JaegerDatasource', () => { refId: '1', }, ], - }; + }) + ); - // Execute the query - const response = await lastValueFrom(ds.query(query)); - // Verify that the response contains the original data plus node graph frames - expect(response.data.length).toBe(3); - }); - - it('should not add node graph frames when nodeGraph is disabled', async () => { - // Create a datasource with nodeGraph disabled - const settings = { - ...defaultSettings, - jsonData: { - ...defaultSettings.jsonData, - nodeGraph: { enabled: false }, - }, - }; - - const ds = new JaegerDatasource(settings); - - // Mock the super.query method to return our mock response - jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { - return of({ - data: [ - { - fields: testResponseDataFrameFields, - values: testResponseDataFrameFields.values, - }, - ], - }); - }); - - // Create a query without queryType (trace ID query) - const query = { - ...defaultQuery, - targets: [ - { - query: '12345', - refId: '1', - }, - ], - }; - - // Execute the query - const response = await lastValueFrom(ds.query(query)); - // Verify that the response contains only the original data - expect(response.data.length).toBe(1); - expect(response.data[0].fields).toMatchObject(testResponseDataFrameFields); - }); + expect(response.data.length).toBe(1); }); }); -describe('when performing testDataSource', () => { - describe('and call succeeds', () => { - it('should return successfully', async () => { - setupFetchMock({ data: ['service1'] }); - - const ds = new JaegerDatasource(defaultSettings); - const response = await ds.testDatasource(); - expect(response.status).toEqual('success'); - expect(response.message).toBe('Data source connected and services found.'); - }); - }); - - describe('and call succeeds, but returns no services', () => { - it('should display an error', async () => { - setupFetchMock(undefined); - - const ds = new JaegerDatasource(defaultSettings); - const response = await ds.testDatasource(); - expect(response.status).toEqual('error'); - expect(response.message).toBe( - 'Data source connected, but no services received. Verify that Jaeger is configured properly.' - ); - }); - }); - - describe('and call returns error with message', () => { - it('should return the formatted error', async () => { - setupFetchMock( - undefined, - throwError({ - statusText: 'Not found', - status: 404, - data: { - message: '404 page not found', - }, - }) - ); - - const ds = new JaegerDatasource(defaultSettings); - const response = await ds.testDatasource(); - expect(response.status).toEqual('error'); - expect(response.message).toBe('Jaeger: Not found. 404. 404 page not found'); - }); - }); - - describe('and call returns error without message', () => { - it('should return JSON error', async () => { - setupFetchMock( - undefined, - throwError({ - statusText: 'Bad gateway', - status: 502, - data: { - errors: ['Could not connect to Jaeger backend'], - }, - }) - ); - - const ds = new JaegerDatasource(defaultSettings); - const response = await ds.testDatasource(); - expect(response.status).toEqual('error'); - expect(response.message).toBe('Jaeger: Bad gateway. 502. {"errors":["Could not connect to Jaeger backend"]}'); - }); - }); -}); - -describe('Test behavior with unmocked time', () => { - // Tolerance for checking timestamps. - // Using a lower number seems to cause flaky tests. - const numDigits = -4; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('getTimeRange()', async () => { +describe('time range', () => { + it('should calculate correct time range', async () => { const ds = new JaegerDatasource(defaultSettings); const timeRange = ds.getTimeRange(); const now = Date.now(); - expect(timeRange.end).toBeCloseTo(now * 1000, numDigits); - expect(timeRange.start).toBeCloseTo((now - 6 * 3600 * 1000) * 1000, numDigits); - }); - - it("call for `query()` when `queryType === 'dependencyGraph'`", async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - const now = Date.now(); - - ds.query({ ...defaultQuery, targets: [{ queryType: 'dependencyGraph', refId: '1' }] }); - - const url = mock.mock.calls[0][0].url; - const endTsMatch = url.match(/endTs=(\d+)/); - expect(endTsMatch).not.toBeNull(); - expect(parseInt(endTsMatch![1], 10)).toBeCloseTo(now, numDigits); - - const lookbackMatch = url.match(/lookback=(\d+)/); - expect(lookbackMatch).not.toBeNull(); - expect(parseInt(lookbackMatch![1], 10)).toBeCloseTo(3600000, -1); // due to rounding, the least significant digit is not reliable - }); - - it("call for `query()` when `queryType === 'dependencyGraph'`, using default range", async () => { - const mock = setupFetchMock({ data: [testResponse] }); - const ds = new JaegerDatasource(defaultSettings); - const now = Date.now(); - const query = JSON.parse(JSON.stringify(defaultQuery)); - // @ts-ignore - query.range = undefined; - - ds.query({ ...query, targets: [{ queryType: 'dependencyGraph', refId: '1' }] }); - - const url = mock.mock.calls[0][0].url; - const endTsMatch = url.match(/endTs=(\d+)/); - expect(endTsMatch).not.toBeNull(); - expect(parseInt(endTsMatch![1], 10)).toBeCloseTo(now, numDigits); - - const lookbackMatch = url.match(/lookback=(\d+)/); - expect(lookbackMatch).not.toBeNull(); - expect(parseInt(lookbackMatch![1], 10)).toBeCloseTo(21600000, -1); + expect(timeRange.end).toBeCloseTo(now * 1000, -4); + expect(timeRange.start).toBeCloseTo((now - 6 * 3600 * 1000) * 1000, -4); }); }); -function setupFetchMock(response: unknown, mock?: ReturnType) { - const defaultMock = () => mock ?? of(createFetchResponse(response)); - - const fetchMock = jest.spyOn(backendSrv, 'fetch'); - fetchMock.mockImplementation(defaultMock); - return fetchMock; +function setupQueryMock(type: 'trace' | 'search') { + return jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => { + if (type === 'search') { + return of(mockSearchResponse); + } else { + return of(mockTraceResponse); + } + }); } const defaultSettings: DataSourceInstanceSettings = { @@ -560,3 +178,25 @@ const defaultSettings: DataSourceInstanceSettings = { }, readOnly: false, }; + +const defaultQuery: DataQueryRequest = { + requestId: '1', + interval: '0', + intervalMs: 10, + panelId: 0, + scopedVars: {}, + range: { + from: dateTime().subtract(1, 'h'), + to: dateTime(), + raw: { from: '1h', to: 'now' }, + }, + timezone: 'browser', + app: 'explore', + startTime: 0, + targets: [ + { + query: '12345', + refId: '1', + }, + ], +}; diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 6aac84c3ca0..11c6a8a3f3b 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -1,6 +1,5 @@ -import { identity, omit, pick, pickBy } from 'lodash'; -import { lastValueFrom, Observable, of } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { DataQueryRequest, @@ -14,25 +13,14 @@ import { MutableDataFrame, ScopedVars, toDataFrame, - urlUtil, } from '@grafana/data'; import { createNodeGraphFrames, NodeGraphOptions, SpanBarOptions } from '@grafana/o11y-ds-frontend'; -import { - BackendSrvRequest, - config, - DataSourceWithBackend, - getBackendSrv, - getTemplateSrv, - TemplateSrv, -} from '@grafana/runtime'; +import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime'; -import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { TraceIdTimeParamsOptions } from './configuration/TraceIdTimeParams'; -import { mapJaegerDependenciesResponse } from './dependencyGraphTransform'; import { createGraphFrames } from './graphTransform'; -import { createTableFrame, createTraceFrame } from './responseTransform'; +import { createTraceFrame } from './responseTransform'; import { JaegerQuery } from './types'; -import { convertTagsLogfmt } from './util'; export interface JaegerJsonData extends DataSourceJsonData { nodeGraph?: NodeGraphOptions; @@ -45,7 +33,7 @@ export class JaegerDatasource extends DataSourceWithBackend, + instanceSettings: DataSourceInstanceSettings, private readonly templateSrv: TemplateSrv = getTemplateSrv() ) { super(instanceSettings); @@ -53,25 +41,14 @@ export class JaegerDatasource extends DataSourceWithBackend) { - if (config.featureToggles.jaegerBackendMigration) { - return await this.getResource(url, params); - } - - const res = await lastValueFrom(this._request('/api/' + url, params, { hideFromInspector: true })); - return res.data.data; + return await this.getResource(url, params); } isSearchFormValid(query: JaegerQuery): boolean { return !!query.service; } - /** - * Migrated to backend with feature toggle `jaegerBackendMigration` - */ query(options: DataQueryRequest): Observable { // At this moment we expect only one target. In case we somehow change the UI to be able to show multiple // traces at one we need to change this. @@ -80,55 +57,6 @@ export class JaegerDatasource extends DataSourceWithBackend { - // If the node graph is enabled and the query is a trace ID query, add the node graph frames to the response - if (this.nodeGraph?.enabled && !target.queryType) { - return addNodeGraphFramesToResponse(response); - } - return response; - }) - ); - } - - // Use the internal Jaeger /dependencies API for rendering the dependency graph. - if (target.queryType === 'dependencyGraph') { - const timeRange = options.range ?? getDefaultTimeRange(); - const endTs = getTime(timeRange.to, true) / 1000; - const lookback = endTs - getTime(timeRange.from, false) / 1000; - return this._request('/api/dependencies', { endTs, lookback }).pipe(map(mapJaegerDependenciesResponse)); - } - - if (target.queryType === 'search' && !this.isSearchFormValid(target)) { - return of({ error: { message: 'You must select a service.' }, data: [] }); - } - - let { start, end } = this.getTimeRange(options.range); - - if (target.queryType !== 'search' && target.query) { - let url = `/api/traces/${encodeURIComponent(this.templateSrv.replace(target.query.trim(), options.scopedVars))}`; - if (this.traceIdTimeParams) { - url += `?start=${start}&end=${end}`; - } - - return this._request(url).pipe( - map((response) => { - const traceData = response?.data?.data?.[0]; - if (!traceData) { - return { data: [emptyTraceDataFrame] }; - } - let data = [createTraceFrame(traceData)]; - if (this.nodeGraph?.enabled) { - data.push(...createGraphFrames(traceData)); - } - return { - data, - }; - }) - ); - } - if (target.queryType === 'upload') { if (!this.uploadedJson) { return of({ data: [] }); @@ -146,38 +74,13 @@ export class JaegerDatasource extends DataSourceWithBackend { - return { - data: [createTableFrame(response.data.data, this.instanceSettings)], - }; + // If the node graph is enabled and the query is a trace ID query, add the node graph frames to the response + if (this.nodeGraph?.enabled && !target.queryType) { + return addNodeGraphFramesToResponse(response); + } + return response; }) ); } @@ -215,49 +118,8 @@ export class JaegerDatasource extends DataSourceWithBackend { - const values = res?.data?.data || []; - const testResult = - values.length > 0 - ? { status: 'success', message: 'Data source connected and services found.' } - : { - status: 'error', - message: - 'Data source connected, but no services received. Verify that Jaeger is configured properly.', - }; - return testResult; - }), - catchError((err) => { - let message = 'Jaeger: '; - if (err.statusText) { - message += err.statusText; - } else { - message += 'Cannot connect to Jaeger'; - } - - if (err.status) { - message += `. ${err.status}`; - } - - if (err.data && err.data.message) { - message += `. ${err.data.message}`; - } else if (err.data) { - message += `. ${JSON.stringify(err.data)}`; - } - return of({ status: 'error', message: message }); - }) - ) - ); + return await super.testDatasource(); } getTimeRange(range = getDefaultTimeRange()): { start: number; end: number } { @@ -270,21 +132,6 @@ export class JaegerDatasource extends DataSourceWithBackend, - options?: Partial - ): Observable> { - const params = data ? urlUtil.serializeParams(data) : ''; - const url = `${this.instanceSettings.url}${apiUrl}${params.length ? `?${params}` : ''}`; - const req = { - ...options, - url, - }; - - return getBackendSrv().fetch(req); - } } function getTime(date: string | DateTime, roundUp: boolean) { diff --git a/public/app/plugins/datasource/jaeger/mockSearchResponse.json b/public/app/plugins/datasource/jaeger/mockSearchResponse.json new file mode 100644 index 00000000000..7ce11be4aea --- /dev/null +++ b/public/app/plugins/datasource/jaeger/mockSearchResponse.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "fields": [ + { + "name": "traceID", + "values": ["test-trace-id"], + "config": { + "displayName": "Trace ID", + "links": [ + { + "title": "Trace: ${__value.raw}", + "internal": { + "query": { + "query": "${__value.raw}" + }, + "datasourceUid": "test-uid", + "datasourceName": "test-name" + } + } + ] + } + }, + { + "name": "traceName", + "values": ["test-service: test-operation"], + "config": { + "displayName": "Trace name" + } + }, + { + "name": "startTime", + "values": [1605873894680], + "config": { + "displayName": "Start time" + } + }, + { + "name": "duration", + "values": [1000], + "config": { + "displayName": "Duration", + "unit": "µs" + } + } + ], + "meta": { + "preferredVisualisationType": "table" + } + } + ] +} diff --git a/public/app/plugins/datasource/jaeger/mockTraceResponse.json b/public/app/plugins/datasource/jaeger/mockTraceResponse.json new file mode 100644 index 00000000000..08867e2197f --- /dev/null +++ b/public/app/plugins/datasource/jaeger/mockTraceResponse.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "fields": [ + { "name": "traceID", "values": ["3fa414edcef6ad90", "3fa414edcef6ad90"] }, + { "name": "spanID", "values": ["3fa414edcef6ad90", "0f5c1808567e4403"] }, + { "name": "parentSpanID", "values": [null, "3fa414edcef6ad90"] }, + { "name": "operationName", "values": ["HTTP GET - api_traces_traceid", "/tempopb.Querier/FindTraceByID"] }, + { "name": "serviceName", "values": ["tempo-querier", "tempo-querier"] }, + { "name": "startTime", "values": [1605873894680.409, 1605873894680.587] }, + { "name": "duration", "values": [1049.141, 1.847] } + ], + "meta": { + "preferredVisualisationType": "trace", + "custom": { + "traceFormat": "jaeger" + } + } + } + ] +}