mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: Adapt ResourcePicker and Advanced components to multiple resources (#61605)
This commit is contained in:
parent
004705a10b
commit
1b86a49622
@ -63,8 +63,9 @@ const ResourceField: React.FC<ResourceFieldProps<string | AzureMetricResource>>
|
|||||||
>
|
>
|
||||||
<ResourcePicker
|
<ResourcePicker
|
||||||
resourcePickerData={datasource.resourcePickerData}
|
resourcePickerData={datasource.resourcePickerData}
|
||||||
resource={resource}
|
// TODO: This should be a list of resources, not a single resource
|
||||||
onApply={handleApply}
|
resources={[resource]}
|
||||||
|
onApply={(resources) => resources && handleApply(resources[0])}
|
||||||
onCancel={closePicker}
|
onCancel={closePicker}
|
||||||
selectableEntryTypes={selectableEntryTypes}
|
selectableEntryTypes={selectableEntryTypes}
|
||||||
queryType={queryType}
|
queryType={queryType}
|
||||||
|
@ -7,29 +7,38 @@ import Advanced from './Advanced';
|
|||||||
describe('AzureMonitor ResourcePicker', () => {
|
describe('AzureMonitor ResourcePicker', () => {
|
||||||
it('should set a parameter as an object', async () => {
|
it('should set a parameter as an object', async () => {
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
const { rerender } = render(<Advanced onChange={onChange} resource={{}} />);
|
const { rerender } = render(<Advanced onChange={onChange} resources={[{}]} />);
|
||||||
const advancedSection = screen.getByText('Advanced');
|
const advancedSection = screen.getByText('Advanced');
|
||||||
advancedSection.click();
|
advancedSection.click();
|
||||||
|
|
||||||
const subsInput = await screen.findByLabelText('Subscription');
|
const subsInput = await screen.findByLabelText('Subscription');
|
||||||
await userEvent.type(subsInput, 'd');
|
await userEvent.type(subsInput, 'd');
|
||||||
expect(onChange).toHaveBeenCalledWith({ subscription: 'd' });
|
expect(onChange).toHaveBeenCalledWith([{ subscription: 'd' }]);
|
||||||
|
|
||||||
rerender(<Advanced onChange={onChange} resource={{ subscription: 'def-123' }} />);
|
rerender(<Advanced onChange={onChange} resources={[{ subscription: 'def-123' }]} />);
|
||||||
expect(screen.getByLabelText('Subscription').outerHTML).toMatch('value="def-123"');
|
expect(screen.getByLabelText('Subscription').outerHTML).toMatch('value="def-123"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set a parameter as uri', async () => {
|
it('should set a parameter as uri', async () => {
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
const { rerender } = render(<Advanced onChange={onChange} resource={''} />);
|
const { rerender } = render(<Advanced onChange={onChange} resources={['']} />);
|
||||||
const advancedSection = screen.getByText('Advanced');
|
const advancedSection = screen.getByText('Advanced');
|
||||||
advancedSection.click();
|
advancedSection.click();
|
||||||
|
|
||||||
const subsInput = await screen.findByLabelText('Resource URI');
|
const subsInput = await screen.findByLabelText('Resource URI');
|
||||||
await userEvent.type(subsInput, '/');
|
await userEvent.type(subsInput, '/');
|
||||||
expect(onChange).toHaveBeenCalledWith('/');
|
expect(onChange).toHaveBeenCalledWith(['/']);
|
||||||
|
|
||||||
rerender(<Advanced onChange={onChange} resource={'/subscriptions/sub'} />);
|
rerender(<Advanced onChange={onChange} resources={['/subscriptions/sub']} />);
|
||||||
expect(screen.getByLabelText('Resource URI').outerHTML).toMatch('value="/subscriptions/sub"');
|
expect(screen.getByLabelText('Resource URI').outerHTML).toMatch('value="/subscriptions/sub"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render multiple resources', async () => {
|
||||||
|
render(<Advanced onChange={jest.fn()} resources={['/subscriptions/sub1', '/subscriptions/sub2']} />);
|
||||||
|
const advancedSection = screen.getByText('Advanced');
|
||||||
|
advancedSection.click();
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue('/subscriptions/sub1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue('/subscriptions/sub2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,12 +7,18 @@ import { AzureMetricResource } from '../../types';
|
|||||||
import { Space } from '../Space';
|
import { Space } from '../Space';
|
||||||
|
|
||||||
interface ResourcePickerProps<T> {
|
interface ResourcePickerProps<T> {
|
||||||
resource: T;
|
resources: T[];
|
||||||
onChange: (resource: T) => void;
|
onChange: (resources: T[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Advanced = ({ resource, onChange }: ResourcePickerProps<string | AzureMetricResource>) => {
|
const Advanced = ({ resources, onChange }: ResourcePickerProps<string | AzureMetricResource>) => {
|
||||||
const [isAdvancedOpen, setIsAdvancedOpen] = useState(!!resource && JSON.stringify(resource).includes('$'));
|
const [isAdvancedOpen, setIsAdvancedOpen] = useState(!!resources.length && JSON.stringify(resources).includes('$'));
|
||||||
|
|
||||||
|
const onResourceChange = (resource: string | AzureMetricResource, index: number) => {
|
||||||
|
const newResources = [...resources];
|
||||||
|
newResources[index] = resource;
|
||||||
|
onChange(newResources);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid={selectors.components.queryEditor.resourcePicker.advanced.collapse}>
|
<div data-testid={selectors.components.queryEditor.resourcePicker.advanced.collapse}>
|
||||||
@ -22,104 +28,115 @@ const Advanced = ({ resource, onChange }: ResourcePickerProps<string | AzureMetr
|
|||||||
isOpen={isAdvancedOpen}
|
isOpen={isAdvancedOpen}
|
||||||
onToggle={() => setIsAdvancedOpen(!isAdvancedOpen)}
|
onToggle={() => setIsAdvancedOpen(!isAdvancedOpen)}
|
||||||
>
|
>
|
||||||
{typeof resource === 'string' ? (
|
{resources.map((resource, index) => (
|
||||||
<>
|
<div key={`resource-${index + 1}`}>
|
||||||
{' '}
|
{typeof resource === 'string' ? (
|
||||||
<Label htmlFor="input-advanced-resource-picker">
|
<>
|
||||||
<h6>
|
<Label htmlFor={`input-advanced-resource-picker-${index + 1}`}>
|
||||||
Resource URI{' '}
|
<h6>
|
||||||
<Tooltip
|
Resource URI{' '}
|
||||||
content={
|
<Tooltip
|
||||||
<>
|
content={
|
||||||
Manually edit the{' '}
|
<>
|
||||||
<a
|
Manually edit the{' '}
|
||||||
href="https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-standard-columns#_resourceid"
|
<a
|
||||||
rel="noopener noreferrer"
|
href="https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-standard-columns#_resourceid"
|
||||||
target="_blank"
|
rel="noopener noreferrer"
|
||||||
>
|
target="_blank"
|
||||||
resource uri.{' '}
|
>
|
||||||
</a>
|
resource uri.{' '}
|
||||||
Supports the use of multiple template variables (ex: /subscriptions/$subId/resourceGroups/$rg)
|
</a>
|
||||||
</>
|
Supports the use of multiple template variables (ex: /subscriptions/$subId/resourceGroups/$rg)
|
||||||
}
|
</>
|
||||||
placement="right"
|
}
|
||||||
interactive={true}
|
placement="right"
|
||||||
|
interactive={true}
|
||||||
|
>
|
||||||
|
<Icon name="info-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
</h6>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id={`input-advanced-resource-picker-${index + 1}`}
|
||||||
|
value={resource}
|
||||||
|
onChange={(event) => onResourceChange(event.currentTarget.value, index)}
|
||||||
|
placeholder="ex: /subscriptions/$subId"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<InlineField
|
||||||
|
label="Subscription"
|
||||||
|
grow
|
||||||
|
transparent
|
||||||
|
htmlFor={`input-advanced-resource-picker-subscription-${index + 1}`}
|
||||||
|
labelWidth={15}
|
||||||
|
data-testid={selectors.components.queryEditor.resourcePicker.advanced.subscription.input}
|
||||||
>
|
>
|
||||||
<Icon name="info-circle" />
|
<Input
|
||||||
</Tooltip>
|
id={`input-advanced-resource-picker-subscription-${index + 1}`}
|
||||||
</h6>
|
value={resource?.subscription ?? ''}
|
||||||
</Label>
|
onChange={(event) =>
|
||||||
<Input
|
onResourceChange({ ...resource, subscription: event.currentTarget.value }, index)
|
||||||
id="input-advanced-resource-picker"
|
}
|
||||||
value={resource}
|
placeholder="aaaaaaaa-bbbb-cccc-dddd-eeeeeeee"
|
||||||
onChange={(event) => onChange(event.currentTarget.value)}
|
/>
|
||||||
placeholder="ex: /subscriptions/$subId"
|
</InlineField>
|
||||||
/>
|
<InlineField
|
||||||
</>
|
label="Resource Group"
|
||||||
) : (
|
grow
|
||||||
<>
|
transparent
|
||||||
<InlineField
|
htmlFor={`input-advanced-resource-picker-resourceGroup-${index + 1}`}
|
||||||
label="Subscription"
|
labelWidth={15}
|
||||||
grow
|
data-testid={selectors.components.queryEditor.resourcePicker.advanced.resourceGroup.input}
|
||||||
transparent
|
>
|
||||||
htmlFor="input-advanced-resource-picker-subscription"
|
<Input
|
||||||
labelWidth={15}
|
id={`input-advanced-resource-picker-resourceGroup-${index + 1}`}
|
||||||
data-testid={selectors.components.queryEditor.resourcePicker.advanced.subscription.input}
|
value={resource?.resourceGroup ?? ''}
|
||||||
>
|
onChange={(event) =>
|
||||||
<Input
|
onResourceChange({ ...resource, resourceGroup: event.currentTarget.value }, index)
|
||||||
id="input-advanced-resource-picker-subscription"
|
}
|
||||||
value={resource?.subscription ?? ''}
|
placeholder="resource-group"
|
||||||
onChange={(event) => onChange({ ...resource, subscription: event.currentTarget.value })}
|
/>
|
||||||
placeholder="aaaaaaaa-bbbb-cccc-dddd-eeeeeeee"
|
</InlineField>
|
||||||
/>
|
<InlineField
|
||||||
</InlineField>
|
label="Namespace"
|
||||||
<InlineField
|
grow
|
||||||
label="Resource Group"
|
transparent
|
||||||
grow
|
htmlFor={`input-advanced-resource-picker-metricNamespace-${index + 1}`}
|
||||||
transparent
|
labelWidth={15}
|
||||||
htmlFor="input-advanced-resource-picker-resourceGroup"
|
data-testid={selectors.components.queryEditor.resourcePicker.advanced.namespace.input}
|
||||||
labelWidth={15}
|
>
|
||||||
data-testid={selectors.components.queryEditor.resourcePicker.advanced.resourceGroup.input}
|
<Input
|
||||||
>
|
id={`input-advanced-resource-picker-metricNamespace-${index + 1}`}
|
||||||
<Input
|
value={resource?.metricNamespace ?? ''}
|
||||||
id="input-advanced-resource-picker-resourceGroup"
|
onChange={(event) =>
|
||||||
value={resource?.resourceGroup ?? ''}
|
onResourceChange({ ...resource, metricNamespace: event.currentTarget.value }, index)
|
||||||
onChange={(event) => onChange({ ...resource, resourceGroup: event.currentTarget.value })}
|
}
|
||||||
placeholder="resource-group"
|
placeholder="Microsoft.Insights/metricNamespaces"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
<InlineField
|
<InlineField
|
||||||
label="Namespace"
|
label="Resource Name"
|
||||||
grow
|
grow
|
||||||
transparent
|
transparent
|
||||||
htmlFor="input-advanced-resource-picker-metricNamespace"
|
htmlFor={`input-advanced-resource-picker-resourceName-${index + 1}`}
|
||||||
labelWidth={15}
|
labelWidth={15}
|
||||||
data-testid={selectors.components.queryEditor.resourcePicker.advanced.namespace.input}
|
data-testid={selectors.components.queryEditor.resourcePicker.advanced.resource.input}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="input-advanced-resource-picker-metricNamespace"
|
id={`input-advanced-resource-picker-resourceName-${index + 1}`}
|
||||||
value={resource?.metricNamespace ?? ''}
|
value={resource?.resourceName ?? ''}
|
||||||
onChange={(event) => onChange({ ...resource, metricNamespace: event.currentTarget.value })}
|
onChange={(event) =>
|
||||||
placeholder="Microsoft.Insights/metricNamespaces"
|
onResourceChange({ ...resource, resourceName: event.currentTarget.value }, index)
|
||||||
/>
|
}
|
||||||
</InlineField>
|
placeholder="name"
|
||||||
<InlineField
|
/>
|
||||||
label="Resource Name"
|
</InlineField>
|
||||||
grow
|
</>
|
||||||
transparent
|
)}
|
||||||
htmlFor="input-advanced-resource-picker-resourceName"
|
</div>
|
||||||
labelWidth={15}
|
))}
|
||||||
data-testid={selectors.components.queryEditor.resourcePicker.advanced.resource.input}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
id="input-advanced-resource-picker-resourceName"
|
|
||||||
value={resource?.resourceName ?? ''}
|
|
||||||
onChange={(event) => onChange({ ...resource, resourceName: event.currentTarget.value })}
|
|
||||||
placeholder="name"
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Space v={2} />
|
<Space v={2} />
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,15 @@ import { ResourceRowType } from './types';
|
|||||||
|
|
||||||
import ResourcePicker from '.';
|
import ResourcePicker from '.';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||||
|
getTemplateSrv: () => ({
|
||||||
|
replace: (val: string) => {
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
const noResourceURI = '';
|
const noResourceURI = '';
|
||||||
const singleSubscriptionSelectionURI = '/subscriptions/def-456';
|
const singleSubscriptionSelectionURI = '/subscriptions/def-456';
|
||||||
const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3';
|
const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3';
|
||||||
@ -50,7 +59,7 @@ const queryType: ResourcePickerQueryType = 'logs';
|
|||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
templateVariables: [],
|
templateVariables: [],
|
||||||
resource: noResourceURI,
|
resources: [noResourceURI],
|
||||||
resourcePickerData: createMockResourcePickerData(),
|
resourcePickerData: createMockResourcePickerData(),
|
||||||
onCancel: noop,
|
onCancel: noop,
|
||||||
onApply: noop,
|
onApply: noop,
|
||||||
@ -68,7 +77,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||||
});
|
});
|
||||||
it('should pre-load subscriptions when there is no existing selection', async () => {
|
it('should pre-load subscriptions when there is no existing selection', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={noResourceURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[noResourceURI]} />);
|
||||||
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
||||||
expect(subscriptionCheckbox).toBeInTheDocument();
|
expect(subscriptionCheckbox).toBeInTheDocument();
|
||||||
expect(subscriptionCheckbox).not.toBeChecked();
|
expect(subscriptionCheckbox).not.toBeChecked();
|
||||||
@ -77,7 +86,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show a subscription as selected if there is one saved', async () => {
|
it('should show a subscription as selected if there is one saved', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={singleSubscriptionSelectionURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[singleSubscriptionSelectionURI]} />);
|
||||||
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
|
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
|
||||||
expect(subscriptionCheckboxes.length).toBe(2);
|
expect(subscriptionCheckboxes.length).toBe(2);
|
||||||
expect(subscriptionCheckboxes[0]).toBeChecked();
|
expect(subscriptionCheckboxes[0]).toBeChecked();
|
||||||
@ -85,7 +94,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show a resourceGroup as selected if there is one saved', async () => {
|
it('should show a resourceGroup as selected if there is one saved', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={singleResourceGroupSelectionURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[singleResourceGroupSelectionURI]} />);
|
||||||
const resourceGroupCheckboxes = await screen.findAllByLabelText('A Great Resource Group');
|
const resourceGroupCheckboxes = await screen.findAllByLabelText('A Great Resource Group');
|
||||||
expect(resourceGroupCheckboxes.length).toBe(2);
|
expect(resourceGroupCheckboxes.length).toBe(2);
|
||||||
expect(resourceGroupCheckboxes[0]).toBeChecked();
|
expect(resourceGroupCheckboxes[0]).toBeChecked();
|
||||||
@ -93,7 +102,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show scroll down to a resource and mark it as selected if there is one saved', async () => {
|
it('should show scroll down to a resource and mark it as selected if there is one saved', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={singleResourceSelectionURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[singleResourceSelectionURI]} />);
|
||||||
const resourceCheckboxes = await screen.findAllByLabelText('db-server');
|
const resourceCheckboxes = await screen.findAllByLabelText('db-server');
|
||||||
expect(resourceCheckboxes.length).toBe(2);
|
expect(resourceCheckboxes.length).toBe(2);
|
||||||
expect(resourceCheckboxes[0]).toBeChecked();
|
expect(resourceCheckboxes[0]).toBeChecked();
|
||||||
@ -101,7 +110,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('opens the selected nested resources', async () => {
|
it('opens the selected nested resources', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={singleResourceSelectionURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[singleResourceSelectionURI]} />);
|
||||||
const collapseSubscriptionBtn = await screen.findByLabelText('Collapse Dev Subscription');
|
const collapseSubscriptionBtn = await screen.findByLabelText('Collapse Dev Subscription');
|
||||||
expect(collapseSubscriptionBtn).toBeInTheDocument();
|
expect(collapseSubscriptionBtn).toBeInTheDocument();
|
||||||
const collapseResourceGroupBtn = await screen.findByLabelText('Collapse A Great Resource Group');
|
const collapseResourceGroupBtn = await screen.findByLabelText('Collapse A Great Resource Group');
|
||||||
@ -109,7 +118,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('scrolls down to the selected resource', async () => {
|
it('scrolls down to the selected resource', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={singleResourceSelectionURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[singleResourceSelectionURI]} />);
|
||||||
await screen.findByLabelText('Collapse A Great Resource Group');
|
await screen.findByLabelText('Collapse A Great Resource Group');
|
||||||
expect(window.HTMLElement.prototype.scrollIntoView).toBeCalledTimes(1);
|
expect(window.HTMLElement.prototype.scrollIntoView).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
@ -133,12 +142,40 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
applyButton.click();
|
applyButton.click();
|
||||||
expect(onApply).toBeCalledTimes(1);
|
expect(onApply).toBeCalledTimes(1);
|
||||||
expect(onApply).toBeCalledWith('/subscriptions/def-123');
|
expect(onApply).toBeCalledWith(['/subscriptions/def-123']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onApply removing an element', async () => {
|
||||||
|
const onApply = jest.fn();
|
||||||
|
render(<ResourcePicker {...defaultProps} resources={['/subscriptions/def-123']} onApply={onApply} />);
|
||||||
|
const subscriptionCheckbox = await screen.findAllByLabelText('Primary Subscription');
|
||||||
|
expect(subscriptionCheckbox).toHaveLength(2);
|
||||||
|
expect(subscriptionCheckbox.at(0)).toBeChecked();
|
||||||
|
subscriptionCheckbox.at(0)?.click();
|
||||||
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
|
applyButton.click();
|
||||||
|
expect(onApply).toBeCalledTimes(1);
|
||||||
|
expect(onApply).toBeCalledWith([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onApply removing an element ignoring the case', async () => {
|
||||||
|
const onApply = jest.fn();
|
||||||
|
render(
|
||||||
|
<ResourcePicker {...defaultProps} resources={['/subscriptions/def-456/resourceGroups/DEV-3']} onApply={onApply} />
|
||||||
|
);
|
||||||
|
const subscriptionCheckbox = await screen.findAllByLabelText('A Great Resource Group');
|
||||||
|
expect(subscriptionCheckbox).toHaveLength(2);
|
||||||
|
expect(subscriptionCheckbox.at(0)).toBeChecked();
|
||||||
|
subscriptionCheckbox.at(0)?.click();
|
||||||
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
|
applyButton.click();
|
||||||
|
expect(onApply).toBeCalledTimes(1);
|
||||||
|
expect(onApply).toBeCalledWith([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onApply with a new subscription when a user clicks on the checkbox in the row', async () => {
|
it('should call onApply with a new subscription when a user clicks on the checkbox in the row', async () => {
|
||||||
const onApply = jest.fn();
|
const onApply = jest.fn();
|
||||||
render(<ResourcePicker {...defaultProps} onApply={onApply} resource={{}} />);
|
render(<ResourcePicker {...defaultProps} onApply={onApply} resources={[]} />);
|
||||||
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
||||||
expect(subscriptionCheckbox).toBeInTheDocument();
|
expect(subscriptionCheckbox).toBeInTheDocument();
|
||||||
expect(subscriptionCheckbox).not.toBeChecked();
|
expect(subscriptionCheckbox).not.toBeChecked();
|
||||||
@ -146,7 +183,20 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
applyButton.click();
|
applyButton.click();
|
||||||
expect(onApply).toBeCalledTimes(1);
|
expect(onApply).toBeCalledTimes(1);
|
||||||
expect(onApply).toBeCalledWith({ subscription: 'def-123' });
|
expect(onApply).toBeCalledWith([{ subscription: 'def-123' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onApply removing a resource element', async () => {
|
||||||
|
const onApply = jest.fn();
|
||||||
|
render(<ResourcePicker {...defaultProps} onApply={onApply} resources={[{ subscription: 'def-123' }]} />);
|
||||||
|
const subscriptionCheckbox = await screen.findAllByLabelText('Primary Subscription');
|
||||||
|
expect(subscriptionCheckbox).toHaveLength(2);
|
||||||
|
expect(subscriptionCheckbox.at(0)).toBeChecked();
|
||||||
|
subscriptionCheckbox.at(0)?.click();
|
||||||
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
|
applyButton.click();
|
||||||
|
expect(onApply).toBeCalledTimes(1);
|
||||||
|
expect(onApply).toBeCalledWith([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onApply with a new subscription uri when a user types it in the selection box', async () => {
|
it('should call onApply with a new subscription uri when a user types it in the selection box', async () => {
|
||||||
@ -166,12 +216,12 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
applyButton.click();
|
applyButton.click();
|
||||||
|
|
||||||
expect(onApply).toBeCalledTimes(1);
|
expect(onApply).toBeCalledTimes(1);
|
||||||
expect(onApply).toBeCalledWith('/subscriptions/def-123');
|
expect(onApply).toBeCalledWith(['/subscriptions/def-123']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call onApply with a new subscription when a user types it in the selection box', async () => {
|
it('should call onApply with a new subscription when a user types it in the selection box', async () => {
|
||||||
const onApply = jest.fn();
|
const onApply = jest.fn();
|
||||||
render(<ResourcePicker {...defaultProps} onApply={onApply} resource={{}} />);
|
render(<ResourcePicker {...defaultProps} onApply={onApply} resources={[{}]} />);
|
||||||
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
||||||
expect(subscriptionCheckbox).toBeInTheDocument();
|
expect(subscriptionCheckbox).toBeInTheDocument();
|
||||||
expect(subscriptionCheckbox).not.toBeChecked();
|
expect(subscriptionCheckbox).not.toBeChecked();
|
||||||
@ -186,11 +236,11 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
applyButton.click();
|
applyButton.click();
|
||||||
|
|
||||||
expect(onApply).toBeCalledTimes(1);
|
expect(onApply).toBeCalledTimes(1);
|
||||||
expect(onApply).toBeCalledWith({ subscription: 'def-123' });
|
expect(onApply).toBeCalledWith([{ subscription: 'def-123' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show unselect a subscription if the value is manually edited', async () => {
|
it('should show unselect a subscription if the value is manually edited', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={{ subscription: 'def-456' }} />);
|
render(<ResourcePicker {...defaultProps} resources={[{ subscription: 'def-456' }]} />);
|
||||||
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
|
const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription');
|
||||||
expect(subscriptionCheckboxes.length).toBe(2);
|
expect(subscriptionCheckboxes.length).toBe(2);
|
||||||
expect(subscriptionCheckboxes[0]).toBeChecked();
|
expect(subscriptionCheckboxes[0]).toBeChecked();
|
||||||
@ -264,7 +314,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('resets result when the user clears their search', async () => {
|
it('resets result when the user clears their search', async () => {
|
||||||
render(<ResourcePicker {...defaultProps} resource={noResourceURI} />);
|
render(<ResourcePicker {...defaultProps} resources={[noResourceURI]} />);
|
||||||
const subscriptionCheckboxBeforeSearch = await screen.findByLabelText('Primary Subscription');
|
const subscriptionCheckboxBeforeSearch = await screen.findByLabelText('Primary Subscription');
|
||||||
expect(subscriptionCheckboxBeforeSearch).toBeInTheDocument();
|
expect(subscriptionCheckboxBeforeSearch).toBeInTheDocument();
|
||||||
|
|
||||||
@ -295,7 +345,7 @@ describe('AzureMonitor ResourcePicker', () => {
|
|||||||
{...defaultProps}
|
{...defaultProps}
|
||||||
queryType={'metrics'}
|
queryType={'metrics'}
|
||||||
resourcePickerData={resourcePickerData}
|
resourcePickerData={resourcePickerData}
|
||||||
resource={noResourceURI}
|
resources={[noResourceURI]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const subscriptionExpand = await screen.findByLabelText('Expand Primary Subscription');
|
const subscriptionExpand = await screen.findByLabelText('Expand Primary Subscription');
|
||||||
|
@ -15,21 +15,21 @@ import NestedRow from './NestedRow';
|
|||||||
import Search from './Search';
|
import Search from './Search';
|
||||||
import getStyles from './styles';
|
import getStyles from './styles';
|
||||||
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
|
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
|
||||||
import { findRow, parseResourceDetails, resourceToString } from './utils';
|
import { findRows, parseMultipleResourceDetails, resourcesToStrings, matchURI, resourceToString } from './utils';
|
||||||
|
|
||||||
interface ResourcePickerProps<T> {
|
interface ResourcePickerProps<T> {
|
||||||
resourcePickerData: ResourcePickerData;
|
resourcePickerData: ResourcePickerData;
|
||||||
resource: T;
|
resources: T[];
|
||||||
selectableEntryTypes: ResourceRowType[];
|
selectableEntryTypes: ResourceRowType[];
|
||||||
queryType: ResourcePickerQueryType;
|
queryType: ResourcePickerQueryType;
|
||||||
|
|
||||||
onApply: (resource?: T) => void;
|
onApply: (resources: T[]) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResourcePicker = ({
|
const ResourcePicker = ({
|
||||||
resourcePickerData,
|
resourcePickerData,
|
||||||
resource,
|
resources,
|
||||||
onApply,
|
onApply,
|
||||||
onCancel,
|
onCancel,
|
||||||
selectableEntryTypes,
|
selectableEntryTypes,
|
||||||
@ -40,14 +40,14 @@ const ResourcePicker = ({
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [rows, setRows] = useState<ResourceRowGroup>([]);
|
const [rows, setRows] = useState<ResourceRowGroup>([]);
|
||||||
const [selectedRows, setSelectedRows] = useState<ResourceRowGroup>([]);
|
const [selectedRows, setSelectedRows] = useState<ResourceRowGroup>([]);
|
||||||
const [internalSelected, setInternalSelected] = useState(resource);
|
const [internalSelected, setInternalSelected] = useState(resources);
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
|
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
|
||||||
const [shouldShowLimitFlag, setShouldShowLimitFlag] = useState(false);
|
const [shouldShowLimitFlag, setShouldShowLimitFlag] = useState(false);
|
||||||
|
|
||||||
// Sync the resourceURI prop to internal state
|
// Sync the resourceURI prop to internal state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInternalSelected(resource);
|
setInternalSelected(resources);
|
||||||
}, [resource]);
|
}, [resources]);
|
||||||
|
|
||||||
const loadInitialData = useCallback(async () => {
|
const loadInitialData = useCallback(async () => {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
@ -55,7 +55,7 @@ const ResourcePicker = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const resources = await resourcePickerData.fetchInitialRows(
|
const resources = await resourcePickerData.fetchInitialRows(
|
||||||
queryType,
|
queryType,
|
||||||
parseResourceDetails(internalSelected ?? {})
|
parseMultipleResourceDetails(internalSelected ?? {})
|
||||||
);
|
);
|
||||||
setRows(resources);
|
setRows(resources);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -75,14 +75,9 @@ const ResourcePicker = ({
|
|||||||
setSelectedRows([]);
|
setSelectedRows([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = internalSelected && findRow(rows, resourceToString(internalSelected));
|
const found = internalSelected && findRows(rows, resourcesToStrings(internalSelected));
|
||||||
if (found) {
|
if (found && found.length) {
|
||||||
return setSelectedRows([
|
return setSelectedRows(found);
|
||||||
{
|
|
||||||
...found,
|
|
||||||
children: undefined,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
return setSelectedRows([]);
|
return setSelectedRows([]);
|
||||||
}, [internalSelected, rows]);
|
}, [internalSelected, rows]);
|
||||||
@ -109,19 +104,29 @@ const ResourcePicker = ({
|
|||||||
[resourcePickerData, rows, queryType]
|
[resourcePickerData, rows, queryType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const resourceIsString = typeof resource === 'string';
|
const resourceIsString = resources?.length && typeof resources[0] === 'string';
|
||||||
const handleSelectionChanged = useCallback(
|
const handleSelectionChanged = useCallback(
|
||||||
(row: ResourceRow, isSelected: boolean) => {
|
(row: ResourceRow, isSelected: boolean) => {
|
||||||
isSelected
|
if (isSelected) {
|
||||||
? setInternalSelected(resourceIsString ? row.uri : parseResourceDetails(row.uri, row.location))
|
const newRes = resourceIsString ? row.uri : parseMultipleResourceDetails([row.uri], row.location)[0];
|
||||||
: setInternalSelected(resourceIsString ? '' : {});
|
const newSelected = (internalSelected ? internalSelected.concat(newRes) : [newRes]).filter((r) => {
|
||||||
|
// avoid setting empty resources
|
||||||
|
return typeof r === 'string' ? r !== '' : r.subscription;
|
||||||
|
});
|
||||||
|
setInternalSelected(newSelected);
|
||||||
|
} else {
|
||||||
|
const newInternalSelected = internalSelected?.filter((r) => {
|
||||||
|
return !matchURI(resourceToString(r), row.uri);
|
||||||
|
});
|
||||||
|
setInternalSelected(newInternalSelected);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[resourceIsString]
|
[resourceIsString, internalSelected, setInternalSelected]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleApply = useCallback(() => {
|
const handleApply = useCallback(() => {
|
||||||
if (internalSelected) {
|
if (internalSelected) {
|
||||||
onApply(resourceIsString ? internalSelected : parseResourceDetails(internalSelected));
|
onApply(resourceIsString ? internalSelected : parseMultipleResourceDetails(internalSelected));
|
||||||
}
|
}
|
||||||
}, [resourceIsString, internalSelected, onApply]);
|
}, [resourceIsString, internalSelected, onApply]);
|
||||||
|
|
||||||
@ -230,7 +235,7 @@ const ResourcePicker = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Advanced resource={internalSelected} onChange={(r) => setInternalSelected(r)} />
|
<Advanced resources={internalSelected} onChange={(r) => setInternalSelected(r)} />
|
||||||
<Space v={2} />
|
<Space v={2} />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
import createMockQuery from '../../__mocks__/query';
|
import createMockQuery from '../../__mocks__/query';
|
||||||
|
|
||||||
import { ResourceRowGroup, ResourceRowType } from './types';
|
import { ResourceRowGroup, ResourceRowType } from './types';
|
||||||
import { findRow, parseResourceURI, setResource } from './utils';
|
import {
|
||||||
|
findRow,
|
||||||
|
findRows,
|
||||||
|
parseMultipleResourceDetails,
|
||||||
|
parseResourceDetails,
|
||||||
|
parseResourceURI,
|
||||||
|
resourcesToStrings,
|
||||||
|
setResource,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||||
|
getTemplateSrv: () => ({
|
||||||
|
replace: (val: string) => {
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('AzureMonitor ResourcePicker utils', () => {
|
describe('AzureMonitor ResourcePicker utils', () => {
|
||||||
describe('parseResourceURI', () => {
|
describe('parseResourceURI', () => {
|
||||||
@ -163,6 +180,17 @@ describe('AzureMonitor ResourcePicker utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findRows', () => {
|
||||||
|
it('should find multiple rows', () => {
|
||||||
|
const rows: ResourceRowGroup = [
|
||||||
|
{ id: 'sub1', uri: '/subscriptions/sub1', name: '', type: ResourceRowType.Subscription, typeLabel: '' },
|
||||||
|
{ id: 'sub2', uri: '/subscriptions/sub2', name: '', type: ResourceRowType.Subscription, typeLabel: '' },
|
||||||
|
{ id: 'sub3', uri: '/subscriptions/sub3', name: '', type: ResourceRowType.Subscription, typeLabel: '' },
|
||||||
|
];
|
||||||
|
expect(findRows(rows, ['/subscriptions/sub1', '/subscriptions/sub2'])).toEqual([rows[0], rows[1]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setResource', () => {
|
describe('setResource', () => {
|
||||||
it('updates a resource with a resource URI for Log Analytics', () => {
|
it('updates a resource with a resource URI for Log Analytics', () => {
|
||||||
expect(setResource(createMockQuery(), '/subscription/sub')).toMatchObject({
|
expect(setResource(createMockQuery(), '/subscription/sub')).toMatchObject({
|
||||||
@ -196,4 +224,68 @@ describe('AzureMonitor ResourcePicker utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parseResourceDetails', () => {
|
||||||
|
it('parses a string resource', () => {
|
||||||
|
expect(
|
||||||
|
parseResourceDetails(
|
||||||
|
'/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.Sql/servers/foo/databases/bar',
|
||||||
|
'useast'
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
metricNamespace: 'Microsoft.Sql/servers/databases',
|
||||||
|
resourceGroup: 'cloud-datasources',
|
||||||
|
resourceName: 'foo/bar',
|
||||||
|
subscription: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||||
|
region: 'useast',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseMultipleResourceDetails', () => {
|
||||||
|
it('parses multiple string resources', () => {
|
||||||
|
expect(
|
||||||
|
parseMultipleResourceDetails(
|
||||||
|
[
|
||||||
|
'/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.Sql/servers/foo/databases/bar',
|
||||||
|
'/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.Sql/servers/other/databases/resource',
|
||||||
|
],
|
||||||
|
'useast'
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
metricNamespace: 'Microsoft.Sql/servers/databases',
|
||||||
|
resourceGroup: 'cloud-datasources',
|
||||||
|
resourceName: 'foo/bar',
|
||||||
|
subscription: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||||
|
region: 'useast',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metricNamespace: 'Microsoft.Sql/servers/databases',
|
||||||
|
resourceGroup: 'cloud-datasources',
|
||||||
|
resourceName: 'other/resource',
|
||||||
|
subscription: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||||
|
region: 'useast',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resourcesToStrings', () => {
|
||||||
|
it('converts a resource to a string', () => {
|
||||||
|
expect(
|
||||||
|
resourcesToStrings([
|
||||||
|
{
|
||||||
|
metricNamespace: 'Microsoft.Sql/servers/databases',
|
||||||
|
resourceGroup: 'cloud-datasources',
|
||||||
|
resourceName: 'foo/bar',
|
||||||
|
subscription: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||||
|
region: 'useast',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
).toEqual([
|
||||||
|
'/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.Sql/servers/foo/databases/bar',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -43,6 +43,12 @@ export function parseResourceURI(resourceURI: string): AzureMetricResource {
|
|||||||
return { subscription, resourceGroup, metricNamespace, resourceName };
|
return { subscription, resourceGroup, metricNamespace, resourceName };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseMultipleResourceDetails(resources: Array<string | AzureMetricResource>, location?: string) {
|
||||||
|
return resources.map((resource) => {
|
||||||
|
return parseResourceDetails(resource, location);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function parseResourceDetails(resource: string | AzureMetricResource, location?: string) {
|
export function parseResourceDetails(resource: string | AzureMetricResource, location?: string) {
|
||||||
if (typeof resource === 'string') {
|
if (typeof resource === 'string') {
|
||||||
const res = parseResourceURI(resource);
|
const res = parseResourceURI(resource);
|
||||||
@ -54,6 +60,10 @@ export function parseResourceDetails(resource: string | AzureMetricResource, loc
|
|||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resourcesToStrings(resources: Array<string | AzureMetricResource>) {
|
||||||
|
return resources.map((resource) => resourceToString(resource));
|
||||||
|
}
|
||||||
|
|
||||||
export function resourceToString(resource?: string | AzureMetricResource) {
|
export function resourceToString(resource?: string | AzureMetricResource) {
|
||||||
return resource
|
return resource
|
||||||
? typeof resource === 'string'
|
? typeof resource === 'string'
|
||||||
@ -82,7 +92,7 @@ function compareNamespaceAndName(
|
|||||||
return rowNamespace === resourceNamespace && rowName === resourceName;
|
return rowNamespace === resourceNamespace && rowName === resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchURI(rowURI: string, resourceURI: string) {
|
export function matchURI(rowURI: string, resourceURI: string) {
|
||||||
const targetParams = parseResourceDetails(resourceURI);
|
const targetParams = parseResourceDetails(resourceURI);
|
||||||
const rowParams = parseResourceDetails(rowURI);
|
const rowParams = parseResourceDetails(rowURI);
|
||||||
|
|
||||||
@ -98,6 +108,17 @@ function matchURI(rowURI: string, resourceURI: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findRows(rows: ResourceRowGroup, uris: string[]): ResourceRow[] {
|
||||||
|
const result: ResourceRow[] = [];
|
||||||
|
uris.forEach((uri) => {
|
||||||
|
const row = findRow(rows, uri);
|
||||||
|
if (row) {
|
||||||
|
result.push(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function findRow(rows: ResourceRowGroup, uri: string): ResourceRow | undefined {
|
export function findRow(rows: ResourceRowGroup, uri: string): ResourceRow | undefined {
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (matchURI(row.uri, uri)) {
|
if (matchURI(row.uri, uri)) {
|
||||||
|
@ -10,6 +10,15 @@ import { AzureGraphResponse } from '../types';
|
|||||||
|
|
||||||
import ResourcePickerData from './resourcePickerData';
|
import ResourcePickerData from './resourcePickerData';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...(jest.requireActual('@grafana/runtime') as unknown as object),
|
||||||
|
getTemplateSrv: () => ({
|
||||||
|
replace: (val: string) => {
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
const createResourcePickerData = (responses: AzureGraphResponse[]) => {
|
const createResourcePickerData = (responses: AzureGraphResponse[]) => {
|
||||||
const instanceSettings = createMockInstanceSetttings();
|
const instanceSettings = createMockInstanceSetttings();
|
||||||
const mockDatasource = createMockDatasource();
|
const mockDatasource = createMockDatasource();
|
||||||
@ -412,4 +421,52 @@ describe('AzureMonitor resourcePickerData', () => {
|
|||||||
expect(locations.get('northeurope')?.supportsLogs).toBe(false);
|
expect(locations.get('northeurope')?.supportsLogs).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('fetchInitialRows', () => {
|
||||||
|
it('returns a list of subscriptions', async () => {
|
||||||
|
const { resourcePickerData } = createResourcePickerData([createMockARGSubscriptionResponse()]);
|
||||||
|
const rows = await resourcePickerData.fetchInitialRows('logs');
|
||||||
|
expect(rows.length).toEqual(createMockARGSubscriptionResponse().data.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches resource groups and resources', async () => {
|
||||||
|
const { resourcePickerData } = createResourcePickerData([createMockARGSubscriptionResponse()]);
|
||||||
|
resourcePickerData.getResourceGroupsBySubscriptionId = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue([{ id: 'rg1', uri: '/subscriptions/1/resourceGroups/rg1' }]);
|
||||||
|
resourcePickerData.getResourcesForResourceGroup = jest.fn().mockResolvedValue([
|
||||||
|
{ id: 'vm1', uri: '/subscriptions/1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1' },
|
||||||
|
{ id: 'vm2', uri: '/subscriptions/1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm2' },
|
||||||
|
]);
|
||||||
|
const rows = await resourcePickerData.fetchInitialRows('logs', [
|
||||||
|
{
|
||||||
|
subscription: '1',
|
||||||
|
resourceGroup: 'rg1',
|
||||||
|
resourceName: 'vm1',
|
||||||
|
metricNamespace: 'Microsoft.Compute/virtualMachines',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subscription: '1',
|
||||||
|
resourceGroup: 'rg1',
|
||||||
|
resourceName: 'vm2',
|
||||||
|
metricNamespace: 'Microsoft.Compute/virtualMachines',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(rows[0]).toMatchObject({
|
||||||
|
id: '1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'rg1',
|
||||||
|
children: [{ id: 'vm1' }, { id: 'vm2' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// getResourceGroupsBySubscriptionId should only be called once because the subscription
|
||||||
|
// of both resources is the same
|
||||||
|
expect(resourcePickerData.getResourceGroupsBySubscriptionId).toBeCalledTimes(1);
|
||||||
|
// getResourcesForResourceGroup should only be called once because the resource group
|
||||||
|
// of both resources is the same
|
||||||
|
expect(resourcePickerData.getResourcesForResourceGroup).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,13 @@ import { DataSourceWithBackend } from '@grafana/runtime';
|
|||||||
import { logsResourceTypes, resourceTypeDisplayNames } from '../azureMetadata';
|
import { logsResourceTypes, resourceTypeDisplayNames } from '../azureMetadata';
|
||||||
import AzureMonitorDatasource from '../azure_monitor/azure_monitor_datasource';
|
import AzureMonitorDatasource from '../azure_monitor/azure_monitor_datasource';
|
||||||
import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
||||||
import { addResources, parseResourceDetails, parseResourceURI } from '../components/ResourcePicker/utils';
|
import {
|
||||||
|
addResources,
|
||||||
|
findRow,
|
||||||
|
parseResourceDetails,
|
||||||
|
parseResourceURI,
|
||||||
|
resourceToString,
|
||||||
|
} from '../components/ResourcePicker/utils';
|
||||||
import {
|
import {
|
||||||
AzureDataSourceJsonData,
|
AzureDataSourceJsonData,
|
||||||
AzureGraphResponse,
|
AzureGraphResponse,
|
||||||
@ -46,7 +52,7 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
|
|
||||||
async fetchInitialRows(
|
async fetchInitialRows(
|
||||||
type: ResourcePickerQueryType,
|
type: ResourcePickerQueryType,
|
||||||
currentSelection?: AzureMetricResource
|
currentSelection?: AzureMetricResource[]
|
||||||
): Promise<ResourceRowGroup> {
|
): Promise<ResourceRowGroup> {
|
||||||
const subscriptions = await this.getSubscriptions();
|
const subscriptions = await this.getSubscriptions();
|
||||||
|
|
||||||
@ -60,19 +66,29 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resources = subscriptions;
|
let resources = subscriptions;
|
||||||
if (currentSelection.subscription) {
|
const promises = currentSelection.map((selection) => async () => {
|
||||||
const resourceGroupURI = `/subscriptions/${currentSelection.subscription}/resourceGroups/${currentSelection.resourceGroup}`;
|
if (selection.subscription) {
|
||||||
|
const resourceGroupURI = `/subscriptions/${selection.subscription}/resourceGroups/${selection.resourceGroup}`;
|
||||||
|
|
||||||
if (currentSelection.resourceGroup) {
|
if (selection.resourceGroup && !findRow(resources, resourceGroupURI)) {
|
||||||
const resourceGroups = await this.getResourceGroupsBySubscriptionId(currentSelection.subscription, type);
|
const resourceGroups = await this.getResourceGroupsBySubscriptionId(selection.subscription, type);
|
||||||
resources = addResources(resources, `/subscriptions/${currentSelection.subscription}`, resourceGroups);
|
resources = addResources(resources, `/subscriptions/${selection.subscription}`, resourceGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSelection.resourceName) {
|
const resourceURI = resourceToString(selection);
|
||||||
const resourcesForResourceGroup = await this.getResourcesForResourceGroup(resourceGroupURI, type);
|
if (selection.resourceName && !findRow(resources, resourceURI)) {
|
||||||
resources = addResources(resources, resourceGroupURI, resourcesForResourceGroup);
|
const resourcesForResourceGroup = await this.getResourcesForResourceGroup(resourceGroupURI, type);
|
||||||
|
resources = addResources(resources, resourceGroupURI, resourcesForResourceGroup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const promise of promises) {
|
||||||
|
// Fetch resources one by one, avoiding re-fetching the same resource
|
||||||
|
// and race conditions updating the resources array
|
||||||
|
await promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user