Files
grafana/public/app/core/components/TraceToLogs/TraceToLogsSettings.tsx
Joey 0e90dfcc9c Tracing: Upgrade tracing data source configuration editors (#68764)
* Add tracing config sub sections

* Export common sections and update divider in additional settings section

* Max width and margin bottom

* Add feature name to config link

* Update SpanBarSettings

* remove import
2023-06-01 15:52:40 +01:00

269 lines
8.3 KiB
TypeScript

import { css } from '@emotion/css';
import React, { useCallback, useMemo } from 'react';
import { DataSourceJsonData, DataSourceInstanceSettings, DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { ConfigSection } from '@grafana/experimental';
import { DataSourcePicker } from '@grafana/runtime';
import { InlineField, InlineFieldRow, Input, InlineSwitch } from '@grafana/ui';
import { ConfigDescriptionLink } from 'app/core/components/ConfigDescriptionLink';
import { TagMappingInput } from './TagMappingInput';
// @deprecated use getTraceToLogsOptions to get the v2 version of this config from jsonData
export interface TraceToLogsOptions {
datasourceUid?: string;
tags?: string[];
mappedTags?: Array<{ key: string; value?: string }>;
mapTagNamesEnabled?: boolean;
spanStartTimeShift?: string;
spanEndTimeShift?: string;
filterByTraceID?: boolean;
filterBySpanID?: boolean;
lokiSearch?: boolean; // legacy
}
export interface TraceToLogsOptionsV2 {
datasourceUid?: string;
tags?: Array<{ key: string; value?: string }>;
spanStartTimeShift?: string;
spanEndTimeShift?: string;
filterByTraceID?: boolean;
filterBySpanID?: boolean;
query?: string;
customQuery: boolean;
}
export interface TraceToLogsData extends DataSourceJsonData {
tracesToLogs?: TraceToLogsOptions;
tracesToLogsV2?: TraceToLogsOptionsV2;
}
/**
* Gets new version of the traceToLogs config from the json data either returning directly or transforming the old
* version to new and returning that.
*/
export function getTraceToLogsOptions(data?: TraceToLogsData): TraceToLogsOptionsV2 | undefined {
if (data?.tracesToLogsV2) {
return data.tracesToLogsV2;
}
if (!data?.tracesToLogs) {
return undefined;
}
const traceToLogs: TraceToLogsOptionsV2 = {
customQuery: false,
};
traceToLogs.datasourceUid = data.tracesToLogs.datasourceUid;
traceToLogs.tags = data.tracesToLogs.mapTagNamesEnabled
? data.tracesToLogs.mappedTags
: data.tracesToLogs.tags?.map((tag) => ({ key: tag }));
traceToLogs.filterByTraceID = data.tracesToLogs.filterByTraceID;
traceToLogs.filterBySpanID = data.tracesToLogs.filterBySpanID;
traceToLogs.spanStartTimeShift = data.tracesToLogs.spanStartTimeShift;
traceToLogs.spanEndTimeShift = data.tracesToLogs.spanEndTimeShift;
return traceToLogs;
}
interface Props extends DataSourcePluginOptionsEditorProps<TraceToLogsData> {}
export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
const supportedDataSourceTypes = [
'loki',
'elasticsearch',
'grafana-splunk-datasource', // external
'grafana-opensearch-datasource', // external
'grafana-falconlogscale-datasource', // external
'googlecloud-logging-datasource', // external
];
const traceToLogs = useMemo(
(): TraceToLogsOptionsV2 => getTraceToLogsOptions(options.jsonData) || { customQuery: false },
[options.jsonData]
);
const { query = '', tags, customQuery } = traceToLogs;
const updateTracesToLogs = useCallback(
(value: Partial<TraceToLogsOptionsV2>) => {
// Cannot use updateDatasourcePluginJsonDataOption here as we need to update 2 keys, and they would overwrite each
// other as updateDatasourcePluginJsonDataOption isn't synchronized
onOptionsChange({
...options,
jsonData: {
...options.jsonData,
tracesToLogsV2: {
...traceToLogs,
...value,
},
tracesToLogs: undefined,
},
});
},
[onOptionsChange, options, traceToLogs]
);
return (
<div className={css({ width: '100%' })}>
<InlineFieldRow>
<InlineField
tooltip="The logs data source the trace is going to navigate to"
label="Data source"
labelWidth={26}
>
<DataSourcePicker
inputId="trace-to-logs-data-source-picker"
filter={(ds) => supportedDataSourceTypes.includes(ds.type)}
current={traceToLogs.datasourceUid}
noDefault={true}
width={40}
onChange={(ds: DataSourceInstanceSettings) =>
updateTracesToLogs({
datasourceUid: ds.uid,
})
}
/>
</InlineField>
</InlineFieldRow>
<TimeRangeShift
type={'start'}
value={traceToLogs.spanStartTimeShift || ''}
onChange={(val) => updateTracesToLogs({ spanStartTimeShift: val })}
/>
<TimeRangeShift
type={'end'}
value={traceToLogs.spanEndTimeShift || ''}
onChange={(val) => updateTracesToLogs({ spanEndTimeShift: val })}
/>
<InlineFieldRow>
<InlineField
tooltip="Tags that will be used in the query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
label="Tags"
labelWidth={26}
>
<TagMappingInput values={tags ?? []} onChange={(v) => updateTracesToLogs({ tags: v })} />
</InlineField>
</InlineFieldRow>
<IdFilter
disabled={customQuery}
type={'trace'}
id={'filterByTraceID'}
value={Boolean(traceToLogs.filterByTraceID)}
onChange={(val) => updateTracesToLogs({ filterByTraceID: val })}
/>
<IdFilter
disabled={customQuery}
type={'span'}
id={'filterBySpanID'}
value={Boolean(traceToLogs.filterBySpanID)}
onChange={(val) => updateTracesToLogs({ filterBySpanID: val })}
/>
<InlineFieldRow>
<InlineField
tooltip="Use a custom query with the possibility to interpolate variables from the trace or span"
label="Use custom query"
labelWidth={26}
>
<InlineSwitch
id={'customQuerySwitch'}
value={customQuery}
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
updateTracesToLogs({ customQuery: event.currentTarget.checked })
}
/>
</InlineField>
</InlineFieldRow>
{customQuery && (
<InlineField
label="Query"
labelWidth={26}
tooltip="The query that will run when navigating from a trace to logs data source. Interpolate tags using the `$__tags` keyword"
grow
>
<Input
label="Query"
type="text"
allowFullScreen
value={query}
onChange={(e) => updateTracesToLogs({ query: e.currentTarget.value })}
/>
</InlineField>
)}
</div>
);
}
interface IdFilterProps {
type: 'trace' | 'span';
id: string;
value: boolean;
onChange: (val: boolean) => void;
disabled: boolean;
}
function IdFilter(props: IdFilterProps) {
return (
<InlineFieldRow>
<InlineField
disabled={props.disabled}
label={`Filter by ${props.type} ID`}
labelWidth={26}
grow
tooltip={`Filters logs by ${props.type} ID`}
>
<InlineSwitch
id={props.id}
value={props.value}
onChange={(event: React.SyntheticEvent<HTMLInputElement>) => props.onChange(event.currentTarget.checked)}
/>
</InlineField>
</InlineFieldRow>
);
}
interface TimeRangeShiftProps {
type: 'start' | 'end';
value: string;
onChange: (val: string) => void;
}
function TimeRangeShift(props: TimeRangeShiftProps) {
return (
<InlineFieldRow>
<InlineField
label={`Span ${props.type} time shift`}
labelWidth={26}
grow
tooltip={`Shifts the ${props.type} time of the span. Default: 0 (Time units can be used here, for example: 5s, -1m, 3h)`}
>
<Input
type="text"
placeholder="0"
width={40}
onChange={(e) => props.onChange(e.currentTarget.value)}
value={props.value}
/>
</InlineField>
</InlineFieldRow>
);
}
export const TraceToLogsSection = ({ options, onOptionsChange }: DataSourcePluginOptionsEditorProps) => {
return (
<ConfigSection
title="Trace to logs"
description={
<ConfigDescriptionLink
description="Navigate from a trace span to the selected data source's logs."
suffix={`${options.type}/#trace-to-logs`}
feature="trace to logs"
/>
}
isCollapsible={true}
isInitiallyOpen={true}
>
<TraceToLogsSettings options={options} onOptionsChange={onOptionsChange} />
</ConfigSection>
);
};