mirror of https://github.com/grafana/grafana
Tracing: Release trace to logs feature (#29443)
* Remove feature flag * Add data source setting for Jaeger * Refactor trace to logs settings * Fix tests * Get ds settings in two steps * Add info to settings * Update docs for trace to logs * Update yarn.lock * Apply suggestions from code review Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update TraceToLogsSettings after merge with master * Add config for tags * Add tags to check for keys * Apply suggestions from code review Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>pull/29762/head
parent
8a3fdc3055
commit
b3838d372e
@ -0,0 +1,72 @@ |
||||
import { |
||||
DataSourceJsonData, |
||||
DataSourcePluginOptionsEditorProps, |
||||
GrafanaTheme, |
||||
updateDatasourcePluginJsonDataOption, |
||||
} from '@grafana/data'; |
||||
import { InlineFormLabel, TagsInput, useStyles } from '@grafana/ui'; |
||||
import { css } from 'emotion'; |
||||
import React from 'react'; |
||||
import { DataSourcePicker } from './Select/DataSourcePicker'; |
||||
|
||||
export interface TraceToLogsOptions { |
||||
datasourceUid?: string; |
||||
tags?: string[]; |
||||
} |
||||
|
||||
export interface TraceToLogsData extends DataSourceJsonData { |
||||
tracesToLogs?: TraceToLogsOptions; |
||||
} |
||||
|
||||
interface Props extends DataSourcePluginOptionsEditorProps<TraceToLogsData> {} |
||||
|
||||
export function TraceToLogsSettings({ options, onOptionsChange }: Props) { |
||||
const styles = useStyles(getStyles); |
||||
|
||||
return ( |
||||
<> |
||||
<h3 className="page-heading">Trace to logs</h3> |
||||
|
||||
<div className={styles.infoText}> |
||||
Trace to logs let's you navigate from a trace span to the selected data source's log. |
||||
</div> |
||||
|
||||
<div className="gf-form"> |
||||
<InlineFormLabel tooltip="The data source the trace is going to navigate to">Data source</InlineFormLabel> |
||||
<DataSourcePicker |
||||
pluginId="loki" |
||||
current={options.jsonData.tracesToLogs?.datasourceUid} |
||||
noDefault={true} |
||||
onChange={ds => |
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', { |
||||
datasourceUid: ds.uid, |
||||
tags: options.jsonData.tracesToLogs?.tags, |
||||
}) |
||||
} |
||||
/> |
||||
</div> |
||||
|
||||
<div className="gf-form"> |
||||
<InlineFormLabel tooltip="Tags that will be used in the Loki query. Default tags: 'cluster', 'hostname', 'namespace', 'pod'"> |
||||
Tags |
||||
</InlineFormLabel> |
||||
<TagsInput |
||||
tags={options.jsonData.tracesToLogs?.tags} |
||||
onChange={tags => |
||||
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'tracesToLogs', { |
||||
datasourceUid: options.jsonData.tracesToLogs?.datasourceUid, |
||||
tags: tags, |
||||
}) |
||||
} |
||||
/> |
||||
</div> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({ |
||||
infoText: css` |
||||
padding-bottom: ${theme.spacing.md}; |
||||
color: ${theme.colors.textSemiWeak}; |
||||
`,
|
||||
}); |
@ -1,82 +1,133 @@ |
||||
import { createSpanLinkFactory } from './createSpanLink'; |
||||
import { config, setDataSourceSrv, setTemplateSrv } from '@grafana/runtime'; |
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; |
||||
import { setDataSourceSrv, setTemplateSrv } from '@grafana/runtime'; |
||||
import { createSpanLinkFactory } from './createSpanLink'; |
||||
|
||||
describe('createSpanLinkFactory', () => { |
||||
beforeAll(() => { |
||||
config.featureToggles.traceToLogs = true; |
||||
}); |
||||
|
||||
afterAll(() => { |
||||
config.featureToggles.traceToLogs = false; |
||||
}); |
||||
|
||||
it('returns undefined if there is no loki data source', () => { |
||||
setDataSourceSrv({ |
||||
getList() { |
||||
return []; |
||||
}, |
||||
} as any); |
||||
it('returns undefined if there is no data source uid', () => { |
||||
const splitOpenFn = jest.fn(); |
||||
const createLink = createSpanLinkFactory(splitOpenFn); |
||||
expect(createLink).not.toBeDefined(); |
||||
}); |
||||
|
||||
it('creates correct link', () => { |
||||
setDataSourceSrv({ |
||||
getList() { |
||||
return [ |
||||
{ |
||||
name: 'loki1', |
||||
uid: 'lokiUid', |
||||
meta: { |
||||
id: 'loki', |
||||
}, |
||||
} as DataSourceInstanceSettings, |
||||
]; |
||||
}, |
||||
getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined { |
||||
if (uid === 'lokiUid') { |
||||
describe('should return link', () => { |
||||
beforeAll(() => { |
||||
setDataSourceSrv({ |
||||
getInstanceSettings(uid: string): DataSourceInstanceSettings | undefined { |
||||
return { |
||||
uid: 'loki1', |
||||
name: 'loki1', |
||||
} as any; |
||||
} |
||||
return undefined; |
||||
}, |
||||
} as any); |
||||
}, |
||||
} as any); |
||||
|
||||
setTemplateSrv({ |
||||
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string { |
||||
return target!; |
||||
}, |
||||
} as any); |
||||
setTemplateSrv({ |
||||
replace(target?: string, scopedVars?: ScopedVars, format?: string | Function): string { |
||||
return target!; |
||||
}, |
||||
} as any); |
||||
}); |
||||
|
||||
const splitOpenFn = jest.fn(); |
||||
const createLink = createSpanLinkFactory(splitOpenFn); |
||||
expect(createLink).toBeDefined(); |
||||
const linkDef = createLink!({ |
||||
startTime: new Date('2020-10-14T01:00:00Z').valueOf() * 1000, |
||||
duration: 1000 * 1000, |
||||
process: { |
||||
it('with default keys when tags not configured', () => { |
||||
const splitOpenFn = jest.fn(); |
||||
const createLink = createSpanLinkFactory(splitOpenFn, { datasourceUid: 'lokiUid' }); |
||||
expect(createLink).toBeDefined(); |
||||
const linkDef = createLink!({ |
||||
startTime: new Date('2020-10-14T01:00:00Z').valueOf() * 1000, |
||||
duration: 1000 * 1000, |
||||
tags: [ |
||||
{ |
||||
key: 'cluster', |
||||
value: 'cluster1', |
||||
key: 'host', |
||||
value: 'host', |
||||
}, |
||||
], |
||||
process: { |
||||
tags: [ |
||||
{ |
||||
key: 'cluster', |
||||
value: 'cluster1', |
||||
}, |
||||
{ |
||||
key: 'hostname', |
||||
value: 'hostname1', |
||||
}, |
||||
{ |
||||
key: 'label2', |
||||
value: 'val2', |
||||
}, |
||||
], |
||||
} as any, |
||||
} as any); |
||||
|
||||
expect(linkDef.href).toBe( |
||||
`/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}` |
||||
); |
||||
}); |
||||
|
||||
it('with tags that passed in and without tags that are not in the span', () => { |
||||
const splitOpenFn = jest.fn(); |
||||
const createLink = createSpanLinkFactory(splitOpenFn, { datasourceUid: 'lokiUid', tags: ['ip', 'newTag'] }); |
||||
expect(createLink).toBeDefined(); |
||||
const linkDef = createLink!({ |
||||
startTime: new Date('2020-10-14T01:00:00Z').valueOf() * 1000, |
||||
duration: 1000 * 1000, |
||||
tags: [ |
||||
{ |
||||
key: 'hostname', |
||||
value: 'hostname1', |
||||
key: 'host', |
||||
value: 'host', |
||||
}, |
||||
], |
||||
process: { |
||||
tags: [ |
||||
{ |
||||
key: 'hostname', |
||||
value: 'hostname1', |
||||
}, |
||||
{ |
||||
key: 'ip', |
||||
value: '192.168.0.1', |
||||
}, |
||||
], |
||||
} as any, |
||||
} as any); |
||||
|
||||
expect(linkDef.href).toBe( |
||||
`/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\"}","refId":""}]}` |
||||
); |
||||
}); |
||||
|
||||
it('from tags and process tags as well', () => { |
||||
const splitOpenFn = jest.fn(); |
||||
const createLink = createSpanLinkFactory(splitOpenFn, { |
||||
datasourceUid: 'lokiUid', |
||||
tags: ['ip', 'host'], |
||||
}); |
||||
expect(createLink).toBeDefined(); |
||||
const linkDef = createLink!({ |
||||
startTime: new Date('2020-10-14T01:00:00Z').valueOf() * 1000, |
||||
duration: 1000 * 1000, |
||||
tags: [ |
||||
{ |
||||
key: 'label2', |
||||
value: 'val2', |
||||
key: 'host', |
||||
value: 'host', |
||||
}, |
||||
], |
||||
} as any, |
||||
} as any); |
||||
process: { |
||||
tags: [ |
||||
{ |
||||
key: 'hostname', |
||||
value: 'hostname1', |
||||
}, |
||||
{ |
||||
key: 'ip', |
||||
value: '192.168.0.1', |
||||
}, |
||||
], |
||||
} as any, |
||||
} as any); |
||||
|
||||
expect(linkDef.href).toBe( |
||||
`/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"loki1","queries":[{"expr":"{cluster=\\"cluster1\\", hostname=\\"hostname1\\"}","refId":""}]}` |
||||
); |
||||
expect(linkDef.href).toBe( |
||||
`/explore?left={"range":{"from":"20201014T000000","to":"20201014T010006"},"datasource":"loki1","queries":[{"expr":"{ip=\\"192.168.0.1\\", host=\\"host\\"}","refId":""}]}` |
||||
); |
||||
}); |
||||
}); |
||||
}); |
||||
|
@ -1,16 +1,21 @@ |
||||
import React from 'react'; |
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; |
||||
import { DataSourceHttpSettings } from '@grafana/ui'; |
||||
import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; |
||||
import React from 'react'; |
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps; |
||||
|
||||
export const ConfigEditor: React.FC<Props> = ({ options, onOptionsChange }) => { |
||||
return ( |
||||
<DataSourceHttpSettings |
||||
defaultUrl="http://localhost:16686" |
||||
dataSourceConfig={options} |
||||
showAccessOptions={false} |
||||
onChange={onOptionsChange} |
||||
/> |
||||
<> |
||||
<DataSourceHttpSettings |
||||
defaultUrl="http://localhost:16686" |
||||
dataSourceConfig={options} |
||||
showAccessOptions={false} |
||||
onChange={onOptionsChange} |
||||
/> |
||||
|
||||
<TraceToLogsSettings options={options} onOptionsChange={onOptionsChange} /> |
||||
</> |
||||
); |
||||
}; |
||||
|
@ -1,16 +1,21 @@ |
||||
import React from 'react'; |
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; |
||||
import { DataSourceHttpSettings } from '@grafana/ui'; |
||||
import { TraceToLogsSettings } from 'app/core/components/TraceToLogsSettings'; |
||||
import React from 'react'; |
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps; |
||||
|
||||
export const ConfigEditor: React.FC<Props> = ({ options, onOptionsChange }) => { |
||||
return ( |
||||
<DataSourceHttpSettings |
||||
defaultUrl={'http://localhost:9411'} |
||||
dataSourceConfig={options} |
||||
showAccessOptions={true} |
||||
onChange={onOptionsChange} |
||||
/> |
||||
<> |
||||
<DataSourceHttpSettings |
||||
defaultUrl="http://localhost:9411" |
||||
dataSourceConfig={options} |
||||
showAccessOptions={true} |
||||
onChange={onOptionsChange} |
||||
/> |
||||
|
||||
<TraceToLogsSettings options={options} onOptionsChange={onOptionsChange} /> |
||||
</> |
||||
); |
||||
}; |
||||
|
Loading…
Reference in new issue