diff --git a/.betterer.results b/.betterer.results index 6e1d53ec622..c939e8e657e 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3630,70 +3630,13 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "7"], [0, 0, 0, "Unexpected any. Specify a different type.", "8"] ], - "public/app/plugins/datasource/azuremonitor/__mocks__/query_ctrl.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"] - ], - "public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"] - ], - "public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"] - ], - "public/app/plugins/datasource/azuremonitor/azure_log_analytics/response_parser.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Do not use any type assertions.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"], - [0, 0, 0, "Unexpected any. Specify a different type.", "13"], - [0, 0, 0, "Unexpected any. Specify a different type.", "14"] - ], "public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"] - ], - "public/app/plugins/datasource/azuremonitor/azure_monitor/response_parser.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"] - ], - "public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] + [0, 0, 0, "Do not use any type assertions.", "0"] ], "public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Do not use any type assertions.", "3"] + [0, 0, 0, "Do not use any type assertions.", "1"], + [0, 0, 0, "Do not use any type assertions.", "2"] ], "public/app/plugins/datasource/azuremonitor/components/MonitorConfig.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -3701,28 +3644,6 @@ exports[`better eslint`] = { "public/app/plugins/datasource/azuremonitor/components/QueryEditor/QueryEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/plugins/datasource/azuremonitor/datasource.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/azuremonitor/log_analytics/querystring_builder.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] - ], - "public/app/plugins/datasource/azuremonitor/time_grain_converter.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], - "public/app/plugins/datasource/azuremonitor/types/types.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"] - ], - "public/app/plugins/datasource/azuremonitor/utils/common.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"] - ], "public/app/plugins/datasource/azuremonitor/utils/messageFromError.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts b/public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts index 48864ab66b4..dc973f158e4 100644 --- a/public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/__mocks__/datasource.ts @@ -1,11 +1,31 @@ +import { DataSourceInstanceSettings } from '@grafana/data'; import { ContextSrv } from 'app/core/services/context_srv'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; +import { TemplateSrv } from 'app/features/templating/template_srv'; import Datasource from '../datasource'; +import { AzureDataSourceJsonData } from '../types'; + +import { createMockInstanceSetttings } from './instanceSettings'; +import { DeepPartial } from './utils'; + +export interface Context { + instanceSettings: DataSourceInstanceSettings; + templateSrv: TemplateSrv; + datasource: Datasource; + getResource: jest.Mock; +} + +export function createContext(overrides?: DeepPartial): Context { + const instanceSettings = createMockInstanceSetttings(overrides?.instanceSettings); + return { + instanceSettings, + templateSrv: new TemplateSrv(), + datasource: new Datasource(instanceSettings), + getResource: jest.fn(), + }; +} -type DeepPartial = { - [P in keyof T]?: DeepPartial; -}; const contextSrv = new ContextSrv(); const timeSrv = new TimeSrv(contextSrv); diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts b/public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts index e50f92359d0..7a378285151 100644 --- a/public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts +++ b/public/app/plugins/datasource/azuremonitor/__mocks__/instanceSettings.ts @@ -1,29 +1,71 @@ -import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data'; +import { DataSourceInstanceSettings, PluginType } from '@grafana/data'; -import { AzureDataSourceInstanceSettings, AzureDataSourceJsonData } from '../types'; +import { AzureDataSourceInstanceSettings } from '../types'; + +import { DeepPartial, mapPartialArrayObject } from './utils'; export const createMockInstanceSetttings = ( - overrides?: Partial, - jsonDataOverrides?: Partial -): AzureDataSourceInstanceSettings => ({ - url: '/ds/1', - id: 1, - uid: 'abc', - type: 'azuremonitor', - access: 'proxy', - meta: {} as DataSourcePluginMeta, - name: 'azure', - readOnly: false, - ...overrides, + overrides?: DeepPartial +): AzureDataSourceInstanceSettings => { + const metaOverrides = overrides?.meta; + return { + url: '/ds/1', + id: 1, + uid: 'abc', + type: 'azuremonitor', + access: 'proxy', + name: 'azure', + readOnly: false, + ...overrides, + meta: { + id: 'grafana-azure-monitor-datasource', + name: 'Azure Monitor', + type: PluginType.datasource, + module: 'path_to_module', + baseUrl: 'base_url', + ...metaOverrides, + info: { + description: 'Azure Monitor', + updated: 'updated', + version: '1.0.0', + ...metaOverrides?.info, + screenshots: mapPartialArrayObject( + { name: 'Azure Screenshot', path: 'path_to_screenshot' }, + metaOverrides?.info?.screenshots + ), + links: mapPartialArrayObject( + { name: 'Azure Link', url: 'link_url', target: '_blank' }, + metaOverrides?.info?.links + ), + author: { + name: 'test', + ...metaOverrides?.info?.author, + }, + logos: { + large: 'large.logo', + small: 'small.logo', + ...metaOverrides?.info?.logos, + }, + build: { + time: 0, + repo: 'repo', + branch: 'branch', + hash: 'hash', + number: 1, + pr: 1, + ...metaOverrides?.info?.build, + }, + }, + }, + jsonData: { + cloudName: 'azuremonitor', + azureAuthType: 'clientsecret', - jsonData: { - cloudName: 'azuremonitor', - azureAuthType: 'clientsecret', - - // monitor - tenantId: 'abc-123', - clientId: 'def-456', - subscriptionId: 'ghi-789', - ...jsonDataOverrides, - }, -}); + // monitor + tenantId: 'abc-123', + clientId: 'def-456', + subscriptionId: 'ghi-789', + ...overrides?.jsonData, + }, + }; +}; diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/query_ctrl.ts b/public/app/plugins/datasource/azuremonitor/__mocks__/query_ctrl.ts deleted file mode 100644 index 7ae8bca81c0..00000000000 --- a/public/app/plugins/datasource/azuremonitor/__mocks__/query_ctrl.ts +++ /dev/null @@ -1,16 +0,0 @@ -export class QueryCtrl { - target: any; - datasource: any; - panelCtrl: any; - panel: any; - hasRawMode = false; - error = ''; - - constructor(public $scope: any) { - this.panelCtrl = this.panelCtrl || { panel: {} }; - this.target = this.target || { target: '' }; - this.panel = this.panelCtrl.panel; - } - - refresh() {} -} diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/sdk.ts b/public/app/plugins/datasource/azuremonitor/__mocks__/sdk.ts deleted file mode 100644 index b8e8501ce71..00000000000 --- a/public/app/plugins/datasource/azuremonitor/__mocks__/sdk.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { QueryCtrl } from './query_ctrl'; - -export { QueryCtrl }; diff --git a/public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts b/public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts index c16be43fc86..1f29c41c66f 100644 --- a/public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts +++ b/public/app/plugins/datasource/azuremonitor/__mocks__/utils.ts @@ -38,3 +38,19 @@ export function createTemplateVariables(templateableProps: string[], value = '') }); return templateVariables; } + +export type DeepPartial = { + [attr in keyof K]?: K[attr] extends object ? DeepPartial : K[attr]; +}; + +export function mapPartialArrayObject(defaultValue: T, arr?: Array>): T[] { + if (!arr) { + return [defaultValue]; + } + return arr.map((item?: DeepPartial) => { + if (!item) { + return defaultValue; + } + return { ...item, ...defaultValue }; + }); +} diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts index 6ec08fc7e07..5a03accb62d 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/__mocks__/schema.ts @@ -162,7 +162,7 @@ export default class FakeSchemaData { }; } - static getlogAnalyticsFakeMetadata(): any { + static getlogAnalyticsFakeMetadata() { return { tables: [ { diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts index a74db99a1fd..4259936f878 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.test.ts @@ -1,6 +1,6 @@ -import { toUtc } from '@grafana/data'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import { Context, createContext } from '../__mocks__/datasource'; import createMockQuery from '../__mocks__/query'; import { createTemplateVariables } from '../__mocks__/utils'; import { singleVariable } from '../__mocks__/variables'; @@ -18,39 +18,29 @@ jest.mock('@grafana/runtime', () => ({ getTemplateSrv: () => templateSrv, })); -const makeResourceURI = ( - resourceName: string, - resourceGroup = 'test-resource-group', - subscriptionID = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' -) => - `/subscriptions/${subscriptionID}/resourceGroups/${resourceGroup}/providers/Microsoft.OperationalInsights/workspaces/${resourceName}`; - describe('AzureLogAnalyticsDatasource', () => { - const ctx: any = {}; + let ctx: Context; beforeEach(() => { templateSrv.init([singleVariable]); templateSrv.getVariables = jest.fn().mockReturnValue([singleVariable]); - ctx.instanceSettings = { - jsonData: { subscriptionId: 'xxx' }, - url: 'http://azureloganalyticsapi', - templateSrv: templateSrv, - }; - - ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings); + ctx = createContext({ + instanceSettings: { jsonData: { subscriptionId: 'xxx' }, url: 'http://azureloganalyticsapi' }, + }); + ctx.templateSrv = templateSrv; }); describe('When performing getSchema', () => { beforeEach(() => { - ctx.mockGetResource = jest.fn().mockImplementation((path: string) => { + ctx.getResource = jest.fn().mockImplementation((path: string) => { expect(path).toContain('metadata'); return Promise.resolve(FakeSchemaData.getlogAnalyticsFakeMetadata()); }); - ctx.ds.azureLogAnalyticsDatasource.getResource = ctx.mockGetResource; + ctx.datasource.azureLogAnalyticsDatasource.getResource = ctx.getResource; }); it('should return a schema to use with monaco-kusto', async () => { - const result = await ctx.ds.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); + const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); expect(result.database.tables).toHaveLength(2); expect(result.database.tables[0].name).toBe('Alert'); @@ -81,12 +71,12 @@ describe('AzureLogAnalyticsDatasource', () => { }); it('should interpolate variables when making a request for a schema with a uri that contains template variables', async () => { - await ctx.ds.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace/$var1'); - expect(ctx.mockGetResource).lastCalledWith('loganalytics/v1myWorkspace/var1-foo/metadata'); + await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace/$var1'); + expect(ctx.getResource).lastCalledWith('loganalytics/v1myWorkspace/var1-foo/metadata'); }); it('should include macros as suggested functions', async () => { - const result = await ctx.ds.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); + const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); expect(result.database.functions.map((f: { name: string }) => f.name)).toEqual([ 'Func1', '_AzureBackup_GetVaults', @@ -99,123 +89,42 @@ describe('AzureLogAnalyticsDatasource', () => { }); it('should include template variables as global parameters', async () => { - const result = await ctx.ds.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); + const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace'); expect(result.globalParameters.map((f: { name: string }) => f.name)).toEqual([`$${singleVariable.name}`]); }); }); - describe('When performing annotationQuery', () => { - const tableResponse = { - tables: [ - { - name: 'PrimaryResult', - columns: [ - { - name: 'TimeGenerated', - type: 'datetime', - }, - { - name: 'Text', - type: 'string', - }, - { - name: 'Tags', - type: 'string', - }, - ], - rows: [ - ['2018-06-02T20:20:00Z', 'Computer1', 'tag1,tag2'], - ['2018-06-02T20:28:00Z', 'Computer2', 'tag2'], - ], - }, - ], - }; - - const workspaceResponse = { - value: [ - { - name: 'aworkspace', - id: makeResourceURI('a-workspace'), - properties: { - source: 'Azure', - customerId: 'abc1b44e-3e57-4410-b027-6cc0ae6dee67', - }, - }, - ], - }; - - let annotationResults: any[]; - - beforeEach(async () => { - ctx.ds.azureLogAnalyticsDatasource.getResource = jest.fn().mockImplementation((path: string) => { - if (path.indexOf('Microsoft.OperationalInsights/workspaces') > -1) { - return Promise.resolve(workspaceResponse); - } else { - return Promise.resolve(tableResponse); - } - }); - - annotationResults = await ctx.ds.annotationQuery({ - annotation: { - rawQuery: 'Heartbeat | where $__timeFilter()| project TimeGenerated, Text=Computer, tags="test"', - workspace: 'abc1b44e-3e57-4410-b027-6cc0ae6dee67', - }, - range: { - from: toUtc('2017-08-22T20:00:00Z'), - to: toUtc('2017-08-22T23:59:00Z'), - }, - rangeRaw: { - from: 'now-4h', - to: 'now', - }, - }); - }); - - it('should return a list of categories in the correct format', () => { - expect(annotationResults.length).toBe(2); - - expect(annotationResults[0].time).toBe(1527970800000); - expect(annotationResults[0].text).toBe('Computer1'); - expect(annotationResults[0].tags[0]).toBe('tag1'); - expect(annotationResults[0].tags[1]).toBe('tag2'); - - expect(annotationResults[1].time).toBe(1527971280000); - expect(annotationResults[1].text).toBe('Computer2'); - expect(annotationResults[1].tags[0]).toBe('tag2'); - }); - }); - describe('When performing getWorkspaces', () => { beforeEach(() => { - ctx.ds.azureLogAnalyticsDatasource.getWorkspaceList = jest + ctx.datasource.azureLogAnalyticsDatasource.getResource = jest .fn() .mockResolvedValue({ value: [{ name: 'foobar', id: 'foo', properties: { customerId: 'bar' } }] }); }); it('should return the workspace id', async () => { - const workspaces = await ctx.ds.azureLogAnalyticsDatasource.getWorkspaces('sub'); + const workspaces = await ctx.datasource.azureLogAnalyticsDatasource.getWorkspaces('sub'); expect(workspaces).toEqual([{ text: 'foobar', value: 'foo' }]); }); }); describe('When performing getFirstWorkspace', () => { beforeEach(() => { - ctx.ds.azureLogAnalyticsDatasource.getDefaultOrFirstSubscription = jest.fn().mockResolvedValue('foo'); - ctx.ds.azureLogAnalyticsDatasource.getWorkspaces = jest + ctx.datasource.azureLogAnalyticsDatasource.getDefaultOrFirstSubscription = jest.fn().mockResolvedValue('foo'); + ctx.datasource.azureLogAnalyticsDatasource.getWorkspaces = jest .fn() .mockResolvedValue([{ text: 'foobar', value: 'foo' }]); - ctx.ds.azureLogAnalyticsDatasource.firstWorkspace = undefined; + ctx.datasource.azureLogAnalyticsDatasource.firstWorkspace = undefined; }); it('should return the stored workspace', async () => { - ctx.ds.azureLogAnalyticsDatasource.firstWorkspace = 'bar'; - const workspace = await ctx.ds.azureLogAnalyticsDatasource.getFirstWorkspace(); + ctx.datasource.azureLogAnalyticsDatasource.firstWorkspace = 'bar'; + const workspace = await ctx.datasource.azureLogAnalyticsDatasource.getFirstWorkspace(); expect(workspace).toEqual('bar'); - expect(ctx.ds.azureLogAnalyticsDatasource.getDefaultOrFirstSubscription).not.toHaveBeenCalled(); + expect(ctx.datasource.azureLogAnalyticsDatasource.getDefaultOrFirstSubscription).not.toHaveBeenCalled(); }); it('should return the first workspace', async () => { - const workspace = await ctx.ds.azureLogAnalyticsDatasource.getFirstWorkspace(); + const workspace = await ctx.datasource.azureLogAnalyticsDatasource.getFirstWorkspace(); expect(workspace).toEqual('foo'); }); }); @@ -252,15 +161,9 @@ describe('AzureLogAnalyticsDatasource', () => { }); describe('When performing filterQuery', () => { - const ctx: any = {}; let laDatasource: AzureLogAnalyticsDatasource; beforeEach(() => { - ctx.instanceSettings = { - jsonData: { subscriptionId: 'xxx' }, - url: 'http://azureloganalyticsapi', - }; - laDatasource = new AzureLogAnalyticsDatasource(ctx.instanceSettings); }); @@ -351,7 +254,7 @@ describe('AzureLogAnalyticsDatasource', () => { it('should return a query unchanged if no template variables are provided', () => { const query = createMockQuery(); query.queryType = AzureQueryType.LogAnalytics; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toEqual(query); }); @@ -369,7 +272,7 @@ describe('AzureLogAnalyticsDatasource', () => { ...query.azureLogAnalytics, ...azureLogAnalytics, }; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toHaveProperty('datasource'); expect(templatedQuery[0].azureLogAnalytics).toMatchObject({ query: templateVariables.get('query')?.templateVariable.current.value, @@ -389,7 +292,7 @@ describe('AzureLogAnalyticsDatasource', () => { ...query.azureLogAnalytics, ...azureLogAnalytics, }; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toHaveProperty('datasource'); expect(templatedQuery[0].azureLogAnalytics).toMatchObject({ resources: ['resource1', 'resource2'], @@ -413,7 +316,7 @@ describe('AzureLogAnalyticsDatasource', () => { ...azureTraces, }; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toHaveProperty('datasource'); expect(templatedQuery[0].azureTraces).toMatchObject({ query: templateVariables.get('query')?.templateVariable.current.value, @@ -441,7 +344,7 @@ describe('AzureLogAnalyticsDatasource', () => { ...query.azureTraces, ...azureTraces, }; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toHaveProperty('datasource'); expect(templatedQuery[0].azureTraces).toMatchObject({ resources: ['resource1', 'resource2'], diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.ts index 47622fe8c3f..23ff4921b92 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/azure_log_analytics_datasource.ts @@ -1,28 +1,24 @@ import { map } from 'lodash'; -import { DataSourceInstanceSettings, DataSourceRef, ScopedVars } from '@grafana/data'; +import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; import { TimeSrv, getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; -import { isGUIDish } from '../components/ResourcePicker/utils'; +import ResponseParser from '../azure_monitor/response_parser'; import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials'; -import LogAnalyticsQuerystringBuilder from '../log_analytics/querystring_builder'; import { + AzureAPIResponse, AzureDataSourceJsonData, AzureLogsVariable, AzureMonitorQuery, AzureQueryType, DatasourceValidationResult, + Subscription, + Workspace, } from '../types'; import { interpolateVariable, routeNames } from '../utils/common'; -import ResponseParser, { transformMetadataToKustoSchema } from './response_parser'; - -interface AdhocQuery { - datasource: DataSourceRef; - path: string; - resultFormat: string; -} +import { transformMetadataToKustoSchema } from './utils'; export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< AzureMonitorQuery, @@ -70,7 +66,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< } const path = `${this.azureMonitorPath}?api-version=2019-03-01`; - return await this.getResource(path).then((result: any) => { + return await this.getResource>(path).then((result) => { return ResponseParser.parseSubscriptions(result); }); } @@ -79,7 +75,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< const response = await this.getWorkspaceList(subscription); return ( - map(response.value, (val: any) => { + map(response.value, (val: Workspace) => { return { text: val.name, value: val.id, @@ -88,13 +84,13 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< ); } - private getWorkspaceList(subscription: string): Promise { + private getWorkspaceList(subscription: string): Promise> { const subscriptionId = getTemplateSrv().replace(subscription || this.defaultSubscriptionId); const workspaceListUrl = this.azureMonitorPath + `/${subscriptionId}/providers/Microsoft.OperationalInsights/workspaces?api-version=2017-04-26-preview`; - return this.getResource(workspaceListUrl); + return this.getResource>(workspaceListUrl); } async getMetadata(resourceUri: string) { @@ -201,29 +197,6 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< return this.instanceSettings.jsonData.logAnalyticsDefaultWorkspace; } - private buildQuery(query: string, options: any, workspace: string): AdhocQuery[] { - const querystringBuilder = new LogAnalyticsQuerystringBuilder( - getTemplateSrv().replace(query, {}, interpolateVariable), - options, - 'TimeGenerated' - ); - - const querystring = querystringBuilder.generate().uriString; - const path = isGUIDish(workspace) - ? `${this.resourcePath}/v1/workspaces/${workspace}/query?${querystring}` - : `${this.resourcePath}/v1${workspace}/query?${querystring}`; - - const queries = [ - { - datasource: this.getRef(), - path: path, - resultFormat: 'table', - }, - ]; - - return queries; - } - async getDefaultOrFirstSubscription(): Promise { if (this.defaultSubscriptionId) { return this.defaultSubscriptionId; @@ -252,40 +225,6 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend< return workspace; } - annotationQuery(options: any) { - if (!options.annotation.rawQuery) { - return Promise.reject({ - message: 'Query missing in annotation definition', - }); - } - - const queries = this.buildQuery(options.annotation.rawQuery, options, options.annotation.workspace); - const promises = this.doQueries(queries); - - return Promise.all(promises).then((results) => { - const annotations = new ResponseParser(results).transformToAnnotations(options); - return annotations; - }); - } - - doQueries(queries: AdhocQuery[]) { - return map(queries, (query) => { - return this.getResource(query.path) - .then((result: any) => { - return { - result: result, - query: query, - }; - }) - .catch((err: any) => { - throw { - error: err, - query: query, - }; - }); - }); - } - private validateDatasource(): DatasourceValidationResult | undefined { const authType = getAuthType(this.instanceSettings); diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/response_parser.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/response_parser.ts deleted file mode 100644 index da97cbe4a45..00000000000 --- a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/response_parser.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { concat, find, flattenDeep, forEach, get, map } from 'lodash'; - -import { AnnotationEvent, dateTime, TimeSeries, VariableModel } from '@grafana/data'; - -import { AzureLogsTableData, AzureLogsVariable } from '../types'; -import { AzureLogAnalyticsMetadata } from '../types/logAnalyticsMetadata'; - -export default class ResponseParser { - declare columns: string[]; - constructor(private results: any) {} - - parseQueryResult(): any { - let data: any[] = []; - let columns: any[] = []; - for (let i = 0; i < this.results.length; i++) { - if (this.results[i].result.tables.length === 0) { - continue; - } - columns = this.results[i].result.tables[0].columns; - const rows = this.results[i].result.tables[0].rows; - - if (this.results[i].query.resultFormat === 'time_series') { - data = concat(data, this.parseTimeSeriesResult(this.results[i].query, columns, rows)); - } else { - data = concat(data, this.parseTableResult(this.results[i].query, columns, rows)); - } - } - - return data; - } - - parseTimeSeriesResult(query: { refId: string; query: any }, columns: any[], rows: any): TimeSeries[] { - const data: TimeSeries[] = []; - let timeIndex = -1; - let metricIndex = -1; - let valueIndex = -1; - - for (let i = 0; i < columns.length; i++) { - if (timeIndex === -1 && columns[i].type === 'datetime') { - timeIndex = i; - } - - if (metricIndex === -1 && columns[i].type === 'string') { - metricIndex = i; - } - - if (valueIndex === -1 && ['int', 'long', 'real', 'double'].indexOf(columns[i].type) > -1) { - valueIndex = i; - } - } - - if (timeIndex === -1) { - throw new Error('No datetime column found in the result. The Time Series format requires a time column.'); - } - - forEach(rows, (row) => { - const epoch = ResponseParser.dateTimeToEpoch(row[timeIndex]); - const metricName = metricIndex > -1 ? row[metricIndex] : columns[valueIndex].name; - const bucket = ResponseParser.findOrCreateBucket(data, metricName); - bucket.datapoints.push([row[valueIndex], epoch]); - bucket.refId = query.refId; - bucket.meta = { - executedQueryString: query.query, - }; - }); - - return data; - } - - parseTableResult(query: { refId: string; query: string }, columns: any[], rows: any[]): AzureLogsTableData { - const tableResult: AzureLogsTableData = { - type: 'table', - columns: map(columns, (col) => { - return { text: col.name, type: col.type }; - }), - rows: rows, - refId: query.refId, - meta: { - executedQueryString: query.query, - }, - }; - - return tableResult; - } - - parseToVariables(): AzureLogsVariable[] { - const queryResult = this.parseQueryResult(); - - const variables: AzureLogsVariable[] = []; - forEach(queryResult, (result) => { - forEach(flattenDeep(result.rows), (row) => { - variables.push({ - text: row, - value: row, - } as AzureLogsVariable); - }); - }); - - return variables; - } - - transformToAnnotations(options: any) { - const queryResult = this.parseQueryResult(); - - const list: AnnotationEvent[] = []; - - forEach(queryResult, (result) => { - let timeIndex = -1; - let textIndex = -1; - let tagsIndex = -1; - - for (let i = 0; i < result.columns.length; i++) { - if (timeIndex === -1 && result.columns[i].type === 'datetime') { - timeIndex = i; - } - - if (textIndex === -1 && result.columns[i].text.toLowerCase() === 'text') { - textIndex = i; - } - - if (tagsIndex === -1 && result.columns[i].text.toLowerCase() === 'tags') { - tagsIndex = i; - } - } - - forEach(result.rows, (row) => { - list.push({ - annotation: options.annotation, - time: Math.floor(ResponseParser.dateTimeToEpoch(row[timeIndex])), - text: row[textIndex] ? row[textIndex].toString() : '', - tags: row[tagsIndex] ? row[tagsIndex].trim().split(/\s*,\s*/) : [], - }); - }); - }); - - return list; - } - - static findOrCreateBucket(data: TimeSeries[], target: any): TimeSeries { - let dataTarget: any = find(data, ['target', target]); - if (!dataTarget) { - dataTarget = { target: target, datapoints: [], refId: '', query: '' }; - data.push(dataTarget); - } - - return dataTarget; - } - - static dateTimeToEpoch(dateTimeValue: any) { - return dateTime(dateTimeValue).valueOf(); - } - - static parseSubscriptions(result: any): Array<{ text: string; value: string }> { - const list: Array<{ text: string; value: string }> = []; - - if (!result) { - return list; - } - - const valueFieldName = 'subscriptionId'; - const textFieldName = 'displayName'; - for (let i = 0; i < result.value.length; i++) { - if (!find(list, ['value', get(result.value[i], valueFieldName)])) { - list.push({ - text: `${get(result.value[i], textFieldName)}`, - value: get(result.value[i], valueFieldName), - }); - } - } - - return list; - } -} - -// matches (name):(type) = (defaultValue) -// e.g. fromRangeStart:datetime = datetime(null) -// - name: fromRangeStart -// - type: datetime -// - defaultValue: datetime(null) -const METADATA_FUNCTION_PARAMS = /([\w\W]+):([\w]+)(?:\s?=\s?([\w\W]+))?/; - -function transformMetadataFunction(sourceSchema: AzureLogAnalyticsMetadata) { - if (!sourceSchema.functions) { - return []; - } - - return sourceSchema.functions.map((fn) => { - const params = - fn.parameters && - fn.parameters - .split(', ') - .map((arg) => { - const match = arg.match(METADATA_FUNCTION_PARAMS); - if (!match) { - return; - } - - const [, name, type, defaultValue] = match; - - return { - name, - type, - defaultValue, - cslDefaultValue: defaultValue, - }; - }) - .filter((v: T): v is Exclude => !!v); - - return { - name: fn.name, - body: fn.body, - inputParameters: params || [], - }; - }); -} - -export function transformMetadataToKustoSchema( - sourceSchema: AzureLogAnalyticsMetadata, - nameOrIdOrSomething: string, - templateVariables: VariableModel[] -) { - const database = { - name: nameOrIdOrSomething, - tables: sourceSchema.tables, - functions: transformMetadataFunction(sourceSchema), - majorVersion: 0, - minorVersion: 0, - }; - - // Adding macros as known functions - database.functions.push( - { - name: '$__timeFilter', - body: '{ true }', - inputParameters: [ - { - name: 'timeColumn', - type: 'System.String', - defaultValue: '""', - cslDefaultValue: '""', - }, - ], - }, - { - name: '$__timeFrom', - body: '{ datetime(2018-06-05T18:09:58.907Z) }', - inputParameters: [], - }, - { - name: '$__timeTo', - body: '{ datetime(2018-06-05T20:09:58.907Z) }', - inputParameters: [], - }, - { - name: '$__escapeMulti', - body: `{ @'\\grafana-vm\Network(eth0)\Total', @'\\hello!'}`, - inputParameters: [ - { - name: '$myVar', - type: 'System.String', - defaultValue: '$myVar', - cslDefaultValue: '$myVar', - }, - ], - }, - { - name: '$__contains', - body: `{ colName in ('value1','value2') }`, - inputParameters: [ - { - name: 'colName', - type: 'System.String', - defaultValue: 'colName', - cslDefaultValue: 'colName', - }, - { - name: '$myVar', - type: 'System.String', - defaultValue: '$myVar', - cslDefaultValue: '$myVar', - }, - ], - } - ); - - // Adding macros as global parameters - const globalParameters = templateVariables.map((v) => { - return { - name: `$${v.name}`, - type: 'dynamic', - }; - }); - - return { - clusterType: 'Engine', - cluster: { - connectionString: nameOrIdOrSomething, - databases: [database], - }, - database: database, - globalParameters, - }; -} diff --git a/public/app/plugins/datasource/azuremonitor/azure_log_analytics/utils.ts b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/utils.ts new file mode 100644 index 00000000000..60f9a10008e --- /dev/null +++ b/public/app/plugins/datasource/azuremonitor/azure_log_analytics/utils.ts @@ -0,0 +1,133 @@ +import { VariableModel } from '@grafana/data'; + +import { AzureLogAnalyticsMetadata } from '../types/logAnalyticsMetadata'; + +// matches (name):(type) = (defaultValue) +// e.g. fromRangeStart:datetime = datetime(null) +// - name: fromRangeStart +// - type: datetime +// - defaultValue: datetime(null) +const METADATA_FUNCTION_PARAMS = /([\w\W]+):([\w]+)(?:\s?=\s?([\w\W]+))?/; + +function transformMetadataFunction(sourceSchema: AzureLogAnalyticsMetadata) { + if (!sourceSchema.functions) { + return []; + } + + return sourceSchema.functions.map((fn) => { + const params = + fn.parameters && + fn.parameters + .split(', ') + .map((arg) => { + const match = arg.match(METADATA_FUNCTION_PARAMS); + if (!match) { + return; + } + + const [, name, type, defaultValue] = match; + + return { + name, + type, + defaultValue, + cslDefaultValue: defaultValue, + }; + }) + .filter((v: T): v is Exclude => !!v); + + return { + name: fn.name, + body: fn.body, + inputParameters: params || [], + }; + }); +} + +export function transformMetadataToKustoSchema( + sourceSchema: AzureLogAnalyticsMetadata, + nameOrIdOrSomething: string, + templateVariables: VariableModel[] +) { + const database = { + name: nameOrIdOrSomething, + tables: sourceSchema.tables, + functions: transformMetadataFunction(sourceSchema), + majorVersion: 0, + minorVersion: 0, + }; + + // Adding macros as known functions + database.functions.push( + { + name: '$__timeFilter', + body: '{ true }', + inputParameters: [ + { + name: 'timeColumn', + type: 'System.String', + defaultValue: '""', + cslDefaultValue: '""', + }, + ], + }, + { + name: '$__timeFrom', + body: '{ datetime(2018-06-05T18:09:58.907Z) }', + inputParameters: [], + }, + { + name: '$__timeTo', + body: '{ datetime(2018-06-05T20:09:58.907Z) }', + inputParameters: [], + }, + { + name: '$__escapeMulti', + body: `{ @'\\grafana-vm\Network(eth0)\Total', @'\\hello!'}`, + inputParameters: [ + { + name: '$myVar', + type: 'System.String', + defaultValue: '$myVar', + cslDefaultValue: '$myVar', + }, + ], + }, + { + name: '$__contains', + body: `{ colName in ('value1','value2') }`, + inputParameters: [ + { + name: 'colName', + type: 'System.String', + defaultValue: 'colName', + cslDefaultValue: 'colName', + }, + { + name: '$myVar', + type: 'System.String', + defaultValue: '$myVar', + cslDefaultValue: '$myVar', + }, + ], + } + ); + + // Adding macros as global parameters + const globalParameters = templateVariables.map((v) => { + return { + name: `$${v.name}`, + type: 'dynamic', + }; + }); + + return { + clusterType: 'Engine', + cluster: { + connectionString: nameOrIdOrSomething, + databases: [database], + }, + database: database, + globalParameters, + }; +} diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts index 0f84b62322a..b6427b8425a 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.test.ts @@ -7,7 +7,7 @@ import createMockQuery from '../__mocks__/query'; import { createTemplateVariables } from '../__mocks__/utils'; import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables'; import AzureMonitorDatasource from '../datasource'; -import { AzureDataSourceJsonData, AzureMonitorLocationsResponse, AzureQueryType } from '../types'; +import { AzureAPIResponse, AzureDataSourceJsonData, AzureQueryType, Location } from '../types'; const templateSrv = new TemplateSrv(); @@ -477,7 +477,7 @@ describe('AzureMonitorDatasource', () => { }); describe('When performing getLocations', () => { - const sub1Response: AzureMonitorLocationsResponse = { + const sub1Response: AzureAPIResponse = { value: [ { id: '/subscriptions/mock-subscription-id-1/locations/northeurope', @@ -497,7 +497,7 @@ describe('AzureMonitorDatasource', () => { ], }; - const sub2Response: AzureMonitorLocationsResponse = { + const sub2Response: AzureAPIResponse = { value: [ { id: '/subscriptions/mock-subscription-id-2/locations/eastus2', diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts index 5621e34d3cc..79cfcd8f64d 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts @@ -1,3 +1,4 @@ +import { Namespace } from 'i18next'; import { find, startsWith } from 'lodash'; import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data'; @@ -8,11 +9,8 @@ import { getAuthType, getAzureCloud, getAzurePortalUrl } from '../credentials'; import TimegrainConverter from '../time_grain_converter'; import { AzureDataSourceJsonData, - AzureMonitorMetricNamespacesResponse, - AzureMonitorMetricNamesResponse, AzureMonitorMetricsMetadataResponse, AzureMonitorQuery, - AzureMonitorResourceGroupsResponse, AzureQueryType, DatasourceValidationResult, GetMetricNamespacesQuery, @@ -21,8 +19,12 @@ import { AzureMetricQuery, AzureMonitorLocations, AzureMonitorProvidersResponse, - AzureMonitorLocationsResponse, + AzureAPIResponse, AzureGetResourceNamesQuery, + Subscription, + Location, + ResourceGroup, + Metric, } from '../types'; import { routeNames } from '../utils/common'; import migrateQuery from '../utils/migrateQuery'; @@ -162,7 +164,9 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { + return this.getResource>( + `${this.resourcePath}/subscriptions?api-version=2019-03-01` + ).then((result) => { return ResponseParser.parseSubscriptions(result); }); } @@ -170,8 +174,8 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { - return ResponseParser.parseResponseValues(result, 'name', 'name'); + ).then((result: AzureAPIResponse) => { + return ResponseParser.parseResponseValues(result, 'name', 'name'); }); } @@ -199,7 +203,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { + return this.getResource(url).then(async (result) => { let list: Array<{ text: string; value: string }> = []; if (startsWith(metricNamespace?.toLowerCase(), 'microsoft.storage/storageaccounts/')) { list = ResponseParser.parseResourceNames(result, 'microsoft.storage/storageaccounts'); @@ -239,7 +243,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { + .then((result: AzureAPIResponse) => { return ResponseParser.parseResponseValues( result, 'properties.metricNamespaceName', @@ -273,7 +277,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend { + return this.getResource(url).then((result: AzureAPIResponse) => { return ResponseParser.parseResponseValues(result, 'name.localizedValue', 'name.value'); }); } @@ -366,7 +370,7 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend(); for (const subscription of subscriptions) { const subLocations = ResponseParser.parseLocations( - await this.getResource( + await this.getResource>( `${routeNames.azureMonitor}/subscriptions/${this.templateSrv.replace(subscription)}/locations?api-version=${ this.locationsApiVersion }` diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/response_parser.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/response_parser.ts index 63eac9c4ecb..226af005510 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/response_parser.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/response_parser.ts @@ -1,5 +1,7 @@ import { find, get } from 'lodash'; +import { FetchResponse } from '@grafana/runtime'; + import TimeGrainConverter from '../time_grain_converter'; import { AzureMonitorLocalizedValue, @@ -7,11 +9,14 @@ import { AzureMonitorMetricAvailabilityMetadata, AzureMonitorMetricsMetadataResponse, AzureMonitorOption, - AzureMonitorLocationsResponse, + AzureAPIResponse, + Location, + Subscription, + Resource, } from '../types'; export default class ResponseParser { - static parseResponseValues( - result: any, + static parseResponseValues( + result: AzureAPIResponse, textFieldName: string, valueFieldName: string ): Array<{ text: string; value: string }> { @@ -35,7 +40,10 @@ export default class ResponseParser { return list; } - static parseResourceNames(result: any, metricNamespace?: string): Array<{ text: string; value: string }> { + static parseResourceNames( + result: AzureAPIResponse, + metricNamespace?: string + ): Array<{ text: string; value: string }> { const list: Array<{ text: string; value: string }> = []; if (!result) { @@ -110,7 +118,7 @@ export default class ResponseParser { }); } - static parseSubscriptions(result: any): Array<{ text: string; value: string }> { + static parseSubscriptions(result: AzureAPIResponse): Array<{ text: string; value: string }> { const list: Array<{ text: string; value: string }> = []; if (!result) { @@ -131,7 +139,9 @@ export default class ResponseParser { return list; } - static parseSubscriptionsForSelect(result: any): Array<{ label: string; value: string }> { + static parseSubscriptionsForSelect( + result?: FetchResponse> + ): Array<{ label: string; value: string }> { const list: Array<{ label: string; value: string }> = []; if (!result) { @@ -152,28 +162,7 @@ export default class ResponseParser { return list; } - static parseWorkspacesForSelect(result: any): Array<{ label: string; value: string }> { - const list: Array<{ label: string; value: string }> = []; - - if (!result) { - return list; - } - - const valueFieldName = 'customerId'; - const textFieldName = 'name'; - for (let i = 0; i < result.data.value.length; i++) { - if (!find(list, ['value', get(result.data.value[i].properties, valueFieldName)])) { - list.push({ - label: get(result.data.value[i], textFieldName), - value: get(result.data.value[i].properties, valueFieldName), - }); - } - } - - return list; - } - - static parseLocations(result: AzureMonitorLocationsResponse) { + static parseLocations(result: AzureAPIResponse) { const locations: AzureMonitorLocations[] = []; if (!result) { diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts index 7e29fe295c1..b704d7527c9 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.test.ts @@ -3,14 +3,13 @@ import { set, get } from 'lodash'; import { backendSrv } from 'app/core/services/backend_srv'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import { Context, createContext } from '../__mocks__/datasource'; import createMockQuery from '../__mocks__/query'; import { createTemplateVariables } from '../__mocks__/utils'; import { multiVariable, singleVariable, subscriptionsVariable } from '../__mocks__/variables'; import AzureMonitorDatasource from '../datasource'; import { AzureQueryType } from '../types'; -import AzureResourceGraphDatasource from './azure_resource_graph_datasource'; - const templateSrv = new TemplateSrv({ getVariables: () => [subscriptionsVariable, singleVariable, multiVariable], getVariableWithName: jest.fn(), @@ -32,15 +31,15 @@ describe('AzureResourceGraphDatasource', () => { datasourceRequestMock.mockImplementation(jest.fn()); }); - const ctx: any = {}; + let ctx: Context; beforeEach(() => { - ctx.instanceSettings = { - url: 'http://azureresourcegraphapi', - jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f', cloudName: 'azuremonitor' }, - }; - - ctx.ds = new AzureResourceGraphDatasource(ctx.instanceSettings); + ctx = createContext({ + instanceSettings: { + url: 'http://azureresourcegraphapi', + jsonData: { subscriptionId: '9935389e-9122-4ef9-95f9-1513dd24753f', cloudName: 'azuremonitor' }, + }, + }); }); describe('When performing interpolateVariablesInQueries for azure_resource_graph', () => { @@ -51,7 +50,7 @@ describe('AzureResourceGraphDatasource', () => { it('should return a query unchanged if no template variables are provided', () => { const query = createMockQuery(); query.queryType = AzureQueryType.AzureResourceGraph; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.azureResourceGraphDatasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toEqual(query); }); @@ -70,7 +69,7 @@ describe('AzureResourceGraphDatasource', () => { ...query.azureResourceGraph, ...azureResourceGraph, }; - const templatedQuery = ctx.ds.interpolateVariablesInQueries([query], {}); + const templatedQuery = ctx.datasource.azureResourceGraphDatasource.interpolateVariablesInQueries([query], {}); expect(templatedQuery[0]).toHaveProperty('datasource'); for (const [path, templateVariable] of templateVariables.entries()) { expect(get(templatedQuery[0].azureResourceGraph, path)).toEqual( @@ -86,53 +85,63 @@ describe('AzureResourceGraphDatasource', () => { }); it('should expand single value template variable', () => { - const target = { + const target = createMockQuery({ + subscriptions: [], azureResourceGraph: { query: 'Resources | $var1', resultFormat: '', }, - }; - expect(ctx.ds.applyTemplateVariables(target)).toStrictEqual({ - azureResourceGraph: { query: 'Resources | var1-foo', resultFormat: 'table' }, - queryType: 'Azure Resource Graph', - subscriptions: [], }); + expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual( + expect.objectContaining({ + ...target, + azureResourceGraph: { query: 'Resources | var1-foo', resultFormat: 'table' }, + queryType: 'Azure Resource Graph', + subscriptions: [], + }) + ); }); it('should expand multi value template variable', () => { - const target = { + const target = createMockQuery({ + subscriptions: [], azureResourceGraph: { query: 'resources | where $__contains(name, $var3)', resultFormat: '', }, - }; - expect(ctx.ds.applyTemplateVariables(target)).toStrictEqual({ - azureResourceGraph: { - query: `resources | where $__contains(name, 'var3-foo','var3-baz')`, - resultFormat: 'table', - }, - queryType: 'Azure Resource Graph', - subscriptions: [], }); + expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual( + expect.objectContaining({ + ...target, + azureResourceGraph: { + query: `resources | where $__contains(name, 'var3-foo','var3-baz')`, + resultFormat: 'table', + }, + queryType: 'Azure Resource Graph', + subscriptions: [], + }) + ); }); }); it('should apply subscription variable', () => { - const target = { + const target = createMockQuery({ subscriptions: ['$subs'], azureResourceGraph: { query: 'resources | where $__contains(name, $var3)', resultFormat: '', }, - }; - expect(ctx.ds.applyTemplateVariables(target)).toStrictEqual({ - azureResourceGraph: { - query: `resources | where $__contains(name, 'var3-foo','var3-baz')`, - resultFormat: 'table', - }, - queryType: 'Azure Resource Graph', - subscriptions: ['sub-foo', 'sub-baz'], }); + expect(ctx.datasource.azureResourceGraphDatasource.applyTemplateVariables(target, {})).toEqual( + expect.objectContaining({ + azureResourceGraph: { + query: `resources | where $__contains(name, 'var3-foo','var3-baz')`, + resultFormat: 'table', + }, + queryType: 'Azure Resource Graph', + subscriptions: ['sub-foo', 'sub-baz'], + }) + ); }); describe('When performing targetContainsTemplate', () => { diff --git a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts index c1e8c036e69..4eebe3b35cb 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_resource_graph/azure_resource_graph_datasource.ts @@ -25,7 +25,7 @@ export default class AzureResourceGraphDatasource extends DataSourceWithBackend< const variableNames = templateSrv.getVariables().map((v) => `$${v.name}`); const subscriptionVar = _.find(target.subscriptions, (sub) => _.includes(variableNames, sub)); const interpolatedSubscriptions = templateSrv - .replace(subscriptionVar, scopedVars, (v: any) => v) + .replace(subscriptionVar, scopedVars, (v: string[] | string) => v) .split(',') .filter((v) => v.length > 0); const subscriptions = [ diff --git a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor.tsx index 986b39e76ed..8dc4ffd5052 100644 --- a/public/app/plugins/datasource/azuremonitor/components/ConfigEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/ConfigEditor.tsx @@ -6,7 +6,13 @@ import { Alert, SecureSocksProxySettings } from '@grafana/ui'; import { config } from 'app/core/config'; import ResponseParser from '../azure_monitor/response_parser'; -import { AzureDataSourceJsonData, AzureDataSourceSecureJsonData, AzureDataSourceSettings } from '../types'; +import { + AzureAPIResponse, + AzureDataSourceJsonData, + AzureDataSourceSecureJsonData, + AzureDataSourceSettings, + Subscription, +} from '../types'; import { routeNames } from '../utils/common'; import { MonitorConfig } from './MonitorConfig'; @@ -62,7 +68,7 @@ export class ConfigEditor extends PureComponent { const query = `?api-version=2019-03-01`; try { const result = await getBackendSrv() - .fetch({ + .fetch>({ url: this.baseURL + query, method: 'GET', }) diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx index 148ff64815b..2387cf9ed9c 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx @@ -1,3 +1,4 @@ +import { Uri } from 'monaco-editor'; import React, { useCallback, useEffect, useRef } from 'react'; import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui'; @@ -15,7 +16,7 @@ interface MonacoPromise { interface MonacoLanguages { kusto: { getKustoWorker: () => Promise< - (url: any) => Promise<{ + (url: Uri) => Promise<{ setSchema: (schema: any, clusterUrl: string, name: string) => void; }> >; diff --git a/public/app/plugins/datasource/azuremonitor/datasource.ts b/public/app/plugins/datasource/azuremonitor/datasource.ts index 2f6c444c25f..dfc1aeb9476 100644 --- a/public/app/plugins/datasource/azuremonitor/datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/datasource.ts @@ -139,10 +139,6 @@ export default class Datasource extends DataSourceWithBackend { - let builder: LogAnalyticsQuerystringBuilder; - - beforeEach(() => { - builder = new LogAnalyticsQuerystringBuilder( - 'query=Tablename | where $__timeFilter()', - { - interval: '5m', - range: { - from: dateTime().subtract(24, 'hours'), - to: dateTime(), - }, - rangeRaw: { - from: 'now-24h', - to: 'now', - }, - }, - 'TimeGenerated' - ); - }); - - describe('when $__timeFilter has no column parameter', () => { - it('should generate a time filter condition with TimeGenerated as the datetime field', () => { - const query = builder.generate().uriString; - - expect(query).toContain('where%20TimeGenerated%20%3E%3D%20datetime('); - }); - }); - - describe('when $__timeFilter has a column parameter', () => { - beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where $__timeFilter(myTime)'; - }); - - it('should generate a time filter condition with myTime as the datetime field', () => { - const query = builder.generate().uriString; - - expect(query).toContain('where%20myTime%20%3E%3D%20datetime('); - }); - }); - - describe('when $__contains and multi template variable has custom All value', () => { - beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where $__contains(col, all)'; - }); - - it('should generate a where..in clause', () => { - const query = builder.generate().rawQuery; - - expect(query).toContain(`where 1 == 1`); - }); - }); - - describe('when $__contains and multi template variable has one selected value', () => { - beforeEach(() => { - builder.rawQueryString = `query=Tablename | where $__contains(col, 'val1')`; - }); - - it('should generate a where..in clause', () => { - const query = builder.generate().rawQuery; - - expect(query).toContain(`where col in ('val1')`); - }); - }); - - describe('when $__contains and multi template variable has multiple selected values', () => { - beforeEach(() => { - builder.rawQueryString = `query=Tablename | where $__contains(col, 'val1','val2')`; - }); - - it('should generate a where..in clause', () => { - const query = builder.generate().rawQuery; - - expect(query).toContain(`where col in ('val1','val2')`); - }); - }); - - describe('when $__interval is in the query', () => { - beforeEach(() => { - builder.rawQueryString = 'query=Tablename | summarize count() by Category, bin(TimeGenerated, $__interval)'; - }); - - it('should replace $__interval with the inbuilt interval option', () => { - const query = builder.generate().uriString; - - expect(query).toContain('bin(TimeGenerated%2C%205m'); - }); - }); - - describe('when using $__timeFrom and $__timeTo is in the query and range is until now', () => { - beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where myTime >= $__timeFrom() and myTime <= $__timeTo()'; - }); - - it('should replace $__timeFrom and $__timeTo with a datetime and the now() function', () => { - const query = builder.generate().uriString; - - expect(query).toContain('where%20myTime%20%3E%3D%20datetime('); - expect(query).toContain('myTime%20%3C%3D%20datetime('); - }); - }); - - describe('when using $__timeFrom and $__timeTo is in the query and range is a specific interval', () => { - beforeEach(() => { - builder.rawQueryString = 'query=Tablename | where myTime >= $__timeFrom() and myTime <= $__timeTo()'; - builder.options.range.to = dateTime().subtract(1, 'hour'); - builder.options.rangeRaw.to = 'now-1h'; - }); - - it('should replace $__timeFrom and $__timeTo with datetimes', () => { - const query = builder.generate().uriString; - - expect(query).toContain('where%20myTime%20%3E%3D%20datetime('); - expect(query).toContain('myTime%20%3C%3D%20datetime('); - }); - }); - - describe('when using $__escape and multi template variable has one selected value', () => { - beforeEach(() => { - builder.rawQueryString = `$__escapeMulti('\\grafana-vm\Network(eth0)\Total Bytes Received')`; - }); - - it('should replace $__escape(val) with KQL style escaped string', () => { - const query = builder.generate().uriString; - expect(query).toContain(`%40'%5Cgrafana-vmNetwork(eth0)Total%20Bytes%20Received'`); - }); - }); - - describe('when using $__escape and multi template variable has multiple selected values', () => { - beforeEach(() => { - builder.rawQueryString = `CounterPath in ($__escapeMulti('\\grafana-vm\Network(eth0)\Total','\\grafana-vm\Network(eth0)\Total'))`; - }); - - it('should replace $__escape(val) with multiple KQL style escaped string', () => { - const query = builder.generate().uriString; - expect(query).toContain( - `CounterPath%20in%20(%40'%5Cgrafana-vmNetwork(eth0)Total'%2C%20%40'%5Cgrafana-vmNetwork(eth0)Total')` - ); - }); - }); - - describe('when using $__escape and multi template variable has one selected value that contains comma', () => { - beforeEach(() => { - builder.rawQueryString = `$__escapeMulti('\\grafana-vm,\Network(eth0)\Total Bytes Received')`; - }); - - it('should replace $__escape(val) with KQL style escaped string', () => { - const query = builder.generate().uriString; - expect(query).toContain(`%40'%5Cgrafana-vm%2CNetwork(eth0)Total%20Bytes%20Received'`); - }); - }); - - describe(`when using $__escape and multi template variable value is not wrapped in single '`, () => { - beforeEach(() => { - builder.rawQueryString = `$__escapeMulti(\\grafana-vm,\Network(eth0)\Total Bytes Received)`; - }); - - it('should not replace macro', () => { - const query = builder.generate().uriString; - expect(query).toContain(`%24__escapeMulti(%5Cgrafana-vm%2CNetwork(eth0)Total%20Bytes%20Received)`); - }); - }); - - describe('when there is no raw range', () => { - it('should still generate a time filter condition', () => { - builder = new LogAnalyticsQuerystringBuilder( - 'query=Tablename | where $__timeFilter()', - { - interval: '5m', - range: { - from: dateTime().subtract(24, 'hours'), - to: dateTime(), - }, - }, - 'TimeGenerated' - ); - const query = builder.generate().uriString; - - expect(query).toContain('where%20TimeGenerated%20%20%3E%3D%20datetime('); - }); - }); -}); diff --git a/public/app/plugins/datasource/azuremonitor/log_analytics/querystring_builder.ts b/public/app/plugins/datasource/azuremonitor/log_analytics/querystring_builder.ts deleted file mode 100644 index 8e5226dbe6b..00000000000 --- a/public/app/plugins/datasource/azuremonitor/log_analytics/querystring_builder.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { dateTime } from '@grafana/data'; - -export default class LogAnalyticsQuerystringBuilder { - constructor(public rawQueryString: string, public options: any, public defaultTimeField: any) {} - - generate() { - let queryString = this.rawQueryString; - const macroRegexp = /\$__([_a-zA-Z0-9]+)\(([^()]*)\)/gi; - queryString = queryString.replace(macroRegexp, (match, p1, p2) => { - if (p1 === 'contains') { - return this.getMultiContains(p2); - } - - return match; - }); - - queryString = queryString.replace(/\$__escapeMulti\(('[^]*')\)/gi, (match, p1) => this.escape(p1)); - - if (this.options) { - queryString = queryString.replace(macroRegexp, (match, p1, p2) => { - if (p1 === 'timeFilter') { - return this.getTimeFilter(p2, this.options); - } - if (p1 === 'timeFrom') { - return this.getFrom(this.options); - } - if (p1 === 'timeTo') { - return this.getUntil(this.options); - } - - return match; - }); - queryString = queryString.replace(/\$__interval/gi, this.options.interval); - } - const rawQuery = queryString; - queryString = encodeURIComponent(queryString); - const uriString = `query=${queryString}`; - - return { uriString, rawQuery }; - } - - getFrom(options: any) { - const from = options.range.from; - return `datetime(${dateTime(from).startOf('minute').toISOString()})`; - } - - getUntil(options: any) { - if (options.rangeRaw?.to === 'now') { - const now = Date.now(); - return `datetime(${dateTime(now).startOf('minute').toISOString()})`; - } else { - const until = options.range.to; - return `datetime(${dateTime(until).startOf('minute').toISOString()})`; - } - } - - getTimeFilter(timeFieldArg: any, options: any) { - const timeField = timeFieldArg || this.defaultTimeField; - if (options.rangeRaw?.to === 'now') { - return `${timeField} >= ${this.getFrom(options)}`; - } else { - return `${timeField} >= ${this.getFrom(options)} and ${timeField} <= ${this.getUntil(options)}`; - } - } - - getMultiContains(inputs: string) { - const firstCommaIndex = inputs.indexOf(','); - const field = inputs.substring(0, firstCommaIndex); - const templateVar = inputs.substring(inputs.indexOf(',') + 1); - - if (templateVar && templateVar.toLowerCase().trim() === 'all') { - return '1 == 1'; - } - - return `${field.trim()} in (${templateVar.trim()})`; - } - - escape(inputs: string) { - return inputs - .substring(1, inputs.length - 1) - .split(`','`) - .map((v) => `@'${v}'`) - .join(', '); - } -} diff --git a/public/app/plugins/datasource/azuremonitor/time_grain_converter.ts b/public/app/plugins/datasource/azuremonitor/time_grain_converter.ts index 66f8a689ebf..11d60e1cfed 100644 --- a/public/app/plugins/datasource/azuremonitor/time_grain_converter.ts +++ b/public/app/plugins/datasource/azuremonitor/time_grain_converter.ts @@ -3,7 +3,7 @@ import { includes, filter } from 'lodash'; import { rangeUtil } from '@grafana/data'; export default class TimeGrainConverter { - static createISO8601Duration(timeGrain: string | number, timeGrainUnit: any) { + static createISO8601Duration(timeGrain: string | number, timeGrainUnit: string) { const timeIntervals = ['hour', 'minute', 'h', 'm']; if (includes(timeIntervals, timeGrainUnit)) { return `PT${timeGrain}${timeGrainUnit[0].toUpperCase()}`; @@ -33,7 +33,7 @@ export default class TimeGrainConverter { return TimeGrainConverter.createISO8601Duration(timeGrain, unit); } - static findClosestTimeGrain(interval: any, allowedTimeGrains: string[]) { + static findClosestTimeGrain(interval: string, allowedTimeGrains: string[]) { const timeGrains = filter(allowedTimeGrains, (o) => o !== 'auto'); let closest = timeGrains[0]; diff --git a/public/app/plugins/datasource/azuremonitor/types/types.ts b/public/app/plugins/datasource/azuremonitor/types/types.ts index b727b37a9a0..ef33d7adccc 100644 --- a/public/app/plugins/datasource/azuremonitor/types/types.ts +++ b/public/app/plugins/datasource/azuremonitor/types/types.ts @@ -4,7 +4,6 @@ import { DataSourceSettings, PanelData, SelectableValue, - TableData, } from '@grafana/data'; import Datasource from '../datasource'; @@ -101,23 +100,6 @@ export interface AzureMonitorMetricMetadataItem { metricAvailabilities?: AzureMonitorMetricAvailabilityMetadata[]; } -export interface AzureMonitorMetricNamespacesResponse { - value: AzureMonitorMetricNamespaceItem[]; -} - -export interface AzureMonitorMetricNamespaceItem { - name: string; - properties: { metricNamespacename: string }; -} - -export interface AzureMonitorMetricNamesResponse { - value: AzureMonitorMetricNameItem[]; -} - -export interface AzureMonitorMetricNameItem { - name: { value: string; localizedValue: string }; -} - export interface AzureMonitorMetricAvailabilityMetadata { timeGrain: string; retention: string; @@ -128,30 +110,11 @@ export interface AzureMonitorLocalizedValue { localizedValue: string; } -export interface AzureMonitorResourceGroupsResponse { - data: { - value: Array<{ name: string }>; - }; - status: number; - statusText: string; -} - export interface AzureLogsVariable { text: string; value: string; } -export interface AzureLogsTableData extends TableData { - columns: AzureLogsTableColumn[]; - rows: any[]; - type: string; -} - -export interface AzureLogsTableColumn { - text: string; - type: string; -} - export interface AzureMonitorOption { label: string; value: T; @@ -293,11 +256,17 @@ export interface ProviderResourceType { capabilities: string; } -export interface AzureMonitorLocationsResponse { - value: Location[]; +export interface AzureAPIResponse { + value: T[]; + count?: { + type: string; + value: number; + }; + status?: number; + statusText?: string; } -interface Location { +export interface Location { id: string; name: string; displayName: string; @@ -319,3 +288,100 @@ interface LocationPairedRegion { name: string; id: string; } + +export interface Subscription { + id: string; + authorizationSource: string; + subscriptionId: string; + tenantId: string; + displayName: string; + state: string; + subscriptionPolicies: { + locationPlacementId: string; + quotaId: string; + spendingLimit: string; + }; +} + +export interface Workspace { + properties: { + customerId: string; + provisioningState: string; + sku: { + name: string; + }; + retentionInDays: number; + publicNetworkAccessForQuery: string; + publicNetworkAccessForIngestion: string; + }; + id: string; + name: string; + type: string; + location: string; + tags: Record; +} + +export interface Resource { + changedTime: string; + createdTime: string; + extendedLocation: { name: string; type: string }; + id: string; + identity: { principalId: string; tenantId: string; type: string; userAssignedIdentities: string[] }; + kind: string; + location: string; + managedBy: string; + name: string; + plan: { name: string; product: string; promotionCode: string; publisher: string; version: string }; + properties: Record; + provisioningState: string; + sku: { capacity: number; family: string; model: string; name: string; size: string; tier: string }; + tags: Record; + type: string; +} + +export interface ResourceGroup { + id: string; + location: string; + managedBy: string; + name: string; + properties: { provisioningState: string }; + tags: object; + type: string; +} + +export interface Namespace { + classification: { + Custom: string; + Platform: string; + Qos: string; + }; + id: string; + name: string; + properties: { metricNamespaceName: string }; + type: string; +} + +export interface Metric { + displayDescription: string; + errorCode: string; + errorMessage: string; + id: string; + name: AzureMonitorLocalizedValue; + timeseries: Array<{ data: MetricValue[]; metadatavalues: MetricMetadataValue[] }>; + type: string; + unit: string; +} + +interface MetricValue { + average: number; + count: number; + maximum: number; + minimum: number; + timeStamp: string; + total: number; +} + +interface MetricMetadataValue { + name: AzureMonitorLocalizedValue; + value: string; +} diff --git a/public/app/plugins/datasource/azuremonitor/utils/common.ts b/public/app/plugins/datasource/azuremonitor/utils/common.ts index 46b4e5f52e7..bc6d284bfb6 100644 --- a/public/app/plugins/datasource/azuremonitor/utils/common.ts +++ b/public/app/plugins/datasource/azuremonitor/utils/common.ts @@ -1,9 +1,8 @@ import { map } from 'lodash'; -import { rangeUtil, SelectableValue } from '@grafana/data'; +import { SelectableValue } from '@grafana/data'; import { VariableWithMultiSupport } from 'app/features/variables/types'; -import TimegrainConverter from '../time_grain_converter'; import { AzureMonitorOption, VariableOptionGroup } from '../types'; export const hasOption = (options: AzureMonitorOption[], value: string): boolean => @@ -37,16 +36,6 @@ export const addValueToOptions = ( return options; }; -export function convertTimeGrainsToMs(timeGrains: T[]) { - const allowedTimeGrainsMs: number[] = []; - timeGrains.forEach((tg: any) => { - if (tg.value !== 'auto') { - allowedTimeGrainsMs.push(rangeUtil.intervalToMs(TimegrainConverter.createKbnUnitFromISO8601Duration(tg.value))); - } - }); - return allowedTimeGrainsMs; -} - // Route definitions shared with the backend. // Check: /pkg/tsdb/azuremonitor/azuremonitor-resource-handler.go export const routeNames = { @@ -56,7 +45,10 @@ export const routeNames = { resourceGraph: 'resourcegraph', }; -export function interpolateVariable(value: any, variable: VariableWithMultiSupport) { +export function interpolateVariable( + value: string | number | Array, + variable: VariableWithMultiSupport +) { if (typeof value === 'string') { // When enabling multiple responses, quote the value to mimic the array result below // even if only one response is selected. This does not apply if only the "include all"