Azure Monitor : Add support for the resource picker to be configurable to only select some entry types (#46735)

Co-authored-by: Kevin Yu <kevinwcyu@users.noreply.github.com>
Co-authored-by: Andres Martinez Gotor <andres.mgotor@gmail.com>
Co-authored-by: Isabella Siu <isabella.siu@grafana.com>
Co-authored-by: Sarah Zinger <sarah.zinger@grafana.com>
This commit is contained in:
Yaelle Chaudy
2022-03-25 12:22:28 +01:00
committed by GitHub
parent 3c5e68a349
commit c00f488f89
10 changed files with 378 additions and 277 deletions

View File

@ -7,6 +7,7 @@ import Datasource from '../../datasource';
import { AzureQueryEditorFieldProps, AzureResourceSummaryItem } from '../../types';
import { Field } from '../Field';
import ResourcePicker from '../ResourcePicker';
import { ResourceRowType } from '../ResourcePicker/types';
import { parseResourceURI } from '../ResourcePicker/utils';
import { Space } from '../Space';
import { setResource } from './setQueryValue';
@ -65,6 +66,12 @@ const ResourceField: React.FC<AzureQueryEditorFieldProps> = ({ query, datasource
templateVariables={templateVariables}
onApply={handleApply}
onCancel={closePicker}
selectableEntryTypes={[
ResourceRowType.Subscription,
ResourceRowType.ResourceGroup,
ResourceRowType.Resource,
ResourceRowType.Variable,
]}
/>
</Modal>

View File

@ -0,0 +1,31 @@
import { Icon } from '@grafana/ui';
import React from 'react';
import { ResourceRow, ResourceRowType } from './types';
interface EntryIconProps {
entry: ResourceRow;
isOpen: boolean;
}
export const EntryIcon: React.FC<EntryIconProps> = ({ isOpen, entry: { type } }) => {
switch (type) {
case ResourceRowType.Subscription:
return <Icon name="layer-group" />;
case ResourceRowType.ResourceGroup:
return <Icon name={isOpen ? 'folder-open' : 'folder'} />;
case ResourceRowType.Resource:
return <Icon name="cube" />;
case ResourceRowType.VariableGroup:
return <Icon name="x" />;
case ResourceRowType.Variable:
return <Icon name="x" />;
default:
return null;
}
};

View File

@ -0,0 +1,29 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { NestedEntry } from './NestedEntry';
import { ResourceRowType } from './types';
const defaultProps = {
level: 0,
entry: { id: '123', uri: 'someuri', name: '123', type: ResourceRowType.Resource, typeLabel: '' },
isSelected: false,
isSelectable: false,
isOpen: false,
isDisabled: false,
onToggleCollapse: jest.fn(),
onSelectedChange: jest.fn(),
};
describe('NestedEntry', () => {
it('should be selectable', () => {
render(<NestedEntry {...defaultProps} isSelectable={true} />);
const box = screen.getByRole('checkbox');
expect(box).toBeInTheDocument();
});
it('should not be selectable', () => {
render(<NestedEntry {...defaultProps} />);
const box = screen.queryByRole('checkbox');
expect(box).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,104 @@
import { cx } from '@emotion/css';
import { Checkbox, IconButton, useStyles2, useTheme2 } from '@grafana/ui';
import React, { useCallback, useEffect } from 'react';
import { Space } from '../Space';
import { EntryIcon } from './EntryIcon';
import getStyles from './styles';
import { ResourceRow } from './types';
interface NestedEntryProps {
level: number;
entry: ResourceRow;
isSelected: boolean;
isSelectable: boolean;
isOpen: boolean;
isDisabled: boolean;
onToggleCollapse: (row: ResourceRow) => void;
onSelectedChange: (row: ResourceRow, selected: boolean) => void;
}
export const NestedEntry: React.FC<NestedEntryProps> = ({
entry,
isSelected,
isDisabled,
isOpen,
isSelectable,
level,
onToggleCollapse,
onSelectedChange,
}) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
const hasChildren = !!entry.children;
// Subscriptions, resource groups, resources, and variables are all selectable, so
// the top-level variable group is the only thing that cannot be selected.
// const isSelectable = entry.type !== ResourceRowType.VariableGroup;
// const isSelectable = selectableEntryTypes?.some((e) => e === entry.type);
const handleToggleCollapse = useCallback(() => {
onToggleCollapse(entry);
}, [onToggleCollapse, entry]);
const handleSelectedChanged = useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
const isSelected = ev.target.checked;
onSelectedChange(entry, isSelected);
},
[entry, onSelectedChange]
);
const checkboxId = `checkbox_${entry.id}`;
// Scroll to the selected element if it's not in the view
// Only do it once, when the component is mounted
useEffect(() => {
if (isSelected) {
document.getElementById(checkboxId)?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div className={styles.nestedEntry} style={{ marginLeft: level * (3 * theme.spacing.gridSize) }}>
{/* When groups are selectable, I *think* we will want to show a 2-wide space instead
of the collapse button for leaf rows that have no children to get them to align */}
{hasChildren ? (
<IconButton
className={styles.collapseButton}
name={isOpen ? 'angle-down' : 'angle-right'}
aria-label={isOpen ? `Collapse ${entry.name}` : `Expand ${entry.name}`}
onClick={handleToggleCollapse}
id={entry.id}
/>
) : (
<Space layout="inline" h={2} />
)}
<Space layout="inline" h={2} />
{isSelectable && (
<>
<Checkbox
id={checkboxId}
onChange={handleSelectedChanged}
disabled={isDisabled}
value={isSelected}
className={styles.nestedRowCheckbox}
/>
<Space layout="inline" h={2} />
</>
)}
<EntryIcon entry={entry} isOpen={isOpen} />
<Space layout="inline" h={1} />
<label htmlFor={checkboxId} className={cx(styles.entryContentItem, styles.truncated)}>
{entry.name}
</label>
</div>
);
};

View File

@ -5,7 +5,7 @@ import { useStyles2 } from '@grafana/ui';
import NestedRows from './NestedRows';
import getStyles from './styles';
import { ResourceRow, ResourceRowGroup } from './types';
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
interface NestedResourceTableProps {
rows: ResourceRowGroup;
@ -13,6 +13,7 @@ interface NestedResourceTableProps {
noHeader?: boolean;
requestNestedRows: (row: ResourceRow) => Promise<void>;
onRowSelectedChange: (row: ResourceRow, selected: boolean) => void;
selectableEntryTypes: ResourceRowType[];
}
const NestedResourceTable: React.FC<NestedResourceTableProps> = ({
@ -21,6 +22,7 @@ const NestedResourceTable: React.FC<NestedResourceTableProps> = ({
noHeader,
requestNestedRows,
onRowSelectedChange,
selectableEntryTypes,
}) => {
const styles = useStyles2(getStyles);
@ -47,6 +49,7 @@ const NestedResourceTable: React.FC<NestedResourceTableProps> = ({
level={0}
requestNestedRows={requestNestedRows}
onRowSelectedChange={onRowSelectedChange}
selectableEntryTypes={selectableEntryTypes}
/>
</tbody>
</table>

View File

@ -0,0 +1,58 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import NestedRow from './NestedRow';
import { ResourceRowType } from './types';
const defaultProps = {
row: {
id: '1',
uri: 'some-uri',
name: '1',
type: ResourceRowType.Resource,
typeLabel: '1',
},
level: 0,
selectedRows: [],
requestNestedRows: jest.fn(),
onRowSelectedChange: jest.fn(),
selectableEntryTypes: [],
};
describe('NestedRow', () => {
it('should not display a checkbox when the type of row is empty', () => {
render(
<table>
<tbody>
<NestedRow {...defaultProps} />
</tbody>
</table>
);
const box = screen.queryByRole('checkbox');
expect(box).not.toBeInTheDocument();
});
it('should display a checkbox when the type of row is in selectableEntryTypes', () => {
render(
<table>
<tbody>
<NestedRow {...defaultProps} selectableEntryTypes={[ResourceRowType.Resource]} />
</tbody>
</table>
);
const box = screen.queryByRole('checkbox');
expect(box).toBeInTheDocument();
});
it('should not display a checkbox when the type of row is not in selectableEntryTypes', () => {
render(
<table>
<tbody>
<NestedRow {...defaultProps} selectableEntryTypes={[ResourceRowType.ResourceGroup]} />
</tbody>
</table>
);
const box = screen.queryByRole('checkbox');
expect(box).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,101 @@
import { cx } from '@emotion/css';
import { FadeTransition, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import React, { useEffect, useState } from 'react';
import { NestedEntry } from './NestedEntry';
import NestedRows from './NestedRows';
import getStyles from './styles';
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
import { findRow } from './utils';
interface NestedRowProps {
row: ResourceRow;
level: number;
selectedRows: ResourceRowGroup;
requestNestedRows: (row: ResourceRow) => Promise<void>;
onRowSelectedChange: (row: ResourceRow, selected: boolean) => void;
selectableEntryTypes: ResourceRowType[];
}
const NestedRow: React.FC<NestedRowProps> = ({
row,
selectedRows,
level,
requestNestedRows,
onRowSelectedChange,
selectableEntryTypes,
}) => {
const styles = useStyles2(getStyles);
const [rowStatus, setRowStatus] = useState<'open' | 'closed' | 'loading'>('closed');
const isSelected = !!selectedRows.find((v) => v.id === row.id);
const isDisabled = selectedRows.length > 0 && !isSelected;
const isOpen = rowStatus === 'open';
const onRowToggleCollapse = async () => {
if (rowStatus === 'open') {
setRowStatus('closed');
return;
}
setRowStatus('loading');
requestNestedRows(row)
.then(() => setRowStatus('open'))
.catch(() => setRowStatus('closed'));
};
// opens the resource group on load of component if there was a previously saved selection
useEffect(() => {
// Assuming we don't have multi-select yet
const selectedRow = selectedRows[0];
const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.id);
if (containsChild) {
setRowStatus('open');
}
}, [selectedRows, row]);
return (
<>
<tr className={cx(styles.row, isDisabled && styles.disabledRow)} key={row.id}>
<td className={styles.cell}>
<NestedEntry
level={level}
isSelected={isSelected}
isDisabled={isDisabled}
isOpen={isOpen}
entry={row}
onToggleCollapse={onRowToggleCollapse}
onSelectedChange={onRowSelectedChange}
isSelectable={selectableEntryTypes.some((type) => type === row.type)}
/>
</td>
<td className={styles.cell}>{row.typeLabel}</td>
<td className={styles.cell}>{row.location ?? '-'}</td>
</tr>
{isOpen && row.children && Object.keys(row.children).length > 0 && (
<NestedRows
rows={row.children}
selectedRows={selectedRows}
level={level + 1}
requestNestedRows={requestNestedRows}
onRowSelectedChange={onRowSelectedChange}
selectableEntryTypes={selectableEntryTypes}
/>
)}
<FadeTransition visible={rowStatus === 'loading'}>
<tr>
<td className={cx(styles.cell, styles.loadingCell)} colSpan={3}>
<LoadingPlaceholder text="Loading..." className={styles.spinner} />
</td>
</tr>
</FadeTransition>
</>
);
};
export default NestedRow;

View File

@ -1,11 +1,7 @@
import { cx } from '@emotion/css';
import { Checkbox, FadeTransition, Icon, IconButton, LoadingPlaceholder, useStyles2, useTheme2 } from '@grafana/ui';
import React, { useCallback, useEffect, useState } from 'react';
import React from 'react';
import NestedRow from './NestedRow';
import { Space } from '../Space';
import getStyles from './styles';
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
import { findRow } from './utils';
interface NestedRowsProps {
rows: ResourceRowGroup;
@ -13,6 +9,7 @@ interface NestedRowsProps {
selectedRows: ResourceRowGroup;
requestNestedRows: (row: ResourceRow) => Promise<void>;
onRowSelectedChange: (row: ResourceRow, selected: boolean) => void;
selectableEntryTypes: ResourceRowType[];
}
const NestedRows: React.FC<NestedRowsProps> = ({
@ -21,6 +18,7 @@ const NestedRows: React.FC<NestedRowsProps> = ({
level,
requestNestedRows,
onRowSelectedChange,
selectableEntryTypes,
}) => (
<>
{rows.map((row) => (
@ -31,208 +29,10 @@ const NestedRows: React.FC<NestedRowsProps> = ({
level={level}
requestNestedRows={requestNestedRows}
onRowSelectedChange={onRowSelectedChange}
selectableEntryTypes={selectableEntryTypes}
/>
))}
</>
);
interface NestedRowProps {
row: ResourceRow;
level: number;
selectedRows: ResourceRowGroup;
requestNestedRows: (row: ResourceRow) => Promise<void>;
onRowSelectedChange: (row: ResourceRow, selected: boolean) => void;
}
const NestedRow: React.FC<NestedRowProps> = ({ row, selectedRows, level, requestNestedRows, onRowSelectedChange }) => {
const styles = useStyles2(getStyles);
const [rowStatus, setRowStatus] = useState<'open' | 'closed' | 'loading'>('closed');
const isSelected = !!selectedRows.find((v) => v.id === row.id);
const isDisabled = selectedRows.length > 0 && !isSelected;
const isOpen = rowStatus === 'open';
const onRowToggleCollapse = async () => {
if (rowStatus === 'open') {
setRowStatus('closed');
return;
}
setRowStatus('loading');
requestNestedRows(row)
.then(() => setRowStatus('open'))
.catch(() => setRowStatus('closed'));
};
// opens the resource group on load of component if there was a previously saved selection
useEffect(() => {
// Assuming we don't have multi-select yet
const selectedRow = selectedRows[0];
const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.uri);
if (containsChild) {
setRowStatus('open');
}
}, [selectedRows, row]);
return (
<>
<tr className={cx(styles.row, isDisabled && styles.disabledRow)} key={row.id}>
<td className={styles.cell}>
<NestedEntry
level={level}
isSelected={isSelected}
isDisabled={isDisabled}
isOpen={isOpen}
entry={row}
onToggleCollapse={onRowToggleCollapse}
onSelectedChange={onRowSelectedChange}
/>
</td>
<td className={styles.cell}>{row.typeLabel}</td>
<td className={styles.cell}>{row.location ?? '-'}</td>
</tr>
{isOpen && row.children && Object.keys(row.children).length > 0 && (
<NestedRows
rows={row.children}
selectedRows={selectedRows}
level={level + 1}
requestNestedRows={requestNestedRows}
onRowSelectedChange={onRowSelectedChange}
/>
)}
<FadeTransition visible={rowStatus === 'loading'}>
<tr>
<td className={cx(styles.cell, styles.loadingCell)} colSpan={3}>
<LoadingPlaceholder text="Loading..." className={styles.spinner} />
</td>
</tr>
</FadeTransition>
</>
);
};
interface EntryIconProps {
entry: ResourceRow;
isOpen: boolean;
}
const EntryIcon: React.FC<EntryIconProps> = ({ isOpen, entry: { type } }) => {
switch (type) {
case ResourceRowType.Subscription:
return <Icon name="layer-group" />;
case ResourceRowType.ResourceGroup:
return <Icon name={isOpen ? 'folder-open' : 'folder'} />;
case ResourceRowType.Resource:
return <Icon name="cube" />;
case ResourceRowType.VariableGroup:
return <Icon name="x" />;
case ResourceRowType.Variable:
return <Icon name="x" />;
default:
return null;
}
};
interface NestedEntryProps {
level: number;
entry: ResourceRow;
isSelected: boolean;
isOpen: boolean;
isDisabled: boolean;
onToggleCollapse: (row: ResourceRow) => void;
onSelectedChange: (row: ResourceRow, selected: boolean) => void;
}
const NestedEntry: React.FC<NestedEntryProps> = ({
entry,
isSelected,
isDisabled,
isOpen,
level,
onToggleCollapse,
onSelectedChange,
}) => {
const theme = useTheme2();
const styles = useStyles2(getStyles);
const hasChildren = !!entry.children;
// Subscriptions, resource groups, resources, and variables are all selectable, so
// the top-level variable group is the only thing that cannot be selected.
const isSelectable = entry.type !== ResourceRowType.VariableGroup;
const handleToggleCollapse = useCallback(() => {
onToggleCollapse(entry);
}, [onToggleCollapse, entry]);
const handleSelectedChanged = useCallback(
(ev: React.ChangeEvent<HTMLInputElement>) => {
const isSelected = ev.target.checked;
onSelectedChange(entry, isSelected);
},
[entry, onSelectedChange]
);
const checkboxId = `checkbox_${entry.id}`;
// Scroll to the selected element if it's not in the view
// Only do it once, when the component is mounted
useEffect(() => {
if (isSelected) {
document.getElementById(checkboxId)?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div className={styles.nestedEntry} style={{ marginLeft: level * (3 * theme.spacing.gridSize) }}>
{/* When groups are selectable, I *think* we will want to show a 2-wide space instead
of the collapse button for leaf rows that have no children to get them to align */}
{hasChildren ? (
<IconButton
className={styles.collapseButton}
name={isOpen ? 'angle-down' : 'angle-right'}
aria-label={isOpen ? `Collapse ${entry.name}` : `Expand ${entry.name}`}
onClick={handleToggleCollapse}
id={entry.id}
/>
) : (
<Space layout="inline" h={2} />
)}
<Space layout="inline" h={2} />
{isSelectable && (
<>
<Checkbox
id={checkboxId}
onChange={handleSelectedChanged}
disabled={isDisabled}
value={isSelected}
className={styles.nestedRowCheckbox}
/>
<Space layout="inline" h={2} />
</>
)}
<EntryIcon entry={entry} isOpen={isOpen} />
<Space layout="inline" h={1} />
<label htmlFor={checkboxId} className={cx(styles.entryContentItem, styles.truncated)}>
{entry.name}
</label>
</div>
);
};
export default NestedRows;

View File

@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import React from 'react';
import ResourcePicker from '.';
@ -8,6 +8,7 @@ import {
createMockSubscriptions,
mockResourcesByResourceGroup,
} from '../../__mocks__/resourcePickerRows';
import { ResourceRowType } from './types';
const noResourceURI = '';
const singleSubscriptionSelectionURI = '/subscriptions/def-456';
@ -15,28 +16,31 @@ const singleResourceGroupSelectionURI = '/subscriptions/def-456/resourceGroups/d
const singleResourceSelectionURI =
'/subscriptions/def-456/resourceGroups/dev-3/providers/Microsoft.Compute/virtualMachines/db-server';
const createResourcePickerDataMock = () => {
return createMockResourcePickerData({
const noop: any = () => {};
const defaultProps = {
templateVariables: [],
resourceURI: noResourceURI,
resourcePickerData: createMockResourcePickerData({
getSubscriptions: jest.fn().mockResolvedValue(createMockSubscriptions()),
getResourceGroupsBySubscriptionId: jest.fn().mockResolvedValue(createMockResourceGroupsBySubscription()),
getResourcesForResourceGroup: jest.fn().mockResolvedValue(mockResourcesByResourceGroup()),
});
}),
onCancel: noop,
onApply: noop,
selectableEntryTypes: [
ResourceRowType.Subscription,
ResourceRowType.ResourceGroup,
ResourceRowType.Resource,
ResourceRowType.Variable,
],
};
describe('AzureMonitor ResourcePicker', () => {
const noop: any = () => {};
beforeEach(() => {
window.HTMLElement.prototype.scrollIntoView = function () {};
});
it('should pre-load subscriptions when there is no existing selection', async () => {
render(
<ResourcePicker
templateVariables={[]}
resourcePickerData={createResourcePickerDataMock()}
resourceURI={noResourceURI}
onCancel={noop}
onApply={noop}
/>
);
render(<ResourcePicker {...defaultProps} resourceURI={noResourceURI} />);
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
expect(subscriptionCheckbox).toBeInTheDocument();
expect(subscriptionCheckbox).not.toBeChecked();
@ -45,58 +49,25 @@ describe('AzureMonitor ResourcePicker', () => {
});
it('should show a subscription as selected if there is one saved', async () => {
render(
<ResourcePicker
templateVariables={[]}
resourcePickerData={createResourcePickerDataMock()}
resourceURI={singleSubscriptionSelectionURI}
onCancel={noop}
onApply={noop}
/>
);
render(<ResourcePicker {...defaultProps} resourceURI={singleSubscriptionSelectionURI} />);
const subscriptionCheckbox = await screen.findByLabelText('Dev Subscription');
expect(subscriptionCheckbox).toBeChecked();
});
it('should show a resource group as selected if there is one saved', async () => {
render(
<ResourcePicker
templateVariables={[]}
resourcePickerData={createResourcePickerDataMock()}
resourceURI={singleResourceGroupSelectionURI}
onCancel={noop}
onApply={noop}
/>
);
it('should show a resourceGroup as selected if there is one saved', async () => {
render(<ResourcePicker {...defaultProps} resourceURI={singleResourceGroupSelectionURI} />);
const resourceGroupCheckbox = await screen.findByLabelText('A Great Resource Group');
expect(resourceGroupCheckbox).toBeChecked();
});
it('should show a resource as selected if there is one saved', async () => {
render(
<ResourcePicker
templateVariables={[]}
resourcePickerData={createResourcePickerDataMock()}
resourceURI={singleResourceSelectionURI}
onCancel={noop}
onApply={noop}
/>
);
render(<ResourcePicker {...defaultProps} resourceURI={singleResourceSelectionURI} />);
const resourceCheckbox = await screen.findByLabelText('db-server');
expect(resourceCheckbox).toBeChecked();
});
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}
/>
);
render(<ResourcePicker {...defaultProps} />);
const expandSubscriptionButton = await screen.findByLabelText('Expand Primary Subscription');
expect(expandSubscriptionButton).toBeInTheDocument();
expect(screen.queryByLabelText('A Great Resource Group')).not.toBeInTheDocument();
@ -106,15 +77,7 @@ describe('AzureMonitor ResourcePicker', () => {
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}
/>
);
render(<ResourcePicker {...defaultProps} onApply={onApply} />);
const subscriptionCheckbox = await screen.findByLabelText('Primary Subscription');
expect(subscriptionCheckbox).toBeInTheDocument();
expect(subscriptionCheckbox).not.toBeChecked();
@ -124,18 +87,9 @@ describe('AzureMonitor ResourcePicker', () => {
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}
/>
);
render(<ResourcePicker {...defaultProps} templateVariables={['$workspace']} onApply={onApply} />);
const expandButton = await screen.findByLabelText('Expand Template variables');
expandButton.click();
@ -149,4 +103,14 @@ describe('AzureMonitor ResourcePicker', () => {
expect(onApply).toBeCalledTimes(1);
expect(onApply).toBeCalledWith('$workspace');
});
describe('when rendering resource picker without any selectable entry types', () => {
it('renders no checkboxes', async () => {
await act(async () => {
render(<ResourcePicker {...defaultProps} selectableEntryTypes={[]} />);
});
const checkboxes = screen.queryAllByRole('checkbox');
expect(checkboxes.length).toBe(0);
});
});
});

View File

@ -15,6 +15,7 @@ interface ResourcePickerProps {
resourcePickerData: ResourcePickerData;
resourceURI: string | undefined;
templateVariables: string[];
selectableEntryTypes: ResourceRowType[];
onApply: (resourceURI: string | undefined) => void;
onCancel: () => void;
@ -26,6 +27,7 @@ const ResourcePicker = ({
templateVariables,
onApply,
onCancel,
selectableEntryTypes,
}: ResourcePickerProps) => {
const styles = useStyles2(getStyles);
@ -154,6 +156,7 @@ const ResourcePicker = ({
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
selectableEntryTypes={selectableEntryTypes}
/>
<div className={styles.selectionFooter}>
@ -167,6 +170,7 @@ const ResourcePicker = ({
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
noHeader={true}
selectableEntryTypes={selectableEntryTypes}
/>
</>
)}