mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo: Trace to logs custom query with interpolation (#61702)
This commit is contained in:
@@ -1,26 +1,16 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, KeyValue } from '@grafana/data';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SegmentInput, useStyles2, InlineLabel, Icon } from '@grafana/ui';
|
||||
|
||||
const EQ_WIDTH = 3; // = 24px in inline label
|
||||
|
||||
interface Props {
|
||||
values: Array<KeyValue<string>>;
|
||||
onChange: (values: Array<KeyValue<string>>) => void;
|
||||
values: Array<{ key: string; value?: string }>;
|
||||
onChange: (values: Array<{ key: string; value?: string }>) => void;
|
||||
id?: string;
|
||||
keyPlaceholder?: string;
|
||||
valuePlaceholder?: string;
|
||||
}
|
||||
|
||||
const KeyValueInput = ({
|
||||
values,
|
||||
onChange,
|
||||
id,
|
||||
keyPlaceholder = 'Key',
|
||||
valuePlaceholder = 'Value (optional)',
|
||||
}: Props) => {
|
||||
export const TagMappingInput = ({ values, onChange, id }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
@@ -30,7 +20,7 @@ const KeyValueInput = ({
|
||||
<div className={styles.pair} key={idx}>
|
||||
<SegmentInput
|
||||
id={`${id}-key-${idx}`}
|
||||
placeholder={keyPlaceholder}
|
||||
placeholder={'Tag name'}
|
||||
value={value.key}
|
||||
onChange={(e) => {
|
||||
onChange(
|
||||
@@ -43,13 +33,13 @@ const KeyValueInput = ({
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<InlineLabel aria-label="equals" className={styles.operator} width={EQ_WIDTH}>
|
||||
=
|
||||
<InlineLabel aria-label="equals" className={styles.operator}>
|
||||
as
|
||||
</InlineLabel>
|
||||
<SegmentInput
|
||||
id={`${id}-value-${idx}`}
|
||||
placeholder={valuePlaceholder}
|
||||
value={value.value}
|
||||
placeholder={'New name (optional)'}
|
||||
value={value.value || ''}
|
||||
onChange={(e) => {
|
||||
onChange(
|
||||
values.map((v, i) => {
|
||||
@@ -95,8 +85,6 @@ const KeyValueInput = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default KeyValueInput;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
@@ -110,5 +98,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
`,
|
||||
operator: css`
|
||||
color: ${theme.v1.palette.orange};
|
||||
width: auto;
|
||||
`,
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, DataSourceSettings } from '@grafana/data';
|
||||
import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
import { TraceToLogsData, TraceToLogsSettings } from './TraceToLogsSettings';
|
||||
|
||||
const defaultOptionsOldFormat: DataSourceSettings<TraceToLogsData> = {
|
||||
jsonData: {
|
||||
tracesToLogs: {
|
||||
datasourceUid: 'loki1_uid',
|
||||
tags: ['someTag'],
|
||||
mapTagNamesEnabled: false,
|
||||
spanStartTimeShift: '1m',
|
||||
spanEndTimeShift: '1m',
|
||||
filterByTraceID: true,
|
||||
filterBySpanID: true,
|
||||
},
|
||||
},
|
||||
} as unknown as DataSourceSettings<TraceToLogsData>;
|
||||
|
||||
const defaultOptionsNewFormat: DataSourceSettings<TraceToLogsData> = {
|
||||
jsonData: {
|
||||
tracesToLogsV2: {
|
||||
datasourceUid: 'loki1_uid',
|
||||
tags: [{ key: 'someTag', value: 'newName' }],
|
||||
spanStartTimeShift: '1m',
|
||||
spanEndTimeShift: '1m',
|
||||
filterByTraceID: true,
|
||||
filterBySpanID: true,
|
||||
customQuery: true,
|
||||
query: '{${__tags}}',
|
||||
},
|
||||
},
|
||||
} as unknown as DataSourceSettings<TraceToLogsData>;
|
||||
|
||||
const lokiSettings = {
|
||||
uid: 'loki1_uid',
|
||||
name: 'loki1',
|
||||
type: 'loki',
|
||||
meta: { info: { logos: { small: '' } } },
|
||||
} as unknown as DataSourceInstanceSettings;
|
||||
|
||||
describe('TraceToLogsSettings', () => {
|
||||
beforeAll(() => {
|
||||
setDataSourceSrv({
|
||||
getList() {
|
||||
return [lokiSettings];
|
||||
},
|
||||
getInstanceSettings() {
|
||||
return lokiSettings;
|
||||
},
|
||||
} as unknown as DataSourceSrv);
|
||||
});
|
||||
|
||||
it('should render old format without error', () => {
|
||||
expect(() =>
|
||||
render(<TraceToLogsSettings options={defaultOptionsOldFormat} onOptionsChange={() => {}} />)
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should render new format without error', () => {
|
||||
expect(() =>
|
||||
render(<TraceToLogsSettings options={defaultOptionsNewFormat} onOptionsChange={() => {}} />)
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should render and transform data from old format correctly', () => {
|
||||
render(<TraceToLogsSettings options={defaultOptionsOldFormat} onOptionsChange={() => {}} />);
|
||||
expect(screen.getByText('someTag')).toBeInTheDocument();
|
||||
expect((screen.getByLabelText('Use custom query') as HTMLInputElement).checked).toBeFalsy();
|
||||
expect((screen.getByLabelText('Filter by trace ID') as HTMLInputElement).checked).toBeTruthy();
|
||||
expect((screen.getByLabelText('Filter by span ID') as HTMLInputElement).checked).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders old mapped tags correctly', () => {
|
||||
const options = {
|
||||
...defaultOptionsOldFormat,
|
||||
jsonData: {
|
||||
...defaultOptionsOldFormat.jsonData,
|
||||
tracesToLogs: {
|
||||
...defaultOptionsOldFormat.jsonData.tracesToLogs,
|
||||
tags: undefined,
|
||||
mappedTags: [{ key: 'someTag', value: 'withNewName' }],
|
||||
mapTagNamesEnabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
render(<TraceToLogsSettings options={options} onOptionsChange={() => {}} />);
|
||||
expect(screen.getByText('someTag')).toBeInTheDocument();
|
||||
expect(screen.getByText('withNewName')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('transforms old format to new on change', async () => {
|
||||
const changeMock = jest.fn();
|
||||
render(<TraceToLogsSettings options={defaultOptionsOldFormat} onOptionsChange={changeMock} />);
|
||||
const checkBox = screen.getByLabelText('Filter by trace ID');
|
||||
await userEvent.click(checkBox);
|
||||
expect(changeMock.mock.calls[0]).toEqual([
|
||||
{
|
||||
jsonData: {
|
||||
tracesToLogs: undefined,
|
||||
tracesToLogsV2: {
|
||||
customQuery: false,
|
||||
datasourceUid: 'loki1_uid',
|
||||
filterBySpanID: true,
|
||||
filterByTraceID: false,
|
||||
spanEndTimeShift: '1m',
|
||||
spanStartTimeShift: '1m',
|
||||
tags: [
|
||||
{
|
||||
key: 'someTag',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +1,22 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
DataSourceJsonData,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
GrafanaTheme2,
|
||||
KeyValue,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { InlineField, InlineFieldRow, Input, TagsInput, useStyles2, InlineSwitch } from '@grafana/ui';
|
||||
import { InlineField, InlineFieldRow, Input, useStyles2, InlineSwitch } from '@grafana/ui';
|
||||
|
||||
import KeyValueInput from './KeyValueInput';
|
||||
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<KeyValue<string>>;
|
||||
mappedTags?: Array<{ key: string; value?: string }>;
|
||||
mapTagNamesEnabled?: boolean;
|
||||
spanStartTimeShift?: string;
|
||||
spanEndTimeShift?: string;
|
||||
@@ -26,8 +25,45 @@ export interface TraceToLogsOptions {
|
||||
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> {}
|
||||
@@ -41,6 +77,31 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
'grafana-opensearch-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%' })}>
|
||||
<h3 className="page-heading">Trace to logs</h3>
|
||||
@@ -54,171 +115,143 @@ export function TraceToLogsSettings({ options, onOptionsChange }: Props) {
|
||||
<DataSourcePicker
|
||||
inputId="trace-to-logs-data-source-picker"
|
||||
filter={(ds) => supportedDataSourceTypes.includes(ds.type)}
|
||||
current={options.jsonData.tracesToLogs?.datasourceUid}
|
||||
current={traceToLogs.datasourceUid}
|
||||
noDefault={true}
|
||||
width={40}
|
||||
onChange={(ds: DataSourceInstanceSettings) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
updateTracesToLogs({
|
||||
datasourceUid: ds.uid,
|
||||
tags: options.jsonData.tracesToLogs?.tags,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
{options.jsonData.tracesToLogs?.mapTagNamesEnabled ? (
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
|
||||
label="Tags"
|
||||
labelWidth={26}
|
||||
>
|
||||
<KeyValueInput
|
||||
keyPlaceholder="Tag"
|
||||
values={
|
||||
options.jsonData.tracesToLogs?.mappedTags ??
|
||||
options.jsonData.tracesToLogs?.tags?.map((tag) => ({ key: tag })) ??
|
||||
[]
|
||||
}
|
||||
onChange={(v) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
mappedTags: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
) : (
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"
|
||||
label="Tags"
|
||||
labelWidth={26}
|
||||
>
|
||||
<TagsInput
|
||||
tags={options.jsonData.tracesToLogs?.tags}
|
||||
width={40}
|
||||
onChange={(tags) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
tags: tags,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</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 custom query with 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>
|
||||
)}
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
label="Map tag names"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Map trace tag names to log label names. Ex: k8s.pod.name -> pod"
|
||||
>
|
||||
<InlineSwitch
|
||||
id="mapTagNames"
|
||||
value={options.jsonData.tracesToLogs?.mapTagNamesEnabled ?? false}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
mapTagNamesEnabled: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
label="Span start time shift"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Shifts the start time of the span. Default 0 (Time units can be used here, for example: 5s, 1m, 3h)"
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="1h"
|
||||
width={40}
|
||||
onChange={(v) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
spanStartTimeShift: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
value={options.jsonData.tracesToLogs?.spanStartTimeShift || ''}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
label="Span end time shift"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Shifts the end time of the span. Default 0 Time units can be used here, for example: 5s, 1m, 3h"
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="1h"
|
||||
width={40}
|
||||
onChange={(v) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
spanEndTimeShift: v.currentTarget.value,
|
||||
})
|
||||
}
|
||||
value={options.jsonData.tracesToLogs?.spanEndTimeShift || ''}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
label="Filter by Trace ID"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Filters logs by Trace ID. Appends '|=<trace id>' to the query."
|
||||
>
|
||||
<InlineSwitch
|
||||
id="filterByTraceID"
|
||||
value={options.jsonData.tracesToLogs?.filterByTraceID}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
filterByTraceID: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField
|
||||
label="Filter by Span ID"
|
||||
labelWidth={26}
|
||||
grow
|
||||
tooltip="Filters logs by Span ID. Appends '|=<span id>' to the query."
|
||||
>
|
||||
<InlineSwitch
|
||||
id="filterBySpanID"
|
||||
value={options.jsonData.tracesToLogs?.filterBySpanID}
|
||||
onChange={(event: React.SyntheticEvent<HTMLInputElement>) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', {
|
||||
...options.jsonData.tracesToLogs,
|
||||
filterBySpanID: event.currentTarget.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
</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="1h"
|
||||
width={40}
|
||||
onChange={(e) => props.onChange(e.currentTarget.value)}
|
||||
value={props.value}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
infoText: css`
|
||||
padding-bottom: ${theme.spacing(2)};
|
||||
|
||||
@@ -5,17 +5,16 @@ import {
|
||||
DataSourceJsonData,
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
GrafanaTheme2,
|
||||
KeyValue,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { Button, InlineField, InlineFieldRow, Input, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import KeyValueInput from '../TraceToLogs/KeyValueInput';
|
||||
import { TagMappingInput } from '../TraceToLogs/TagMappingInput';
|
||||
|
||||
export interface TraceToMetricsOptions {
|
||||
datasourceUid?: string;
|
||||
tags?: Array<KeyValue<string>>;
|
||||
tags?: Array<{ key: string; value: string }>;
|
||||
queries: TraceToMetricQuery[];
|
||||
spanStartTimeShift?: string;
|
||||
spanEndTimeShift?: string;
|
||||
@@ -79,8 +78,7 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) {
|
||||
|
||||
<InlineFieldRow>
|
||||
<InlineField tooltip="Tags that will be used in the metrics query." label="Tags" labelWidth={26}>
|
||||
<KeyValueInput
|
||||
keyPlaceholder="Tag"
|
||||
<TagMappingInput
|
||||
values={options.jsonData.tracesToMetrics?.tags ?? []}
|
||||
onChange={(v) =>
|
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToMetrics', {
|
||||
|
||||
Reference in New Issue
Block a user