diff --git a/docs/sources/datasources/jaeger.md b/docs/sources/datasources/jaeger.md index 4139e33f219..0f4509aa46c 100644 --- a/docs/sources/datasources/jaeger.md +++ b/docs/sources/datasources/jaeger.md @@ -39,6 +39,12 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t ![Trace to logs settings](/static/img/docs/explore/trace-to-logs-settings-8-2.png 'Screenshot of the trace to logs settings') +### Node Graph + +This is a configuration for the beta Node Graph visualization. The Node Graph is shown after the trace view is loaded and is disabled by default. + +-- **Enable Node Graph -** Enables the Node Graph visualization. + ## Query traces You can query and display traces from Jaeger via [Explore]({{< relref "../explore/_index.md" >}}). diff --git a/docs/sources/datasources/tempo.md b/docs/sources/datasources/tempo.md index 62993fb6180..3886ee6640b 100644 --- a/docs/sources/datasources/tempo.md +++ b/docs/sources/datasources/tempo.md @@ -38,6 +38,12 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t ![Trace to logs settings](/static/img/docs/explore/trace-to-logs-settings-8-2.png 'Screenshot of the trace to logs settings') +### Node Graph + +This is a configuration for the beta Node Graph visualization. The Node Graph is shown after the trace view is loaded and is disabled by default. + +-- **Enable Node Graph -** Enables the Node Graph visualization. + ## Query traces You can query and display traces from Tempo via [Explore]({{< relref "../explore/_index.md" >}}). diff --git a/docs/sources/datasources/zipkin.md b/docs/sources/datasources/zipkin.md index 85504e86eeb..1b1f4b6df86 100644 --- a/docs/sources/datasources/zipkin.md +++ b/docs/sources/datasources/zipkin.md @@ -39,6 +39,12 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t ![Trace to logs settings](/static/img/docs/explore/trace-to-logs-settings-8-2.png 'Screenshot of the trace to logs settings') +### Node Graph + +This is a configuration for the beta Node Graph visualization. The Node Graph is shown after the trace view is loaded and is disabled by default. + +-- **Enable Node Graph -** Enables the Node Graph visualization. + ## Query traces Querying and displaying traces from Zipkin is available via [Explore]({{< relref "../explore" >}}). diff --git a/public/app/core/components/NodeGraphSettings.tsx b/public/app/core/components/NodeGraphSettings.tsx new file mode 100644 index 00000000000..9a2979474d5 --- /dev/null +++ b/public/app/core/components/NodeGraphSettings.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { css } from '@emotion/css'; +import { + DataSourceJsonData, + DataSourcePluginOptionsEditorProps, + GrafanaTheme, + updateDatasourcePluginJsonDataOption, +} from '@grafana/data'; +import { InlineField, InlineFieldRow, InlineSwitch, useStyles } from '@grafana/ui'; + +export interface NodeGraphOptions { + enabled?: boolean; +} + +export interface NodeGraphData extends DataSourceJsonData { + nodeGraph?: NodeGraphOptions; +} + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export function NodeGraphSettings({ options, onOptionsChange }: Props) { + const styles = useStyles(getStyles); + + return ( +
+

Node Graph

+ + + ) => + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'nodeGraph', { + ...options.jsonData.nodeGraph, + enabled: event.currentTarget.checked, + }) + } + /> + + +
+ ); +} + +const getStyles = (theme: GrafanaTheme) => ({ + container: css` + label: container; + width: 100%; + `, + row: css` + label: row; + align-items: baseline; + `, +}); diff --git a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx index 29d02ee1691..6ce9e60fd92 100644 --- a/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/jaeger/components/ConfigEditor.tsx @@ -1,5 +1,6 @@ import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; import { DataSourceHttpSettings } from '@grafana/ui'; +import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; import React from 'react'; @@ -15,7 +16,12 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => { onChange={onOptionsChange} /> - +
+ +
+
+ +
); }; diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 4a33ec28c20..dd7c3a2443c 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -4,7 +4,7 @@ import { DataQueryRequest, DataSourceInstanceSettings, dateTime, FieldType, Plug import { backendSrv } from 'app/core/services/backend_srv'; import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { ALL_OPERATIONS_KEY } from './components/SearchForm'; -import { JaegerDatasource } from './datasource'; +import { JaegerDatasource, JaegerJsonData } from './datasource'; import mockJson from './mockJsonResponse.json'; import { testResponse, @@ -222,7 +222,7 @@ function setupFetchMock(response: any, mock?: any) { return fetchMock; } -const defaultSettings: DataSourceInstanceSettings = { +const defaultSettings: DataSourceInstanceSettings = { id: 0, uid: '0', type: 'tracing', @@ -237,7 +237,11 @@ const defaultSettings: DataSourceInstanceSettings = { module: '', baseUrl: '', }, - jsonData: {}, + jsonData: { + nodeGraph: { + enabled: true, + }, + }, }; const defaultQuery: DataQueryRequest = { diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 85c776bebdc..87586a1c303 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -6,6 +6,7 @@ import { DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, + DataSourceJsonData, dateMath, DateTime, FieldType, @@ -20,11 +21,21 @@ import { createGraphFrames } from './graphTransform'; import { JaegerQuery } from './types'; import { convertTagsLogfmt } from './util'; import { ALL_OPERATIONS_KEY } from './components/SearchForm'; +import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings'; -export class JaegerDatasource extends DataSourceApi { +export interface JaegerJsonData extends DataSourceJsonData { + nodeGraph?: NodeGraphOptions; +} + +export class JaegerDatasource extends DataSourceApi { uploadedJson: string | ArrayBuffer | null = null; - constructor(private instanceSettings: DataSourceInstanceSettings, private readonly timeSrv: TimeSrv = getTimeSrv()) { + nodeGraph?: NodeGraphOptions; + constructor( + private instanceSettings: DataSourceInstanceSettings, + private readonly timeSrv: TimeSrv = getTimeSrv() + ) { super(instanceSettings); + this.nodeGraph = instanceSettings.jsonData.nodeGraph; } async metadataRequest(url: string, params?: Record): Promise { @@ -47,8 +58,12 @@ export class JaegerDatasource extends DataSourceApi { if (!traceData) { return { data: [emptyTraceDataFrame] }; } + let data = [createTraceFrame(traceData)]; + if (this.nodeGraph?.enabled) { + data.push(...createGraphFrames(traceData)); + } return { - data: [createTraceFrame(traceData), ...createGraphFrames(traceData)], + data, }; }) ); @@ -61,7 +76,11 @@ export class JaegerDatasource extends DataSourceApi { try { const traceData = JSON.parse(this.uploadedJson as string).data[0]; - return of({ data: [createTraceFrame(traceData), ...createGraphFrames(traceData)] }); + let data = [createTraceFrame(traceData)]; + if (this.nodeGraph?.enabled) { + data.push(...createGraphFrames(traceData)); + } + return of({ data }); } catch (error) { return of({ error: { message: 'JSON is not valid Jaeger format' }, data: [] }); } diff --git a/public/app/plugins/datasource/tempo/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx similarity index 85% rename from public/app/plugins/datasource/tempo/ConfigEditor.tsx rename to public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx index deb596c20e5..7fffc672a81 100644 --- a/public/app/plugins/datasource/tempo/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx @@ -5,6 +5,7 @@ import React from 'react'; import { ServiceMapSettings } from './ServiceMapSettings'; import { config } from '@grafana/runtime'; import { SearchSettings } from './SearchSettings'; +import { NodeGraphSettings } from 'app/core/components/NodeGraphSettings'; export type Props = DataSourcePluginOptionsEditorProps; @@ -31,6 +32,9 @@ export const ConfigEditor: React.FC = ({ options, onOptionsChange }) => { )} +
+ +
); }; diff --git a/public/app/plugins/datasource/tempo/SearchSettings.tsx b/public/app/plugins/datasource/tempo/configuration/SearchSettings.tsx similarity index 96% rename from public/app/plugins/datasource/tempo/SearchSettings.tsx rename to public/app/plugins/datasource/tempo/configuration/SearchSettings.tsx index 6ea9e3df021..455f6388770 100644 --- a/public/app/plugins/datasource/tempo/SearchSettings.tsx +++ b/public/app/plugins/datasource/tempo/configuration/SearchSettings.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { DataSourcePluginOptionsEditorProps, GrafanaTheme, updateDatasourcePluginJsonDataOption } from '@grafana/data'; import { InlineField, InlineFieldRow, InlineSwitch, useStyles } from '@grafana/ui'; import React from 'react'; -import { TempoJsonData } from './datasource'; +import { TempoJsonData } from '../datasource'; interface Props extends DataSourcePluginOptionsEditorProps {} diff --git a/public/app/plugins/datasource/tempo/ServiceMapSettings.tsx b/public/app/plugins/datasource/tempo/configuration/ServiceMapSettings.tsx similarity index 97% rename from public/app/plugins/datasource/tempo/ServiceMapSettings.tsx rename to public/app/plugins/datasource/tempo/configuration/ServiceMapSettings.tsx index 285becb76cc..593f1140d73 100644 --- a/public/app/plugins/datasource/tempo/ServiceMapSettings.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ServiceMapSettings.tsx @@ -3,7 +3,7 @@ import { DataSourcePluginOptionsEditorProps, GrafanaTheme, updateDatasourcePlugi import { DataSourcePicker } from '@grafana/runtime'; import { Button, InlineField, InlineFieldRow, useStyles } from '@grafana/ui'; import React from 'react'; -import { TempoJsonData } from './datasource'; +import { TempoJsonData } from '../datasource'; interface Props extends DataSourcePluginOptionsEditorProps {} diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index 9d0dd14b8e7..bbc256b23e7 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -12,7 +12,7 @@ import { import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { BackendDataSourceResponse, FetchResponse, setBackendSrv, setDataSourceSrv } from '@grafana/runtime'; -import { DEFAULT_LIMIT, TempoDatasource, TempoQuery } from './datasource'; +import { DEFAULT_LIMIT, TempoJsonData, TempoDatasource, TempoQuery } from './datasource'; import mockJson from './mockJsonResponse.json'; describe('Tempo data source', () => { @@ -233,7 +233,7 @@ function setupBackendSrv(frame: DataFrame) { } as any); } -const defaultSettings: DataSourceInstanceSettings = { +const defaultSettings: DataSourceInstanceSettings = { id: 0, uid: '0', type: 'tracing', @@ -247,7 +247,11 @@ const defaultSettings: DataSourceInstanceSettings = { module: '', baseUrl: '', }, - jsonData: {}, + jsonData: { + nodeGraph: { + enabled: true, + }, + }, }; const totalsPromMetric = new MutableDataFrame({ diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 443a133f228..a0a344cc9ff 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -27,6 +27,7 @@ import { createTableFrameFromSearch, } from './resultTransformer'; import { tokenizer } from './syntax'; +import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings'; // search = Loki search, nativeSearch = Tempo search for backwards compatibility export type TempoQueryType = 'search' | 'traceId' | 'serviceMap' | 'upload' | 'nativeSearch'; @@ -39,6 +40,7 @@ export interface TempoJsonData extends DataSourceJsonData { search?: { hide?: boolean; }; + nodeGraph?: NodeGraphOptions; } export type TempoQuery = { @@ -64,6 +66,7 @@ export class TempoDatasource extends DataSourceWithBackend) { @@ -71,6 +74,7 @@ export class TempoDatasource extends DataSourceWithBackend): Observable { @@ -137,7 +141,7 @@ export class TempoDatasource extends DataSourceWithBackend = ({ options, onOptionsChange }) => { onChange={onOptionsChange} /> - +
+ +
+ +
+ +
); }; diff --git a/public/app/plugins/datasource/zipkin/datasource.ts b/public/app/plugins/datasource/zipkin/datasource.ts index b5a3364f642..2c814f6c4ef 100644 --- a/public/app/plugins/datasource/zipkin/datasource.ts +++ b/public/app/plugins/datasource/zipkin/datasource.ts @@ -6,6 +6,7 @@ import { DataQueryResponse, DataSourceApi, DataSourceInstanceSettings, + DataSourceJsonData, FieldType, MutableDataFrame, } from '@grafana/data'; @@ -15,11 +16,18 @@ import { apiPrefix } from './constants'; import { ZipkinQuery, ZipkinSpan } from './types'; import { createGraphFrames } from './utils/graphTransform'; import { transformResponse } from './utils/transforms'; +import { NodeGraphOptions } from 'app/core/components/NodeGraphSettings'; -export class ZipkinDatasource extends DataSourceApi { +export interface ZipkinJsonData extends DataSourceJsonData { + nodeGraph?: NodeGraphOptions; +} + +export class ZipkinDatasource extends DataSourceApi { uploadedJson: string | ArrayBuffer | null = null; - constructor(private instanceSettings: DataSourceInstanceSettings) { + nodeGraph?: NodeGraphOptions; + constructor(private instanceSettings: DataSourceInstanceSettings) { super(instanceSettings); + this.nodeGraph = instanceSettings.jsonData.nodeGraph; } query(options: DataQueryRequest): Observable { @@ -31,7 +39,7 @@ export class ZipkinDatasource extends DataSourceApi { try { const traceData = JSON.parse(this.uploadedJson as string); - return of(responseToDataQueryResponse({ data: traceData })); + return of(responseToDataQueryResponse({ data: traceData }, this.nodeGraph?.enabled)); } catch (error) { return of({ error: { message: 'JSON is not valid Zipkin format' }, data: [] }); } @@ -39,7 +47,7 @@ export class ZipkinDatasource extends DataSourceApi { if (target.query) { return this.request(`${apiPrefix}/trace/${encodeURIComponent(target.query)}`).pipe( - map(responseToDataQueryResponse) + map((res) => responseToDataQueryResponse(res, this.nodeGraph?.enabled)) ); } return of(emptyDataQueryResponse); @@ -75,9 +83,13 @@ export class ZipkinDatasource extends DataSourceApi { } } -function responseToDataQueryResponse(response: { data: ZipkinSpan[] }): DataQueryResponse { +function responseToDataQueryResponse(response: { data: ZipkinSpan[] }, nodeGraph = false): DataQueryResponse { + let data = response?.data ? [transformResponse(response?.data)] : []; + if (nodeGraph) { + data.push(...createGraphFrames(response?.data)); + } return { - data: response?.data ? [transformResponse(response?.data), ...createGraphFrames(response?.data)] : [], + data, }; }