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:
Andrej Ocenas
2021-09-14 17:02:41 +02:00
committed by GitHub
parent 3c433dc36d
commit fb1c31e1b6
12 changed files with 469 additions and 203 deletions

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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>
</>
);
}

View File

@@ -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>
`;