diff --git a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx index 6dcca3cea3d..35e05ae4794 100644 --- a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx @@ -23,6 +23,7 @@ import { SecureSocksProxySettings, useStyles2, Divider, Stack } from '@grafana/u import { QuerySettings } from './QuerySettings'; import { ServiceGraphSettings } from './ServiceGraphSettings'; +import { StreamingSection } from './StreamingSection'; import { TraceQLSearchSettings } from './TraceQLSearchSettings'; export type Props = DataSourcePluginOptionsEditorProps; @@ -48,8 +49,11 @@ export const ConfigEditor = ({ options, onOptionsChange }: Props) => { onChange: onOptionsChange, })} /> - + + + + diff --git a/public/app/plugins/datasource/tempo/configuration/StreamingSection.tsx b/public/app/plugins/datasource/tempo/configuration/StreamingSection.tsx new file mode 100644 index 00000000000..bba18945603 --- /dev/null +++ b/public/app/plugins/datasource/tempo/configuration/StreamingSection.tsx @@ -0,0 +1,81 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { + DataSourceJsonData, + DataSourcePluginOptionsEditorProps, + GrafanaTheme2, + updateDatasourcePluginJsonDataOption, +} from '@grafana/data'; +import { ConfigSection } from '@grafana/experimental'; +import { InlineFieldRow, InlineField, InlineSwitch, Alert, Stack, useStyles2 } from '@grafana/ui'; + +import { FeatureName, featuresToTempoVersion } from '../datasource'; + +interface StreamingOptions extends DataSourceJsonData { + streamingEnabled?: { + search?: boolean; + }; +} +interface Props extends DataSourcePluginOptionsEditorProps {} + +export const StreamingSection = ({ options, onOptionsChange }: Props) => { + const styles = useStyles2(getStyles); + return ( + +
{`Enable streaming for different Tempo features. + Currently supported only for search queries and from Tempo version ${featuresToTempoVersion[FeatureName.streaming]} onwards.`}
+ + Learn more + + + } + > + + If your Tempo instance is behind a load balancer or proxy that does not supporting gRPC or HTTP2, streaming will + probably not work and should be disabled. + + + + ) => { + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'streamingEnabled', { + ...options.jsonData.streamingEnabled, + search: event.currentTarget.checked, + }); + }} + /> + + +
+ ); +}; +const getStyles = (theme: GrafanaTheme2) => { + return { + a: css({ + color: theme.colors.text.link, + textDecoration: 'underline', + marginLeft: '5px', + '&:hover': { + textDecoration: 'none', + }, + }), + }; +}; diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index 771220631ee..43dbb94b2a7 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -75,7 +75,7 @@ describe('Tempo data source', () => { const range = { from: dateTime(new Date(2022, 8, 13, 16, 0, 0, 0)), to: dateTime(new Date(2022, 8, 13, 16, 15, 0, 0)), - raw: { from: '15m', to: 'now' }, + raw: { from: 'now-15m', to: 'now' }, }; const traceqlQuery = { targets: [{ refId: 'refid1', queryType: 'traceql', query: '{}' }], @@ -365,9 +365,14 @@ describe('Tempo data source', () => { describe('test the testDatasource function', () => { it('should return a success msg if response.ok is true', async () => { mockObservable = () => of({ ok: true }); + const handleStreamingSearch = jest + .spyOn(TempoDatasource.prototype, 'handleStreamingSearch') + .mockImplementation(() => of({ data: [] })); + const ds = new TempoDatasource(defaultSettings); const response = await ds.testDatasource(); expect(response.status).toBe('success'); + expect(handleStreamingSearch).toHaveBeenCalled(); }); }); @@ -389,7 +394,7 @@ describe('Tempo data source', () => { const range = { from: dateTime(new Date(2022, 8, 13, 16, 0, 0, 0)), to: dateTime(new Date(2022, 8, 13, 16, 15, 0, 0)), - raw: { from: '15m', to: 'now' }, + raw: { from: 'now-15m', to: 'now' }, }; const request = ds.traceIdQueryRequest( @@ -434,7 +439,7 @@ describe('Tempo data source', () => { range: { from: dateTime(new Date(2022, 8, 13, 16, 0, 0, 0)), to: dateTime(new Date(2022, 8, 13, 16, 15, 0, 0)), - raw: { from: '15m', to: 'now' }, + raw: { from: 'now-15m', to: 'now' }, }, }, [{ refId: 'refid1', queryType: 'traceql', query: '' } as TempoQuery] @@ -1264,6 +1269,9 @@ export const defaultSettings: DataSourceInstanceSettings = { nodeGraph: { enabled: true, }, + streamingEnabled: { + search: true, + }, }, readOnly: false, }; diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 93db409c37f..ae754a0c2c7 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -1,5 +1,5 @@ import { groupBy } from 'lodash'; -import { EMPTY, from, lastValueFrom, merge, Observable, of } from 'rxjs'; +import { EMPTY, forkJoin, from, lastValueFrom, merge, Observable, of } from 'rxjs'; import { catchError, concatMap, map, mergeMap, toArray } from 'rxjs/operators'; import semver from 'semver'; @@ -41,7 +41,7 @@ import { } from './SearchTraceQLEditor/utils'; import { TempoVariableQuery, TempoVariableQueryType } from './VariableQueryEditor'; import { PrometheusDatasource, PromQuery } from './_importedDependencies/datasources/prometheus/types'; -import { TraceqlFilter, TraceqlSearchScope } from './dataquery.gen'; +import { SearchTableType, TraceqlFilter, TraceqlSearchScope } from './dataquery.gen'; import { defaultTableFilter, durationMetric, @@ -69,7 +69,7 @@ import { TempoVariableSupport } from './variables'; export const DEFAULT_LIMIT = 20; export const DEFAULT_SPSS = 3; // spans per span set -enum FeatureName { +export enum FeatureName { streaming = 'streaming', } @@ -77,7 +77,7 @@ enum FeatureName { ** feature available. If the running Tempo instance on the user's backend is older than the ** target version, the feature is disabled in Grafana (frontend). */ -const featuresToTempoVersion = { +export const featuresToTempoVersion = { [FeatureName.streaming]: '2.2.0', }; @@ -115,6 +115,10 @@ export class TempoDatasource extends DataSourceWithBackend): Observable { const subQueries: Array> = []; const filteredTargets = options.targets.filter((target) => !target.hide); @@ -317,7 +342,7 @@ export class TempoDatasource extends DataSourceWithBackend => { - if ( - config.featureToggles.traceQLStreaming && - this.isFeatureAvailable(FeatureName.streaming) && - config.liveEnabled - ) { + if (this.isStreamingSearchEnabled()) { return this.handleStreamingSearch(options, targets.traceql, queryValue); } else { return this._request('/api/search', { @@ -717,24 +734,86 @@ export class TempoDatasource extends DataSourceWithBackend { + const observables = []; + const options: BackendSrvRequest = { headers: {}, method: 'GET', url: `${this.instanceSettings.url}/api/echo`, }; - - return await lastValueFrom( + observables.push( getBackendSrv() .fetch(options) .pipe( mergeMap(() => { - return of({ status: 'success', message: 'Data source successfully connected.' }); + return of({ status: 'success', message: 'Health check succeeded' }); }), catchError((err) => { - return of({ status: 'error', message: getErrorMessage(err.data.message, 'Unable to connect with Tempo') }); + return of({ + status: 'error', + message: getErrorMessage(err.data.message, 'Unable to connect with Tempo'), + }); }) ) ); + + if (this.streamingEnabled?.search) { + const now = new Date(); + const from = new Date(now); + from.setMinutes(from.getMinutes() - 15); + observables.push( + this.handleStreamingSearch( + { + range: { + from: dateTime(from), + to: dateTime(now), + raw: { from: 'now-15m', to: 'now' }, + }, + requestId: '', + interval: '', + intervalMs: 0, + scopedVars: {}, + targets: [], + timezone: '', + app: '', + startTime: 0, + }, + [ + { + datasource: this.instanceSettings, + limit: 1, + query: '{}', + queryType: 'traceql', + refId: 'A', + tableType: SearchTableType.Traces, + filters: [], + }, + ], + '{}' + ).pipe( + mergeMap(() => { + return of({ status: 'success', message: 'Streaming test succeeded.' }); + }), + catchError((err) => { + return of({ + status: 'error', + message: getErrorMessage(err.data.message, 'Test for streaming failed, consider disabling streaming'), + }); + }) + ) + ); + } + + return await lastValueFrom( + forkJoin(observables).pipe( + mergeMap((observableResults) => { + const erroredResult = observableResults.find((result) => result.status !== 'success'); + return erroredResult + ? of(erroredResult) + : of({ status: 'success', message: 'Successfully connected to Tempo data source.' }); + }) + ) + ); } getQueryDisplayText(query: TempoQuery) { diff --git a/public/app/plugins/datasource/tempo/streaming.ts b/public/app/plugins/datasource/tempo/streaming.ts index 23eef3182ce..71c336edf2f 100644 --- a/public/app/plugins/datasource/tempo/streaming.ts +++ b/public/app/plugins/datasource/tempo/streaming.ts @@ -20,6 +20,7 @@ import { SearchStreamingState } from './dataquery.gen'; import { DEFAULT_SPSS, TempoDatasource } from './datasource'; import { formatTraceQLResponse } from './resultTransformer'; import { SearchMetrics, TempoJsonData, TempoQuery } from './types'; + function getLiveStreamKey(): string { return uuidv4(); } diff --git a/public/app/plugins/datasource/tempo/types.ts b/public/app/plugins/datasource/tempo/types.ts index 09c31e05b93..31439867edd 100644 --- a/public/app/plugins/datasource/tempo/types.ts +++ b/public/app/plugins/datasource/tempo/types.ts @@ -21,6 +21,9 @@ export interface TempoJsonData extends DataSourceJsonData { spanStartTimeShift?: string; spanEndTimeShift?: string; }; + streamingEnabled?: { + search?: boolean; + }; } export interface TempoQuery extends TempoBase {