From 3e86a1b3d9165e22d60261f9b06250fc60a139b7 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Fri, 20 Jan 2023 11:19:31 +0100 Subject: [PATCH] Azure Monitor: Implement logic to allow multiple selection (#61740) --- e2e/cloud-plugins-suite/azure-monitor.spec.ts | 8 +- .../src/types/featureToggles.gen.ts | 1 + .../azureMetadata/resourceTypes.ts | 22 ++++ .../LogsQueryEditor/LogsQueryEditor.test.tsx | 121 ++++++++++++++++++ .../LogsQueryEditor/LogsQueryEditor.tsx | 22 +++- .../MetricsQueryEditor.test.tsx | 100 +++++++++++++++ .../MetricsQueryEditor/MetricsQueryEditor.tsx | 31 ++++- .../ResourceField/ResourceField.tsx | 5 +- .../ResourcePicker/NestedRow.test.tsx | 13 ++ .../components/ResourcePicker/NestedRow.tsx | 5 +- .../ResourcePicker/ResourcePicker.test.tsx | 1 + .../ResourcePicker/ResourcePicker.tsx | 4 + 12 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.test.tsx diff --git a/e2e/cloud-plugins-suite/azure-monitor.spec.ts b/e2e/cloud-plugins-suite/azure-monitor.spec.ts index 32844502d07..b965fa5880b 100644 --- a/e2e/cloud-plugins-suite/azure-monitor.spec.ts +++ b/e2e/cloud-plugins-suite/azure-monitor.spec.ts @@ -139,8 +139,8 @@ e2e.scenario({ scenario: () => { e2e.flows.addDashboard({ timeRange: { - from: '2022-10-03 00:00:00', - to: '2022-10-03 23:59:59', + from: 'now-6h', + to: 'now', zone: 'Coordinated Universal Time', }, }); @@ -210,8 +210,8 @@ e2e.scenario({ scenario: () => { e2e.flows.addDashboard({ timeRange: { - from: '2022-10-03 00:00:00', - to: '2022-10-03 23:59:59', + from: 'now-6h', + to: 'now', zone: 'Coordinated Universal Time', }, }); diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 99bc06b2590..e19fb2fe59f 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -88,4 +88,5 @@ export interface FeatureToggles { disablePrometheusExemplarSampling?: boolean; alertingBacktesting?: boolean; alertingNoNormalState?: boolean; + azureMultipleResourcePicker?: boolean; } diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azureMetadata/resourceTypes.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azureMetadata/resourceTypes.ts index 9b37f3cbfdf..d9c4f25ace9 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/azureMetadata/resourceTypes.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/azureMetadata/resourceTypes.ts @@ -183,6 +183,7 @@ export const resourceTypeDisplayNames: { [k: string]: string } = { 'microsoft.hpcworkbench/instances/consortiums': 'Consortiums (preview)', 'microsoft.hybridcompute/machines': 'Servers - Azure Arc', 'microsoft.hybridcompute/privatelinkscopes': 'Azure Arc Private Link Scopes', + 'microsoft.hybridcontainerservice/provisionedclusters': 'Provisioned clusters', 'microsoft.hybriddata/datamanagers': 'StorSimple Data Managers', 'microsoft.hybridnetwork/devices': 'Azure Network Function Manager – Devices', 'microsoft.hybridnetwork/networkfunctions': 'Azure Network Function Manager – Network Functions', @@ -269,6 +270,7 @@ export const resourceTypeDisplayNames: { [k: string]: string } = { 'microsoft.network/networkmanagers': 'Network Managers', 'microsoft.network/networksecuritygroups': 'Network security groups', 'microsoft.network/networkwatchers': 'Network Watchers', + 'microsoft.network/networkwatchers/connectionmonitors': 'Connection Monitors', 'microsoft.network/networkwatchers/flowlogs': 'NSG Flow Logs', 'microsoft.network/privatednszones': 'Private DNS zones', 'microsoft.network/privateendpoints': 'Private endpoints', @@ -400,3 +402,23 @@ export const resourceTypeDisplayNames: { [k: string]: string } = { 'microsoft.security/insights/classification': 'Data Sensitivity Security Insights (Preview)', 'microsoft.security/locations/alerts': 'Security Alerts', }; + +// This list has been manually written using the Azure Portal as the source. +// Visit https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/metrics +// and go to Select a scope > Resource types > Multi-resource compatible (preview) +export const multiResourceCompatibleTypes: { [ns: string]: boolean } = { + 'microsoft.cache/redis': true, // 'Azure Cache for Redis' + 'microsoft.dbforpostgresql/flexibleservers': true, // 'Azure Database for PostgreSQL flexible servers' + 'microsoft.storagecache/amlfilesystems': true, // 'Lustre File Systems' + 'microsoft.databoxedge/databoxedgedevices': true, // 'Azure Stack Edge / Data Box Gateway' + 'microsoft.dataprotection/backupvaults': true, // 'Backup vaults' + 'microsoft.netapp/netappaccounts/capacitypools': true, // 'Capacity pools' + 'microsoft.network/networkwatchers/connectionmonitors': true, // 'Connection Monitors' + 'microsoft.keyvault/vaults': true, // 'Key vaults' + 'microsoft.recoveryservices/vaults': true, // 'Recovery Services vaults' + 'microsoft.sql/servers/databases': true, // 'SQL databases' + 'microsoft.sql/servers/elasticpools': true, // 'SQL elastic pools' + 'microsoft.compute/virtualmachinescalesets': true, // 'Virtual machine scale sets' + 'microsoft.compute/virtualmachines': true, // 'Virtual machines' + 'microsoft.signalrservice/webpubsub': true, // 'Web PubSub Service' +}; diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.test.tsx new file mode 100644 index 00000000000..9b6220147b1 --- /dev/null +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.test.tsx @@ -0,0 +1,121 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import config from 'app/core/config'; + +import createMockDatasource from '../../__mocks__/datasource'; +import createMockQuery from '../../__mocks__/query'; +import { createMockResourcePickerData } from '../MetricsQueryEditor/MetricsQueryEditor.test'; + +import LogsQueryEditor from './LogsQueryEditor'; + +jest.mock('@grafana/runtime', () => ({ + ...(jest.requireActual('@grafana/runtime') as unknown as object), + getTemplateSrv: () => ({ + replace: (val: string) => { + return val; + }, + }), +})); + +const variableOptionGroup = { + label: 'Template variables', + options: [], +}; + +beforeEach(() => { + config.featureToggles.azureMultipleResourcePicker = true; +}); + +describe('LogsQueryEdiutor', () => { + const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView; + + beforeEach(() => { + window.HTMLElement.prototype.scrollIntoView = function () {}; + }); + afterEach(() => { + window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + }); + + it('should select multiple resources', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + delete query?.subscription; + delete query?.azureLogAnalytics?.resources; + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' }); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' }); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' }); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('web-server'); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + + const checkbox2 = await screen.findByLabelText('db-server'); + await userEvent.click(checkbox2); + expect(checkbox2).toBeChecked(); + + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + + expect(onChange).toBeCalledWith( + expect.objectContaining({ + azureLogAnalytics: expect.objectContaining({ + resources: [ + '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/web-server', + '/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server', + ], + }), + }) + ); + }); + + it('should disable other resource types when selecting multiple resources', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + delete query?.subscription; + delete query?.azureLogAnalytics?.resources; + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' }); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' }); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' }); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('web-server'); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + + expect(await screen.findByLabelText('web-server_DataDisk')).toBeDisabled(); + }); +}); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx index a4bdb54302c..9513ee1f6d3 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/LogsQueryEditor/LogsQueryEditor.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; import { Alert } from '@grafana/ui'; import Datasource from '../../datasource'; import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery } from '../../types'; import ResourceField from '../ResourceField'; -import { ResourceRowType } from '../ResourcePicker/types'; +import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; +import { parseResourceDetails } from '../ResourcePicker/utils'; import FormatAsField from './FormatAsField'; import QueryField from './QueryField'; @@ -32,6 +34,23 @@ const LogsQueryEditor: React.FC = ({ hideFormatAs, }) => { const migrationError = useMigrations(datasource, query, onChange); + const disableRow = (row: ResourceRow, selectedRows: ResourceRowGroup) => { + if (selectedRows.length === 0) { + // Only if there is some resource(s) selected we should disable rows + return false; + } + // Disable multiple selection until the feature is ready + if (!config.featureToggles.azureMultipleResourcePicker) { + return true; + } + const rowResourceNS = parseResourceDetails(row.uri, row.location).metricNamespace?.toLowerCase(); + const selectedRowSampleNs = parseResourceDetails( + selectedRows[0].uri, + selectedRows[0].location + ).metricNamespace?.toLowerCase(); + // Only resources with the same metricNamespace can be selected + return rowResourceNS !== selectedRowSampleNs; + }; return ( @@ -55,6 +74,7 @@ const LogsQueryEditor: React.FC = ({ ]} resources={query.azureLogAnalytics?.resources ?? []} queryType="logs" + disableRow={disableRow} /> diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx index bae55c8340b..c1e326dc2b8 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx @@ -3,6 +3,8 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; +import config from 'app/core/config'; + import createMockDatasource from '../../__mocks__/datasource'; import { createMockInstanceSetttings } from '../../__mocks__/instanceSettings'; import createMockPanelData from '../../__mocks__/panelData'; @@ -31,6 +33,10 @@ const variableOptionGroup = { options: [], }; +beforeEach(() => { + config.featureToggles.azureMultipleResourcePicker = true; +}); + export function createMockResourcePickerData() { const mockDatasource = createMockDatasource(); const mockResourcePicker = new ResourcePickerData( @@ -170,6 +176,100 @@ describe('MetricsQueryEditor', () => { ); }); + it('should select multiple resources', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + delete query?.subscription; + delete query?.azureMonitor?.resources; + delete query?.azureMonitor?.metricNamespace; + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' }); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' }); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' }); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('web-server'); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + + const checkbox2 = await screen.findByLabelText('db-server'); + await userEvent.click(checkbox2); + expect(checkbox2).toBeChecked(); + + await userEvent.click(await screen.findByRole('button', { name: 'Apply' })); + + expect(onChange).toBeCalledTimes(1); + expect(onChange).toBeCalledWith( + expect.objectContaining({ + subscription: 'def-456', + azureMonitor: expect.objectContaining({ + metricNamespace: 'microsoft.compute/virtualmachines', + resources: [ + expect.objectContaining({ + resourceGroup: 'dev-3', + resourceName: 'web-server', + }), + expect.objectContaining({ + resourceGroup: 'dev-3', + resourceName: 'db-server', + }), + ], + }), + }) + ); + }); + + it('should disable other resource types when selecting multiple resources', async () => { + const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); + const query = createMockQuery(); + delete query?.subscription; + delete query?.azureMonitor?.resources; + delete query?.azureMonitor?.metricNamespace; + const onChange = jest.fn(); + + render( + {}} + /> + ); + + const resourcePickerButton = await screen.findByRole('button', { name: 'Select a resource' }); + resourcePickerButton.click(); + + const subscriptionButton = await screen.findByRole('button', { name: 'Expand Primary Subscription' }); + subscriptionButton.click(); + + const resourceGroupButton = await screen.findByRole('button', { name: 'Expand A Great Resource Group' }); + resourceGroupButton.click(); + + const checkbox = await screen.findByLabelText('web-server'); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); + + expect(await screen.findByLabelText('web-server_DataDisk')).toBeDisabled(); + }); + it('should change the metric name when selected', async () => { const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() }); const onChange = jest.fn(); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx index d34a47acecb..262840b382d 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -2,11 +2,14 @@ import React from 'react'; import { PanelData } from '@grafana/data/src/types'; import { EditorRows, EditorRow, EditorFieldGroup } from '@grafana/experimental'; +import { config } from '@grafana/runtime'; +import { multiResourceCompatibleTypes } from '../../azureMetadata'; import type Datasource from '../../datasource'; import type { AzureMonitorQuery, AzureMonitorOption, AzureMonitorErrorish } from '../../types'; import ResourceField from '../ResourceField'; -import { ResourceRowType } from '../ResourcePicker/types'; +import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; +import { parseResourceDetails } from '../ResourcePicker/utils'; import AggregationField from './AggregationField'; import DimensionFields from './DimensionFields'; @@ -45,6 +48,31 @@ const MetricsQueryEditor: React.FC = ({ resourceName: r.resourceName, region: query.azureMonitor?.region, })) ?? []; + const disableRow = (row: ResourceRow, selectedRows: ResourceRowGroup) => { + if (selectedRows.length === 0) { + // Only if there is some resource(s) selected we should disable rows + return false; + } + if (!config.featureToggles.azureMultipleResourcePicker) { + // Disable multiple selection until the feature is ready + return true; + } + + const rowResource = parseResourceDetails(row.uri, row.location); + const selectedRowSample = parseResourceDetails(selectedRows[0].uri, selectedRows[0].location); + // Only resources: + // - in the same subscription + // - in the same region + // - with the same metric namespace + // - with a metric namespace that is compatible with multi-resource queries + return ( + rowResource.subscription !== selectedRowSample.subscription || + rowResource.region !== selectedRowSample.region || + rowResource.metricNamespace?.toLocaleLowerCase() !== selectedRowSample.metricNamespace?.toLocaleLowerCase() || + !multiResourceCompatibleTypes[rowResource.metricNamespace?.toLocaleLowerCase() ?? ''] + ); + }; + return ( @@ -59,6 +87,7 @@ const MetricsQueryEditor: React.FC = ({ selectableEntryTypes={[ResourceRowType.Resource]} resources={resources ?? []} queryType={'metrics'} + disableRow={disableRow} /> extends AzureQueryEditorFieldProps { @@ -19,6 +19,7 @@ interface ResourceFieldProps extends AzureQueryEditorFieldProps { resources: T[]; inlineField?: boolean; labelWidth?: number; + disableRow: (row: ResourceRow, selectedRows: ResourceRowGroup) => boolean; } const ResourceField: React.FC> = ({ @@ -30,6 +31,7 @@ const ResourceField: React.FC> resources, inlineField, labelWidth, + disableRow, }) => { const styles = useStyles2(getStyles); const [pickerIsOpen, setPickerIsOpen] = useState(false); @@ -68,6 +70,7 @@ const ResourceField: React.FC> onCancel={closePicker} selectableEntryTypes={selectableEntryTypes} queryType={queryType} + disableRow={disableRow} /> diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.test.tsx index a92c9d87991..47ae0943a4c 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.test.tsx @@ -18,6 +18,7 @@ const defaultProps = { onRowSelectedChange: jest.fn(), selectableEntryTypes: [], scrollIntoView: false, + disableRow: jest.fn().mockReturnValue(false), }; describe('NestedRow', () => { @@ -56,4 +57,16 @@ describe('NestedRow', () => { const box = screen.queryByRole('checkbox'); expect(box).not.toBeInTheDocument(); }); + + it('should disable a checkbox if specified', () => { + render( + + + true} /> + +
+ ); + const box = screen.queryByRole('checkbox'); + expect(box).toBeDisabled(); + }); }); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx index eea2a9cfbb3..246e1f193ea 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRow.tsx @@ -15,6 +15,7 @@ interface NestedRowProps { requestNestedRows: (row: ResourceRow) => Promise; onRowSelectedChange: (row: ResourceRow, selected: boolean) => void; selectableEntryTypes: ResourceRowType[]; + disableRow: (row: ResourceRow, selectedRows: ResourceRowGroup) => boolean; scrollIntoView?: boolean; } @@ -26,12 +27,13 @@ const NestedRow: React.FC = ({ onRowSelectedChange, selectableEntryTypes, scrollIntoView, + disableRow, }) => { const styles = useStyles2(getStyles); const [rowStatus, setRowStatus] = useState<'open' | 'closed' | 'loading'>('closed'); const isSelected = !!selectedRows.find((v) => v.uri === row.uri); - const isDisabled = selectedRows.length > 0 && !isSelected; + const isDisabled = !isSelected && disableRow(row, selectedRows); const isOpen = rowStatus === 'open'; const onRowToggleCollapse = async () => { @@ -92,6 +94,7 @@ const NestedRow: React.FC = ({ onRowSelectedChange={onRowSelectedChange} selectableEntryTypes={selectableEntryTypes} scrollIntoView={scrollIntoView} + disableRow={disableRow} /> ))} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx index 6537802765d..0c201f583ab 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.test.tsx @@ -70,6 +70,7 @@ const defaultProps = { ResourceRowType.Variable, ], queryType, + disableRow: jest.fn(), }; describe('AzureMonitor ResourcePicker', () => { diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx index aa9a5ffbb61..6eb36f5776f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/ResourcePicker.tsx @@ -25,6 +25,7 @@ interface ResourcePickerProps { onApply: (resources: T[]) => void; onCancel: () => void; + disableRow: (row: ResourceRow, selectedRows: ResourceRowGroup) => boolean; } const ResourcePicker = ({ @@ -34,6 +35,7 @@ const ResourcePicker = ({ onCancel, selectableEntryTypes, queryType, + disableRow, }: ResourcePickerProps) => { const styles = useStyles2(getStyles); @@ -203,6 +205,7 @@ const ResourcePicker = ({ onRowSelectedChange={handleSelectionChanged} selectableEntryTypes={selectableEntryTypes} scrollIntoView={true} + disableRow={disableRow} /> ))} @@ -226,6 +229,7 @@ const ResourcePicker = ({ requestNestedRows={requestNestedRows} onRowSelectedChange={handleSelectionChanged} selectableEntryTypes={selectableEntryTypes} + disableRow={() => false} /> ))}