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 =
basePath +
'/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);
return Promise.resolve(response);
});
@ -80,7 +80,7 @@ describe('AzureMonitorDatasource', () => {
expect(results.length).toEqual(2);
expect(results[0].text).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');
});
});
@ -405,7 +405,7 @@ describe('AzureMonitorDatasource', () => {
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockImplementation((path: string) => {
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
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);
});
@ -456,7 +456,7 @@ describe('AzureMonitorDatasource', () => {
const basePath = `azuremonitor/subscriptions/${subscription}/resourceGroups`;
expect(path).toBe(
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);
});
@ -467,7 +467,7 @@ describe('AzureMonitorDatasource', () => {
expect(results[0].text).toEqual('storagetest/default');
expect(results[0].value).toEqual('storagetest/default');
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();
ctx.ds.azureMonitorDatasource.getResource = fn;
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
fn.mockImplementationOnce((path: string) => {
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', () => {

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

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

@ -92,7 +92,7 @@ describe('AzureMonitorUrlBuilder', () => {
templateSrv
);
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(
'/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(
'/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(
'/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(

@ -29,7 +29,15 @@ const defaultProps = {
subscription: 'id',
},
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;
@ -166,11 +174,8 @@ describe('VariableEditor:', () => {
it('should run the query if requesting resource groups', async () => {
grafanaRuntime.config.featureToggles.azTemplateVars = true;
const ds = createMockDatasource({
getSubscriptions: jest.fn().mockResolvedValue([{ text: 'Primary Subscription', value: 'sub' }]),
});
const onChange = jest.fn();
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} datasource={ds} />);
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable
@ -195,14 +200,7 @@ describe('VariableEditor:', () => {
it('should show template variables as options ', async () => {
const onChange = jest.fn();
grafanaRuntime.config.featureToggles.azTemplateVars = true;
const ds = createMockDatasource({
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} />);
const { rerender } = render(<VariableEditor {...defaultProps} onChange={onChange} />);
// wait for initial load
await waitFor(() => expect(screen.getByText('Logs')).toBeInTheDocument());
// Select RGs variable
@ -210,7 +208,7 @@ describe('VariableEditor:', () => {
screen.getByText('Resource Groups').click();
// Simulate onChange behavior
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());
// Select a subscription
openMenu(screen.getByLabelText('select subscription'));
@ -218,10 +216,60 @@ describe('VariableEditor:', () => {
screen.getByText('Template Variables').click();
// Simulate onChange behavior
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());
// Template variables of the same type than the current one should not appear
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;
};
const removeOption: SelectableValue = { label: '-', value: '' };
const VariableEditor = (props: Props) => {
const { query, onChange, datasource } = props;
const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [
@ -30,13 +32,19 @@ const VariableEditor = (props: Props) => {
if (config.featureToggles.azTemplateVars) {
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: 'Namespaces', value: AzureQueryType.NamespacesQuery });
AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Resource Names', value: AzureQueryType.ResourceNamesQuery });
}
const [variableOptionGroup, setVariableOptionGroup] = useState<{ label: string; options: AzureMonitorOption[] }>({
label: 'Template Variables',
options: [],
});
const [requireSubscription, setRequireSubscription] = useState(false);
const [hasResourceGroup, setHasResourceGroup] = useState(false);
const [hasNamespace, setHasNamespace] = useState(false);
const [subscriptions, setSubscriptions] = useState<SelectableValue[]>([]);
const [resourceGroups, setResourceGroups] = useState<SelectableValue[]>([]);
const [namespaces, setNamespaces] = useState<SelectableValue[]>([]);
const [errorMessage, setError] = useLastError();
const queryType = typeof query === 'string' ? '' : query.queryType;
@ -47,12 +55,22 @@ const VariableEditor = (props: Props) => {
}, [query, datasource, onChange]);
useEffect(() => {
setRequireSubscription(false);
setHasResourceGroup(false);
setHasNamespace(false);
switch (queryType) {
case AzureQueryType.ResourceGroupsQuery:
setRequireSubscription(true);
break;
default:
setRequireSubscription(false);
case AzureQueryType.NamespacesQuery:
setRequireSubscription(true);
setHasResourceGroup(true);
break;
case AzureQueryType.ResourceNamesQuery:
setRequireSubscription(true);
setHasResourceGroup(true);
setHasNamespace(true);
break;
}
}, [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') {
// still migrating the query
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) => {
onChange(queryChange);
};
@ -113,7 +163,7 @@ const VariableEditor = (props: Props) => {
value={queryType}
/>
</InlineField>
{typeof query === 'object' && query.queryType === AzureQueryType.LogAnalytics && (
{query.queryType === AzureQueryType.LogAnalytics && (
<>
<LogsQueryEditor
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} />
)}
{typeof query === 'object' && requireSubscription && (
{requireSubscription && (
<InlineField label="Select subscription" labelWidth={20}>
<Select
aria-label="select subscription"
@ -148,6 +198,30 @@ const VariableEditor = (props: Props) => {
/>
</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(
this.templateSrv.replace(subscriptionId),
this.templateSrv.replace(resourceGroup),

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

@ -559,5 +559,51 @@ describe('VariableSupport', () => {
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)] : [],
};
}
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:
if (queryObj.grafanaTemplateVariableFn) {
const templateVariablesResults = await this.callGrafanaTemplateVariableFn(
@ -57,7 +75,7 @@ export class VariableSupport extends CustomVariableSupport<DataSource, AzureMoni
return lastValueFrom(this.datasource.query(request));
}
} catch (err) {
return { data: [], error: { message: messageFromError(err) } };
return { data: [], error: new Error(messageFromError(err)) };
}
};

Loading…
Cancel
Save