AzureMonitor: Remove deprecated code (#48328)

This commit is contained in:
Andres Martinez Gotor
2022-04-28 01:27:39 -07:00
committed by GitHub
parent 07bd261cff
commit 6edefe5147
42 changed files with 39 additions and 3523 deletions

View File

@@ -2,8 +2,6 @@ import { AzureMonitorQuery, AzureQueryType } from '../types';
export default function createMockQuery(): AzureMonitorQuery {
return {
appInsights: undefined, // The actualy shape of this at runtime disagrees with the ts interface
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
@@ -34,11 +32,6 @@ export default function createMockQuery(): AzureMonitorQuery {
top: '10',
},
insightsAnalytics: {
query: '',
resultFormat: 'time_series',
},
queryType: AzureQueryType.AzureMonitor,
refId: 'A',
subscription: '99999999-cccc-bbbb-aaaa-9106972f9572',

View File

@@ -45,20 +45,4 @@ describe('AppInsights ConfigEditor', () => {
expect(screen.queryByText('Azure Application Insights')).not.toBeInTheDocument();
});
it('should render application insights config for data sources using application insights', () => {
const options = {
...baseOptions,
jsonData: {
...jsonData,
appInsightsAppId: 'abc-123',
},
secureJsonFields: {
appInsightsApiKey: true,
},
};
render(<ConfigEditor options={options} onOptionsChange={onOptionsChange} />);
expect(screen.queryByText('Azure Application Insights')).toBeInTheDocument();
});
});

View File

@@ -9,9 +9,6 @@ import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSource
import { routeNames } from '../utils/common';
import { MonitorConfig } from './MonitorConfig';
import { AnalyticsConfig } from './deprecated/components/AnalyticsConfig';
import { InsightsConfig } from './deprecated/components/InsightsConfig';
import { gtGrafana9, isAppInsightsConfigured } from './deprecated/utils';
export type Props = DataSourcePluginOptionsEditorProps<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
@@ -91,14 +88,6 @@ export class ConfigEditor extends PureComponent<Props, State> {
return (
<>
<MonitorConfig options={options} updateOptions={this.updateOptions} getSubscriptions={this.getSubscriptions} />
{/* Remove with Grafana 9 */}
{!gtGrafana9() && (
<>
<AnalyticsConfig options={options} updateOptions={this.updateOptions} />
{isAppInsightsConfigured(options) && <InsightsConfig {...this.props} />}
</>
)}
{/* ===================== */}
{error && (
<Alert severity="error" title={error.title}>
<p>{error.description}</p>

View File

@@ -1,6 +1,5 @@
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import selectEvent from 'react-select-event';
import { config } from '@grafana/runtime';
import * as ui from '@grafana/ui';
@@ -8,7 +7,7 @@ import * as ui from '@grafana/ui';
import createMockDatasource from '../../__mocks__/datasource';
import { invalidNamespaceError } from '../../__mocks__/errors';
import createMockQuery from '../../__mocks__/query';
import { AzureQueryType, DeprecatedAzureQueryType } from '../../types';
import { AzureQueryType } from '../../types';
import QueryEditor from './QueryEditor';
@@ -43,34 +42,6 @@ describe('Azure Monitor QueryEditor', () => {
await waitFor(() => expect(screen.queryByTestId('azure-monitor-logs-query-editor')).toBeInTheDocument());
});
it('renders the ApplicationInsights query editor when the query type is Application Insights and renders values in disabled inputs', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: DeprecatedAzureQueryType.ApplicationInsights,
appInsights: {
metricName: 'requests/count',
timeGrain: 'PT1H',
timeGrainCount: '1',
timeGrainType: 'specific',
timeGrainUnit: 'hour',
aggregation: 'average',
dimension: ['request/name'],
dimensionFilter: "request/name eq 'GET Home/Index'",
alias: '{{ request/name }}',
},
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(screen.queryByTestId('azure-monitor-application-insights-query-editor')).toBeInTheDocument()
);
const metricInput = await screen.getByLabelText('Metric');
expect(metricInput).toBeDisabled();
expect(metricInput).toHaveValue('requests/count');
});
it('changes the query type when selected', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = createMockQuery();
@@ -98,42 +69,6 @@ describe('Azure Monitor QueryEditor', () => {
expect(screen.getByText("The resource namespace 'grafanadev' is invalid.")).toBeInTheDocument();
});
it('hides deprecated services', async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: AzureQueryType.AzureMonitor,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() => expect(screen.getByTestId('azure-monitor-metrics-query-editor')).toBeInTheDocument());
const metrics = await screen.findByLabelText('Service');
selectEvent.openMenu(metrics);
expect(screen.queryByText('Application Insights')).not.toBeInTheDocument();
});
it("shows deprecated services when they're selected", async () => {
const mockDatasource = createMockDatasource();
const mockQuery = {
...createMockQuery(),
queryType: DeprecatedAzureQueryType.ApplicationInsights,
};
render(<QueryEditor query={mockQuery} datasource={mockDatasource} onChange={() => {}} onRunQuery={() => {}} />);
await waitFor(() =>
expect(screen.getByTestId('azure-monitor-application-insights-query-editor')).toBeInTheDocument()
);
expect(screen.queryByText('Application Insights')).toBeInTheDocument();
const metrics = await screen.findByLabelText('Service');
await ui.selectOptionInTest(metrics, 'Logs');
expect(screen.queryByText('Application Insights')).toBeInTheDocument();
});
it('renders the new query editor for metrics when enabled with a feature toggle', async () => {
const originalConfigValue = config.featureToggles.azureMonitorResourcePickerForMetrics;

View File

@@ -3,7 +3,7 @@ import React, { useCallback, useMemo } from 'react';
import { QueryEditorProps } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { Alert, CodeEditor } from '@grafana/ui';
import AzureMonitorDatasource from '../../datasource';
import {
@@ -12,7 +12,6 @@ import {
AzureMonitorOption,
AzureMonitorQuery,
AzureQueryType,
DeprecatedAzureQueryType,
} from '../../types';
import useLastError from '../../utils/useLastError';
import ArgQueryEditor from '../ArgQueryEditor';
@@ -20,9 +19,6 @@ import LogsQueryEditor from '../LogsQueryEditor';
import MetricsQueryEditor from '../MetricsQueryEditor';
import NewMetricsQueryEditor from '../NewMetricsQueryEditor/MetricsQueryEditor';
import { Space } from '../Space';
import ApplicationInsightsEditor from '../deprecated/components/ApplicationInsightsEditor';
import InsightsAnalyticsEditor from '../deprecated/components/InsightsAnalyticsEditor';
import { gtGrafana9 } from '../deprecated/utils';
import QueryTypeField from './QueryTypeField';
import usePreparedQuery from './usePreparedQuery';
@@ -145,46 +141,26 @@ const EditorForQueryType: React.FC<EditorForQueryTypeProps> = ({
/>
);
/** Remove with Grafana 9 */
case DeprecatedAzureQueryType.ApplicationInsights:
if (gtGrafana9()) {
return (
<Alert title="Deprecated">
Application Insights has been deprecated.{' '}
<a
href="https://grafana.com/docs/grafana/latest/datasources/azuremonitor/deprecated-application-insights/#application-insights"
target="_blank"
rel="noreferrer"
>
Use the Metrics service instead
</a>
.
</Alert>
);
}
return <ApplicationInsightsEditor query={query} />;
case DeprecatedAzureQueryType.InsightsAnalytics:
if (gtGrafana9()) {
return (
<Alert title="Deprecated">
Insight Analytics has been deprecated.{' '}
<a
href="https://grafana.com/docs/grafana/latest/datasources/azuremonitor/deprecated-application-insights/#insights-analytics"
target="_blank"
rel="noreferrer"
>
Queries can be written with Kusto in the Logs query type by selecting your Application Insights resource
</a>
.
</Alert>
);
}
return <InsightsAnalyticsEditor query={query} />;
/** ===================== */
default:
return <Alert title="Unknown query type" />;
const type = query.queryType as unknown;
return (
<Alert title="Unknown query type">
{(type === 'Application Insights' || type === 'Insights Analytics') && (
<>
{type} was deprecated in Grafana 9. See the{' '}
<a
href="https://grafana.com/docs/grafana/latest/datasources/azuremonitor/deprecated-application-insights/"
target="_blank"
rel="noreferrer"
>
deprecation notice
</a>{' '}
to get more information about how to migrate your queries. This is the current query definition:
<CodeEditor height="200px" readOnly language="json" value={JSON.stringify(query, null, 4)} />
</>
)}
</Alert>
);
}
return null;

View File

@@ -1,11 +1,10 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';
import { AzureMonitorQuery, AzureQueryType, DeprecatedAzureQueryType } from '../../types';
import { AzureMonitorQuery, AzureQueryType } from '../../types';
import { Field } from '../Field';
import { gtGrafana9 } from '../deprecated/utils';
interface QueryTypeFieldProps {
query: AzureMonitorQuery;
@@ -13,29 +12,14 @@ interface QueryTypeFieldProps {
}
const QueryTypeField: React.FC<QueryTypeFieldProps> = ({ query, onQueryChange }) => {
// Use useState to capture the initial value on first mount. We're not interested in when it changes
// We only show App Insights and Insights Analytics if they were initially selected. Otherwise, hide them.
const [initialQueryType] = useState(query.queryType);
const queryTypes: Array<{ value: AzureQueryType | DeprecatedAzureQueryType; label: string }> = [
const queryTypes: Array<{ value: AzureQueryType; label: string }> = [
{ value: AzureQueryType.AzureMonitor, label: 'Metrics' },
{ value: AzureQueryType.LogAnalytics, label: 'Logs' },
{ value: AzureQueryType.AzureResourceGraph, label: 'Azure Resource Graph' },
];
if (
!gtGrafana9() &&
(initialQueryType === DeprecatedAzureQueryType.ApplicationInsights ||
initialQueryType === DeprecatedAzureQueryType.InsightsAnalytics)
) {
queryTypes.push(
{ value: DeprecatedAzureQueryType.ApplicationInsights, label: 'Application Insights' },
{ value: DeprecatedAzureQueryType.InsightsAnalytics, label: 'Insights Analytics' }
);
}
const handleChange = useCallback(
(change: SelectableValue<AzureQueryType | DeprecatedAzureQueryType>) => {
(change: SelectableValue<AzureQueryType>) => {
change.value &&
onQueryChange({
...query,

View File

@@ -1,463 +0,0 @@
import { lastValueFrom, of } from 'rxjs';
import { DataFrame, getFrameDisplayName, toUtc } from '@grafana/data';
import { setBackendSrv } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import AppInsightsDatasource from './app_insights_datasource';
const templateSrv = new TemplateSrv();
jest.mock('app/core/services/backend_srv');
jest.mock('@grafana/runtime', () => ({
...(jest.requireActual('@grafana/runtime') as unknown as object),
getBackendSrv: () => backendSrv,
getTemplateSrv: () => templateSrv,
}));
describe('AppInsightsDatasource', () => {
const fetchMock = jest.spyOn(backendSrv, 'fetch');
const ctx: any = {};
beforeEach(() => {
jest.clearAllMocks();
setBackendSrv(backendSrv);
ctx.instanceSettings = {
jsonData: { appInsightsAppId: '3ad4400f-ea7d-465d-a8fb-43fb20555d85' },
url: 'http://appinsightsapi',
};
ctx.ds = new AppInsightsDatasource(ctx.instanceSettings);
});
describe('When performing testDatasource', () => {
describe('and a list of metrics is returned', () => {
const response = {
metrics: {
'requests/count': {
displayName: 'Server requests',
defaultAggregation: 'sum',
},
'requests/duration': {
displayName: 'Server requests',
defaultAggregation: 'sum',
},
},
dimensions: {
'request/source': {
displayName: 'Request source',
},
},
};
beforeEach(() => {
ctx.ds.getResource = jest.fn().mockImplementation(() => {
return Promise.resolve(response);
});
});
it('should return success status', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('success');
});
});
});
describe('and a PathNotFoundError error is returned', () => {
const error = {
data: {
error: {
code: 'PathNotFoundError',
message: `An error message.`,
},
},
status: 404,
statusText: 'Not Found',
};
beforeEach(() => {
ctx.ds.getResource = jest.fn().mockImplementation(() => {
return Promise.reject(error);
});
});
it.skip('should return error status and a detailed error message', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('error');
expect(results.message).toEqual(
'1. Application Insights: Not Found: Invalid Application Id for Application Insights service. '
);
});
});
});
describe('and an error is returned', () => {
const error = {
data: {
error: {
code: 'SomeOtherError',
message: `An error message.`,
},
},
status: 500,
statusText: 'Error',
};
beforeEach(() => {
ctx.ds.getResource = jest.fn().mockImplementation(() => {
return Promise.reject(error);
});
});
it.skip('should return error status and a detailed error message', () => {
return ctx.ds.testDatasource().then((results: any) => {
expect(results.status).toEqual('error');
expect(results.message).toEqual('1. Application Insights: Error: SomeOtherError. An error message. ');
});
});
});
});
describe('When performing raw query', () => {
const queryString =
'metrics ' +
'| where $__timeFilter(timestamp) ' +
'| where name == "testMetrics" ' +
'| summarize max=max(valueMax) by bin(timestamp, $__interval), partition';
const options = {
range: {
from: toUtc('2017-08-22T20:00:00Z'),
to: toUtc('2017-08-22T23:59:00Z'),
},
targets: [
{
apiVersion: '2016-09-01',
refId: 'A',
queryType: 'Application Insights',
appInsights: {
rawQuery: true,
rawQueryString: queryString,
timeColumn: 'timestamp',
valueColumn: 'max',
segmentColumn: undefined as unknown as string,
},
},
],
};
describe('with no grouping', () => {
const response: any = {
results: {
A: {
refId: 'A',
meta: {},
series: [
{
name: 'PrimaryResult',
points: [[2.2075, 1558278660000]],
},
],
tables: null,
},
},
};
beforeEach(() => {
fetchMock.mockImplementation((options: any) => {
expect(options.url).toContain('/api/ds/query');
expect(options.data.queries.length).toBe(1);
expect(options.data.queries[0].refId).toBe('A');
return of({ data: response, status: 200 } as any);
});
});
it('should return a list of datapoints', () => {
return lastValueFrom(ctx.ds.query(options)).then((results: any) => {
expect(results.data.length).toBe(1);
const data = results.data[0] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('PrimaryResult');
expect(data.fields[0].values.length).toEqual(1);
expect(data.fields[0].values.get(0)).toEqual(1558278660000);
expect(data.fields[1].values.get(0)).toEqual(2.2075);
});
});
});
describe('with grouping', () => {
const response: any = {
results: {
A: {
refId: 'A',
meta: {},
series: [
{
name: 'paritionA',
points: [[2.2075, 1558278660000]],
},
],
tables: null,
},
},
};
beforeEach(() => {
options.targets[0].appInsights.segmentColumn = 'partition';
fetchMock.mockImplementation((options: any) => {
expect(options.url).toContain('/api/ds/query');
expect(options.data.queries.length).toBe(1);
expect(options.data.queries[0].refId).toBe('A');
return of({ data: response, status: 200 } as any);
});
});
it('should return a list of datapoints', () => {
return lastValueFrom(ctx.ds.query(options)).then((results: any) => {
expect(results.data.length).toBe(1);
const data = results.data[0] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('paritionA');
expect(data.fields[0].values.length).toEqual(1);
expect(data.fields[0].values.get(0)).toEqual(1558278660000);
expect(data.fields[1].values.get(0)).toEqual(2.2075);
});
});
});
});
describe('When performing metric query', () => {
const options = {
range: {
from: toUtc('2017-08-22T20:00:00Z'),
to: toUtc('2017-08-22T23:59:00Z'),
},
targets: [
{
apiVersion: '2016-09-01',
refId: 'A',
queryType: 'Application Insights',
appInsights: {
metricName: 'exceptions/server',
dimension: '',
timeGrain: 'none',
},
},
],
};
describe('and with a single value', () => {
const response: any = {
results: {
A: {
refId: 'A',
meta: {},
series: [
{
name: 'exceptions/server',
points: [[2.2075, 1558278660000]],
},
],
tables: null,
},
},
};
beforeEach(() => {
fetchMock.mockImplementation((options: any) => {
expect(options.url).toContain('/api/ds/query');
expect(options.data.queries.length).toBe(1);
expect(options.data.queries[0].refId).toBe('A');
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
return of({ data: response, status: 200 } as any);
});
});
it('should return a single datapoint', () => {
return lastValueFrom(ctx.ds.query(options)).then((results: any) => {
expect(results.data.length).toBe(1);
const data = results.data[0] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('exceptions/server');
expect(data.fields[0].values.get(0)).toEqual(1558278660000);
expect(data.fields[1].values.get(0)).toEqual(2.2075);
});
});
});
describe('and with an interval group and without a segment group by', () => {
const response: any = {
results: {
A: {
refId: 'A',
meta: {},
series: [
{
name: 'exceptions/server',
points: [
[3, 1504108800000],
[6, 1504112400000],
],
},
],
tables: null,
},
},
};
beforeEach(() => {
options.targets[0].appInsights.timeGrain = 'PT30M';
fetchMock.mockImplementation((options: any) => {
expect(options.url).toContain('/api/ds/query');
expect(options.data.queries[0].refId).toBe('A');
expect(options.data.queries[0].appInsights.query).toBeUndefined();
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
expect(options.data.queries[0].appInsights.timeGrain).toBe('PT30M');
return of({ data: response, status: 200 } as any);
});
});
it('should return a list of datapoints', () => {
return lastValueFrom(ctx.ds.query(options)).then((results: any) => {
expect(results.data.length).toBe(1);
const data = results.data[0] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('exceptions/server');
expect(data.fields[0].values.length).toEqual(2);
expect(data.fields[0].values.get(0)).toEqual(1504108800000);
expect(data.fields[1].values.get(0)).toEqual(3);
expect(data.fields[0].values.get(1)).toEqual(1504112400000);
expect(data.fields[1].values.get(1)).toEqual(6);
});
});
});
describe('and with a group by', () => {
const response: any = {
results: {
A: {
refId: 'A',
meta: {},
series: [
{
name: 'exceptions/server{client/city="Miami"}',
points: [
[10, 1504108800000],
[20, 1504112400000],
],
},
{
name: 'exceptions/server{client/city="San Antonio"}',
points: [
[1, 1504108800000],
[2, 1504112400000],
],
},
],
tables: null,
},
},
};
describe('and with no alias specified', () => {
beforeEach(() => {
options.targets[0].appInsights.dimension = 'client/city';
fetchMock.mockImplementation((options: any) => {
expect(options.url).toContain('/api/ds/query');
expect(options.data.queries[0].appInsights.rawQueryString).toBeUndefined();
expect(options.data.queries[0].appInsights.metricName).toBe('exceptions/server');
expect([...options.data.queries[0].appInsights.dimension]).toMatchObject(['client/city']);
return of({ data: response, status: 200 } as any);
});
});
it('should return a list of datapoints', () => {
return lastValueFrom(ctx.ds.query(options)).then((results: any) => {
expect(results.data.length).toBe(2);
let data = results.data[0] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('exceptions/server{client/city="Miami"}');
expect(data.fields[1].values.length).toEqual(2);
expect(data.fields[0].values.get(0)).toEqual(1504108800000);
expect(data.fields[1].values.get(0)).toEqual(10);
expect(data.fields[0].values.get(1)).toEqual(1504112400000);
expect(data.fields[1].values.get(1)).toEqual(20);
data = results.data[1] as DataFrame;
expect(getFrameDisplayName(data)).toEqual('exceptions/server{client/city="San Antonio"}');
expect(data.fields[1].values.length).toEqual(2);
expect(data.fields[0].values.get(0)).toEqual(1504108800000);
expect(data.fields[1].values.get(0)).toEqual(1);
expect(data.fields[0].values.get(1)).toEqual(1504112400000);
expect(data.fields[1].values.get(1)).toEqual(2);
});
});
});
});
});
describe('When getting Metric Names', () => {
const response = {
metrics: {
'exceptions/server': {},
'requests/count': {},
},
};
beforeEach(() => {
ctx.ds.getResource = jest.fn().mockImplementation((path) => {
expect(path).toContain('/metrics/metadata');
return Promise.resolve({ data: response, status: 200 });
});
});
it.skip('should return a list of metric names', () => {
return ctx.ds.getAppInsightsMetricNames().then((results: any) => {
expect(results.length).toBe(2);
expect(results[0].text).toBe('exceptions/server');
expect(results[0].value).toBe('exceptions/server');
expect(results[1].text).toBe('requests/count');
expect(results[1].value).toBe('requests/count');
});
});
});
describe('When getting Metric Metadata', () => {
const response = {
metrics: {
'exceptions/server': {
supportedAggregations: ['sum'],
supportedGroupBy: {
all: ['client/os', 'client/city', 'client/browser'],
},
defaultAggregation: 'sum',
},
'requests/count': {
supportedAggregations: ['avg', 'sum', 'total'],
supportedGroupBy: {
all: ['client/os', 'client/city', 'client/browser'],
},
defaultAggregation: 'avg',
},
},
};
beforeEach(() => {
ctx.ds.getResource = jest.fn().mockImplementation((path) => {
expect(path).toContain('/metrics/metadata');
return Promise.resolve({ data: response, status: 200 });
});
});
it.skip('should return a list of group bys', () => {
return ctx.ds.getAppInsightsMetricMetadata('requests/count').then((results: any) => {
expect(results.primaryAggType).toEqual('avg');
expect(results.supportedAggTypes).toContain('avg');
expect(results.supportedAggTypes).toContain('sum');
expect(results.supportedAggTypes).toContain('total');
expect(results.supportedGroupBy).toContain('client/os');
expect(results.supportedGroupBy).toContain('client/city');
expect(results.supportedGroupBy).toContain('client/browser');
});
});
});
});

View File

@@ -1,171 +0,0 @@
import { isString } from 'lodash';
import { DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
import TimegrainConverter from '../../../time_grain_converter';
import {
AzureDataSourceJsonData,
AzureMonitorQuery,
DatasourceValidationResult,
DeprecatedAzureQueryType,
} from '../../../types';
import { routeNames } from '../../../utils/common';
import ResponseParser from './response_parser';
export interface LogAnalyticsColumn {
text: string;
value: string;
}
export default class AppInsightsDatasource extends DataSourceWithBackend<AzureMonitorQuery, AzureDataSourceJsonData> {
resourcePath: string;
version = 'beta';
applicationId: string;
logAnalyticsColumns: { [key: string]: LogAnalyticsColumn[] } = {};
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
super(instanceSettings);
this.applicationId = instanceSettings.jsonData.appInsightsAppId || '';
this.resourcePath = `${routeNames.appInsights}/${this.version}/apps/${this.applicationId}`;
}
isConfigured(): boolean {
return !!this.applicationId && this.applicationId.length > 0;
}
createRawQueryRequest(item: any, options: DataQueryRequest<AzureMonitorQuery>, target: AzureMonitorQuery) {
if (item.xaxis && !item.timeColumn) {
item.timeColumn = item.xaxis;
}
if (item.yaxis && !item.valueColumn) {
item.valueColumn = item.yaxis;
}
if (item.spliton && !item.segmentColumn) {
item.segmentColumn = item.spliton;
}
return {
type: 'timeSeriesQuery',
raw: false,
appInsights: {
rawQuery: true,
rawQueryString: getTemplateSrv().replace(item.rawQueryString, options.scopedVars),
timeColumn: item.timeColumn,
valueColumn: item.valueColumn,
segmentColumn: item.segmentColumn,
},
};
}
applyTemplateVariables(target: AzureMonitorQuery, scopedVars: ScopedVars): AzureMonitorQuery {
const item = target.appInsights;
if (!item) {
return target;
}
const old: any = item;
// fix for timeGrainUnit which is a deprecated/removed field name
if (old.timeGrainCount) {
item.timeGrain = TimegrainConverter.createISO8601Duration(old.timeGrainCount, item.timeGrainUnit);
} else if (item.timeGrain && item.timeGrainUnit && item.timeGrain !== 'auto') {
item.timeGrain = TimegrainConverter.createISO8601Duration(item.timeGrain, item.timeGrainUnit);
}
// migration for non-standard names
if (old.groupBy && !item.dimension) {
item.dimension = [old.groupBy];
}
if (old.filter && !item.dimensionFilter) {
item.dimensionFilter = old.filter;
}
// Migrate single dimension string to array
if (isString(item.dimension)) {
if (item.dimension === 'None') {
item.dimension = [];
} else {
item.dimension = [item.dimension as string];
}
}
if (!item.dimension) {
item.dimension = [];
}
const templateSrv = getTemplateSrv();
return {
refId: target.refId,
queryType: DeprecatedAzureQueryType.ApplicationInsights,
appInsights: {
timeGrain: templateSrv.replace((item.timeGrain || '').toString(), scopedVars),
metricName: templateSrv.replace(item.metricName, scopedVars),
aggregation: templateSrv.replace(item.aggregation, scopedVars),
dimension: item.dimension.map((d) => templateSrv.replace(d, scopedVars)),
dimensionFilter: templateSrv.replace(item.dimensionFilter, scopedVars),
alias: item.alias,
},
};
}
testDatasource(): Promise<DatasourceValidationResult> {
const path = `${this.resourcePath}/metrics/metadata`;
return this.getResource(path)
.then<DatasourceValidationResult>((response: any) => {
return {
status: 'success',
message: 'Successfully queried the Application Insights service.',
title: 'Success',
};
})
.catch((error: any) => {
let message = 'Application Insights: ';
message += error.statusText ? error.statusText + ': ' : '';
if (error.data && error.data.error && error.data.error.code === 'PathNotFoundError') {
message += 'Invalid Application Id for Application Insights service.';
} else if (error.data && error.data.error) {
message += error.data.error.code + '. ' + error.data.error.message;
} else {
message += 'Cannot connect to Application Insights REST API.';
}
return {
status: 'error',
message: message,
};
});
}
getMetricNames() {
const path = `${this.resourcePath}/metrics/metadata`;
return this.getResource(path).then(ResponseParser.parseMetricNames);
}
getMetricMetadata(metricName: string) {
const path = `${this.resourcePath}/metrics/metadata`;
return this.getResource(path).then((result: any) => {
return new ResponseParser(result).parseMetadata(metricName);
});
}
getGroupBys(metricName: string) {
return this.getMetricMetadata(metricName).then((result: any) => {
return new ResponseParser(result).parseGroupBys();
});
}
getQuerySchema() {
const path = `${this.resourcePath}/query/schema`;
return this.getResource(path).then((result: any) => {
const schema = new ResponseParser(result).parseQuerySchema();
// console.log(schema);
return schema;
});
}
}

View File

@@ -1,236 +0,0 @@
import { concat, filter, find, forEach, indexOf, intersection, isObject, map, without, keys as _keys } from 'lodash';
import { dateTime } from '@grafana/data';
export default class ResponseParser {
constructor(private results: any) {}
parseQueryResult() {
let data: any = [];
let columns: any = [];
for (let i = 0; i < this.results.length; i++) {
if (this.results[i].query.raw) {
const xaxis = this.results[i].query.xaxis;
const yaxises = this.results[i].query.yaxis;
const spliton = this.results[i].query.spliton;
columns = this.results[i].result.Tables[0].Columns;
const rows = this.results[i].result.Tables[0].Rows;
data = concat(data, this.parseRawQueryResultRow(this.results[i].query, columns, rows, xaxis, yaxises, spliton));
} else {
const value = this.results[i].result.value;
const alias = this.results[i].query.alias;
data = concat(data, this.parseQueryResultRow(this.results[i].query, value, alias));
}
}
return data;
}
parseRawQueryResultRow(query: any, columns: any, rows: any, xaxis: string, yaxises: string, spliton: string) {
const data: any[] = [];
const columnsForDropdown = map(columns, (column) => ({ text: column.ColumnName, value: column.ColumnName }));
const xaxisColumn = columns.findIndex((column: any) => column.ColumnName === xaxis);
const yaxisesSplit = yaxises.split(',');
const yaxisColumns: any = {};
forEach(yaxisesSplit, (yaxis) => {
yaxisColumns[yaxis] = columns.findIndex((column: any) => column.ColumnName === yaxis);
});
const splitonColumn = columns.findIndex((column: any) => column.ColumnName === spliton);
const convertTimestamp = xaxis === 'timestamp';
forEach(rows, (row) => {
forEach(yaxisColumns, (yaxisColumn, yaxisName) => {
const bucket =
splitonColumn === -1
? ResponseParser.findOrCreateBucket(data, yaxisName)
: ResponseParser.findOrCreateBucket(data, row[splitonColumn]);
const epoch = convertTimestamp ? ResponseParser.dateTimeToEpoch(row[xaxisColumn]) : row[xaxisColumn];
bucket.datapoints.push([row[yaxisColumn], epoch]);
bucket.refId = query.refId;
bucket.query = query.query;
bucket.columnsForDropdown = columnsForDropdown;
});
});
return data;
}
parseQueryResultRow(query: any, value: any, alias: string) {
const data: any[] = [];
if (ResponseParser.isSingleValue(value)) {
const metricName = ResponseParser.getMetricFieldKey(value);
const aggField = ResponseParser.getKeyForAggregationField(value[metricName]);
const epoch = ResponseParser.dateTimeToEpoch(value.end);
data.push({
target: metricName,
datapoints: [[value[metricName][aggField], epoch]],
refId: query.refId,
query: query.query,
});
return data;
}
const groupedBy = ResponseParser.hasSegmentsField(value.segments[0]);
if (!groupedBy) {
const metricName = ResponseParser.getMetricFieldKey(value.segments[0]);
const dataTarget = ResponseParser.findOrCreateBucket(data, metricName);
for (let i = 0; i < value.segments.length; i++) {
const epoch = ResponseParser.dateTimeToEpoch(value.segments[i].end);
const aggField: string = ResponseParser.getKeyForAggregationField(value.segments[i][metricName]);
dataTarget.datapoints.push([value.segments[i][metricName][aggField], epoch]);
}
dataTarget.refId = query.refId;
dataTarget.query = query.query;
} else {
for (let i = 0; i < value.segments.length; i++) {
const epoch = ResponseParser.dateTimeToEpoch(value.segments[i].end);
for (let j = 0; j < value.segments[i].segments.length; j++) {
const metricName = ResponseParser.getMetricFieldKey(value.segments[i].segments[j]);
const aggField = ResponseParser.getKeyForAggregationField(value.segments[i].segments[j][metricName]);
const target = this.getTargetName(value.segments[i].segments[j], alias);
const bucket = ResponseParser.findOrCreateBucket(data, target);
bucket.datapoints.push([value.segments[i].segments[j][metricName][aggField], epoch]);
bucket.refId = query.refId;
bucket.meta = {
query: query.query,
};
}
}
}
return data;
}
getTargetName(segment: { [x: string]: string }, alias: string) {
let metric = '';
let segmentName = '';
let segmentValue = '';
for (const prop in segment) {
if (isObject(segment[prop])) {
metric = prop;
} else {
segmentName = prop;
segmentValue = segment[prop];
}
}
if (alias) {
const regex = /\{\{([\s\S]+?)\}\}/g;
return alias.replace(regex, (match, g1, g2) => {
const group = g1 || g2;
if (group === 'metric') {
return metric;
} else if (group === 'groupbyname') {
return segmentName;
} else if (group === 'groupbyvalue') {
return segmentValue;
}
return match;
});
}
return metric + `{${segmentName}="${segmentValue}"}`;
}
static isSingleValue(value: any) {
return !ResponseParser.hasSegmentsField(value);
}
static findOrCreateBucket(data: any[], target: string) {
let dataTarget: any = find(data, ['target', target]);
if (!dataTarget) {
dataTarget = { target: target, datapoints: [] };
data.push(dataTarget);
}
return dataTarget;
}
static hasSegmentsField(obj: any) {
const keys = _keys(obj);
return indexOf(keys, 'segments') > -1;
}
static getMetricFieldKey(segment: { [x: string]: any }) {
const keys = _keys(segment);
return filter(without(keys, 'start', 'end'), (key) => {
return isObject(segment[key]);
})[0];
}
static getKeyForAggregationField(dataObj: any): string {
const keys = _keys(dataObj);
return intersection(keys, ['sum', 'avg', 'min', 'max', 'count', 'unique'])[0];
}
static dateTimeToEpoch(dateTimeValue: any) {
return dateTime(dateTimeValue).valueOf();
}
static parseMetricNames(result: { metrics: any }) {
const keys = _keys(result.metrics);
return ResponseParser.toTextValueList(keys);
}
parseMetadata(metricName: string) {
const metric = this.results.metrics[metricName];
if (!metric) {
throw Error('No data found for metric: ' + metricName);
}
return {
primaryAggType: metric.defaultAggregation,
supportedAggTypes: metric.supportedAggregations,
supportedGroupBy: metric.supportedGroupBy.all,
};
}
parseGroupBys() {
return ResponseParser.toTextValueList(this.results.supportedGroupBy);
}
parseQuerySchema() {
const result: any = {
Type: 'AppInsights',
Tables: {},
};
if (this.results && this.results && this.results.Tables) {
for (let i = 0; i < this.results.Tables[0].Rows.length; i++) {
const column = this.results.Tables[0].Rows[i];
const columnTable = column[0];
const columnName = column[1];
const columnType = column[2];
if (result.Tables[columnTable]) {
result.Tables[columnTable].OrderedColumns.push({ Name: columnName, Type: columnType });
} else {
result.Tables[columnTable] = {
Name: columnTable,
OrderedColumns: [{ Name: columnName, Type: columnType }],
};
}
}
}
return result;
}
static toTextValueList(values: any) {
const list: any[] = [];
for (let i = 0; i < values.length; i++) {
list.push({
text: values[i],
value: values[i],
});
}
return list;
}
}

View File

@@ -1,99 +0,0 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import AnalyticsConfig, { Props } from './AnalyticsConfig';
const setup = (propsFunc?: (props: Props) => Props) => {
let props: Props = {
options: {
id: 21,
uid: 'x',
orgId: 1,
name: 'Azure Monitor-10-10',
type: 'grafana-azure-monitor-datasource',
typeName: 'Azure',
typeLogoUrl: '',
access: 'proxy',
url: '',
password: '',
user: '',
database: '',
basicAuth: false,
basicAuthUser: '',
basicAuthPassword: '',
withCredentials: false,
isDefault: false,
secureJsonFields: {},
jsonData: {
cloudName: '',
subscriptionId: '',
},
version: 1,
readOnly: false,
},
updateOptions: jest.fn(),
};
if (propsFunc) {
props = propsFunc(props);
}
return render(<AnalyticsConfig {...props} />);
};
describe('Render', () => {
it('should disable log analytics credentials form', () => {
setup((props) => ({
...props,
options: {
...props.options,
jsonData: {
...props.options.jsonData,
azureLogAnalyticsSameAs: true,
},
},
}));
expect(screen.queryByText('Azure Monitor Logs')).not.toBeInTheDocument();
});
it('should not render the Switch to use different creds for log analytics by default', () => {
setup();
expect(screen.queryByText('is no longer supported', { exact: false })).not.toBeInTheDocument();
});
// Remove this test with deprecated code
it('should not render the Switch if different creds for log analytics were set from before', () => {
setup((props) => ({
...props,
options: {
...props.options,
jsonData: {
...props.options.jsonData,
azureLogAnalyticsSameAs: false,
},
},
}));
expect(screen.queryByText('is no longer supported', { exact: false })).toBeInTheDocument();
});
it('should clean up the error when resetting the credentials', async () => {
const onUpdate = jest.fn();
setup((props) => ({
...props,
options: {
...props.options,
jsonData: {
...props.options.jsonData,
azureLogAnalyticsSameAs: false,
},
},
updateOptions: onUpdate,
}));
expect(screen.queryByText('is no longer supported', { exact: false })).toBeInTheDocument();
await userEvent.click(screen.getByText('Clear Azure Monitor Logs Credentials'));
expect(onUpdate).toHaveBeenCalled();
const newOpts = onUpdate.mock.calls[0][0]({});
expect(newOpts).toEqual({ jsonData: { azureLogAnalyticsSameAs: true } });
});
});

View File

@@ -1,64 +0,0 @@
import React, { FunctionComponent, useMemo } from 'react';
import { Alert, Button } from '@grafana/ui';
import { getCredentials } from '../../../credentials';
import { AzureDataSourceSettings } from '../../../types';
import { AzureCredentialsForm } from '../../AzureCredentialsForm';
export interface Props {
options: AzureDataSourceSettings;
updateOptions: (optionsFunc: (options: AzureDataSourceSettings) => AzureDataSourceSettings) => void;
}
export const AnalyticsConfig: FunctionComponent<Props> = (props: Props) => {
const { updateOptions } = props;
const primaryCredentials = useMemo(() => getCredentials(props.options), [props.options]);
// Only show a section for setting LogAnalytics credentials if
// they were set from before with different values and the
// authType is supported
const logCredentialsEnabled =
primaryCredentials.authType === 'clientsecret' && props.options.jsonData.azureLogAnalyticsSameAs === false;
const onClearAzLogsCreds = () => {
updateOptions((options) => {
return {
...options,
jsonData: {
...options.jsonData,
azureLogAnalyticsSameAs: true,
},
};
});
};
return logCredentialsEnabled ? (
<>
<h3 className="page-heading">Azure Monitor Logs</h3>
<>
<Alert severity="error" title="Deprecated">
Using different credentials for Azure Monitor Logs is no longer supported. Authentication information above
will be used instead. Please create a new data source with the credentials below.
</Alert>
<AzureCredentialsForm
managedIdentityEnabled={false}
credentials={{
...primaryCredentials,
authType: 'clientsecret',
// Use deprecated Log Analytics credentials read-only
// to help with a possible migration
tenantId: props.options.jsonData.logAnalyticsTenantId,
clientId: props.options.jsonData.logAnalyticsClientId,
}}
disabled={true}
>
<Button onClick={onClearAzLogsCreds}>Clear Azure Monitor Logs Credentials</Button>
</AzureCredentialsForm>
</>
</>
) : null;
};
export default AnalyticsConfig;

View File

@@ -1,73 +0,0 @@
import React from 'react';
import { Alert, Input } from '@grafana/ui';
import { Field } from '../../../Field';
import { DeprecatedAzureMonitorQuery } from '../../types';
const ReadOnlyTimeGrain = ({
timeGrainCount,
timeGrainType,
timeGrainUnit,
}: {
timeGrainCount: string;
timeGrainType: string;
timeGrainUnit: string;
}) => {
const timeFields = timeGrainType === 'specific' ? ['specific', timeGrainCount, timeGrainUnit] : [timeGrainType];
return (
<Field label="Timegrain">
<>
{timeFields.map((timeField) => (
<Input value={timeField} disabled={true} onChange={() => {}} key={timeField} width={10} />
))}
</>
</Field>
);
};
const ApplicationInsightsEditor = ({ query }: { query: DeprecatedAzureMonitorQuery }) => {
const groupBy = query.appInsights?.dimension || [];
return (
<div data-testid="azure-monitor-application-insights-query-editor">
<Field label="Metric" disabled={true}>
<Input
value={query.appInsights?.metricName}
disabled={true}
onChange={() => {}}
id="azure-monitor-application-insights-metric"
/>
</Field>
<Field label="Aggregation" disabled={true}>
<Input value={query.appInsights?.aggregation} disabled={true} onChange={() => {}} />
</Field>
{groupBy.length > 0 && (
<Field label="Group by">
<>
{groupBy.map((dimension) => (
<Input value={dimension} disabled={true} onChange={() => {}} key={dimension} />
))}
</>
</Field>
)}
<Field label="Filter" disabled={true}>
<Input value={query.appInsights?.dimensionFilter} disabled={true} onChange={() => {}} />
</Field>
<ReadOnlyTimeGrain
timeGrainCount={query.appInsights?.timeGrainCount || ''}
timeGrainType={query.appInsights?.timeGrainType || 'auto'}
timeGrainUnit={query.appInsights?.timeGrainUnit || 'minute'}
/>
<Field label="Legend format" disabled={true}>
<Input placeholder="Alias patterns" value={query.appInsights?.alias} onChange={() => {}} disabled={true} />
</Field>
<Alert severity="info" title="Deprecated">
Application Insights is deprecated and is now read only. Migrate your queries to Metrics to make changes.
</Alert>
</div>
);
};
export default ApplicationInsightsEditor;

View File

@@ -1,52 +0,0 @@
import React from 'react';
import { Alert, CodeEditor, Select } from '@grafana/ui';
import { AzureMonitorOption } from '../../../../types';
import { Field } from '../../../Field';
import { Space } from '../../../Space';
import { DeprecatedAzureMonitorQuery } from '../../types';
interface InsightsAnalyticsEditorProps {
query: DeprecatedAzureMonitorQuery;
}
const FORMAT_OPTIONS: Array<AzureMonitorOption<string>> = [
{ label: 'Time series', value: 'time_series' },
{ label: 'Table', value: 'table' },
];
const InsightsAnalyticsEditor: React.FC<InsightsAnalyticsEditorProps> = ({ query }) => {
return (
<div data-testid="azure-monitor-insights-analytics-query-editor">
<CodeEditor
language="kusto"
value={query.insightsAnalytics?.query ?? ''}
height={200}
width="100%"
readOnly={true}
showMiniMap={false}
/>
<Field label="Format as">
<Select
menuShouldPortal
inputId="azure-monitor-logs-workspaces-field"
value={query.insightsAnalytics?.resultFormat}
disabled={true}
options={FORMAT_OPTIONS}
onChange={() => {}}
width={38}
/>
</Field>
<Space v={2} />
<Alert severity="info" title="Deprecated">
Insights Analytics is deprecated and is now read only. Migrate your queries to Logs to make changes.
</Alert>
</div>
);
};
export default InsightsAnalyticsEditor;

View File

@@ -1,110 +0,0 @@
import { shallow } from 'enzyme';
import React from 'react';
import { Button, LegacyForms } from '@grafana/ui';
import { Props } from '../../ConfigEditor';
import InsightsConfig from './InsightsConfig';
const { Input } = LegacyForms;
const setup = (propOverrides?: object) => {
const props: Props = {
options: {
id: 21,
uid: 'x',
orgId: 1,
name: 'Azure Monitor-10-10',
type: 'grafana-azure-monitor-datasource',
typeLogoUrl: '',
typeName: 'Azure',
access: 'proxy',
url: '',
password: '',
user: '',
database: '',
basicAuth: false,
basicAuthUser: '',
basicAuthPassword: '',
withCredentials: false,
isDefault: false,
secureJsonFields: {},
jsonData: {
cloudName: '',
subscriptionId: '',
},
secureJsonData: {},
version: 1,
readOnly: false,
},
onOptionsChange: jest.fn(),
};
Object.assign(props, propOverrides);
return shallow(<InsightsConfig {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
it('should disable insights api key input', () => {
const wrapper = setup({
options: {
secureJsonFields: {
appInsightsApiKey: true,
},
jsonData: {
appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
},
secureJsonData: {
appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
},
},
});
expect(wrapper).toMatchSnapshot();
});
it('should enable insights api key input', () => {
const wrapper = setup({
options: {
secureJsonFields: {
appInsightsApiKey: false,
},
jsonData: {
appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
},
secureJsonData: {
appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
},
},
});
expect(wrapper).toMatchSnapshot();
});
it('should disable buttons and inputs', () => {
const wrapper = setup({
options: {
secureJsonFields: {
appInsightsApiKey: true,
},
jsonData: {
appInsightsAppId: 'cddcc020-2c94-460a-a3d0-df3147ffa792',
},
secureJsonData: {
appInsightsApiKey: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
},
readOnly: true,
},
});
const buttons = wrapper.find(Button);
const inputs = wrapper.find(Input);
buttons.forEach((b) => expect(b.prop('disabled')).toBe(true));
inputs.forEach((i) => expect(i.prop('disabled')).toBe(true));
});
});

View File

@@ -1,98 +0,0 @@
import React, { PureComponent } from 'react';
import {
updateDatasourcePluginJsonDataOption,
updateDatasourcePluginResetOption,
updateDatasourcePluginSecureJsonDataOption,
} from '@grafana/data';
import { Alert, Button, InlineFormLabel, LegacyForms } from '@grafana/ui';
import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData } from '../../../types';
import { Props } from '../../ConfigEditor';
const { Input } = LegacyForms;
export class InsightsConfig extends PureComponent<Props> {
private onAppInsightsResetApiKey = () => {
this.resetSecureKey('appInsightsApiKey');
};
private onUpdateJsonDataOption =
(key: keyof AzureDataSourceJsonData) => (event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>) => {
updateDatasourcePluginJsonDataOption(this.props, key, event.currentTarget.value);
};
private onUpdateSecureJsonDataOption =
(key: keyof AzureDataSourceSecureJsonData) =>
(event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>) => {
updateDatasourcePluginSecureJsonDataOption(this.props, key, event.currentTarget.value);
};
private resetSecureKey = (key: keyof AzureDataSourceSecureJsonData) => {
updateDatasourcePluginResetOption(this.props, key);
};
render() {
const { options } = this.props;
return (
<>
<h3 className="page-heading">Azure Application Insights</h3>
<Alert severity="info" title="Application Insights credentials are deprecated">
Configure using Azure AD App Registration above and update existing queries to use Metrics or Logs.
</Alert>
<div className="gf-form-group">
{options.secureJsonFields.appInsightsApiKey ? (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">API Key</InlineFormLabel>
<Input className="width-25" placeholder="configured" disabled={true} />
</div>
<div className="gf-form">
<div className="max-width-30 gf-form-inline">
<Button
variant="secondary"
type="button"
onClick={this.onAppInsightsResetApiKey}
disabled={this.props.options.readOnly}
>
reset
</Button>
</div>
</div>
</div>
) : (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">API Key</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={options.secureJsonData!.appInsightsApiKey || ''}
onChange={this.onUpdateSecureJsonDataOption('appInsightsApiKey')}
disabled={this.props.options.readOnly}
/>
</div>
</div>
</div>
)}
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Application ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
value={options.jsonData.appInsightsAppId || ''}
onChange={this.onUpdateJsonDataOption('appInsightsAppId')}
disabled={this.props.options.readOnly}
/>
</div>
</div>
</div>
</div>
</>
);
}
}
export default InsightsConfig;

View File

@@ -1,208 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should disable insights api key input 1`] = `
<Fragment>
<h3
className="page-heading"
>
Azure Application Insights
</h3>
<Alert
severity="info"
title="Application Insights credentials are deprecated"
>
Configure using Azure AD App Registration above and update existing queries to use Metrics or Logs.
</Alert>
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
API Key
</FormLabel>
<Input
className="width-25"
disabled={true}
placeholder="configured"
/>
</div>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
onClick={[Function]}
type="button"
variant="secondary"
>
reset
</Button>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
value="cddcc020-2c94-460a-a3d0-df3147ffa792"
/>
</div>
</div>
</div>
</div>
</Fragment>
`;
exports[`Render should enable insights api key input 1`] = `
<Fragment>
<h3
className="page-heading"
>
Azure Application Insights
</h3>
<Alert
severity="info"
title="Application Insights credentials are deprecated"
>
Configure using Azure AD App Registration above and update existing queries to use Metrics or Logs.
</Alert>
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
API Key
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
value="cddcc020-2c94-460a-a3d0-df3147ffa792"
/>
</div>
</div>
</div>
</div>
</Fragment>
`;
exports[`Render should render component 1`] = `
<Fragment>
<h3
className="page-heading"
>
Azure Application Insights
</h3>
<Alert
severity="info"
title="Application Insights credentials are deprecated"
>
Configure using Azure AD App Registration above and update existing queries to use Metrics or Logs.
</Alert>
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
API Key
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
disabled={false}
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value=""
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
disabled={false}
onChange={[Function]}
value=""
/>
</div>
</div>
</div>
</div>
</Fragment>
`;

View File

@@ -1,30 +0,0 @@
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime';
import { AzureDataSourceJsonData, DeprecatedAzureQueryType } from '../../../types';
import AppInsightsDatasource from '../app_insights/app_insights_datasource';
import { DeprecatedAzureMonitorQuery } from '../types';
export default class InsightsAnalyticsDatasource extends AppInsightsDatasource {
constructor(instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>) {
super(instanceSettings);
}
applyTemplateVariables(target: DeprecatedAzureMonitorQuery, scopedVars: ScopedVars): DeprecatedAzureMonitorQuery {
const item = target.insightsAnalytics;
if (!item) {
return target;
}
const query = item.rawQueryString && !item.query ? item.rawQueryString : item.query;
return {
refId: target.refId,
queryType: DeprecatedAzureQueryType.InsightsAnalytics,
insightsAnalytics: {
query: getTemplateSrv().replace(query, scopedVars),
resultFormat: item.resultFormat,
},
};
}
}

View File

@@ -1,61 +0,0 @@
import { DataQuery } from '@grafana/data';
import {
AzureLogsQuery,
AzureMetricQuery,
AzureQueryType,
AzureResourceGraphQuery,
DeprecatedAzureQueryType,
} from '../../../types';
import { GrafanaTemplateVariableQuery } from '../../../types/templateVariables';
export interface DeprecatedAzureMonitorQuery extends DataQuery {
queryType?: AzureQueryType | DeprecatedAzureQueryType;
subscription?: string;
/** ARG uses multiple subscriptions */
subscriptions?: string[];
azureMonitor?: AzureMetricQuery;
azureLogAnalytics?: AzureLogsQuery;
azureResourceGraph?: AzureResourceGraphQuery;
grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery;
/** @deprecated App Insights/Insights Analytics deprecated in v8 */
appInsights?: ApplicationInsightsQuery;
/** @deprecated App Insights/Insights Analytics deprecated in v8 */
insightsAnalytics?: InsightsAnalyticsQuery;
}
/**
* Azure Monitor App Insights sub-query properties
* @deprecated App Insights deprecated in v8 in favor of Metrics queries
*/
export interface ApplicationInsightsQuery {
metricName?: string;
timeGrain?: string;
timeGrainCount?: string;
timeGrainType?: string;
timeGrainUnit?: string;
aggregation?: string;
dimension?: string[]; // Was string before 7.1
dimensionFilter?: string;
alias?: string;
/** @deprecated Migrated to Insights Analytics query */
rawQuery?: string;
}
/**
* Azure Monitor Insights Analytics sub-query properties
* @deprecated Insights Analytics deprecated in v8 in favor of Logs queries
*/
export interface InsightsAnalyticsQuery {
query?: string;
resultFormat?: string;
/** @deprecated Migrate field to query */
rawQueryString?: string;
}

View File

@@ -1,14 +0,0 @@
import { gt, valid } from 'semver';
import { config } from '@grafana/runtime';
import { AzureDataSourceSettings } from '../../types';
export function isAppInsightsConfigured(options: AzureDataSourceSettings) {
return !!(options.jsonData.appInsightsAppId && options.secureJsonFields.appInsightsApiKey);
}
export function gtGrafana9() {
// AppInsights configuration will be removed with Grafana 9
return valid(config.buildInfo.version) && gt(config.buildInfo.version, '9.0.0-beta1');
}

View File

@@ -16,18 +16,8 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource';
import AppInsightsDatasource from './components/deprecated/app_insights/app_insights_datasource';
import InsightsAnalyticsDatasource from './components/deprecated/insights_analytics/insights_analytics_datasource';
import { gtGrafana9 } from './components/deprecated/utils';
import { getAzureCloud } from './credentials';
import ResourcePickerData from './resourcePicker/resourcePickerData';
import {
AzureDataSourceJsonData,
AzureMonitorQuery,
AzureQueryType,
DatasourceValidationResult,
DeprecatedAzureQueryType,
} from './types';
import { AzureDataSourceJsonData, AzureMonitorQuery, AzureQueryType, DatasourceValidationResult } from './types';
import migrateAnnotation from './utils/migrateAnnotation';
import { datasourceMigrations } from './utils/migrateQuery';
import { VariableSupport } from './variables';
@@ -41,18 +31,9 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
azureLogAnalyticsDatasource: AzureLogAnalyticsDatasource;
resourcePickerData: ResourcePickerData;
azureResourceGraphDatasource: AzureResourceGraphDatasource;
/** @deprecated */
appInsightsDatasource?: AppInsightsDatasource;
/** @deprecated */
insightsAnalyticsDatasource?: InsightsAnalyticsDatasource;
pseudoDatasource: {
[key in AzureQueryType | DeprecatedAzureQueryType]?:
| AzureMonitorDatasource
| AzureLogAnalyticsDatasource
| AzureResourceGraphDatasource
| AppInsightsDatasource
| InsightsAnalyticsDatasource;
[key in AzureQueryType]?: AzureMonitorDatasource | AzureLogAnalyticsDatasource | AzureResourceGraphDatasource;
} = {};
declare optionsKey: Record<AzureQueryType, string>;
@@ -73,15 +54,6 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
[AzureQueryType.AzureResourceGraph]: this.azureResourceGraphDatasource,
};
const cloud = getAzureCloud(instanceSettings);
if (cloud === 'azuremonitor' || cloud === 'chinaazuremonitor') {
// AppInsights and InsightAnalytics are only supported for Public and Azure China clouds
this.appInsightsDatasource = new AppInsightsDatasource(instanceSettings);
this.insightsAnalyticsDatasource = new InsightsAnalyticsDatasource(instanceSettings);
this.pseudoDatasource[DeprecatedAzureQueryType.ApplicationInsights] = this.appInsightsDatasource;
this.pseudoDatasource[DeprecatedAzureQueryType.InsightsAnalytics] = this.insightsAnalyticsDatasource;
}
this.variables = new VariableSupport(this);
}
@@ -94,7 +66,7 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
}
query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
const byType = new Map<AzureQueryType | DeprecatedAzureQueryType, DataQueryRequest<AzureMonitorQuery>>();
const byType = new Map<AzureQueryType, DataQueryRequest<AzureMonitorQuery>>();
for (const baseTarget of options.targets) {
// Migrate old query structures
@@ -176,10 +148,6 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
promises.push(this.azureMonitorDatasource.testDatasource());
promises.push(this.azureLogAnalyticsDatasource.testDatasource());
if (!gtGrafana9() && this.appInsightsDatasource?.isConfigured()) {
promises.push(this.appInsightsDatasource.testDatasource());
}
return await Promise.all(promises).then((results) => {
let status: 'success' | 'error' = 'success';
let message = '';
@@ -219,19 +187,6 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
);
}
/* Application Insights API method */
getAppInsightsMetricNames() {
return this.appInsightsDatasource?.getMetricNames();
}
getAppInsightsMetricMetadata(metricName: string) {
return this.appInsightsDatasource?.getMetricMetadata(metricName);
}
getAppInsightsColumns(refId: string | number) {
return this.appInsightsDatasource?.logAnalyticsColumns[refId];
}
/*Azure Log Analytics */
getAzureLogAnalyticsWorkspaces(subscriptionId: string) {
return this.azureLogAnalyticsDatasource.getWorkspaces(subscriptionId);
@@ -281,12 +236,6 @@ function hasQueryForType(query: AzureMonitorQuery): boolean {
case AzureQueryType.GrafanaTemplateVariableFn:
return !!query.grafanaTemplateVariableFn;
case DeprecatedAzureQueryType.ApplicationInsights:
return !!query.appInsights;
case DeprecatedAzureQueryType.InsightsAnalytics:
return !!query.insightsAnalytics;
default:
return false;
}

View File

@@ -1,4 +1,4 @@
import { DeprecatedAzureMonitorQuery } from '../components/deprecated/types';
import { DataQuery } from '@grafana/data';
import { GrafanaTemplateVariableQuery } from './templateVariables';
@@ -9,18 +9,12 @@ export enum AzureQueryType {
GrafanaTemplateVariableFn = 'Grafana Template Variable Function',
}
// DeprecatedAzureQueryType won't be available after Grafana 9
export enum DeprecatedAzureQueryType {
ApplicationInsights = 'Application Insights',
InsightsAnalytics = 'Insights Analytics',
}
/**
* Represents the query as it moves through the frontend query editor and datasource files.
* It can represent new queries that are still being edited, so all properties are optional
*/
export interface AzureMonitorQuery extends DeprecatedAzureMonitorQuery {
queryType?: AzureQueryType | DeprecatedAzureQueryType;
export interface AzureMonitorQuery extends DataQuery {
queryType?: AzureQueryType;
subscription?: string;

View File

@@ -52,7 +52,6 @@ const azureMonitorQueryV8 = {
};
const modernMetricsQuery: AzureMonitorQuery = {
appInsights: { dimension: [], metricName: 'select', timeGrain: 'auto' },
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
@@ -75,7 +74,6 @@ const modernMetricsQuery: AzureMonitorQuery = {
top: '10',
},
azureResourceGraph: { resultFormat: 'table' },
insightsAnalytics: { query: '', resultFormat: 'time_series' },
queryType: AzureQueryType.AzureMonitor,
refId: 'A',
subscription: '44693801-6ee6-49de-9b2d-9106972f9572',

View File

@@ -5,7 +5,7 @@ import {
setTimeGrain as setMetricsTimeGrain,
} from '../components/MetricsQueryEditor/setQueryValue';
import TimegrainConverter from '../time_grain_converter';
import { AzureMonitorQuery, AzureQueryType, DeprecatedAzureQueryType } from '../types';
import { AzureMonitorQuery, AzureQueryType } from '../types';
const OLD_DEFAULT_DROPDOWN_VALUE = 'select';
@@ -20,7 +20,6 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer
workingQuery = migrateTimeGrains(workingQuery);
workingQuery = migrateLogAnalyticsToFromTimes(workingQuery);
workingQuery = migrateToDefaultNamespace(workingQuery);
workingQuery = migrateApplicationInsightsDimensions(workingQuery);
workingQuery = migrateMetricsDimensionFilters(workingQuery);
workingQuery = migrateResourceUri(workingQuery);
@@ -40,33 +39,6 @@ function migrateTimeGrains(query: AzureMonitorQuery): AzureMonitorQuery {
delete workingQuery.azureMonitor?.timeGrainUnit;
}
if (workingQuery.appInsights?.timeGrainUnit && workingQuery.appInsights.timeGrain !== 'auto') {
const appInsights = {
...workingQuery.appInsights,
};
if (workingQuery.appInsights.timeGrainCount) {
appInsights.timeGrain = TimegrainConverter.createISO8601Duration(
workingQuery.appInsights.timeGrainCount,
workingQuery.appInsights.timeGrainUnit
);
} else {
appInsights.timeGrainCount = workingQuery.appInsights.timeGrain;
if (workingQuery.appInsights.timeGrain) {
appInsights.timeGrain = TimegrainConverter.createISO8601Duration(
workingQuery.appInsights.timeGrain,
workingQuery.appInsights.timeGrainUnit
);
}
}
workingQuery = {
...workingQuery,
appInsights: appInsights,
};
}
return workingQuery;
}
@@ -107,22 +79,6 @@ function migrateToDefaultNamespace(query: AzureMonitorQuery): AzureMonitorQuery
return query;
}
function migrateApplicationInsightsDimensions(query: AzureMonitorQuery): AzureMonitorQuery {
const dimension = query?.appInsights?.dimension as unknown;
if (dimension && typeof dimension === 'string') {
return {
...query,
appInsights: {
...query.appInsights,
dimension: [dimension],
},
};
}
return query;
}
function migrateMetricsDimensionFilters(query: AzureMonitorQuery): AzureMonitorQuery {
let workingQuery = query;
@@ -166,18 +122,6 @@ function migrateResourceUri(query: AzureMonitorQuery): AzureMonitorQuery {
export function datasourceMigrations(query: AzureMonitorQuery): AzureMonitorQuery {
let workingQuery = query;
if (workingQuery.queryType === DeprecatedAzureQueryType.ApplicationInsights && workingQuery.appInsights?.rawQuery) {
workingQuery = {
...workingQuery,
queryType: DeprecatedAzureQueryType.InsightsAnalytics,
appInsights: undefined,
insightsAnalytics: {
query: workingQuery.appInsights.rawQuery,
resultFormat: 'time_series',
},
};
}
if (!workingQuery.queryType) {
workingQuery = {
...workingQuery,

View File

@@ -17,62 +17,6 @@ jest.mock('@grafana/runtime', () => ({
}));
describe('VariableSupport', () => {
describe('querying for grafana template variable fns', () => {
it('can fetch deprecated log analytics metric names', (done) => {
const expectedResults = ['test'];
const variableSupport = new VariableSupport(
createMockDatasource({
insightsAnalyticsDatasource: {
getMetricNames: jest.fn().mockResolvedValueOnce(expectedResults),
},
})
);
const mockRequest = {
targets: [
{
refId: 'A',
queryType: AzureQueryType.GrafanaTemplateVariableFn,
grafanaTemplateVariableFn: {
kind: 'AppInsightsMetricNameQuery',
rawQuery: 'AppInsightsMetricNames()',
},
} as AzureMonitorQuery,
],
} as DataQueryRequest<AzureMonitorQuery>;
const observables = variableSupport.query(mockRequest);
observables.subscribe((result: DataQueryResponseData) => {
expect(result.data[0].source).toEqual(expectedResults);
done();
});
});
it('can fetch deprecated log analytics groupBys', (done) => {
const expectedResults = ['test'];
const variableSupport = new VariableSupport(
createMockDatasource({
insightsAnalyticsDatasource: {
getGroupBys: jest.fn().mockResolvedValueOnce(expectedResults),
},
})
);
const mockRequest = {
targets: [
{
refId: 'A',
queryType: AzureQueryType.GrafanaTemplateVariableFn,
grafanaTemplateVariableFn: {
kind: 'AppInsightsGroupByQuery',
rawQuery: 'AppInsightsGroupBys(metricname)',
},
} as AzureMonitorQuery,
],
} as DataQueryRequest<AzureMonitorQuery>;
const observables = variableSupport.query(mockRequest);
observables.subscribe((result: DataQueryResponseData) => {
expect(result.data[0].source).toEqual(expectedResults);
done();
});
});
it('can fetch subscriptions', (done) => {
const fakeSubscriptions = ['subscriptionId'];
const variableSupport = new VariableSupport(

View File

@@ -47,17 +47,6 @@ export class VariableSupport extends CustomVariableSupport<DataSource, AzureMoni
}
callGrafanaTemplateVariableFn(query: GrafanaTemplateVariableQuery): Promise<MetricFindValue[]> | null {
// deprecated app insights template variables (will most likely remove in grafana 9)
if (this.datasource.insightsAnalyticsDatasource) {
if (query.kind === 'AppInsightsMetricNameQuery') {
return this.datasource.insightsAnalyticsDatasource.getMetricNames();
}
if (query.kind === 'AppInsightsGroupByQuery') {
return this.datasource.insightsAnalyticsDatasource.getGroupBys(getTemplateSrv().replace(query.metricName));
}
}
if (query.kind === 'SubscriptionsQuery') {
return this.datasource.getSubscriptions();
}