mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch Logs: Add link to Xray data source for trace IDs in logs (#39135)
* Refactor log query handling * Add link to config page * Change message about missing xray to alert * Add xrayTraceLinks * Fix typo in field name * Fix tests and lint * Move test * Add test for trace id link * lint
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { Input, InlineField } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps, onUpdateDatasourceJsonDataOption } from '@grafana/data';
|
||||
import {
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
onUpdateDatasourceJsonDataOption,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { ConnectionConfig } from '@grafana/aws-sdk';
|
||||
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@@ -10,32 +14,15 @@ import { createWarningNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../types';
|
||||
import { CloudWatchDatasource } from '../datasource';
|
||||
import { XrayLinkConfig } from './XrayLinkConfig';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<CloudWatchJsonData, CloudWatchSecureJsonData>;
|
||||
|
||||
export const ConfigEditor: FC<Props> = (props: Props) => {
|
||||
const [datasource, setDatasource] = useState<CloudWatchDatasource>();
|
||||
const { options } = props;
|
||||
|
||||
const addWarning = (message: string) => {
|
||||
store.dispatch(notifyApp(createWarningNotification('CloudWatch Authentication', message)));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDatasourceSrv()
|
||||
.loadDatasource(options.name)
|
||||
.then((datasource: CloudWatchDatasource) => setDatasource(datasource));
|
||||
|
||||
if (options.jsonData.authType === 'arn') {
|
||||
addWarning('Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider');
|
||||
} else if (options.jsonData.authType === 'credentials' && !options.jsonData.profile && !options.jsonData.database) {
|
||||
addWarning(
|
||||
'As of grafana 7.3 authentication type "credentials" should be used only for shared file credentials. \
|
||||
If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \
|
||||
from environment variables or IAM roles'
|
||||
);
|
||||
}
|
||||
}, [options.jsonData.authType, options.jsonData.database, options.jsonData.profile, options.name]);
|
||||
const datasource = useDatasource(options.name);
|
||||
useAuthenticationWarning(options.jsonData);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -55,6 +42,39 @@ export const ConfigEditor: FC<Props> = (props: Props) => {
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
|
||||
<XrayLinkConfig
|
||||
onChange={(uid) => updateDatasourcePluginJsonDataOption(props, 'tracingDatasourceUid', uid)}
|
||||
datasourceUid={options.jsonData.tracingDatasourceUid}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function useAuthenticationWarning(jsonData: CloudWatchJsonData) {
|
||||
const addWarning = (message: string) => {
|
||||
store.dispatch(notifyApp(createWarningNotification('CloudWatch Authentication', message)));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (jsonData.authType === 'arn') {
|
||||
addWarning('Since grafana 7.3 authentication type "arn" is deprecated, falling back to default SDK provider');
|
||||
} else if (jsonData.authType === 'credentials' && !jsonData.profile && !jsonData.database) {
|
||||
addWarning(
|
||||
'As of grafana 7.3 authentication type "credentials" should be used only for shared file credentials. \
|
||||
If you don\'t have a credentials file, switch to the default SDK provider for extracting credentials \
|
||||
from environment variables or IAM roles'
|
||||
);
|
||||
}
|
||||
}, [jsonData.authType, jsonData.database, jsonData.profile]);
|
||||
}
|
||||
|
||||
function useDatasource(datasourceName: string) {
|
||||
const [datasource, setDatasource] = useState<CloudWatchDatasource>();
|
||||
useEffect(() => {
|
||||
getDatasourceSrv()
|
||||
.loadDatasource(datasourceName)
|
||||
.then((datasource: CloudWatchDatasource) => setDatasource(datasource));
|
||||
}, [datasourceName]);
|
||||
return datasource;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { interval, of, throwError } from 'rxjs';
|
||||
import {
|
||||
DataFrame,
|
||||
DataQueryErrorType,
|
||||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
dateMath,
|
||||
getFrameDisplayName,
|
||||
@@ -176,58 +175,6 @@ describe('CloudWatchDatasource', () => {
|
||||
jest.spyOn(rxjsUtils, 'increasingInterval').mockImplementation(() => interval(100));
|
||||
});
|
||||
|
||||
it('should add data links to response', () => {
|
||||
const { ds } = getTestContext();
|
||||
const mockResponse: DataQueryResponse = {
|
||||
data: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
config: {
|
||||
links: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
refId: 'A',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockOptions: any = {
|
||||
targets: [
|
||||
{
|
||||
refId: 'A',
|
||||
expression: 'stats count(@message) by bin(1h)',
|
||||
logGroupNames: ['fake-log-group-one', 'fake-log-group-two'],
|
||||
region: 'default',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const saturatedResponse = ds['addDataLinksToLogsResponse'](mockResponse, mockOptions);
|
||||
expect(saturatedResponse).toMatchObject({
|
||||
data: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
config: {
|
||||
links: [
|
||||
{
|
||||
url:
|
||||
"https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logs-insights:queryDetail=~(end~'2016-12-31T16*3a00*3a00.000Z~start~'2016-12-31T15*3a00*3a00.000Z~timeType~'ABSOLUTE~tz~'UTC~editorString~'stats*20count*28*40message*29*20by*20bin*281h*29~isLiveTail~false~source~(~'fake-log-group-one~'fake-log-group-two))",
|
||||
title: 'View in CloudWatch console',
|
||||
targetBlank: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
refId: 'A',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should stop querying when no more data received a number of times in a row', async () => {
|
||||
const { ds } = getTestContext();
|
||||
const fakeFrames = genMockFrames(20);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Alert, InlineField, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
infoText: css`
|
||||
padding-bottom: ${theme.spacing(2)};
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
});
|
||||
|
||||
interface Props {
|
||||
datasourceUid?: string;
|
||||
onChange: (uid: string) => void;
|
||||
}
|
||||
|
||||
const xRayDsId = 'grafana-x-ray-datasource';
|
||||
|
||||
export function XrayLinkConfig({ datasourceUid, onChange }: Props) {
|
||||
const hasXrayDatasource = Boolean(getDatasourceSrv().getList({ pluginId: xRayDsId }).length);
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="page-heading">X-ray trace link</h3>
|
||||
|
||||
<div className={styles.infoText}>
|
||||
Grafana will automatically create a link to a trace in X-ray data source if logs contain @xrayTraceId field
|
||||
</div>
|
||||
|
||||
{!hasXrayDatasource && (
|
||||
<Alert
|
||||
title={
|
||||
'There is no X-ray datasource to link to. First add an X-ray data source and then link it to Cloud Watch. '
|
||||
}
|
||||
severity="info"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="gf-form-group">
|
||||
<InlineField label="Data source" labelWidth={28} tooltip="X-ray data source containing traces">
|
||||
<DataSourcePicker
|
||||
pluginId={xRayDsId}
|
||||
onChange={(ds) => onChange(ds.uid)}
|
||||
current={datasourceUid}
|
||||
noDefault={true}
|
||||
/>
|
||||
</InlineField>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -62,6 +62,9 @@ exports[`Render should disable access key id field 1`] = `
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
<XrayLinkConfig
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -122,6 +125,9 @@ exports[`Render should render component 1`] = `
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
<XrayLinkConfig
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -187,6 +193,9 @@ exports[`Render should show access key and secret access key fields 1`] = `
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
<XrayLinkConfig
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -252,6 +261,9 @@ exports[`Render should show arn role field 1`] = `
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
<XrayLinkConfig
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -317,5 +329,8 @@ exports[`Render should show credentials profile name field 1`] = `
|
||||
/>
|
||||
</InlineField>
|
||||
</ConnectionConfig>
|
||||
<XrayLinkConfig
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user