The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
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.
grafana/public/app/plugins/datasource/grafana-azure-monitor-datas.../components/ResourcePicker/ResourcePicker.tsx

294 lines
9.4 KiB

import { cx } from '@emotion/css';
import React, { useCallback, useEffect, useState } from 'react';
import { useEffectOnce } from 'react-use';
import { Alert, Button, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
Azure Monitor: E2E Tests (#54619) * Update credentials form with data-testids and aria-labels * Update gitignore * Add example dashboard * Stub out E2E test for creating ds and importing dashboard * Add component selectors * Remove subscription check temporarily * Appropriately set disabled prop * Fix lint issues * Update to use selectors and wait on subscriptions request * Add test for metrics panel - Add required selectors for resource picker and metrics query editor * Add logs and ARG basic query scenarios - More selector updates * Add E2E test for template variables - Tests advanced resource picker - Adds required selectors * Remove log and add annotation e2e test * Update test * Prettier/betterer updates - Remove gitignore change * Lint issues * Update betterer results * Lint issue and remove unneeded import * Don't print certain commands * Avoiding flakiness - Ensure code editor has sufficient time to load in ARG test - Avoid flakiness around correct template variable being selected by typing in resource name * Remove be.visible requirement * Update test * Update selector name * Reuse datasource * Fix datasource reuse and combine query tests * Remove import dashboard step as unneeded * Review - Randomise datasource name - Skip annotations test - Remove unused example dashboard * Update to ensure e2e test works in CI * Update e2e test - Update environment variables (process is not available in cypress) - Add wait on resource picker searches to avoid flakiness - Update subscription and resource group names * Update CODEOWNERS * Parse credentials in CI from outputs file * Update outputs file path * Fix selector * Undo selector change * Update e2e tests - Set default subscription - Fix datasource selection in variable editor - Fix resource picker search flakiness - Set subscription in ARG query test - Fix resource group selection - Update resource group * Review * Review 2
3 years ago
import { selectors } from '../../e2e/selectors';
import ResourcePickerData, { ResourcePickerQueryType } from '../../resourcePicker/resourcePickerData';
import { AzureMetricResource } from '../../types';
import messageFromError from '../../utils/messageFromError';
import { Space } from '../Space';
import AdvancedMulti from './AdvancedMulti';
import NestedRow from './NestedRow';
import Search from './Search';
import getStyles from './styles';
import { ResourceRow, ResourceRowGroup, ResourceRowType } from './types';
import { findRows, parseMultipleResourceDetails, resourcesToStrings, matchURI, resourceToString } from './utils';
interface ResourcePickerProps<T> {
resourcePickerData: ResourcePickerData;
resources: T[];
selectableEntryTypes: ResourceRowType[];
queryType: ResourcePickerQueryType;
onApply: (resources: T[]) => void;
onCancel: () => void;
disableRow: (row: ResourceRow, selectedRows: ResourceRowGroup) => boolean;
renderAdvanced: (resources: T[], onChange: (resources: T[]) => void) => React.ReactNode;
selectionNotice?: (selectedRows: ResourceRowGroup) => string;
}
const ResourcePicker = ({
resourcePickerData,
resources,
onApply,
onCancel,
selectableEntryTypes,
queryType,
disableRow,
renderAdvanced,
selectionNotice,
}: ResourcePickerProps<string | AzureMetricResource>) => {
const styles = useStyles2(getStyles);
const [isLoading, setIsLoading] = useState(false);
const [rows, setRows] = useState<ResourceRowGroup>([]);
const [selectedRows, setSelectedRows] = useState<ResourceRowGroup>([]);
const [internalSelected, setInternalSelected] = useState(resources);
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
const [shouldShowLimitFlag, setShouldShowLimitFlag] = useState(false);
const selectionNoticeText = selectionNotice?.(selectedRows);
// Sync the resourceURI prop to internal state
useEffect(() => {
setInternalSelected(resources);
}, [resources]);
const loadInitialData = useCallback(async () => {
if (!isLoading) {
try {
setIsLoading(true);
const resources = await resourcePickerData.fetchInitialRows(
queryType,
parseMultipleResourceDetails(internalSelected ?? {})
);
setRows(resources);
} catch (error) {
setErrorMessage(messageFromError(error));
}
setIsLoading(false);
}
}, [internalSelected, isLoading, resourcePickerData, queryType]);
useEffectOnce(() => {
loadInitialData();
});
// Avoid using empty resources
const isValid = (r: string | AzureMetricResource) =>
typeof r === 'string' ? r !== '' : r.subscription && r.resourceGroup && r.resourceName && r.metricNamespace;
// set selected row data whenever row or selection changes
useEffect(() => {
if (!internalSelected) {
setSelectedRows([]);
}
const sanitized = internalSelected.filter((r) => isValid(r));
const found = internalSelected && findRows(rows, resourcesToStrings(sanitized));
if (sanitized?.length > found.length) {
// Not all the selected items are in the current rows, so we need to generate the row
// information for those.
return setSelectedRows(resourcePickerData.parseRows(sanitized));
}
if (found && found.length) {
return setSelectedRows(found);
}
return setSelectedRows([]);
}, [internalSelected, rows, resourcePickerData]);
// Request resources for an expanded resource group
const requestNestedRows = useCallback(
async (parentRow: ResourceRow) => {
// clear error message (also when loading cached resources)
setErrorMessage(undefined);
// If we already have children, we don't need to re-fetch them.
if (parentRow.children?.length) {
return;
}
try {
const nestedRows = await resourcePickerData.fetchAndAppendNestedRow(rows, parentRow, queryType);
setRows(nestedRows);
} catch (error) {
setErrorMessage(messageFromError(error));
throw error;
}
},
[resourcePickerData, rows, queryType]
);
const handleSelectionChanged = useCallback(
(row: ResourceRow, isSelected: boolean) => {
if (isSelected) {
const newRes = queryType === 'logs' ? row.uri : parseMultipleResourceDetails([row.uri], row.location)[0];
const newSelected = internalSelected ? internalSelected.concat(newRes) : [newRes];
setInternalSelected(newSelected.filter((r) => isValid(r)));
} else {
const newInternalSelected = internalSelected?.filter((r) => {
return !matchURI(resourceToString(r), row.uri);
});
setInternalSelected(newInternalSelected);
}
},
[queryType, internalSelected, setInternalSelected]
);
const handleApply = useCallback(() => {
if (internalSelected) {
onApply(queryType === 'logs' ? internalSelected : parseMultipleResourceDetails(internalSelected));
}
}, [queryType, internalSelected, onApply]);
const handleSearch = useCallback(
async (searchWord: string) => {
// clear errors and warnings
setErrorMessage(undefined);
setShouldShowLimitFlag(false);
if (!searchWord) {
loadInitialData();
return;
}
try {
setIsLoading(true);
const searchResults = await resourcePickerData.search(searchWord, queryType);
setRows(searchResults);
if (searchResults.length >= resourcePickerData.resultLimit) {
setShouldShowLimitFlag(true);
}
} catch (err) {
setErrorMessage(messageFromError(err));
}
setIsLoading(false);
},
[loadInitialData, resourcePickerData, queryType]
);
return (
<div>
<Search searchFn={handleSearch} />
{shouldShowLimitFlag ? (
<p className={styles.resultLimit}>Showing first {resourcePickerData.resultLimit} results</p>
) : (
<Space v={2} />
)}
<table className={styles.table}>
<thead>
<tr className={cx(styles.row, styles.header)}>
<td className={styles.cell}>Scope</td>
<td className={styles.cell}>Type</td>
<td className={styles.cell}>Location</td>
</tr>
</thead>
</table>
<div className={styles.tableScroller}>
<table className={styles.table}>
<tbody>
{isLoading && (
<tr className={cx(styles.row)}>
<td className={styles.cell}>
<LoadingPlaceholder text={'Loading...'} />
</td>
</tr>
)}
{!isLoading && rows.length === 0 && (
<tr className={cx(styles.row)}>
<td className={styles.cell} aria-live="polite">
No resources found
</td>
</tr>
)}
{!isLoading &&
rows.map((row) => (
<NestedRow
key={row.uri}
row={row}
selectedRows={selectedRows}
level={0}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectableEntryTypes={selectableEntryTypes}
scrollIntoView={true}
disableRow={disableRow}
/>
))}
</tbody>
</table>
</div>
<div className={styles.selectionFooter}>
{selectedRows.length > 0 && (
<>
<h5>Selection</h5>
<div className={styles.tableScroller}>
<table className={styles.table}>
<tbody>
{selectedRows.map((row) => (
<NestedRow
key={row.uri}
row={row}
selectedRows={selectedRows}
level={0}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectableEntryTypes={selectableEntryTypes}
disableRow={() => false}
/>
))}
</tbody>
</table>
</div>
<Space v={2} />
{selectionNoticeText?.length ? (
<Alert title="" severity="info">
{selectionNoticeText}
</Alert>
) : null}
</>
)}
<AdvancedMulti
resources={internalSelected}
onChange={(r) => setInternalSelected(r)}
renderAdvanced={renderAdvanced}
/>
<Space v={2} />
Azure Monitor: E2E Tests (#54619) * Update credentials form with data-testids and aria-labels * Update gitignore * Add example dashboard * Stub out E2E test for creating ds and importing dashboard * Add component selectors * Remove subscription check temporarily * Appropriately set disabled prop * Fix lint issues * Update to use selectors and wait on subscriptions request * Add test for metrics panel - Add required selectors for resource picker and metrics query editor * Add logs and ARG basic query scenarios - More selector updates * Add E2E test for template variables - Tests advanced resource picker - Adds required selectors * Remove log and add annotation e2e test * Update test * Prettier/betterer updates - Remove gitignore change * Lint issues * Update betterer results * Lint issue and remove unneeded import * Don't print certain commands * Avoiding flakiness - Ensure code editor has sufficient time to load in ARG test - Avoid flakiness around correct template variable being selected by typing in resource name * Remove be.visible requirement * Update test * Update selector name * Reuse datasource * Fix datasource reuse and combine query tests * Remove import dashboard step as unneeded * Review - Randomise datasource name - Skip annotations test - Remove unused example dashboard * Update to ensure e2e test works in CI * Update e2e test - Update environment variables (process is not available in cypress) - Add wait on resource picker searches to avoid flakiness - Update subscription and resource group names * Update CODEOWNERS * Parse credentials in CI from outputs file * Update outputs file path * Fix selector * Undo selector change * Update e2e tests - Set default subscription - Fix datasource selection in variable editor - Fix resource picker search flakiness - Set subscription in ARG query test - Fix resource group selection - Update resource group * Review * Review 2
3 years ago
<Button
disabled={!!errorMessage || !internalSelected.every(isValid)}
Azure Monitor: E2E Tests (#54619) * Update credentials form with data-testids and aria-labels * Update gitignore * Add example dashboard * Stub out E2E test for creating ds and importing dashboard * Add component selectors * Remove subscription check temporarily * Appropriately set disabled prop * Fix lint issues * Update to use selectors and wait on subscriptions request * Add test for metrics panel - Add required selectors for resource picker and metrics query editor * Add logs and ARG basic query scenarios - More selector updates * Add E2E test for template variables - Tests advanced resource picker - Adds required selectors * Remove log and add annotation e2e test * Update test * Prettier/betterer updates - Remove gitignore change * Lint issues * Update betterer results * Lint issue and remove unneeded import * Don't print certain commands * Avoiding flakiness - Ensure code editor has sufficient time to load in ARG test - Avoid flakiness around correct template variable being selected by typing in resource name * Remove be.visible requirement * Update test * Update selector name * Reuse datasource * Fix datasource reuse and combine query tests * Remove import dashboard step as unneeded * Review - Randomise datasource name - Skip annotations test - Remove unused example dashboard * Update to ensure e2e test works in CI * Update e2e test - Update environment variables (process is not available in cypress) - Add wait on resource picker searches to avoid flakiness - Update subscription and resource group names * Update CODEOWNERS * Parse credentials in CI from outputs file * Update outputs file path * Fix selector * Undo selector change * Update e2e tests - Set default subscription - Fix datasource selection in variable editor - Fix resource picker search flakiness - Set subscription in ARG query test - Fix resource group selection - Update resource group * Review * Review 2
3 years ago
onClick={handleApply}
data-testid={selectors.components.queryEditor.resourcePicker.apply.button}
>
Apply
</Button>
<Space layout="inline" h={1} />
<Button onClick={onCancel} variant="secondary">
Cancel
</Button>
</div>
{errorMessage && (
<>
<Space v={2} />
<Alert severity="error" title="An error occurred while requesting resources from Azure Monitor">
{errorMessage}
</Alert>
</>
)}
</div>
);
};
export default ResourcePicker;