You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

174 lines
5.5 KiB

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NestedResourceTable from './NestedResourceTable';
import { ResourceRow, ResourceRowGroup } from './types';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import ResourcePickerData from '../../resourcePicker/resourcePickerData';
import { Space } from '../Space';
import { addResources, findRow, parseResourceURI } from './utils';
interface ResourcePickerProps {
resourcePickerData: ResourcePickerData;
resourceURI: string | undefined;
templateVariables: string[];
onApply: (resourceURI: string | undefined) => void;
onCancel: () => void;
}
const ResourcePicker = ({
resourcePickerData,
resourceURI,
templateVariables,
onApply,
onCancel,
}: ResourcePickerProps) => {
const styles = useStyles2(getStyles);
const [azureRows, setAzureRows] = useState<ResourceRowGroup>([]);
const [internalSelected, setInternalSelected] = useState<string | undefined>(resourceURI);
const [isLoading, setIsLoading] = useState(false);
// Sync the resourceURI prop to internal state
useEffect(() => {
setInternalSelected(resourceURI);
}, [resourceURI]);
const rows = useMemo(() => {
const templateVariableRow = resourcePickerData.transformVariablesToRow(templateVariables);
return templateVariables.length ? [...azureRows, templateVariableRow] : azureRows;
}, [resourcePickerData, azureRows, templateVariables]);
// Map the selected item into an array of rows
const selectedResourceRows = useMemo(() => {
const found = internalSelected && findRow(rows, internalSelected);
return found
? [
{
...found,
children: undefined,
},
]
: [];
}, [internalSelected, rows]);
// Request resources for a expanded resource group
const requestNestedRows = useCallback(
async (resourceGroup: ResourceRow) => {
// If we already have children, we don't need to re-fetch them. Also abort if we're expanding the special
// template variable group, though that shouldn't happen in practice
if (resourceGroup.children?.length || resourceGroup.id === ResourcePickerData.templateVariableGroupID) {
return;
}
// fetch and set nested resources for the resourcegroup into the bigger state object
const resources = await resourcePickerData.getResourcesForResourceGroup(resourceGroup);
const newRows = addResources(azureRows, resourceGroup.id, resources);
setAzureRows(newRows);
},
[resourcePickerData, azureRows]
);
// Select
const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => {
isSelected ? setInternalSelected(row.id) : setInternalSelected(undefined);
}, []);
// Request initial data on first mount
useEffect(() => {
setIsLoading(true);
resourcePickerData.getResourcePickerData().then((initalRows) => {
setIsLoading(false);
setAzureRows(initalRows);
});
}, [resourcePickerData]);
// Request sibling resources for a selected resource - in practice should only be on first mount
useEffect(() => {
if (!internalSelected || !rows.length) {
return;
}
// If we can find this resource in the rows, then we don't need to load anything
const foundResourceRow = findRow(rows, internalSelected);
if (foundResourceRow) {
return;
}
const parsedURI = parseResourceURI(internalSelected);
const resourceGroupURI = `/subscriptions/${parsedURI?.subscriptionID}/resourceGroups/${parsedURI?.resourceGroup}`;
const resourceGroupRow = findRow(rows, resourceGroupURI);
if (!resourceGroupRow) {
// We haven't loaded the data from Azure yet
return;
}
requestNestedRows(resourceGroupRow);
}, [requestNestedRows, internalSelected, rows]);
const handleApply = useCallback(() => {
onApply(internalSelected);
}, [internalSelected, onApply]);
return (
<div>
{isLoading ? (
<div className={styles.loadingWrapper}>
<LoadingPlaceholder text={'Loading resources...'} />
</div>
) : (
<>
<NestedResourceTable
rows={rows}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
/>
<div className={styles.selectionFooter}>
{selectedResourceRows.length > 0 && (
<>
<Space v={2} />
<h5>Selection</h5>
<NestedResourceTable
rows={selectedResourceRows}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
noHeader={true}
/>
</>
)}
<Space v={2} />
<Button onClick={handleApply}>Apply</Button>
<Space layout="inline" h={1} />
<Button onClick={onCancel} variant="secondary">
Cancel
</Button>
</div>
</>
)}
</div>
);
};
export default ResourcePicker;
const getStyles = (theme: GrafanaTheme2) => ({
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,
}),
});