mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure Monitor: Small bug fixes for Resource Picker (#46665)
- fixes issue with checkbox styling - fixes issue with selecting subscriptions
This commit is contained in:
parent
721c3207e9
commit
dd49f9d182
@ -41,7 +41,6 @@ export default function createMockDatasource(overrides?: DeepPartial<Datasource>
|
|||||||
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue([]),
|
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue([]),
|
||||||
getResourcesForResourceGroup: jest.fn().mockResolvedValue([]),
|
getResourcesForResourceGroup: jest.fn().mockResolvedValue([]),
|
||||||
getResourceURIFromWorkspace: jest.fn().mockReturnValue(''),
|
getResourceURIFromWorkspace: jest.fn().mockReturnValue(''),
|
||||||
transformVariablesToRow: jest.fn().mockReturnValue({}),
|
|
||||||
},
|
},
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,6 @@ export default function createMockResourcePickerData(overrides?: DeepPartial<Res
|
|||||||
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue([]),
|
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue([]),
|
||||||
getResourcesForResourceGroup: jest.fn().mockResolvedValue([]),
|
getResourcesForResourceGroup: jest.fn().mockResolvedValue([]),
|
||||||
getResourceURIFromWorkspace: jest.fn().mockReturnValue(''),
|
getResourceURIFromWorkspace: jest.fn().mockReturnValue(''),
|
||||||
transformVariablesToRow: jest.fn().mockReturnValue({}),
|
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,115 +1,9 @@
|
|||||||
import { ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
import { ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
||||||
|
|
||||||
export const createMockResourcePickerRows = (): ResourceRowGroup => [
|
|
||||||
{
|
|
||||||
id: '/subscriptions/abc-123',
|
|
||||||
name: 'Primary Subscription',
|
|
||||||
type: ResourceRowType.Subscription,
|
|
||||||
typeLabel: 'Subscription',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: '/subscriptions/abc-123/resourceGroups/prod',
|
|
||||||
name: 'Production',
|
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '/subscriptions/abc-123/resourceGroups/pre-prod',
|
|
||||||
name: 'Pre-production',
|
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: '/subscriptions/def-456',
|
|
||||||
name: 'Dev Subscription',
|
|
||||||
type: ResourceRowType.Subscription,
|
|
||||||
typeLabel: 'Subscription',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev',
|
|
||||||
name: 'Development',
|
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: '/subscription/def-456/resourceGroups/dev/providers/Microsoft.Compute/virtualMachines/web-server',
|
|
||||||
name: 'web-server',
|
|
||||||
typeLabel: 'Microsoft.Compute/virtualMachines',
|
|
||||||
type: ResourceRowType.Resource,
|
|
||||||
location: 'northeurope',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '/subscription/def-456/resourceGroups/dev/providers/Microsoft.Compute/disks/web-server_DataDisk',
|
|
||||||
name: 'web-server_DataDisk',
|
|
||||||
typeLabel: 'Microsoft.Compute/disks',
|
|
||||||
type: ResourceRowType.Resource,
|
|
||||||
location: 'northeurope',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: '/subscription/def-456/resourceGroups/dev/providers/Microsoft.Compute/virtualMachines/db-server',
|
|
||||||
name: 'db-server',
|
|
||||||
typeLabel: 'Microsoft.Compute/virtualMachines',
|
|
||||||
type: ResourceRowType.Resource,
|
|
||||||
location: 'northeurope',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: '/subscription/def-456/resourceGroups/dev/providers/Microsoft.Compute/disks/db-server_DataDisk',
|
|
||||||
name: 'db-server_DataDisk',
|
|
||||||
typeLabel: 'Microsoft.Compute/disks',
|
|
||||||
type: ResourceRowType.Resource,
|
|
||||||
location: 'northeurope',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '/subscriptions/def-456/resourceGroups/test',
|
|
||||||
name: 'Test',
|
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '/subscriptions/def-456/resourceGroups/qa',
|
|
||||||
name: 'QA',
|
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: '$$grafana-templateVariables$$',
|
|
||||||
name: 'Template variables',
|
|
||||||
type: ResourceRowType.VariableGroup,
|
|
||||||
typeLabel: 'Variables',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: '$machine',
|
|
||||||
name: '$machine',
|
|
||||||
type: ResourceRowType.Variable,
|
|
||||||
typeLabel: 'Variable',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '$workspace',
|
|
||||||
name: '$workspace',
|
|
||||||
type: ResourceRowType.Variable,
|
|
||||||
typeLabel: 'Variable',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const createMockSubscriptions = (): ResourceRowGroup => [
|
export const createMockSubscriptions = (): ResourceRowGroup => [
|
||||||
{
|
{
|
||||||
id: 'def-123',
|
id: 'def-123',
|
||||||
|
uri: '/subscriptions/def-123',
|
||||||
name: 'Primary Subscription',
|
name: 'Primary Subscription',
|
||||||
type: ResourceRowType.Subscription,
|
type: ResourceRowType.Subscription,
|
||||||
typeLabel: 'Subscription',
|
typeLabel: 'Subscription',
|
||||||
@ -117,6 +11,7 @@ export const createMockSubscriptions = (): ResourceRowGroup => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'def-456',
|
id: 'def-456',
|
||||||
|
uri: '/subscriptions/def-456',
|
||||||
name: 'Dev Subscription',
|
name: 'Dev Subscription',
|
||||||
type: ResourceRowType.Subscription,
|
type: ResourceRowType.Subscription,
|
||||||
typeLabel: 'Subscription',
|
typeLabel: 'Subscription',
|
||||||
@ -124,6 +19,7 @@ export const createMockSubscriptions = (): ResourceRowGroup => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'def-789',
|
id: 'def-789',
|
||||||
|
uri: '/subscriptions/def-789',
|
||||||
name: 'Test Subscription',
|
name: 'Test Subscription',
|
||||||
type: ResourceRowType.Subscription,
|
type: ResourceRowType.Subscription,
|
||||||
typeLabel: 'Subscription',
|
typeLabel: 'Subscription',
|
||||||
@ -133,35 +29,40 @@ export const createMockSubscriptions = (): ResourceRowGroup => [
|
|||||||
|
|
||||||
export const createMockResourceGroupsBySubscription = (): ResourceRowGroup => [
|
export const createMockResourceGroupsBySubscription = (): ResourceRowGroup => [
|
||||||
{
|
{
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev-1',
|
id: 'dev-1',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-1',
|
||||||
name: 'Development',
|
name: 'Development',
|
||||||
type: ResourceRowType.ResourceGroup,
|
type: ResourceRowType.ResourceGroup,
|
||||||
typeLabel: 'Resource Group',
|
typeLabel: 'Resource Group',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev-2',
|
id: 'dev-2',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-2',
|
||||||
name: 'Development',
|
name: 'Development',
|
||||||
type: ResourceRowType.ResourceGroup,
|
type: ResourceRowType.ResourceGroup,
|
||||||
typeLabel: 'Resource Group',
|
typeLabel: 'Resource Group',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev-3',
|
id: 'dev-3',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-3',
|
||||||
|
name: 'A Great Resource Group',
|
||||||
|
type: ResourceRowType.ResourceGroup,
|
||||||
|
typeLabel: 'Resource Group',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'dev-4',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-4',
|
||||||
name: 'Development',
|
name: 'Development',
|
||||||
type: ResourceRowType.ResourceGroup,
|
type: ResourceRowType.ResourceGroup,
|
||||||
typeLabel: 'Resource Group',
|
typeLabel: 'Resource Group',
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev-4',
|
id: 'dev-5',
|
||||||
name: 'Development',
|
uri: '/subscriptions/def-456/resourceGroups/dev-5',
|
||||||
type: ResourceRowType.ResourceGroup,
|
|
||||||
typeLabel: 'Resource Group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev-5',
|
|
||||||
name: 'Development',
|
name: 'Development',
|
||||||
type: ResourceRowType.ResourceGroup,
|
type: ResourceRowType.ResourceGroup,
|
||||||
typeLabel: 'Resource Group',
|
typeLabel: 'Resource Group',
|
||||||
@ -171,14 +72,16 @@ export const createMockResourceGroupsBySubscription = (): ResourceRowGroup => [
|
|||||||
|
|
||||||
export const mockResourcesByResourceGroup = (): ResourceRowGroup => [
|
export const mockResourcesByResourceGroup = (): ResourceRowGroup => [
|
||||||
{
|
{
|
||||||
id: 'Microsoft.Compute/virtualMachines/web-server',
|
id: 'web-server',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/web-server',
|
||||||
name: 'web-server',
|
name: 'web-server',
|
||||||
typeLabel: 'Microsoft.Compute/virtualMachines',
|
typeLabel: 'Microsoft.Compute/virtualMachines',
|
||||||
type: ResourceRowType.Resource,
|
type: ResourceRowType.Resource,
|
||||||
location: 'northeurope',
|
location: 'northeurope',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Microsoft.Compute/disks/web-server_DataDisk',
|
id: 'web-server_DataDisk',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/disks/web-server_DataDisk',
|
||||||
name: 'web-server_DataDisk',
|
name: 'web-server_DataDisk',
|
||||||
typeLabel: 'Microsoft.Compute/disks',
|
typeLabel: 'Microsoft.Compute/disks',
|
||||||
type: ResourceRowType.Resource,
|
type: ResourceRowType.Resource,
|
||||||
@ -186,7 +89,8 @@ export const mockResourcesByResourceGroup = (): ResourceRowGroup => [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 'Microsoft.Compute/virtualMachines/db-server',
|
id: 'db-server',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server',
|
||||||
name: 'db-server',
|
name: 'db-server',
|
||||||
typeLabel: 'Microsoft.Compute/virtualMachines',
|
typeLabel: 'Microsoft.Compute/virtualMachines',
|
||||||
type: ResourceRowType.Resource,
|
type: ResourceRowType.Resource,
|
||||||
@ -194,7 +98,8 @@ export const mockResourcesByResourceGroup = (): ResourceRowGroup => [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 'Microsoft.Compute/disks/db-server_DataDisk',
|
id: 'db-server_DataDisk',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/disks/db-server_DataDisk',
|
||||||
name: 'db-server_DataDisk',
|
name: 'db-server_DataDisk',
|
||||||
typeLabel: 'Microsoft.Compute/disks',
|
typeLabel: 'Microsoft.Compute/disks',
|
||||||
type: ResourceRowType.Resource,
|
type: ResourceRowType.Resource,
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
import { act, render, screen } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { createMockResourcePickerRows } from '../../__mocks__/resourcePickerRows';
|
|
||||||
import NestedResourceTable from './NestedResourceTable';
|
|
||||||
import { findRow } from './utils';
|
|
||||||
|
|
||||||
describe('AzureMonitor NestedResourceTable', () => {
|
|
||||||
const noop: any = () => {};
|
|
||||||
|
|
||||||
const getElementById = document.getElementById;
|
|
||||||
beforeEach(() => {
|
|
||||||
document.getElementById = jest.fn().mockReturnValue({
|
|
||||||
scrollIntoView: jest.fn(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterEach(() => {
|
|
||||||
document.getElementById = getElementById;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders subscriptions', () => {
|
|
||||||
const rows = createMockResourcePickerRows();
|
|
||||||
|
|
||||||
render(<NestedResourceTable rows={rows} selectedRows={[]} requestNestedRows={noop} onRowSelectedChange={noop} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Primary Subscription')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Dev Subscription')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('opens to the selected resource', () => {
|
|
||||||
const rows = createMockResourcePickerRows();
|
|
||||||
const selected = findRow(
|
|
||||||
rows,
|
|
||||||
'/subscription/def-456/resourceGroups/dev/providers/Microsoft.Compute/disks/web-server_DataDisk'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!selected) {
|
|
||||||
throw new Error("couldn't find row, test data stale");
|
|
||||||
}
|
|
||||||
|
|
||||||
render(
|
|
||||||
<NestedResourceTable rows={rows} selectedRows={[selected]} requestNestedRows={noop} onRowSelectedChange={noop} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText('web-server_DataDisk')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("expands subscriptions when they're clicked", async () => {
|
|
||||||
const rows = createMockResourcePickerRows();
|
|
||||||
const promise = Promise.resolve();
|
|
||||||
const requestNestedRows = jest.fn().mockReturnValue(promise);
|
|
||||||
render(
|
|
||||||
<NestedResourceTable
|
|
||||||
rows={rows}
|
|
||||||
selectedRows={[]}
|
|
||||||
requestNestedRows={requestNestedRows}
|
|
||||||
onRowSelectedChange={noop}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const expandButton = screen.getAllByLabelText('Expand')[1];
|
|
||||||
userEvent.click(expandButton);
|
|
||||||
|
|
||||||
expect(requestNestedRows).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: '/subscriptions/def-456',
|
|
||||||
name: 'Dev Subscription',
|
|
||||||
typeLabel: 'Subscription',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText('Development')).not.toBeInTheDocument();
|
|
||||||
await act(() => promise);
|
|
||||||
expect(screen.getByText('Development')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('supports selecting variables', async () => {
|
|
||||||
const rows = createMockResourcePickerRows();
|
|
||||||
const promise = Promise.resolve();
|
|
||||||
const requestNestedRows = jest.fn().mockReturnValue(promise);
|
|
||||||
const onRowSelectedChange = jest.fn();
|
|
||||||
render(
|
|
||||||
<NestedResourceTable
|
|
||||||
rows={rows}
|
|
||||||
selectedRows={[]}
|
|
||||||
requestNestedRows={requestNestedRows}
|
|
||||||
onRowSelectedChange={onRowSelectedChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const expandButton = screen.getAllByLabelText('Expand')[2];
|
|
||||||
userEvent.click(expandButton);
|
|
||||||
|
|
||||||
await act(() => promise);
|
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText('$workspace');
|
|
||||||
userEvent.click(checkbox);
|
|
||||||
|
|
||||||
expect(onRowSelectedChange).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: '$workspace',
|
|
||||||
name: '$workspace',
|
|
||||||
}),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -67,8 +67,7 @@ const NestedRow: React.FC<NestedRowProps> = ({ row, selectedRows, level, request
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Assuming we don't have multi-select yet
|
// Assuming we don't have multi-select yet
|
||||||
const selectedRow = selectedRows[0];
|
const selectedRow = selectedRows[0];
|
||||||
|
const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.uri);
|
||||||
const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.id);
|
|
||||||
|
|
||||||
if (containsChild) {
|
if (containsChild) {
|
||||||
setRowStatus('open');
|
setRowStatus('open');
|
||||||
@ -203,7 +202,7 @@ const NestedEntry: React.FC<NestedEntryProps> = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
className={styles.collapseButton}
|
className={styles.collapseButton}
|
||||||
name={isOpen ? 'angle-down' : 'angle-right'}
|
name={isOpen ? 'angle-down' : 'angle-right'}
|
||||||
aria-label={isOpen ? 'Collapse' : 'Expand'}
|
aria-label={isOpen ? `Collapse ${entry.name}` : `Expand ${entry.name}`}
|
||||||
onClick={handleToggleCollapse}
|
onClick={handleToggleCollapse}
|
||||||
id={entry.id}
|
id={entry.id}
|
||||||
/>
|
/>
|
||||||
@ -215,7 +214,13 @@ const NestedEntry: React.FC<NestedEntryProps> = ({
|
|||||||
|
|
||||||
{isSelectable && (
|
{isSelectable && (
|
||||||
<>
|
<>
|
||||||
<Checkbox id={checkboxId} onChange={handleSelectedChanged} disabled={isDisabled} value={isSelected} />
|
<Checkbox
|
||||||
|
id={checkboxId}
|
||||||
|
onChange={handleSelectedChanged}
|
||||||
|
disabled={isDisabled}
|
||||||
|
value={isSelected}
|
||||||
|
className={styles.nestedRowCheckbox}
|
||||||
|
/>
|
||||||
<Space layout="inline" h={2} />
|
<Space layout="inline" h={2} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -10,119 +10,143 @@ import {
|
|||||||
} from '../../__mocks__/resourcePickerRows';
|
} from '../../__mocks__/resourcePickerRows';
|
||||||
|
|
||||||
const noResourceURI = '';
|
const noResourceURI = '';
|
||||||
const singleSubscriptionSelectionURI = 'def-456';
|
const singleSubscriptionSelectionURI = '/subscriptions/def-456';
|
||||||
const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3';
|
const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/dev-3';
|
||||||
const singleResourceSelectionURI =
|
const singleResourceSelectionURI =
|
||||||
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-serverproviders/Microsoft.Compute/virtualMachines/db-server';
|
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server';
|
||||||
|
|
||||||
|
const createResourcePickerDataMock = () => {
|
||||||
|
return createMockResourcePickerData({
|
||||||
|
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
|
||||||
|
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()),
|
||||||
|
getResourcesForResourceGroup: jest.fn().mockResolvedValue(mockResourcesByResourceGroup()),
|
||||||
|
});
|
||||||
|
};
|
||||||
describe('AzureMonitor ResourcePicker', () => {
|
describe('AzureMonitor ResourcePicker', () => {
|
||||||
const noop: any = () => {};
|
const noop: any = () => {};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.HTMLElement.prototype.scrollIntoView = function () {};
|
window.HTMLElement.prototype.scrollIntoView = function () {};
|
||||||
});
|
});
|
||||||
describe('when rendering the resource picker without a selection', () => {
|
it('should pre-load subscriptions when there is no existing selection', async () => {
|
||||||
it('should load subscriptions', async () => {
|
|
||||||
const resourePickerDataMock = createMockResourcePickerData({
|
|
||||||
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
|
|
||||||
getResourceGroupsBySubscriptionId: jest.fn(),
|
|
||||||
getResourcesForResourceGroup: jest.fn(),
|
|
||||||
});
|
|
||||||
render(
|
render(
|
||||||
<ResourcePicker
|
<ResourcePicker
|
||||||
templateVariables={[]}
|
templateVariables={[]}
|
||||||
resourcePickerData={resourePickerDataMock}
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
resourceURI={noResourceURI}
|
resourceURI={noResourceURI}
|
||||||
onCancel={noop}
|
onCancel={noop}
|
||||||
onApply={noop}
|
onApply={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
||||||
expect(await screen.findByText('Primary Subscription')).toBeInTheDocument();
|
expect(subscriptionCheckbox).toBeInTheDocument();
|
||||||
expect(resourePickerDataMock.getSubscriptions).toHaveBeenCalledTimes(1);
|
expect(subscriptionCheckbox).not.toBeChecked();
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).not.toHaveBeenCalled();
|
const uncheckedCheckboxes = await screen.findAllByRole('checkbox', { checked: false });
|
||||||
expect(resourePickerDataMock.getResourcesForResourceGroup).not.toHaveBeenCalled();
|
expect(uncheckedCheckboxes.length).toBe(3);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when rendering the resource picker with a subscription selected', () => {
|
it('should show a subscription as selected if there is one saved', async () => {
|
||||||
it('should load subscriptions once', async () => {
|
|
||||||
const resourePickerDataMock = createMockResourcePickerData({
|
|
||||||
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
|
|
||||||
getResourceGroupsBySubscriptionId: jest.fn(),
|
|
||||||
getResourcesForResourceGroup: jest.fn(),
|
|
||||||
});
|
|
||||||
render(
|
render(
|
||||||
<ResourcePicker
|
<ResourcePicker
|
||||||
templateVariables={[]}
|
templateVariables={[]}
|
||||||
resourcePickerData={resourePickerDataMock}
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
resourceURI={singleSubscriptionSelectionURI}
|
resourceURI={singleSubscriptionSelectionURI}
|
||||||
onCancel={noop}
|
onCancel={noop}
|
||||||
onApply={noop}
|
onApply={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const subscriptionCheckbox = await screen.findByLabelText('Dev Subscription');
|
||||||
expect(await screen.findByText('Primary Subscription')).toBeInTheDocument();
|
expect(subscriptionCheckbox).toBeChecked();
|
||||||
expect(resourePickerDataMock.getSubscriptions).toHaveBeenCalledTimes(1);
|
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).not.toHaveBeenCalled();
|
|
||||||
expect(resourePickerDataMock.getResourcesForResourceGroup).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when rendering the resource picker with a resource group selected', () => {
|
it('should show a resource group as selected if there is one saved', async () => {
|
||||||
it('should load subscriptions and resource groups for its parent subscription once', async () => {
|
|
||||||
const resourePickerDataMock = createMockResourcePickerData({
|
|
||||||
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
|
|
||||||
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()),
|
|
||||||
getResourcesForResourceGroup: jest.fn(),
|
|
||||||
});
|
|
||||||
render(
|
render(
|
||||||
<ResourcePicker
|
<ResourcePicker
|
||||||
templateVariables={[]}
|
templateVariables={[]}
|
||||||
resourcePickerData={resourePickerDataMock}
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
resourceURI={singleResourceGroupSelectionURI}
|
resourceURI={singleResourceGroupSelectionURI}
|
||||||
onCancel={noop}
|
onCancel={noop}
|
||||||
onApply={noop}
|
onApply={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const resourceGroupCheckbox = await screen.findByLabelText('A Great Resource Group');
|
||||||
expect(await screen.findByText('Primary Subscription')).toBeInTheDocument();
|
expect(resourceGroupCheckbox).toBeChecked();
|
||||||
expect(resourePickerDataMock.getSubscriptions).toHaveBeenCalledTimes(1);
|
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).toHaveBeenCalledTimes(1);
|
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).toHaveBeenLastCalledWith(
|
|
||||||
singleSubscriptionSelectionURI
|
|
||||||
);
|
|
||||||
expect(resourePickerDataMock.getResourcesForResourceGroup).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when rendering the resource picker with a resource selected', () => {
|
it('should show a resource as selected if there is one saved', async () => {
|
||||||
it('should load subscriptions, resource groups and resources once', async () => {
|
|
||||||
const resourePickerDataMock = createMockResourcePickerData({
|
|
||||||
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
|
|
||||||
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()),
|
|
||||||
getResourcesForResourceGroup: jest.fn().mockResolvedValue(mockResourcesByResourceGroup()),
|
|
||||||
});
|
|
||||||
render(
|
render(
|
||||||
<ResourcePicker
|
<ResourcePicker
|
||||||
templateVariables={[]}
|
templateVariables={[]}
|
||||||
resourcePickerData={resourePickerDataMock}
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
resourceURI={singleResourceSelectionURI}
|
resourceURI={singleResourceSelectionURI}
|
||||||
onCancel={noop}
|
onCancel={noop}
|
||||||
onApply={noop}
|
onApply={noop}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByText('Primary Subscription')).toBeInTheDocument();
|
const resourceCheckbox = await screen.findByLabelText('db-server');
|
||||||
expect(resourePickerDataMock.getSubscriptions).toHaveBeenCalledTimes(1);
|
expect(resourceCheckbox).toBeChecked();
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).toHaveBeenCalledTimes(1);
|
});
|
||||||
expect(resourePickerDataMock.getResourceGroupsBySubscriptionId).toHaveBeenLastCalledWith(
|
|
||||||
singleSubscriptionSelectionURI
|
it('should be able to expand a subscription when clicked and reveal resource groups', async () => {
|
||||||
|
render(
|
||||||
|
<ResourcePicker
|
||||||
|
templateVariables={[]}
|
||||||
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
|
resourceURI={noResourceURI}
|
||||||
|
onCancel={noop}
|
||||||
|
onApply={noop}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
expect(resourePickerDataMock.getResourcesForResourceGroup).toHaveBeenCalledTimes(1);
|
const expandSubscriptionButton = await screen.findByLabelText('Expand Primary Subscription');
|
||||||
expect(resourePickerDataMock.getResourcesForResourceGroup).toHaveBeenLastCalledWith(
|
expect(expandSubscriptionButton).toBeInTheDocument();
|
||||||
singleResourceGroupSelectionURI
|
expect(screen.queryByLabelText('A Great Resource Group')).not.toBeInTheDocument();
|
||||||
|
expandSubscriptionButton.click();
|
||||||
|
expect(await screen.findByLabelText('A Great Resource Group')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onApply with a new subscription uri when a user selects it', async () => {
|
||||||
|
const onApply = jest.fn();
|
||||||
|
render(
|
||||||
|
<ResourcePicker
|
||||||
|
templateVariables={[]}
|
||||||
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
|
resourceURI={noResourceURI}
|
||||||
|
onCancel={noop}
|
||||||
|
onApply={onApply}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
|
||||||
|
expect(subscriptionCheckbox).toBeInTheDocument();
|
||||||
|
expect(subscriptionCheckbox).not.toBeChecked();
|
||||||
|
subscriptionCheckbox.click();
|
||||||
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
|
applyButton.click();
|
||||||
|
expect(onApply).toBeCalledTimes(1);
|
||||||
|
expect(onApply).toBeCalledWith('/subscriptions/def-123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onApply with a template variable when a user selects it', async () => {
|
||||||
|
const onApply = jest.fn();
|
||||||
|
render(
|
||||||
|
<ResourcePicker
|
||||||
|
templateVariables={['$workspace']}
|
||||||
|
resourcePickerData={createResourcePickerDataMock()}
|
||||||
|
resourceURI={noResourceURI}
|
||||||
|
onCancel={noop}
|
||||||
|
onApply={onApply}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandButton = await screen.findByLabelText('Expand Template variables');
|
||||||
|
expandButton.click();
|
||||||
|
|
||||||
|
const workSpaceCheckbox = await screen.findByLabelText('$workspace');
|
||||||
|
workSpaceCheckbox.click();
|
||||||
|
|
||||||
|
const applyButton = screen.getByRole('button', { name: 'Apply' });
|
||||||
|
applyButton.click();
|
||||||
|
|
||||||
|
expect(onApply).toBeCalledTimes(1);
|
||||||
|
expect(onApply).toBeCalledWith('$workspace');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@ import NestedResourceTable from './NestedResourceTable';
|
|||||||
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
|
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
|
||||||
import { addResources, findRow, parseResourceURI } from './utils';
|
import { addResources, findRow, parseResourceURI } from './utils';
|
||||||
|
|
||||||
|
const TEMPLATE_VARIABLE_GROUP_ID = '$$grafana-templateVariables$$';
|
||||||
interface ResourcePickerProps {
|
interface ResourcePickerProps {
|
||||||
resourcePickerData: ResourcePickerData;
|
resourcePickerData: ResourcePickerData;
|
||||||
resourceURI: string | undefined;
|
resourceURI: string | undefined;
|
||||||
@ -31,12 +32,12 @@ const ResourcePicker = ({
|
|||||||
type LoadingStatus = 'NotStarted' | 'Started' | 'Done';
|
type LoadingStatus = 'NotStarted' | 'Started' | 'Done';
|
||||||
const [loadingStatus, setLoadingStatus] = useState<LoadingStatus>('NotStarted');
|
const [loadingStatus, setLoadingStatus] = useState<LoadingStatus>('NotStarted');
|
||||||
const [azureRows, setAzureRows] = useState<ResourceRowGroup>([]);
|
const [azureRows, setAzureRows] = useState<ResourceRowGroup>([]);
|
||||||
const [internalSelected, setInternalSelected] = useState<string | undefined>(resourceURI);
|
const [internalSelectedURI, setInternalSelectedURI] = useState<string | undefined>(resourceURI);
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
|
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
// Sync the resourceURI prop to internal state
|
// Sync the resourceURI prop to internal state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInternalSelected(resourceURI);
|
setInternalSelectedURI(resourceURI);
|
||||||
}, [resourceURI]);
|
}, [resourceURI]);
|
||||||
|
|
||||||
// Request initial data on first mount
|
// Request initial data on first mount
|
||||||
@ -46,13 +47,13 @@ const ResourcePicker = ({
|
|||||||
try {
|
try {
|
||||||
setLoadingStatus('Started');
|
setLoadingStatus('Started');
|
||||||
let resources = await resourcePickerData.getSubscriptions();
|
let resources = await resourcePickerData.getSubscriptions();
|
||||||
if (!internalSelected) {
|
if (!internalSelectedURI) {
|
||||||
setAzureRows(resources);
|
setAzureRows(resources);
|
||||||
setLoadingStatus('Done');
|
setLoadingStatus('Done');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedURI = parseResourceURI(internalSelected ?? '');
|
const parsedURI = parseResourceURI(internalSelectedURI ?? '');
|
||||||
if (parsedURI) {
|
if (parsedURI) {
|
||||||
const resourceGroupURI = `/subscriptions/${parsedURI.subscriptionID}/resourceGroups/${parsedURI.resourceGroup}`;
|
const resourceGroupURI = `/subscriptions/${parsedURI.subscriptionID}/resourceGroups/${parsedURI.resourceGroup}`;
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ const ResourcePicker = ({
|
|||||||
const resourceGroups = await resourcePickerData.getResourceGroupsBySubscriptionId(
|
const resourceGroups = await resourcePickerData.getResourceGroupsBySubscriptionId(
|
||||||
parsedURI.subscriptionID
|
parsedURI.subscriptionID
|
||||||
);
|
);
|
||||||
resources = addResources(resources, parsedURI.subscriptionID, resourceGroups);
|
resources = addResources(resources, `/subscriptions/${parsedURI.subscriptionID}`, resourceGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a resource was previously selected, but the resources under the parent resource group have not been loaded yet
|
// if a resource was previously selected, but the resources under the parent resource group have not been loaded yet
|
||||||
@ -80,16 +81,16 @@ const ResourcePicker = ({
|
|||||||
|
|
||||||
loadInitialData();
|
loadInitialData();
|
||||||
}
|
}
|
||||||
}, [resourcePickerData, internalSelected, azureRows, loadingStatus]);
|
}, [resourcePickerData, internalSelectedURI, azureRows, loadingStatus]);
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
const templateVariableRow = resourcePickerData.transformVariablesToRow(templateVariables);
|
const templateVariableRow = transformVariablesToRow(templateVariables);
|
||||||
return templateVariables.length ? [...azureRows, templateVariableRow] : azureRows;
|
return templateVariables.length ? [...azureRows, templateVariableRow] : azureRows;
|
||||||
}, [resourcePickerData, azureRows, templateVariables]);
|
}, [azureRows, templateVariables]);
|
||||||
|
|
||||||
// Map the selected item into an array of rows
|
// Map the selected item into an array of rows
|
||||||
const selectedResourceRows = useMemo(() => {
|
const selectedResourceRows = useMemo(() => {
|
||||||
const found = internalSelected && findRow(rows, internalSelected);
|
const found = internalSelectedURI && findRow(rows, internalSelectedURI);
|
||||||
return found
|
return found
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -98,7 +99,7 @@ const ResourcePicker = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
}, [internalSelected, rows]);
|
}, [internalSelectedURI, rows]);
|
||||||
|
|
||||||
// Request resources for a expanded resource group
|
// Request resources for a expanded resource group
|
||||||
const requestNestedRows = useCallback(
|
const requestNestedRows = useCallback(
|
||||||
@ -110,7 +111,7 @@ const ResourcePicker = ({
|
|||||||
// template variable group, though that shouldn't happen in practice
|
// template variable group, though that shouldn't happen in practice
|
||||||
if (
|
if (
|
||||||
resourceGroupOrSubscription.children?.length ||
|
resourceGroupOrSubscription.children?.length ||
|
||||||
resourceGroupOrSubscription.id === ResourcePickerData.templateVariableGroupID
|
resourceGroupOrSubscription.uri === TEMPLATE_VARIABLE_GROUP_ID
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -121,7 +122,7 @@ const ResourcePicker = ({
|
|||||||
? await resourcePickerData.getResourceGroupsBySubscriptionId(resourceGroupOrSubscription.id)
|
? await resourcePickerData.getResourceGroupsBySubscriptionId(resourceGroupOrSubscription.id)
|
||||||
: await resourcePickerData.getResourcesForResourceGroup(resourceGroupOrSubscription.id);
|
: await resourcePickerData.getResourcesForResourceGroup(resourceGroupOrSubscription.id);
|
||||||
|
|
||||||
const newRows = addResources(azureRows, resourceGroupOrSubscription.id, rows);
|
const newRows = addResources(azureRows, resourceGroupOrSubscription.uri, rows);
|
||||||
|
|
||||||
setAzureRows(newRows);
|
setAzureRows(newRows);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -132,14 +133,13 @@ const ResourcePicker = ({
|
|||||||
[resourcePickerData, azureRows]
|
[resourcePickerData, azureRows]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Select
|
|
||||||
const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => {
|
const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => {
|
||||||
isSelected ? setInternalSelected(row.id) : setInternalSelected(undefined);
|
isSelected ? setInternalSelectedURI(row.uri) : setInternalSelectedURI(undefined);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleApply = useCallback(() => {
|
const handleApply = useCallback(() => {
|
||||||
onApply(internalSelected);
|
onApply(internalSelectedURI);
|
||||||
}, [internalSelected, onApply]);
|
}, [internalSelectedURI, onApply]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -211,3 +211,20 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
color: theme.colors.text.secondary,
|
color: theme.colors.text.secondary,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function transformVariablesToRow(templateVariables: string[]): ResourceRow {
|
||||||
|
return {
|
||||||
|
id: TEMPLATE_VARIABLE_GROUP_ID,
|
||||||
|
uri: TEMPLATE_VARIABLE_GROUP_ID,
|
||||||
|
name: 'Template variables',
|
||||||
|
type: ResourceRowType.VariableGroup,
|
||||||
|
typeLabel: 'Variables',
|
||||||
|
children: templateVariables.map((v) => ({
|
||||||
|
id: v,
|
||||||
|
uri: v,
|
||||||
|
name: v,
|
||||||
|
type: ResourceRowType.Variable,
|
||||||
|
typeLabel: 'Variable',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -64,6 +64,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
nestedRowCheckbox: css({
|
||||||
|
zIndex: 0,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
@ -7,7 +7,8 @@ export enum ResourceRowType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceRow {
|
export interface ResourceRow {
|
||||||
id: string;
|
id: string; // azure's raw data id usually passes along a uri (except in the case of subscriptions), to make things less confusing for ourselves we parse the id string out of the uri or vice versa
|
||||||
|
uri: string; // ex: /subscriptions/subid123
|
||||||
name: string;
|
name: string;
|
||||||
type: ResourceRowType;
|
type: ResourceRowType;
|
||||||
typeLabel: string;
|
typeLabel: string;
|
||||||
|
@ -26,14 +26,14 @@ export function isGUIDish(input: string) {
|
|||||||
return !!input.match(/^[A-Z0-9]+/i);
|
return !!input.match(/^[A-Z0-9]+/i);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findRow(rows: ResourceRowGroup, id: string): ResourceRow | undefined {
|
export function findRow(rows: ResourceRowGroup, uri: string): ResourceRow | undefined {
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (row.id.toLowerCase() === id.toLowerCase()) {
|
if (row.uri.toLowerCase() === uri.toLowerCase()) {
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.children) {
|
if (row.children) {
|
||||||
const result = findRow(row.children, id);
|
const result = findRow(row.children, uri);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
@ -44,9 +44,9 @@ export function findRow(rows: ResourceRowGroup, id: string): ResourceRow | undef
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addResources(rows: ResourceRowGroup, targetResourceGroupID: string, newResources: ResourceRowGroup) {
|
export function addResources(rows: ResourceRowGroup, targetParentId: string, newResources: ResourceRowGroup) {
|
||||||
return produce(rows, (draftState) => {
|
return produce(rows, (draftState) => {
|
||||||
const draftRow = findRow(draftState, targetResourceGroupID);
|
const draftRow = findRow(draftState, targetParentId);
|
||||||
|
|
||||||
if (!draftRow) {
|
if (!draftRow) {
|
||||||
// This case shouldn't happen often because we're usually coming here from a resource we already have
|
// This case shouldn't happen often because we're usually coming here from a resource we already have
|
||||||
|
@ -4,129 +4,246 @@ import {
|
|||||||
createMockARGSubscriptionResponse,
|
createMockARGSubscriptionResponse,
|
||||||
} from '../__mocks__/argResourcePickerResponse';
|
} from '../__mocks__/argResourcePickerResponse';
|
||||||
import { createMockInstanceSetttings } from '../__mocks__/instanceSettings';
|
import { createMockInstanceSetttings } from '../__mocks__/instanceSettings';
|
||||||
import { ResourceRowType } from '../components/ResourcePicker/types';
|
|
||||||
import ResourcePickerData from './resourcePickerData';
|
import ResourcePickerData from './resourcePickerData';
|
||||||
|
import { AzureGraphResponse } from '../types';
|
||||||
|
|
||||||
|
const createResourcePickerData = (responses: AzureGraphResponse[]) => {
|
||||||
const instanceSettings = createMockInstanceSetttings();
|
const instanceSettings = createMockInstanceSetttings();
|
||||||
const resourcePickerData = new ResourcePickerData(instanceSettings);
|
const resourcePickerData = new ResourcePickerData(instanceSettings);
|
||||||
let postResource: jest.Mock;
|
|
||||||
|
|
||||||
|
const postResource = jest.fn();
|
||||||
|
responses.forEach((res) => {
|
||||||
|
postResource.mockResolvedValueOnce(res);
|
||||||
|
});
|
||||||
|
resourcePickerData.postResource = postResource;
|
||||||
|
|
||||||
|
return { resourcePickerData, postResource };
|
||||||
|
};
|
||||||
describe('AzureMonitor resourcePickerData', () => {
|
describe('AzureMonitor resourcePickerData', () => {
|
||||||
describe('getSubscriptions', () => {
|
describe('getSubscriptions', () => {
|
||||||
beforeEach(() => {
|
it('makes 1 call to ARG with the correct path and query arguments', async () => {
|
||||||
postResource = jest.fn().mockResolvedValue(createMockARGSubscriptionResponse());
|
const mockResponse = createMockARGSubscriptionResponse();
|
||||||
resourcePickerData.postResource = postResource;
|
const { resourcePickerData, postResource } = createResourcePickerData([mockResponse]);
|
||||||
});
|
|
||||||
|
|
||||||
it('calls ARG API', async () => {
|
|
||||||
await resourcePickerData.getSubscriptions();
|
await resourcePickerData.getSubscriptions();
|
||||||
|
|
||||||
expect(postResource).toHaveBeenCalled();
|
expect(postResource).toBeCalledTimes(1);
|
||||||
const argQuery = postResource.mock.calls[0][1].query;
|
const firstCall = postResource.mock.calls[0];
|
||||||
|
const [path, postBody] = firstCall;
|
||||||
|
expect(path).toEqual('resourcegraph/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01');
|
||||||
|
expect(postBody.query).toContain("where type == 'microsoft.resources/subscriptions'");
|
||||||
|
});
|
||||||
|
it('returns formatted subscriptions', async () => {
|
||||||
|
const mockResponse = createMockARGSubscriptionResponse();
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
|
||||||
expect(argQuery).toContain(`where type == 'microsoft.resources/subscriptions'`);
|
const subscriptions = await resourcePickerData.getSubscriptions();
|
||||||
|
expect(subscriptions.length).toEqual(6);
|
||||||
|
expect(subscriptions[0]).toEqual({
|
||||||
|
id: '1',
|
||||||
|
name: 'Primary Subscription',
|
||||||
|
type: 'Subscription',
|
||||||
|
typeLabel: 'Subscription',
|
||||||
|
uri: '/subscriptions/1',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there is more than one page', () => {
|
it('makes multiple requests when arg returns a skipToken and passes the right skipToken to each subsequent call', async () => {
|
||||||
beforeEach(() => {
|
|
||||||
const response1 = {
|
const response1 = {
|
||||||
...createMockARGSubscriptionResponse(),
|
...createMockARGSubscriptionResponse(),
|
||||||
$skipToken: 'aaa',
|
$skipToken: 'skipfirst100',
|
||||||
};
|
};
|
||||||
const response2 = createMockARGSubscriptionResponse();
|
const response2 = createMockARGSubscriptionResponse();
|
||||||
postResource = jest.fn();
|
const { resourcePickerData, postResource } = createResourcePickerData([response1, response2]);
|
||||||
postResource.mockResolvedValueOnce(response1);
|
|
||||||
postResource.mockResolvedValueOnce(response2);
|
|
||||||
resourcePickerData.postResource = postResource;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should requests additional pages', async () => {
|
|
||||||
await resourcePickerData.getSubscriptions();
|
await resourcePickerData.getSubscriptions();
|
||||||
|
|
||||||
expect(postResource).toHaveBeenCalledTimes(2);
|
expect(postResource).toHaveBeenCalledTimes(2);
|
||||||
});
|
|
||||||
|
|
||||||
it('should use the skipToken of the previous page', async () => {
|
|
||||||
await resourcePickerData.getSubscriptions();
|
|
||||||
const secondCall = postResource.mock.calls[1];
|
const secondCall = postResource.mock.calls[1];
|
||||||
expect(secondCall[1]).toMatchObject({ options: { $skipToken: 'aaa', resultFormat: 'objectArray' } });
|
const [_, postBody] = secondCall;
|
||||||
|
expect(postBody.options.$skipToken).toEqual('skipfirst100');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns a concatenates a formatted array of subscriptions when there are multiple pages from arg', async () => {
|
||||||
|
const response1 = {
|
||||||
|
...createMockARGSubscriptionResponse(),
|
||||||
|
$skipToken: 'skipfirst100',
|
||||||
|
};
|
||||||
|
const response2 = createMockARGSubscriptionResponse();
|
||||||
|
const { resourcePickerData } = createResourcePickerData([response1, response2]);
|
||||||
|
|
||||||
|
const subscriptions = await resourcePickerData.getSubscriptions();
|
||||||
|
|
||||||
|
expect(subscriptions.length).toEqual(12);
|
||||||
|
expect(subscriptions[0]).toEqual({
|
||||||
|
id: '1',
|
||||||
|
name: 'Primary Subscription',
|
||||||
|
type: 'Subscription',
|
||||||
|
typeLabel: 'Subscription',
|
||||||
|
uri: '/subscriptions/1',
|
||||||
|
children: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getResourcesForResourceGroup', () => {
|
it('throws an error if it does not recieve data from arg', async () => {
|
||||||
beforeEach(() => {
|
const mockResponse = { data: [] };
|
||||||
postResource = jest.fn().mockResolvedValue(createMockARGResourceGroupsResponse());
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
resourcePickerData.postResource = postResource;
|
try {
|
||||||
|
await resourcePickerData.getSubscriptions();
|
||||||
|
throw Error('expected getSubscriptions to fail but it succeeded');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toEqual('unable to fetch subscriptions');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls ARG API', async () => {
|
describe('getResourceGroupsBySubscriptionId', () => {
|
||||||
|
it('makes 1 call to ARG with the correct path and query arguments', async () => {
|
||||||
|
const mockResponse = createMockARGResourceGroupsResponse();
|
||||||
|
const { resourcePickerData, postResource } = createResourcePickerData([mockResponse]);
|
||||||
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
|
||||||
expect(postResource).toHaveBeenCalled();
|
expect(postResource).toBeCalledTimes(1);
|
||||||
const argQuery = postResource.mock.calls[0][1].query;
|
const firstCall = postResource.mock.calls[0];
|
||||||
|
const [path, postBody] = firstCall;
|
||||||
|
expect(path).toEqual('resourcegraph/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01');
|
||||||
|
expect(postBody.query).toContain("type == 'microsoft.resources/subscriptions/resourcegroups'");
|
||||||
|
expect(postBody.query).toContain("where subscriptionId == '123'");
|
||||||
|
});
|
||||||
|
it('returns formatted resourceGroups', async () => {
|
||||||
|
const mockResponse = createMockARGResourceGroupsResponse();
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
|
||||||
expect(argQuery).toContain(`| where subscriptionId == '123'`);
|
const resourceGroups = await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
expect(resourceGroups.length).toEqual(6);
|
||||||
|
expect(resourceGroups[0]).toEqual({
|
||||||
|
id: 'prod',
|
||||||
|
name: 'Production',
|
||||||
|
type: 'ResourceGroup',
|
||||||
|
typeLabel: 'Resource Group',
|
||||||
|
uri: '/subscriptions/abc-123/resourceGroups/prod',
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there is more than one page', () => {
|
it('makes multiple requests when it is returned a skip token', async () => {
|
||||||
beforeEach(() => {
|
|
||||||
const response1 = {
|
const response1 = {
|
||||||
...createMockARGResourceGroupsResponse(),
|
...createMockARGResourceGroupsResponse(),
|
||||||
$skipToken: 'aaa',
|
$skipToken: 'skipfirst100',
|
||||||
};
|
};
|
||||||
const response2 = createMockARGResourceGroupsResponse();
|
const response2 = createMockARGResourceGroupsResponse();
|
||||||
postResource = jest.fn();
|
const { resourcePickerData, postResource } = createResourcePickerData([response1, response2]);
|
||||||
postResource.mockResolvedValueOnce(response1);
|
|
||||||
postResource.mockResolvedValueOnce(response2);
|
|
||||||
resourcePickerData.postResource = postResource;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should requests additional pages', async () => {
|
|
||||||
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
|
||||||
expect(postResource).toHaveBeenCalledTimes(2);
|
expect(postResource).toHaveBeenCalledTimes(2);
|
||||||
|
const secondCall = postResource.mock.calls[1];
|
||||||
|
const [_, postBody] = secondCall;
|
||||||
|
expect(postBody.options.$skipToken).toEqual('skipfirst100');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the skipToken of the previous page', async () => {
|
it('returns a concatonized and formatted array of resourceGroups when there are multiple pages', async () => {
|
||||||
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
const response1 = {
|
||||||
const secondCall = postResource.mock.calls[1];
|
...createMockARGResourceGroupsResponse(),
|
||||||
expect(secondCall[1]).toMatchObject({ options: { $skipToken: 'aaa', resultFormat: 'objectArray' } });
|
$skipToken: 'skipfirst100',
|
||||||
|
};
|
||||||
|
const response2 = createMockARGResourceGroupsResponse();
|
||||||
|
const { resourcePickerData } = createResourcePickerData([response1, response2]);
|
||||||
|
|
||||||
|
const resourceGroups = await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
|
||||||
|
expect(resourceGroups.length).toEqual(12);
|
||||||
|
expect(resourceGroups[0]).toEqual({
|
||||||
|
id: 'prod',
|
||||||
|
name: 'Production',
|
||||||
|
type: 'ResourceGroup',
|
||||||
|
typeLabel: 'Resource Group',
|
||||||
|
uri: '/subscriptions/abc-123/resourceGroups/prod',
|
||||||
|
children: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws an error if it does not receive data', async () => {
|
||||||
|
const mockResponse = { data: [] };
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
try {
|
||||||
|
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
throw Error('expected getSubscriptions to fail but it succeeded');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toEqual('unable to fetch resource groups');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if it recieves data with a malformed uri', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
resourceGroupURI: '/a-differently-formatted/uri/than/the/type/we/planned/to/parse',
|
||||||
|
resourceGroupName: 'Production',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
try {
|
||||||
|
await resourcePickerData.getResourceGroupsBySubscriptionId('123');
|
||||||
|
throw Error('expected getResourceGroupsBySubscriptionId to fail but it succeeded');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toEqual('unable to fetch resource groups');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getResourcesForResourceGroup', () => {
|
describe('getResourcesForResourceGroup', () => {
|
||||||
const resourceRow = {
|
it('makes 1 call to ARG with the correct path and query arguments', async () => {
|
||||||
id: '/subscriptions/def-456/resourceGroups/dev',
|
const mockResponse = createARGResourcesResponse();
|
||||||
name: 'Dev',
|
const { resourcePickerData, postResource } = createResourcePickerData([mockResponse]);
|
||||||
type: ResourceRowType.ResourceGroup,
|
await resourcePickerData.getResourcesForResourceGroup('dev');
|
||||||
typeLabel: 'Resource group',
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
expect(postResource).toBeCalledTimes(1);
|
||||||
postResource = jest.fn().mockResolvedValue(createARGResourcesResponse());
|
const firstCall = postResource.mock.calls[0];
|
||||||
resourcePickerData.postResource = postResource;
|
const [path, postBody] = firstCall;
|
||||||
|
expect(path).toEqual('resourcegraph/providers/Microsoft.ResourceGraph/resources?api-version=2021-03-01');
|
||||||
|
expect(postBody.query).toContain('resources');
|
||||||
|
expect(postBody.query).toContain('where id hasprefix "dev"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('requests resources for the specified resource row', async () => {
|
|
||||||
await resourcePickerData.getResourcesForResourceGroup(resourceRow.id);
|
|
||||||
|
|
||||||
expect(postResource).toHaveBeenCalled();
|
|
||||||
const argQuery = postResource.mock.calls[0][1].query;
|
|
||||||
|
|
||||||
expect(argQuery).toContain(resourceRow.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns formatted resources', async () => {
|
it('returns formatted resources', async () => {
|
||||||
const results = await resourcePickerData.getResourcesForResourceGroup(resourceRow.id);
|
const mockResponse = createARGResourcesResponse();
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
|
||||||
expect(results.map((v) => v.id)).toEqual([
|
const resources = await resourcePickerData.getResourcesForResourceGroup('dev');
|
||||||
'/subscriptions/def-456/resourceGroups/dev/providers/Microsoft.Compute/virtualMachines/web-server',
|
|
||||||
'/subscriptions/def-456/resourceGroups/dev/providers/Microsoft.Compute/disks/web-server_DataDisk',
|
|
||||||
'/subscriptions/def-456/resourceGroups/dev/providers/Microsoft.Compute/virtualMachines/db-server',
|
|
||||||
'/subscriptions/def-456/resourceGroups/dev/providers/Microsoft.Compute/disks/db-server_DataDisk',
|
|
||||||
]);
|
|
||||||
|
|
||||||
results.forEach((v) => expect(v.type).toEqual(ResourceRowType.Resource));
|
expect(resources.length).toEqual(4);
|
||||||
|
expect(resources[0]).toEqual({
|
||||||
|
id: 'web-server',
|
||||||
|
name: 'web-server',
|
||||||
|
type: 'Resource',
|
||||||
|
location: 'North Europe',
|
||||||
|
resourceGroupName: 'dev',
|
||||||
|
typeLabel: 'Microsoft.Compute/virtualMachines',
|
||||||
|
uri: '/subscriptions/def-456/resourceGroups/dev/providers/Microsoft.Compute/virtualMachines/web-server',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if it recieves data with a malformed uri', async () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '/a-differently-formatted/uri/than/the/type/we/planned/to/parse',
|
||||||
|
name: 'web-server',
|
||||||
|
type: 'Microsoft.Compute/virtualMachines',
|
||||||
|
resourceGroup: 'dev',
|
||||||
|
subscriptionId: 'def-456',
|
||||||
|
location: 'northeurope',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const { resourcePickerData } = createResourcePickerData([mockResponse]);
|
||||||
|
try {
|
||||||
|
await resourcePickerData.getResourcesForResourceGroup('dev');
|
||||||
|
throw Error('expected getResourcesForResourceGroup to fail but it succeeded');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).toEqual('unable to fetch resource details');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
logsSupportedResourceTypesKusto,
|
logsSupportedResourceTypesKusto,
|
||||||
resourceTypeDisplayNames,
|
resourceTypeDisplayNames,
|
||||||
} from '../azureMetadata';
|
} from '../azureMetadata';
|
||||||
import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
import { ResourceRowGroup, ResourceRowType } from '../components/ResourcePicker/types';
|
||||||
import { parseResourceURI } from '../components/ResourcePicker/utils';
|
import { parseResourceURI } from '../components/ResourcePicker/utils';
|
||||||
import {
|
import {
|
||||||
AzureDataSourceJsonData,
|
AzureDataSourceJsonData,
|
||||||
@ -31,8 +31,6 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
this.resourcePath = `${routeNames.resourceGraph}`;
|
this.resourcePath = `${routeNames.resourceGraph}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly templateVariableGroupID = '$$grafana-templateVariables$$';
|
|
||||||
|
|
||||||
async getSubscriptions(): Promise<ResourceRowGroup> {
|
async getSubscriptions(): Promise<ResourceRowGroup> {
|
||||||
const query = `
|
const query = `
|
||||||
resources
|
resources
|
||||||
@ -59,7 +57,7 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
}
|
}
|
||||||
const resourceResponse = await this.makeResourceGraphRequest<RawAzureSubscriptionItem[]>(query, 1, options);
|
const resourceResponse = await this.makeResourceGraphRequest<RawAzureSubscriptionItem[]>(query, 1, options);
|
||||||
if (!resourceResponse.data.length) {
|
if (!resourceResponse.data.length) {
|
||||||
throw new Error('unable to fetch resource details');
|
throw new Error('unable to fetch subscriptions');
|
||||||
}
|
}
|
||||||
resources = resources.concat(resourceResponse.data);
|
resources = resources.concat(resourceResponse.data);
|
||||||
$skipToken = resourceResponse.$skipToken;
|
$skipToken = resourceResponse.$skipToken;
|
||||||
@ -69,13 +67,14 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
return resources.map((subscription) => ({
|
return resources.map((subscription) => ({
|
||||||
name: subscription.subscriptionName,
|
name: subscription.subscriptionName,
|
||||||
id: subscription.subscriptionId,
|
id: subscription.subscriptionId,
|
||||||
|
uri: `/subscriptions/${subscription.subscriptionId}`,
|
||||||
typeLabel: 'Subscription',
|
typeLabel: 'Subscription',
|
||||||
type: ResourceRowType.Subscription,
|
type: ResourceRowType.Subscription,
|
||||||
children: [],
|
children: [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResourceGroupsBySubscriptionId(subscriptionId: string) {
|
async getResourceGroupsBySubscriptionId(subscriptionId: string): Promise<ResourceRowGroup> {
|
||||||
const query = `
|
const query = `
|
||||||
resources
|
resources
|
||||||
| join kind=inner (
|
| join kind=inner (
|
||||||
@ -89,7 +88,7 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
| summarize count() by resourceGroupName, resourceGroupURI
|
| summarize count() by resourceGroupName, resourceGroupURI
|
||||||
| order by resourceGroupURI asc`;
|
| order by resourceGroupURI asc`;
|
||||||
|
|
||||||
let resources: RawAzureResourceGroupItem[] = [];
|
let resourceGroups: RawAzureResourceGroupItem[] = [];
|
||||||
let allFetched = false;
|
let allFetched = false;
|
||||||
let $skipToken = undefined;
|
let $skipToken = undefined;
|
||||||
while (!allFetched) {
|
while (!allFetched) {
|
||||||
@ -102,32 +101,54 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
}
|
}
|
||||||
const resourceResponse = await this.makeResourceGraphRequest<RawAzureResourceGroupItem[]>(query, 1, options);
|
const resourceResponse = await this.makeResourceGraphRequest<RawAzureResourceGroupItem[]>(query, 1, options);
|
||||||
if (!resourceResponse.data.length) {
|
if (!resourceResponse.data.length) {
|
||||||
throw new Error('unable to fetch resource details');
|
throw new Error('unable to fetch resource groups');
|
||||||
}
|
}
|
||||||
resources = resources.concat(resourceResponse.data);
|
resourceGroups = resourceGroups.concat(resourceResponse.data);
|
||||||
$skipToken = resourceResponse.$skipToken;
|
$skipToken = resourceResponse.$skipToken;
|
||||||
allFetched = !$skipToken;
|
allFetched = !$skipToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
return resources.map((r) => ({
|
return resourceGroups.map((r) => {
|
||||||
|
const parsedUri = parseResourceURI(r.resourceGroupURI);
|
||||||
|
if (!parsedUri || !parsedUri.resourceGroup) {
|
||||||
|
throw new Error('unable to fetch resource groups');
|
||||||
|
}
|
||||||
|
return {
|
||||||
name: r.resourceGroupName,
|
name: r.resourceGroupName,
|
||||||
id: r.resourceGroupURI,
|
uri: r.resourceGroupURI,
|
||||||
|
id: parsedUri.resourceGroup,
|
||||||
type: ResourceRowType.ResourceGroup,
|
type: ResourceRowType.ResourceGroup,
|
||||||
typeLabel: 'Resource Group',
|
typeLabel: 'Resource Group',
|
||||||
children: [],
|
children: [],
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResourcesForResourceGroup(resourceGroupId: string) {
|
async getResourcesForResourceGroup(resourceGroupId: string): Promise<ResourceRowGroup> {
|
||||||
const { data: response } = await this.makeResourceGraphRequest<RawAzureResourceItem[]>(`
|
const { data: response } = await this.makeResourceGraphRequest<RawAzureResourceItem[]>(`
|
||||||
resources
|
resources
|
||||||
| where id hasprefix "${resourceGroupId}"
|
| where id hasprefix "${resourceGroupId}"
|
||||||
| where type in (${logsSupportedResourceTypesKusto}) and location in (${logsSupportedLocationsKusto})
|
| where type in (${logsSupportedResourceTypesKusto}) and location in (${logsSupportedLocationsKusto})
|
||||||
`);
|
`);
|
||||||
|
|
||||||
return formatResourceGroupChildren(response);
|
return response.map((item) => {
|
||||||
|
const parsedUri = parseResourceURI(item.id);
|
||||||
|
if (!parsedUri || !parsedUri.resource) {
|
||||||
|
throw new Error('unable to fetch resource details');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
id: parsedUri.resource,
|
||||||
|
uri: item.id,
|
||||||
|
resourceGroupName: item.resourceGroup,
|
||||||
|
type: ResourceRowType.Resource,
|
||||||
|
typeLabel: resourceTypeDisplayNames[item.type] || item.type,
|
||||||
|
location: locationDisplayNames[item.location] || item.location,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used to make the select resource button that launches the resource picker show a nicer file path to users
|
||||||
async getResourceURIDisplayProperties(resourceURI: string): Promise<AzureResourceSummaryItem> {
|
async getResourceURIDisplayProperties(resourceURI: string): Promise<AzureResourceSummaryItem> {
|
||||||
const { subscriptionID, resourceGroup } = parseResourceURI(resourceURI) ?? {};
|
const { subscriptionID, resourceGroup } = parseResourceURI(resourceURI) ?? {};
|
||||||
|
|
||||||
@ -206,30 +227,4 @@ export default class ResourcePickerData extends DataSourceWithBackend<AzureMonit
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transformVariablesToRow(templateVariables: string[]): ResourceRow {
|
|
||||||
return {
|
|
||||||
id: ResourcePickerData.templateVariableGroupID,
|
|
||||||
name: 'Template variables',
|
|
||||||
type: ResourceRowType.VariableGroup,
|
|
||||||
typeLabel: 'Variables',
|
|
||||||
children: templateVariables.map((v) => ({
|
|
||||||
id: v,
|
|
||||||
name: v,
|
|
||||||
type: ResourceRowType.Variable,
|
|
||||||
typeLabel: 'Variable',
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatResourceGroupChildren(rawData: RawAzureResourceItem[]): ResourceRowGroup {
|
|
||||||
return rawData.map((item) => ({
|
|
||||||
name: item.name,
|
|
||||||
id: item.id,
|
|
||||||
resourceGroupName: item.resourceGroup,
|
|
||||||
type: ResourceRowType.Resource,
|
|
||||||
typeLabel: resourceTypeDisplayNames[item.type] || item.type,
|
|
||||||
location: locationDisplayNames[item.location] || item.location,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user