Commit 319a0585 by Andrej Ocenas Committed by GitHub

Logs: Add href to internal link (#23757)

parent eae11f53
......@@ -25,7 +25,7 @@ const stripBaseFromUrl = (url: string): string => {
* @param url
* @internal
*/
const assureBaseUrl = (url: string) => {
const assureBaseUrl = (url: string): string => {
if (url.startsWith('/')) {
return `${grafanaConfig ? grafanaConfig().appSubUrl : ''}${stripBaseFromUrl(url)}`;
}
......
import { ScopedVars, DataSourceApi } from '@grafana/data';
import { ScopedVars, DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
/**
* This is the entry point for communicating with a datasource that is added as
......@@ -14,6 +14,11 @@ export interface DataSourceSrv {
* @param scopedVars - variables used to interpolate a templated passed as name.
*/
get(name?: string, scopedVars?: ScopedVars): Promise<DataSourceApi>;
/**
* Returns metadata based on UID.
*/
getDataSourceSettingsByUid(uid: string): DataSourceInstanceSettings | undefined;
}
let singletonInstance: DataSourceSrv;
......
......@@ -22,6 +22,7 @@ describe('getAlertingValidationMessage', () => {
const getMock = jest.fn().mockResolvedValue(datasource);
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
};
const targets: ElasticsearchQuery[] = [
{ refId: 'A', query: '@hostname:$hostname', isLogsQuery: false },
......@@ -58,6 +59,7 @@ describe('getAlertingValidationMessage', () => {
return Promise.resolve(alertingDatasource);
},
getDataSourceSettingsByUid(): any {},
};
const targets: any[] = [
{ refId: 'A', query: 'some query', datasource: 'alertingDatasource' },
......@@ -81,6 +83,7 @@ describe('getAlertingValidationMessage', () => {
const getMock = jest.fn().mockResolvedValue(datasource);
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
};
const targets: ElasticsearchQuery[] = [
{ refId: 'A', query: '@hostname:$hostname', isLogsQuery: false },
......@@ -106,6 +109,7 @@ describe('getAlertingValidationMessage', () => {
const getMock = jest.fn().mockResolvedValue(datasource);
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
};
const targets: ElasticsearchQuery[] = [
{ refId: 'A', query: '@hostname:hostname', isLogsQuery: false },
......@@ -131,6 +135,7 @@ describe('getAlertingValidationMessage', () => {
const getMock = jest.fn().mockResolvedValue(datasource);
const datasourceSrv: DataSourceSrv = {
get: getMock,
getDataSourceSettingsByUid(): any {},
};
const targets: ElasticsearchQuery[] = [
{ refId: 'A', query: '@hostname:hostname', isLogsQuery: false },
......
......@@ -4,23 +4,23 @@ import { connect } from 'react-redux';
import { Collapse } from '@grafana/ui';
import {
AbsoluteTimeRange,
DataSourceApi,
RawTimeRange,
Field,
GraphSeriesXY,
LogLevel,
TimeZone,
AbsoluteTimeRange,
LogRowModel,
LogsDedupStrategy,
TimeRange,
LogsMetaItem,
GraphSeriesXY,
Field,
RawTimeRange,
TimeRange,
TimeZone,
} from '@grafana/data';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { StoreState } from 'app/types';
import { changeDedupStrategy, updateTimeRange, splitOpen } from './state/actions';
import { changeDedupStrategy, splitOpen, updateTimeRange } from './state/actions';
import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes';
import { deduplicatedRowsSelector } from 'app/features/explore/state/selectors';
import { getTimeZone } from '../profile/state/selectors';
......@@ -28,7 +28,7 @@ import { LiveLogsWithTheme } from './LiveLogs';
import { Logs } from './Logs';
import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition';
import { LiveTailControls } from './useLiveTailControls';
import { getLinksFromLogsField } from '../panel/panellinks/linkSuppliers';
import { getFieldLinksForExplore } from './utils/links';
interface LogsContainerProps {
datasourceInstance?: DataSourceApi;
......@@ -89,28 +89,8 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
return [];
};
/**
* Get links from the filed of a dataframe that was given to as and in addition check if there is associated
* metadata with datasource in which case we will add onClick to open the link in new split window. This assumes
* that we just supply datasource name and field value and Explore split window will know how to render that
* appropriately. This is for example used for transition from log with traceId to trace datasource to show that
* trace.
* @param field
* @param rowIndex
*/
getFieldLinks = (field: Field, rowIndex: number) => {
const data = getLinksFromLogsField(field, rowIndex);
return data.map(d => {
if (d.link.meta?.datasourceUid) {
return {
...d.linkModel,
onClick: () => {
this.props.splitOpen({ dataSourceUid: d.link.meta.datasourceUid, query: field.values.get(rowIndex) });
},
};
}
return d.linkModel;
});
return getFieldLinksForExplore(field, rowIndex, this.props.splitOpen, this.props.range);
};
render() {
......
......@@ -702,7 +702,7 @@ export function splitClose(itemId: ExploreId): ThunkResult<void> {
* Otherwise it copies the left state to be the right state. The copy keeps all query modifications but wipes the query
* results.
*/
export function splitOpen(options?: { dataSourceUid: string; query: string }): ThunkResult<void> {
export function splitOpen(options?: { datasourceUid: string; query: string }): ThunkResult<void> {
return async (dispatch, getState) => {
// Clone left state to become the right state
const leftState: ExploreItemState = getState().explore[ExploreId.left];
......@@ -727,7 +727,7 @@ export function splitOpen(options?: { dataSourceUid: string; query: string }): T
} as DataQuery,
];
const dataSourceSettings = getDatasourceSrv().getDataSourceSettingsByUid(options.dataSourceUid);
const dataSourceSettings = getDatasourceSrv().getDataSourceSettingsByUid(options.datasourceUid);
await dispatch(changeDatasource(ExploreId.right, dataSourceSettings.name));
await dispatch(setQueriesAction({ exploreId: ExploreId.right, queries }));
}
......
import { getFieldLinksForExplore } from './links';
import {
ArrayVector,
DataLink,
DataSourceInstanceSettings,
dateTime,
Field,
FieldType,
LinkModel,
ScopedVars,
TimeRange,
} from '@grafana/data';
import { setLinkSrv } from '../../panel/panellinks/link_srv';
import { setDataSourceSrv } from '@grafana/runtime';
describe('getFieldLinksForExplore', () => {
it('returns correct link model for external link', () => {
const { field, range } = setup({
title: 'external',
url: 'http://regionalhost',
});
const links = getFieldLinksForExplore(field, 0, jest.fn(), range);
expect(links[0].href).toBe('http://regionalhost');
expect(links[0].title).toBe('external');
});
it('returns correct link model for internal link', () => {
const { field, range } = setup({
title: 'test',
url: 'query_1',
meta: {
datasourceUid: 'uid_1',
},
});
const splitfn = jest.fn();
const links = getFieldLinksForExplore(field, 0, splitfn, range);
expect(links[0].href).toBe(
'/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
);
expect(links[0].title).toBe('test');
links[0].onClick({});
expect(splitfn).toBeCalledWith({ datasourceUid: 'uid_1', query: 'query_1' });
});
});
function setup(link: DataLink) {
setLinkSrv({
getDataLinkUIModel(link: DataLink, scopedVars: ScopedVars, origin: any): LinkModel<any> {
return {
href: link.url,
title: link.title,
target: '_blank',
origin: origin,
};
},
});
setDataSourceSrv({
getDataSourceSettingsByUid(uid: string) {
return {
id: 1,
uid: 'uid_1',
type: 'metrics',
name: 'test_ds',
meta: {},
jsonData: {},
} as DataSourceInstanceSettings;
},
} as any);
const field: Field<string> = {
name: 'flux-dimensions',
type: FieldType.string,
values: new ArrayVector([]),
config: {
links: [link],
},
};
const range: TimeRange = {
from: dateTime(),
to: dateTime(),
raw: {
from: 'now-1h',
to: 'now',
},
};
return { range, field };
}
import { splitOpen } from '../state/actions';
import { ExploreMode, Field, LinkModel, locationUtil, TimeRange } from '@grafana/data';
import { getLinksFromLogsField } from '../../panel/panellinks/linkSuppliers';
import { serializeStateToUrlParam } from '../../../core/utils/explore';
import { getDataSourceSrv } from '@grafana/runtime';
/**
* Get links from the filed of a dataframe that was given to as and in addition check if there is associated
* metadata with datasource in which case we will add onClick to open the link in new split window. This assumes
* that we just supply datasource name and field value and Explore split window will know how to render that
* appropriately. This is for example used for transition from log with traceId to trace datasource to show that
* trace.
*/
export function getFieldLinksForExplore(
field: Field,
rowIndex: number,
splitOpenFn: typeof splitOpen,
range: TimeRange
): Array<LinkModel<Field>> {
const data = getLinksFromLogsField(field, rowIndex);
return data.map(d => {
if (d.link.meta?.datasourceUid) {
return {
...d.linkModel,
onClick: () => {
splitOpenFn({
datasourceUid: d.link.meta.datasourceUid,
// TODO: fix the ambiguity here
// This looks weird but in case meta.datasourceUid is set we save the query in url which will get
// interpolated into href
query: d.linkModel.href,
});
},
// We need to create real href here as the linkModel.href actually contains query. As in this case this is
// meant to be internal link (opens split view by default) the href will also points to explore but this
// way you can open it in new tab.
href: generateInternalHref(d.link.meta.datasourceUid, d.linkModel.href, range),
};
}
return d.linkModel;
});
}
/**
* Generates href for internal derived field link.
*/
function generateInternalHref(datasourceUid: string, query: string, range: TimeRange): string {
return locationUtil.assureBaseUrl(
`/explore?left=${serializeStateToUrlParam({
range: range.raw,
datasource: getDataSourceSrv().getDataSourceSettingsByUid(datasourceUid).name,
// Again hardcoded for Jaeger query structure
// TODO: fix
queries: [{ query }],
// This should get overwritten if datasource does not support that mode and we do not know what mode is
// preferred anyway.
mode: ExploreMode.Metrics,
ui: {
showingGraph: true,
showingTable: true,
showingLogs: true,
},
})}`
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment