Azure Monitor: Add template variables for namespaces and resource names (#52247)

pull/52324/head^2
Andres Martinez Gotor 3 years ago committed by GitHub
parent 8fc51932f5
commit 10b9830cec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.test.ts
  2. 20
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/azure_monitor_datasource.ts
  3. 4
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/response_parser.ts
  4. 8
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.test.ts
  5. 2
      public/app/plugins/datasource/grafana-azure-monitor-datasource/azure_monitor/url_builder.ts
  6. 78
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.test.tsx
  7. 84
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/VariableEditor/VariableEditor.tsx
  8. 10
      public/app/plugins/datasource/grafana-azure-monitor-datasource/datasource.ts
  9. 6
      public/app/plugins/datasource/grafana-azure-monitor-datasource/types/query.ts
  10. 46
      public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.test.ts
  11. 20
      public/app/plugins/datasource/grafana-azure-monitor-datasource/variables.ts

@ -64,7 +64,7 @@ describe('AzureMonitorDatasource', () => {
const expected = const expected =
basePath + basePath +
'/providers/microsoft.insights/components/resource1' + '/providers/microsoft.insights/components/resource1' +
'/providers/microsoft.insights/metricNamespaces?api-version=2017-12-01-preview'; '/providers/microsoft.insights/metricNamespaces?region=global&api-version=2017-12-01-preview';
expect(path).toBe(expected); expect(path).toBe(expected);
return Promise.resolve(response); return Promise.resolve(response);
}); });
@ -80,7 +80,7 @@ describe('AzureMonitorDatasource', () => {
expect(results.length).toEqual(2); expect(results.length).toEqual(2);
expect(results[0].text).toEqual('Azure.ApplicationInsights'); expect(results[0].text).toEqual('Azure.ApplicationInsights');
expect(results[0].value).toEqual('Azure.ApplicationInsights'); expect(results[0].value).toEqual('Azure.ApplicationInsights');
expect(results[1].text).toEqual('microsoft.insights-components'); expect(results[1].text).toEqual('microsoft.insights/components');
expect(results[1].value).toEqual('microsoft.insights/components'); expect(results[1].value).toEqual('microsoft.insights/components');
}); });
}); });
@ -405,7 +405,7 @@ describe('AzureMonitorDatasource', () => {
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => { ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`; const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
expect(path).toBe( expect(path).toBe(
`${basePath}/${resourceGroup}/resources?$filter=resourceType eq '${metricDefinition}'&api-version=2021-04-01` `${basePath}/${resourceGroup}/resources?api-version=2021-04-01&$filter=resourceType eq '${metricDefinition}'`
); );
return Promise.resolve(response); return Promise.resolve(response);
}); });
@ -456,7 +456,7 @@ describe('AzureMonitorDatasource', () => {
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`; const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
expect(path).toBe( expect(path).toBe(
basePath + basePath +
`/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01` `/${resourceGroup}/resources?api-version=2021-04-01&$filter=resourceType eq '${validMetricDefinition}'`
); );
return Promise.resolve(response); return Promise.resolve(response);
}); });
@ -467,7 +467,7 @@ describe('AzureMonitorDatasource', () => {
expect(results[0].text).toEqual('storagetest/default'); expect(results[0].text).toEqual('storagetest/default');
expect(results[0].value).toEqual('storagetest/default'); expect(results[0].value).toEqual('storagetest/default');
expect(ctx.ds.azureMonitorDatasource.getResource).toHaveBeenCalledWith( expect(ctx.ds.azureMonitorDatasource.getResource).toHaveBeenCalledWith(
`azuremonitor/subscriptions/${subscription}/resourceGroups/${resourceGroup}/resources?$filter=resourceType eq '${validMetricDefinition}'&api-version=2021-04-01` `azuremonitor/subscriptions/${subscription}/resourceGroups/${resourceGroup}/resources?api-version=2021-04-01&$filter=resourceType eq '${validMetricDefinition}'`
); );
}); });
}); });
@ -497,7 +497,7 @@ describe('AzureMonitorDatasource', () => {
const fn = jest.fn(); const fn = jest.fn();
ctx.ds.azureMonitorDatasource.getResource = fn; ctx.ds.azureMonitorDatasource.getResource = fn;
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`; const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
const expectedPath = `${basePath}/${resourceGroup}/resources?$filter=resourceType eq '${metricDefinition}'&api-version=2021-04-01`; const expectedPath = `${basePath}/${resourceGroup}/resources?api-version=2021-04-01&$filter=resourceType eq '${metricDefinition}'`;
// first page // first page
fn.mockImplementationOnce((path: string) => { fn.mockImplementationOnce((path: string) => {
expect(path).toBe(expectedPath); expect(path).toBe(expectedPath);
@ -520,6 +520,35 @@ describe('AzureMonitorDatasource', () => {
}); });
}); });
}); });
describe('without a resource group or a metric definition', () => {
const response = {
value: [
{
name: 'Failure Anomalies - nodeapp',
type: 'microsoft.insights/alertrules',
},
{
name: resourceGroup,
type: metricDefinition,
},
],
};
beforeEach(() => {
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
const basePath = `azuremonitor/subscriptions/${subscription}/resources?api-version=2021-04-01`;
expect(path).toBe(basePath);
return Promise.resolve(response);
});
});
it('should return list of Resource Names', () => {
return ctx.ds.getResourceNames(subscription).then((results: Array<{ text: string; value: string }>) => {
expect(results.length).toEqual(2);
});
});
});
}); });
describe('When performing getMetricNames', () => { describe('When performing getMetricNames', () => {

@ -204,14 +204,18 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
}); });
} }
getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string, skipToken?: string) { getResourceNames(subscriptionId: string, resourceGroup?: string, metricDefinition?: string, skipToken?: string) {
const validMetricDefinition = startsWith(metricDefinition, 'Microsoft.Storage/storageAccounts/') const validMetricDefinition = startsWith(metricDefinition, 'Microsoft.Storage/storageAccounts/')
? 'Microsoft.Storage/storageAccounts' ? 'Microsoft.Storage/storageAccounts'
: metricDefinition; : metricDefinition;
let url = let url = `${this.resourcePath}/subscriptions/${subscriptionId}`;
`${this.resourcePath}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/resources?` + if (resourceGroup) {
`$filter=resourceType eq '${validMetricDefinition}'&` + url += `/resourceGroups/${resourceGroup}`;
`api-version=${this.listByResourceGroupApiVersion}`; }
url += `/resources?api-version=${this.listByResourceGroupApiVersion}`;
if (validMetricDefinition) {
url += `&$filter=resourceType eq '${validMetricDefinition}'`;
}
if (skipToken) { if (skipToken) {
url += `&$skiptoken=${skipToken}`; url += `&$skiptoken=${skipToken}`;
} }
@ -251,7 +255,11 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
); );
return this.getResource(url) return this.getResource(url)
.then((result: AzureMonitorMetricNamespacesResponse) => { .then((result: AzureMonitorMetricNamespacesResponse) => {
return ResponseParser.parseResponseValues(result, 'name', 'properties.metricNamespaceName'); return ResponseParser.parseResponseValues(
result,
'properties.metricNamespaceName',
'properties.metricNamespaceName'
);
}) })
.then((result) => { .then((result) => {
if (url.includes('Microsoft.Storage/storageAccounts')) { if (url.includes('Microsoft.Storage/storageAccounts')) {

@ -33,7 +33,7 @@ export default class ResponseParser {
return list; return list;
} }
static parseResourceNames(result: any, metricDefinition: string): Array<{ text: string; value: string }> { static parseResourceNames(result: any, metricDefinition?: string): Array<{ text: string; value: string }> {
const list: Array<{ text: string; value: string }> = []; const list: Array<{ text: string; value: string }> = [];
if (!result) { if (!result) {
@ -43,7 +43,7 @@ export default class ResponseParser {
for (let i = 0; i < result.value.length; i++) { for (let i = 0; i < result.value.length; i++) {
if ( if (
typeof result.value[i].type === 'string' && typeof result.value[i].type === 'string' &&
result.value[i].type.toLocaleLowerCase() === metricDefinition.toLocaleLowerCase() (!metricDefinition || result.value[i].type.toLocaleLowerCase() === metricDefinition.toLocaleLowerCase())
) { ) {
list.push({ list.push({
text: result.value[i].name, text: result.value[i].name,

@ -92,7 +92,7 @@ describe('AzureMonitorUrlBuilder', () => {
templateSrv templateSrv
); );
expect(url).toBe( expect(url).toBe(
'/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' '/subscriptions/sub/resource-uri/resource/providers/microsoft.insights/metricNamespaces?region=global&api-version=2017-05-01-preview'
); );
}); });
}); });
@ -130,7 +130,7 @@ describe('AzureMonitorUrlBuilder', () => {
); );
expect(url).toBe( expect(url).toBe(
'/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' + '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.NetApp/netAppAccounts/rn1/capacityPools/rn2/volumes/rn3/' +
'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' 'providers/microsoft.insights/metricNamespaces?region=global&api-version=2017-05-01-preview'
); );
}); });
}); });
@ -150,7 +150,7 @@ describe('AzureMonitorUrlBuilder', () => {
); );
expect(url).toBe( expect(url).toBe(
'/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' + '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn1/databases/rn2/' +
'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' 'providers/microsoft.insights/metricNamespaces?region=global&api-version=2017-05-01-preview'
); );
}); });
}); });
@ -170,7 +170,7 @@ describe('AzureMonitorUrlBuilder', () => {
); );
expect(url).toBe( expect(url).toBe(
'/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' + '/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Sql/servers/rn/' +
'providers/microsoft.insights/metricNamespaces?api-version=2017-05-01-preview' 'providers/microsoft.insights/metricNamespaces?region=global&api-version=2017-05-01-preview'
); );
}); });
}); });

@ -57,7 +57,7 @@ export default class UrlBuilder {
); );
} }
return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?api-version=${apiVersion}`; return `${baseUrl}${resourceUri}/providers/microsoft.insights/metricNamespaces?region=global&api-version=${apiVersion}`;
} }
static buildAzureMonitorGetMetricNamesUrl( static buildAzureMonitorGetMetricNamesUrl(

@ -29,7 +29,15 @@ const defaultProps = {
subscription: 'id', subscription: 'id',
}, },
onChange: jest.fn(), onChange: jest.fn(),
datasource: createMockDatasource(), datasource: createMockDatasource({
getSubscriptions: jest.fn().mockResolvedValue([{ text: 'Primary Subscription', value: 'sub' }]),
getResourceGroups: jest.fn().mockResolvedValue([{ text: 'rg', value: 'rg' }]),
getMetricNamespaces: jest.fn().mockResolvedValue([{ text: 'foo/bar', value: 'foo/bar' }]),
getVariablesRaw: jest.fn().mockReturnValue([
{ label: 'query0', name: 'sub0' },
{ label: 'query1', name: 'rg', query: { queryType: AzureQueryType.ResourceGroupsQuery } },
]),
}),
}; };
const originalConfigValue = grafanaRuntime.config.featureToggles.azTemplateVars; const originalConfigValue = grafanaRuntime.config.featureToggles.azTemplateVars;
@ -166,11 +174,8 @@ describe('VariableEditor:', () => {
it('should run the query if requesting resource groups', async () => { it('should run the query if requesting resource groups', async () => {
grafanaRuntime.config.featureToggles.azTemplateVars = true; grafanaRuntime.config.featureToggles.azTemplateVars = true;
const ds = createMockDatasource({
getSubscriptions: jest.fn().mockResolvedValue([{ text: 'Primary Subscription', value: 'sub' }]),
});
const onChange = jest.fn(); const onChange = jest.fn();
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} datasource={ds} />); const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
// wait for initial load // wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable // Select RGs variable
@ -195,14 +200,7 @@ describe('VariableEditor:', () => {
it('should show template variables as options ', async () => { it('should show template variables as options ', async () => {
const onChange = jest.fn(); const onChange = jest.fn();
grafanaRuntime.config.featureToggles.azTemplateVars = true; grafanaRuntime.config.featureToggles.azTemplateVars = true;
const ds = createMockDatasource({ const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
getSubscriptions: jest.fn().mockResolvedValue([{ text: 'Primary Subscription', value: 'sub' }]),
getVariablesRaw: jest.fn().mockReturnValue([
{ label: 'query0', name: 'sub0' },
{ label: 'query1', name: 'rg', query: { queryType: AzureQueryType.ResourceGroupsQuery } },
]),
});
const { rerender } = render(<VariableEditor {...defaultProps} datasource={ds} onChange={onChange} />);
// wait for initial load // wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable // Select RGs variable
@ -210,7 +208,7 @@ describe('VariableEditor:', () => {
screen.getByText('Resource Groups').click(); screen.getByText('Resource Groups').click();
// Simulate onChange behavior // Simulate onChange behavior
const newQuery = onChange.mock.calls.at(-1)[0]; const newQuery = onChange.mock.calls.at(-1)[0];
rerender(<VariableEditor {...defaultProps} query={newQuery} onChange={onChange} datasource={ds} />); rerender(<VariableEditor {...defaultProps} query={newQuery} onChange={onChange} />);
await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
// Select a subscription // Select a subscription
openMenu(screen.getByLabelText('select subscription')); openMenu(screen.getByLabelText('select subscription'));
@ -218,10 +216,60 @@ describe('VariableEditor:', () => {
screen.getByText('Template Variables').click(); screen.getByText('Template Variables').click();
// Simulate onChange behavior // Simulate onChange behavior
const lastQuery = onChange.mock.calls.at(-1)[0]; const lastQuery = onChange.mock.calls.at(-1)[0];
rerender(<VariableEditor {...defaultProps} query={lastQuery} onChange={onChange} datasource={ds} />); rerender(<VariableEditor {...defaultProps} query={lastQuery} onChange={onChange} />);
await waitFor(() => expect(screen.getByText('query0')).toBeInTheDocument()); await waitFor(() => expect(screen.getByText('query0')).toBeInTheDocument());
// Template variables of the same type than the current one should not appear // Template variables of the same type than the current one should not appear
expect(screen.queryByText('query1')).not.toBeInTheDocument(); expect(screen.queryByText('query1')).not.toBeInTheDocument();
}); });
it('should run the query if requesting namespaces', async () => {
grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable
openMenu(screen.getByLabelText('select query type'));
screen.getByText('Namespaces').click();
// Simulate onChange behavior
const newQuery = onChange.mock.calls.at(-1)[0];
rerender(<VariableEditor {...defaultProps} query={newQuery} onChange={onChange} />);
await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
// Select a subscription
openMenu(screen.getByLabelText('select subscription'));
screen.getByText('Primary Subscription').click();
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
queryType: AzureQueryType.NamespacesQuery,
subscription: 'sub',
refId: 'A',
})
);
});
it('should run the query if requesting resource names', async () => {
grafanaRuntime.config.featureToggles.azTemplateVars = true;
const onChange = jest.fn();
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable
openMenu(screen.getByLabelText('select query type'));
screen.getByText('Resource Names').click();
// Simulate onChange behavior
const newQuery = onChange.mock.calls.at(-1)[0];
rerender(<VariableEditor {...defaultProps} query={newQuery} onChange={onChange} />);
await waitFor(() => expect(screen.getByText('Select subscription')).toBeInTheDocument());
// Select a subscription
openMenu(screen.getByLabelText('select subscription'));
screen.getByText('Primary Subscription').click();
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
queryType: AzureQueryType.ResourceNamesQuery,
subscription: 'sub',
refId: 'A',
})
);
});
}); });
}); });

@ -21,6 +21,8 @@ type Props = {
datasource: DataSource; datasource: DataSource;
}; };
const removeOption: SelectableValue = { label: '-', value: '' };
const VariableEditor = (props: Props) => { const VariableEditor = (props: Props) => {
const { query, onChange, datasource } = props; const { query, onChange, datasource } = props;
const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [ const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [
@ -30,13 +32,19 @@ const VariableEditor = (props: Props) => {
if (config.featureToggles.azTemplateVars) { if (config.featureToggles.azTemplateVars) {
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Subscriptions', value: AzureQueryType.SubscriptionsQuery }); AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Subscriptions', value: AzureQueryType.SubscriptionsQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Groups', value: AzureQueryType.ResourceGroupsQuery }); AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Groups', value: AzureQueryType.ResourceGroupsQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Namespaces', value: AzureQueryType.NamespacesQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Names', value: AzureQueryType.ResourceNamesQuery });
} }
const [variableOptionGroup, setVariableOptionGroup] = useState<{ label: string; options: AzureMonitorOption[] }>({ const [variableOptionGroup, setVariableOptionGroup] = useState<{ label: string; options: AzureMonitorOption[] }>({
label: 'Template Variables', label: 'Template Variables',
options: [], options: [],
}); });
const [requireSubscription, setRequireSubscription] = useState(false); const [requireSubscription, setRequireSubscription] = useState(false);
const [hasResourceGroup, setHasResourceGroup] = useState(false);
const [hasNamespace, setHasNamespace] = useState(false);
const [subscriptions, setSubscriptions] = useState<SelectableValue[]>([]); const [subscriptions, setSubscriptions] = useState<SelectableValue[]>([]);
const [resourceGroups, setResourceGroups] = useState<SelectableValue[]>([]);
const [namespaces, setNamespaces] = useState<SelectableValue[]>([]);
const [errorMessage, setError] = useLastError(); const [errorMessage, setError] = useLastError();
const queryType = typeof query === 'string' ? '' : query.queryType; const queryType = typeof query === 'string' ? '' : query.queryType;
@ -47,12 +55,22 @@ const VariableEditor = (props: Props) => {
}, [query, datasource, onChange]); }, [query, datasource, onChange]);
useEffect(() => { useEffect(() => {
setRequireSubscription(false);
setHasResourceGroup(false);
setHasNamespace(false);
switch (queryType) { switch (queryType) {
case AzureQueryType.ResourceGroupsQuery: case AzureQueryType.ResourceGroupsQuery:
setRequireSubscription(true); setRequireSubscription(true);
break; break;
default: case AzureQueryType.NamespacesQuery:
setRequireSubscription(false); setRequireSubscription(true);
setHasResourceGroup(true);
break;
case AzureQueryType.ResourceNamesQuery:
setRequireSubscription(true);
setHasResourceGroup(true);
setHasNamespace(true);
break;
} }
}, [queryType]); }, [queryType]);
@ -75,6 +93,24 @@ const VariableEditor = (props: Props) => {
}); });
}); });
const subscription = typeof query === 'object' && query.subscription;
useEffect(() => {
if (subscription) {
datasource.getResourceGroups(subscription).then((rgs) => {
setResourceGroups(rgs.map((s) => ({ label: s.text, value: s.value })));
});
}
}, [datasource, subscription]);
const resourceGroup = (typeof query === 'object' && query.resourceGroup) || '';
useEffect(() => {
if (subscription) {
datasource.getMetricNamespaces(subscription, resourceGroup).then((rgs) => {
setNamespaces(rgs.map((s) => ({ label: s.text, value: s.value })));
});
}
}, [datasource, subscription, resourceGroup]);
if (typeof query === 'string') { if (typeof query === 'string') {
// still migrating the query // still migrating the query
return null; return null;
@ -98,6 +134,20 @@ const VariableEditor = (props: Props) => {
} }
}; };
const onChangeResourceGroup = (selectableValue: SelectableValue) => {
onChange({
...query,
resourceGroup: selectableValue.value,
});
};
const onChangeNamespace = (selectableValue: SelectableValue) => {
onChange({
...query,
namespace: selectableValue.value,
});
};
const onLogsQueryChange = (queryChange: AzureMonitorQuery) => { const onLogsQueryChange = (queryChange: AzureMonitorQuery) => {
onChange(queryChange); onChange(queryChange);
}; };
@ -113,7 +163,7 @@ const VariableEditor = (props: Props) => {
value={queryType} value={queryType}
/> />
</InlineField> </InlineField>
{typeof query === 'object' && query.queryType === AzureQueryType.LogAnalytics && ( {query.queryType === AzureQueryType.LogAnalytics && (
<> <>
<LogsQueryEditor <LogsQueryEditor
subscriptionId={query.subscription} subscriptionId={query.subscription}
@ -134,10 +184,10 @@ const VariableEditor = (props: Props) => {
)} )}
</> </>
)} )}
{typeof query === 'object' && query.queryType === AzureQueryType.GrafanaTemplateVariableFn && ( {query.queryType === AzureQueryType.GrafanaTemplateVariableFn && (
<GrafanaTemplateVariableFnInput query={query} updateQuery={props.onChange} datasource={datasource} /> <GrafanaTemplateVariableFnInput query={query} updateQuery={props.onChange} datasource={datasource} />
)} )}
{typeof query === 'object' && requireSubscription && ( {requireSubscription && (
<InlineField label="Select subscription" labelWidth={20}> <InlineField label="Select subscription" labelWidth={20}>
<Select <Select
aria-label="select subscription" aria-label="select subscription"
@ -148,6 +198,30 @@ const VariableEditor = (props: Props) => {
/> />
</InlineField> </InlineField>
)} )}
{hasResourceGroup && (
<InlineField label="Select Resource Group" labelWidth={20}>
<Select
aria-label="select resource group"
onChange={onChangeResourceGroup}
options={resourceGroups.concat(variableOptionGroup, removeOption)}
width={25}
value={query.resourceGroup}
placeholder="Optional"
/>
</InlineField>
)}
{hasNamespace && (
<InlineField label="Select Namespace" labelWidth={20}>
<Select
aria-label="select namespace"
onChange={onChangeNamespace}
options={namespaces.concat(variableOptionGroup, removeOption)}
width={25}
value={query.namespace}
placeholder="Optional"
/>
</InlineField>
)}
</> </>
); );
}; };

@ -154,7 +154,15 @@ export default class Datasource extends DataSourceWithBackend<AzureMonitorQuery,
); );
} }
getResourceNames(subscriptionId: string, resourceGroup: string, metricDefinition: string) { getMetricNamespaces(subscriptionId: string, resourceGroup?: string) {
let url = `/subscriptions/${subscriptionId}`;
if (resourceGroup) {
url += `/resourceGroups/${resourceGroup};`;
}
return this.azureMonitorDatasource.getMetricNamespaces({ resourceUri: url });
}
getResourceNames(subscriptionId: string, resourceGroup?: string, metricDefinition?: string) {
return this.azureMonitorDatasource.getResourceNames( return this.azureMonitorDatasource.getResourceNames(
this.templateSrv.replace(subscriptionId), this.templateSrv.replace(subscriptionId),
this.templateSrv.replace(resourceGroup), this.templateSrv.replace(resourceGroup),

@ -8,6 +8,8 @@ export enum AzureQueryType {
AzureResourceGraph = 'Azure Resource Graph', AzureResourceGraph = 'Azure Resource Graph',
SubscriptionsQuery = 'Azure Subscriptions', SubscriptionsQuery = 'Azure Subscriptions',
ResourceGroupsQuery = 'Azure Resource Groups', ResourceGroupsQuery = 'Azure Resource Groups',
NamespacesQuery = 'Azure Namespaces',
ResourceNamesQuery = 'Azure Resource Names',
/** Deprecated */ /** Deprecated */
GrafanaTemplateVariableFn = 'Grafana Template Variable Function', GrafanaTemplateVariableFn = 'Grafana Template Variable Function',
} }
@ -28,6 +30,10 @@ export interface AzureMonitorQuery extends DataQuery {
azureLogAnalytics?: AzureLogsQuery; azureLogAnalytics?: AzureLogsQuery;
azureResourceGraph?: AzureResourceGraphQuery; azureResourceGraph?: AzureResourceGraphQuery;
grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery; grafanaTemplateVariableFn?: GrafanaTemplateVariableQuery;
/** Template variables params */
resourceGroup?: string;
namespace?: string;
} }
/** /**

@ -559,5 +559,51 @@ describe('VariableSupport', () => {
done(); done();
}); });
}); });
it('can fetch namespaces', (done) => {
const expectedResults = ['test'];
const variableSupport = new VariableSupport(
createMockDatasource({
getMetricNamespaces: jest.fn().mockResolvedValueOnce(expectedResults),
})
);
const mockRequest = {
targets: [
{
refId: 'A',
queryType: AzureQueryType.NamespacesQuery,
subscription: 'sub',
} 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 resource names', (done) => {
const expectedResults = ['test'];
const variableSupport = new VariableSupport(
createMockDatasource({
getResourceNames: jest.fn().mockResolvedValueOnce(expectedResults),
})
);
const mockRequest = {
targets: [
{
refId: 'A',
queryType: AzureQueryType.ResourceNamesQuery,
subscription: 'sub',
} as AzureMonitorQuery,
],
} as DataQueryRequest<AzureMonitorQuery>;
const observables = variableSupport.query(mockRequest);
observables.subscribe((result: DataQueryResponseData) => {
expect(result.data[0].source).toEqual(expectedResults);
done();
});
});
}); });
}); });

@ -43,6 +43,24 @@ export class VariableSupport extends CustomVariableSupport<DataSource, AzureMoni
data: rgs?.length ? [toDataFrame(rgs)] : [], data: rgs?.length ? [toDataFrame(rgs)] : [],
}; };
} }
case AzureQueryType.NamespacesQuery:
if (queryObj.subscription) {
const rgs = await this.datasource.getMetricNamespaces(queryObj.subscription, queryObj.resourceGroup);
return {
data: rgs?.length ? [toDataFrame(rgs)] : [],
};
}
case AzureQueryType.ResourceNamesQuery:
if (queryObj.subscription) {
const rgs = await this.datasource.getResourceNames(
queryObj.subscription,
queryObj.resourceGroup,
queryObj.namespace
);
return {
data: rgs?.length ? [toDataFrame(rgs)] : [],
};
}
case AzureQueryType.GrafanaTemplateVariableFn: case AzureQueryType.GrafanaTemplateVariableFn:
if (queryObj.grafanaTemplateVariableFn) { if (queryObj.grafanaTemplateVariableFn) {
const templateVariablesResults = await this.callGrafanaTemplateVariableFn( const templateVariablesResults = await this.callGrafanaTemplateVariableFn(
@ -57,7 +75,7 @@ export class VariableSupport extends CustomVariableSupport<DataSource, AzureMoni
return lastValueFrom(this.datasource.query(request)); return lastValueFrom(this.datasource.query(request));
} }
} catch (err) { } catch (err) {
return { data: [], error: { message: messageFromError(err) } }; return { data: [], error: new Error(messageFromError(err)) };
} }
}; };

Loading…
Cancel
Save