diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx index 4dba5bf0666..cba1183529a 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedEntry.tsx @@ -16,6 +16,7 @@ interface NestedEntryProps { isSelectable: boolean; isOpen: boolean; isDisabled: boolean; + scrollIntoView?: boolean; onToggleCollapse: (row: ResourceRow) => void; onSelectedChange: (row: ResourceRow, selected: boolean) => void; } @@ -26,6 +27,7 @@ export const NestedEntry: React.FC = ({ isDisabled, isOpen, isSelectable, + scrollIntoView, level, onToggleCollapse, onSelectedChange, @@ -33,10 +35,6 @@ export const NestedEntry: React.FC = ({ 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); @@ -50,12 +48,12 @@ export const NestedEntry: React.FC = ({ [entry, onSelectedChange] ); - const checkboxId = `checkbox_${entry.id}`; + const checkboxId = `${scrollIntoView ? 'table' : 'summary'}_checkbox_${entry.uri}`; // Scroll to the selected element if it's not in the view // Only do it once, when the component is mounted useEffect(() => { - if (isSelected) { + if (isSelected && scrollIntoView) { document.getElementById(checkboxId)?.scrollIntoView({ behavior: 'smooth', block: 'center', @@ -65,9 +63,6 @@ export const NestedEntry: React.FC = ({ return (
- {/* 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 ? ( Promise; - onRowSelectedChange: (row: ResourceRow, selected: boolean) => void; - selectableEntryTypes: ResourceRowType[]; -} - -const NestedResourceTable: React.FC = ({ - rows, - selectedRows, - noHeader, - requestNestedRows, - onRowSelectedChange, - selectableEntryTypes, -}) => { - const styles = useStyles2(getStyles); - - return ( - <> - - {!noHeader && ( - - - - - - - - )} -
ScopeTypeLocation
- -
- - - - -
-
- - ); -}; - -export default NestedResourceTable; 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 62583286bf1..15d11998a84 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 @@ -4,7 +4,6 @@ import React, { useEffect, useState } from 'react'; import { FadeTransition, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { NestedEntry } from './NestedEntry'; -import NestedRows from './NestedRows'; import getStyles from './styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; import { findRow } from './utils'; @@ -16,6 +15,7 @@ interface NestedRowProps { requestNestedRows: (row: ResourceRow) => Promise; onRowSelectedChange: (row: ResourceRow, selected: boolean) => void; selectableEntryTypes: ResourceRowType[]; + scrollIntoView?: boolean; } const NestedRow: React.FC = ({ @@ -25,11 +25,12 @@ const NestedRow: React.FC = ({ requestNestedRows, onRowSelectedChange, selectableEntryTypes, + scrollIntoView, }) => { const styles = useStyles2(getStyles); const [rowStatus, setRowStatus] = useState<'open' | 'closed' | 'loading'>('closed'); - const isSelected = !!selectedRows.find((v) => v.id === row.id); + const isSelected = !!selectedRows.find((v) => v.uri === row.uri); const isDisabled = selectedRows.length > 0 && !isSelected; const isOpen = rowStatus === 'open'; @@ -49,7 +50,7 @@ const NestedRow: React.FC = ({ // Assuming we don't have multi-select yet const selectedRow = selectedRows[0]; - const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.id); + const containsChild = selectedRow && !!findRow(row.children ?? [], selectedRow.uri); if (containsChild) { setRowStatus('open'); @@ -69,6 +70,7 @@ const NestedRow: React.FC = ({ onToggleCollapse={onRowToggleCollapse} onSelectedChange={onRowSelectedChange} isSelectable={selectableEntryTypes.some((type) => type === row.type)} + scrollIntoView={scrollIntoView} /> @@ -77,16 +79,21 @@ const NestedRow: React.FC = ({ {row.location ?? '-'} - {isOpen && row.children && Object.keys(row.children).length > 0 && ( - - )} + {isOpen && + row.children && + Object.keys(row.children).length > 0 && + row.children.map((childRow) => ( + + ))} diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx deleted file mode 100644 index df223499781..00000000000 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/NestedRows.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import NestedRow from './NestedRow'; -import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; - -interface NestedRowsProps { - rows: ResourceRowGroup; - level: number; - selectedRows: ResourceRowGroup; - requestNestedRows: (row: ResourceRow) => Promise; - onRowSelectedChange: (row: ResourceRow, selected: boolean) => void; - selectableEntryTypes: ResourceRowType[]; -} - -const NestedRows: React.FC = ({ - rows, - selectedRows, - level, - requestNestedRows, - onRowSelectedChange, - selectableEntryTypes, -}) => ( - <> - {rows.map((row) => ( - - ))} - -); - -export default NestedRows; 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 26196935b62..645ea671155 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 @@ -40,7 +40,7 @@ const defaultProps = { describe('AzureMonitor ResourcePicker', () => { beforeEach(() => { - window.HTMLElement.prototype.scrollIntoView = function () {}; + window.HTMLElement.prototype.scrollIntoView = jest.fn(); }); it('should pre-load subscriptions when there is no existing selection', async () => { render(); @@ -53,20 +53,40 @@ describe('AzureMonitor ResourcePicker', () => { it('should show a subscription as selected if there is one saved', async () => { render(); - const subscriptionCheckbox = await screen.findByLabelText('Dev Subscription'); - expect(subscriptionCheckbox).toBeChecked(); + const subscriptionCheckboxes = await screen.findAllByLabelText('Dev Subscription'); + expect(subscriptionCheckboxes.length).toBe(2); + expect(subscriptionCheckboxes[0]).toBeChecked(); + expect(subscriptionCheckboxes[1]).toBeChecked(); }); it('should show a resourceGroup as selected if there is one saved', async () => { render(); - const resourceGroupCheckbox = await screen.findByLabelText('A Great Resource Group'); - expect(resourceGroupCheckbox).toBeChecked(); + const resourceGroupCheckboxes = await screen.findAllByLabelText('A Great Resource Group'); + expect(resourceGroupCheckboxes.length).toBe(2); + expect(resourceGroupCheckboxes[0]).toBeChecked(); + expect(resourceGroupCheckboxes[1]).toBeChecked(); }); it('should show a resource as selected if there is one saved', async () => { render(); - const resourceCheckbox = await screen.findByLabelText('db-server'); - expect(resourceCheckbox).toBeChecked(); + const resourceCheckboxes = await screen.findAllByLabelText('db-server'); + expect(resourceCheckboxes.length).toBe(2); + expect(resourceCheckboxes[0]).toBeChecked(); + expect(resourceCheckboxes[1]).toBeChecked(); + }); + + it('opens the selected nested resources', async () => { + render(); + const collapseSubscriptionBtn = await screen.findByLabelText('Collapse Dev Subscription'); + expect(collapseSubscriptionBtn).toBeInTheDocument(); + const collapseResourceGroupBtn = await screen.findByLabelText('Collapse A Great Resource Group'); + expect(collapseResourceGroupBtn).toBeInTheDocument(); + }); + + it('scrolls down to the selected resource', async () => { + render(); + await screen.findByLabelText('Collapse A Great Resource Group'); + expect(window.HTMLElement.prototype.scrollIntoView).toBeCalledTimes(1); }); it('should be able to expand a subscription when clicked and reveal resource groups', async () => { 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 16edcf26bf0..5929cd8efe0 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 @@ -1,14 +1,14 @@ -import { css } from '@emotion/css'; +import { cx } from '@emotion/css'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { Alert, Button, Icon, Input, LoadingPlaceholder, Tooltip, useStyles2, Collapse, Label } from '@grafana/ui'; import ResourcePickerData from '../../resourcePicker/resourcePickerData'; import messageFromError from '../../utils/messageFromError'; import { Space } from '../Space'; -import NestedResourceTable from './NestedResourceTable'; +import NestedRow from './NestedRow'; +import getStyles from './styles'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types'; import { addResources, findRow, parseResourceURI } from './utils'; @@ -142,29 +142,61 @@ const ResourcePicker = ({
) : ( <> - + + + + + + + + +
ScopeTypeLocation
+ +
+ + + {azureRows.map((row) => ( + + ))} + +
+
{selectedResourceRows.length > 0 && ( <>
Selection
- + +
+ + + {selectedResourceRows.map((row) => ( + + ))} + +
+
)} + ({ - selectionFooter: css({ - position: 'sticky', - bottom: 0, - background: theme.colors.background.primary, - paddingTop: theme.spacing(2), - }), - loadingWrapper: css({ - textAlign: 'center', - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - color: theme.colors.text.secondary, - }), -}); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts index 9a1d65d2a24..395263e9f2b 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/components/ResourcePicker/styles.ts @@ -69,6 +69,20 @@ const getStyles = (theme: GrafanaTheme2) => ({ nestedRowCheckbox: css({ zIndex: 0, }), + + selectionFooter: css({ + position: 'sticky', + bottom: 0, + background: theme.colors.background.primary, + paddingTop: theme.spacing(2), + }), + + loadingWrapper: css({ + textAlign: 'center', + paddingTop: theme.spacing(2), + paddingBottom: theme.spacing(2), + color: theme.colors.text.secondary, + }), }); export default getStyles;