mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 00:08:10 -05:00
Azure Monitor: Adapt ResourceField to multiple resources (#61618)
This commit is contained in:
+1
-5
@@ -53,11 +53,7 @@ const LogsQueryEditor: React.FC<LogsQueryEditorProps> = ({
|
||||
ResourceRowType.Resource,
|
||||
ResourceRowType.Variable,
|
||||
]}
|
||||
resource={
|
||||
query.azureLogAnalytics?.resources && query.azureLogAnalytics.resources.length
|
||||
? query.azureLogAnalytics.resources[0]
|
||||
: ''
|
||||
}
|
||||
resources={query.azureLogAnalytics?.resources ?? []}
|
||||
queryType="logs"
|
||||
/>
|
||||
</EditorFieldGroup>
|
||||
|
||||
+37
-2
@@ -77,6 +77,41 @@ describe('MetricsQueryEditor', () => {
|
||||
expect(await screen.findByTestId('azure-monitor-metrics-query-editor-with-experimental-ui')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the current resource in the ResourcePicker', async () => {
|
||||
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
|
||||
const query = createMockQuery({
|
||||
subscription: 'def-456',
|
||||
azureMonitor: {
|
||||
metricNamespace: 'Microsoft.Compute/virtualMachines',
|
||||
resources: [
|
||||
{
|
||||
resourceGroup: 'dev-3',
|
||||
resourceName: 'web-server',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<MetricsQueryEditor
|
||||
data={mockPanelData}
|
||||
query={query}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onChange={onChange}
|
||||
setError={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
const resourcePickerButton = await screen.findByRole('button', { name: 'web-server' });
|
||||
expect(resourcePickerButton).toBeInTheDocument();
|
||||
resourcePickerButton.click();
|
||||
|
||||
const selection = await screen.findAllByLabelText('web-server');
|
||||
expect(selection).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should change resource when a resource is selected in the ResourcePicker', async () => {
|
||||
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
|
||||
const query = createMockQuery();
|
||||
@@ -125,10 +160,10 @@ describe('MetricsQueryEditor', () => {
|
||||
azureMonitor: expect.objectContaining({
|
||||
metricNamespace: 'microsoft.compute/virtualmachines',
|
||||
resources: [
|
||||
{
|
||||
expect.objectContaining({
|
||||
resourceGroup: 'dev-3',
|
||||
resourceName: 'web-server',
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
+10
-9
@@ -4,7 +4,7 @@ import { PanelData } from '@grafana/data/src/types';
|
||||
import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental';
|
||||
|
||||
import type Datasource from '../../datasource';
|
||||
import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish, AzureMetricResource } from '../../types';
|
||||
import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types';
|
||||
import ResourceField from '../ResourceField';
|
||||
import { ResourceRowType } from '../ResourcePicker/types';
|
||||
|
||||
@@ -37,13 +37,14 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
const metricsMetadata = useMetricMetadata(query, datasource, onChange);
|
||||
const metricNamespaces = useMetricNamespaces(query, datasource, onChange, setError);
|
||||
const metricNames = useMetricNames(query, datasource, onChange, setError);
|
||||
const resource: AzureMetricResource = {
|
||||
subscription: query.subscription,
|
||||
resourceGroup: query.azureMonitor?.resources?.[0]?.resourceGroup,
|
||||
metricNamespace: query.azureMonitor?.metricNamespace,
|
||||
resourceName: query.azureMonitor?.resources?.[0]?.resourceName,
|
||||
region: query.azureMonitor?.region,
|
||||
};
|
||||
const resources =
|
||||
query.azureMonitor?.resources?.map((r) => ({
|
||||
subscription: query.subscription,
|
||||
resourceGroup: r.resourceGroup,
|
||||
metricNamespace: query.azureMonitor?.metricNamespace,
|
||||
resourceName: r.resourceName,
|
||||
region: query.azureMonitor?.region,
|
||||
})) ?? [];
|
||||
return (
|
||||
<span data-testid="azure-monitor-metrics-query-editor-with-experimental-ui">
|
||||
<EditorRows>
|
||||
@@ -56,7 +57,7 @@ const MetricsQueryEditor: React.FC<MetricsQueryEditorProps> = ({
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
selectableEntryTypes={[ResourceRowType.Resource]}
|
||||
resource={resource}
|
||||
resources={resources ?? []}
|
||||
queryType={'metrics'}
|
||||
/>
|
||||
<MetricNamespaceField
|
||||
|
||||
+40
-46
@@ -1,7 +1,7 @@
|
||||
import { cx } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Button, Icon, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { Button, Icon, Modal, useStyles2, IconName } from '@grafana/ui';
|
||||
|
||||
import Datasource from '../../datasource';
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
@@ -11,12 +11,12 @@ import { Field } from '../Field';
|
||||
import ResourcePicker from '../ResourcePicker';
|
||||
import getStyles from '../ResourcePicker/styles';
|
||||
import { ResourceRowType } from '../ResourcePicker/types';
|
||||
import { parseResourceDetails, setResource } from '../ResourcePicker/utils';
|
||||
import { parseMultipleResourceDetails, setResources } from '../ResourcePicker/utils';
|
||||
|
||||
interface ResourceFieldProps<T> extends AzureQueryEditorFieldProps {
|
||||
selectableEntryTypes: ResourceRowType[];
|
||||
queryType: ResourcePickerQueryType;
|
||||
resource: T;
|
||||
resources: T[];
|
||||
inlineField?: boolean;
|
||||
labelWidth?: number;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
||||
onQueryChange,
|
||||
selectableEntryTypes,
|
||||
queryType,
|
||||
resource,
|
||||
resources,
|
||||
inlineField,
|
||||
labelWidth,
|
||||
}) => {
|
||||
@@ -43,11 +43,11 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
||||
}, []);
|
||||
|
||||
const handleApply = useCallback(
|
||||
(resource: string | AzureMetricResource | undefined) => {
|
||||
onQueryChange(setResource(query, resource));
|
||||
(resources: Array<string | AzureMetricResource>) => {
|
||||
onQueryChange(setResources(query, queryType, resources));
|
||||
closePicker();
|
||||
},
|
||||
[closePicker, onQueryChange, query]
|
||||
[closePicker, onQueryChange, query, queryType]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -63,9 +63,8 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
||||
>
|
||||
<ResourcePicker
|
||||
resourcePickerData={datasource.resourcePickerData}
|
||||
// TODO: This should be a list of resources, not a single resource
|
||||
resources={[resource]}
|
||||
onApply={(resources) => resources && handleApply(resources[0])}
|
||||
resources={resources}
|
||||
onApply={handleApply}
|
||||
onCancel={closePicker}
|
||||
selectableEntryTypes={selectableEntryTypes}
|
||||
queryType={queryType}
|
||||
@@ -73,7 +72,7 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
||||
</Modal>
|
||||
<Field label="Resource" inlineField={inlineField} labelWidth={labelWidth}>
|
||||
<Button className={styles.resourceFieldButton} variant="secondary" onClick={handleOpenPicker} type="button">
|
||||
<ResourceLabel resource={resource} datasource={datasource} />
|
||||
<ResourceLabel resources={resources} datasource={datasource} />
|
||||
</Button>
|
||||
</Field>
|
||||
</span>
|
||||
@@ -81,58 +80,53 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
||||
};
|
||||
|
||||
interface ResourceLabelProps<T> {
|
||||
resource: T;
|
||||
resources: T[];
|
||||
datasource: Datasource;
|
||||
}
|
||||
|
||||
const ResourceLabel = ({ resource, datasource }: ResourceLabelProps<string | AzureMetricResource>) => {
|
||||
const [resourceComponents, setResourceComponents] = useState(parseResourceDetails(resource ?? ''));
|
||||
const ResourceLabel = ({ resources, datasource }: ResourceLabelProps<string | AzureMetricResource>) => {
|
||||
const [resourcesComponents, setResourcesComponents] = useState(parseMultipleResourceDetails(resources));
|
||||
|
||||
useEffect(() => {
|
||||
if (resource && parseResourceDetails(resource)) {
|
||||
typeof resource === 'string'
|
||||
? datasource.resourcePickerData.getResourceURIDisplayProperties(resource).then(setResourceComponents)
|
||||
: setResourceComponents(resource);
|
||||
} else {
|
||||
setResourceComponents({});
|
||||
}
|
||||
}, [datasource.resourcePickerData, resource]);
|
||||
setResourcesComponents(parseMultipleResourceDetails(resources));
|
||||
}, [resources]);
|
||||
|
||||
if (!resource || (typeof resource === 'object' && !resource.subscription)) {
|
||||
if (!resources.length) {
|
||||
return <>Select a resource</>;
|
||||
}
|
||||
|
||||
if (resourceComponents) {
|
||||
return <FormattedResource resource={resourceComponents} />;
|
||||
}
|
||||
|
||||
return <>{resource}</>;
|
||||
return <FormattedResource resources={resourcesComponents} />;
|
||||
};
|
||||
|
||||
interface FormattedResourceProps {
|
||||
resource: AzureMetricResource;
|
||||
resources: AzureMetricResource[];
|
||||
}
|
||||
|
||||
const FormattedResource = ({ resource }: FormattedResourceProps) => {
|
||||
const FormattedResource = ({ resources }: FormattedResourceProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (resource.resourceName) {
|
||||
return (
|
||||
<span className={cx(styles.truncated, styles.resourceField)}>
|
||||
<Icon name="cube" /> {resource.resourceName.split('/')[0]}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (resource.resourceGroup) {
|
||||
return (
|
||||
<span>
|
||||
<Icon name="folder" /> {resource.resourceGroup}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
let icon: IconName = 'cube';
|
||||
const items: string[] = [];
|
||||
resources.forEach((resource) => {
|
||||
if (resource.resourceName) {
|
||||
items.push(resource.resourceName.split('/')[0]);
|
||||
return;
|
||||
}
|
||||
if (resource.resourceGroup) {
|
||||
icon = 'folder';
|
||||
items.push(resource.resourceGroup);
|
||||
return;
|
||||
}
|
||||
if (resource.subscription) {
|
||||
icon = 'layer-group';
|
||||
items.push(resource.subscription);
|
||||
return;
|
||||
}
|
||||
});
|
||||
return (
|
||||
<span>
|
||||
<Icon name="layer-group" /> {resource.subscription}
|
||||
<span className={cx(styles.truncated, styles.resourceField)}>
|
||||
<Icon name={icon} />
|
||||
{items.join(', ')}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ const Advanced = ({ resources, onChange }: ResourcePickerProps<string | AzureMet
|
||||
isOpen={isAdvancedOpen}
|
||||
onToggle={() => setIsAdvancedOpen(!isAdvancedOpen)}
|
||||
>
|
||||
{resources.map((resource, index) => (
|
||||
{(resources.length ? resources : [{}]).map((resource, index) => (
|
||||
<div key={`resource-${index + 1}`}>
|
||||
{typeof resource === 'string' ? (
|
||||
<>
|
||||
|
||||
+12
-10
@@ -8,7 +8,7 @@ import {
|
||||
parseResourceDetails,
|
||||
parseResourceURI,
|
||||
resourcesToStrings,
|
||||
setResource,
|
||||
setResources,
|
||||
} from './utils';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
@@ -191,22 +191,24 @@ describe('AzureMonitor ResourcePicker utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setResource', () => {
|
||||
describe('setResources', () => {
|
||||
it('updates a resource with a resource URI for Log Analytics', () => {
|
||||
expect(setResource(createMockQuery(), '/subscription/sub')).toMatchObject({
|
||||
expect(setResources(createMockQuery(), 'logs', ['/subscription/sub'])).toMatchObject({
|
||||
azureLogAnalytics: { resources: ['/subscription/sub'] },
|
||||
});
|
||||
});
|
||||
|
||||
it('updates a resource with a resource parameters for Metrics', () => {
|
||||
expect(
|
||||
setResource(createMockQuery(), {
|
||||
subscription: 'sub',
|
||||
resourceGroup: 'rg',
|
||||
metricNamespace: 'Microsoft.Storage/storageAccounts',
|
||||
resourceName: 'testacct',
|
||||
region: 'westus',
|
||||
})
|
||||
setResources(createMockQuery(), 'metrics', [
|
||||
{
|
||||
subscription: 'sub',
|
||||
resourceGroup: 'rg',
|
||||
metricNamespace: 'Microsoft.Storage/storageAccounts',
|
||||
resourceName: 'testacct',
|
||||
region: 'westus',
|
||||
},
|
||||
])
|
||||
).toMatchObject({
|
||||
subscription: 'sub',
|
||||
azureMonitor: {
|
||||
|
||||
+13
-7
@@ -3,6 +3,7 @@ import produce from 'immer';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import UrlBuilder from '../../azure_monitor/url_builder';
|
||||
import { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData';
|
||||
import { AzureMetricResource, AzureMonitorQuery } from '../../types';
|
||||
|
||||
import { ResourceRow, ResourceRowGroup } from './types';
|
||||
@@ -153,26 +154,31 @@ export function addResources(rows: ResourceRowGroup, targetParentId: string, new
|
||||
});
|
||||
}
|
||||
|
||||
export function setResource(query: AzureMonitorQuery, resource?: string | AzureMetricResource): AzureMonitorQuery {
|
||||
if (typeof resource === 'string') {
|
||||
export function setResources(
|
||||
query: AzureMonitorQuery,
|
||||
type: ResourcePickerQueryType,
|
||||
resources: Array<string | AzureMetricResource>
|
||||
): AzureMonitorQuery {
|
||||
if (type === 'logs') {
|
||||
// Resource URI for LogAnalytics
|
||||
return {
|
||||
...query,
|
||||
azureLogAnalytics: {
|
||||
...query.azureLogAnalytics,
|
||||
resources: [resource],
|
||||
resources: resourcesToStrings(resources),
|
||||
},
|
||||
};
|
||||
}
|
||||
// Resource object for metrics
|
||||
const parsedResource = resources.length ? parseResourceDetails(resources[0]) : {};
|
||||
return {
|
||||
...query,
|
||||
subscription: resource?.subscription,
|
||||
subscription: parsedResource.subscription,
|
||||
azureMonitor: {
|
||||
...query.azureMonitor,
|
||||
metricNamespace: resource?.metricNamespace?.toLocaleLowerCase(),
|
||||
region: resource?.region,
|
||||
resources: [{ resourceGroup: resource?.resourceGroup, resourceName: resource?.resourceName }],
|
||||
metricNamespace: parsedResource.metricNamespace?.toLocaleLowerCase(),
|
||||
region: parsedResource.region,
|
||||
resources: parseMultipleResourceDetails(resources),
|
||||
metricName: undefined,
|
||||
aggregation: undefined,
|
||||
timeGrain: '',
|
||||
|
||||
Reference in New Issue
Block a user