/usr/share/grafana/public/app/plugins/datasource/azuremonitor/components/VariableEditor
import { get, isEqual } from 'lodash'; import { useEffect, useState } from 'react'; import { useEffectOnce } from 'react-use'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { getTemplateSrv } from '@grafana/runtime'; import { Alert, Field, Select, Space } from '@grafana/ui'; import UrlBuilder from '../../azure_monitor/url_builder'; import DataSource from '../../datasource'; import { selectors } from '../../e2e/selectors'; import { migrateQuery } from '../../grafanaTemplateVariableFns'; import { AzureMonitorQuery, AzureQueryType } from '../../types/query'; import { AzureMonitorOption } from '../../types/types'; import useLastError from '../../utils/useLastError'; import ArgQueryEditor from '../ArgQueryEditor/ArgQueryEditor'; import LogsQueryEditor from '../LogsQueryEditor/LogsQueryEditor'; import { parseResourceURI } from '../ResourcePicker/utils'; import GrafanaTemplateVariableFnInput from './GrafanaTemplateVariableFn'; type Props = { query: AzureMonitorQuery | string; onChange: (query: AzureMonitorQuery) => void; datasource: DataSource; }; const removeOption: SelectableValue = { label: '-', value: '' }; const VariableEditor = (props: Props) => { const { query, onChange, datasource } = props; const AZURE_QUERY_VARIABLE_TYPE_OPTIONS = [ { label: 'Subscriptions', value: AzureQueryType.SubscriptionsQuery }, { label: 'Resource Groups', value: AzureQueryType.ResourceGroupsQuery }, { label: 'Namespaces', value: AzureQueryType.NamespacesQuery }, { label: 'Regions', value: AzureQueryType.LocationsQuery }, { label: 'Resource Names', value: AzureQueryType.ResourceNamesQuery }, { label: 'Metric Names', value: AzureQueryType.MetricNamesQuery }, { label: 'Workspaces', value: AzureQueryType.WorkspacesQuery }, { label: 'Resource Graph', value: AzureQueryType.AzureResourceGraph }, { label: 'Logs', value: AzureQueryType.LogAnalytics }, { label: 'Custom Namespaces', value: AzureQueryType.CustomNamespacesQuery }, { label: 'Custom Metric Names', value: AzureQueryType.CustomMetricNamesQuery }, ]; if (typeof props.query === 'object' && props.query.queryType === AzureQueryType.GrafanaTemplateVariableFn) { // Add the option for the GrafanaTemplateVariableFn only if it's already in use AZURE_QUERY_VARIABLE_TYPE_OPTIONS.push({ label: 'Grafana Query Function', value: AzureQueryType.GrafanaTemplateVariableFn, }); } const [variableOptionGroup, setVariableOptionGroup] = useState<{ label: string; options: AzureMonitorOption[] }>({ label: 'Template Variables', options: [], }); const [requireSubscription, setRequireSubscription] = useState(false); const [hasResourceGroup, setHasResourceGroup] = useState(false); const [hasNamespace, setHasNamespace] = useState(false); const [hasRegion, setHasRegion] = useState(false); const [requireResourceGroup, setRequireResourceGroup] = useState(false); const [requireNamespace, setRequireNamespace] = useState(false); const [requireCustomNamespace, setRequireCustomNamespace] = useState(false); const [requireResource, setRequireResource] = useState(false); const [subscriptions, setSubscriptions] = useState<SelectableValue[]>([]); const [resourceGroups, setResourceGroups] = useState<SelectableValue[]>([]); const [namespaces, setNamespaces] = useState<SelectableValue[]>([]); const [customNamespaces, setCustomNamespaces] = useState<SelectableValue[]>([]); const [resources, setResources] = useState<SelectableValue[]>([]); const [regions, setRegions] = useState<SelectableValue[]>([]); const [errorMessage, setError] = useLastError(); const queryType = typeof query === 'string' ? '' : query.queryType; useEffect(() => { migrateQuery(query, { datasource: datasource }).then((migratedQuery) => { if (!isEqual(query, migratedQuery)) { onChange(migratedQuery); } }); }, [query, datasource, onChange]); useEffect(() => { setRequireSubscription(false); setHasResourceGroup(false); setHasNamespace(false); setRequireResourceGroup(false); setRequireNamespace(false); setRequireResource(false); setRequireCustomNamespace(false); switch (queryType) { case AzureQueryType.ResourceGroupsQuery: case AzureQueryType.WorkspacesQuery: setRequireSubscription(true); break; case AzureQueryType.NamespacesQuery: setRequireSubscription(true); setHasResourceGroup(true); break; case AzureQueryType.ResourceNamesQuery: setRequireSubscription(true); setHasResourceGroup(true); setHasNamespace(true); setHasRegion(true); break; case AzureQueryType.MetricNamesQuery: setRequireSubscription(true); setRequireResourceGroup(true); setRequireNamespace(true); setRequireResource(true); break; case AzureQueryType.LocationsQuery: setRequireSubscription(true); break; case AzureQueryType.CustomNamespacesQuery: setRequireSubscription(true); setRequireResourceGroup(true); setRequireNamespace(true); setRequireResource(true); break; case AzureQueryType.CustomMetricNamesQuery: setRequireSubscription(true); setRequireResourceGroup(true); setRequireResource(true); setRequireNamespace(true); setRequireCustomNamespace(true); break; } }, [queryType]); useEffect(() => { const options: AzureMonitorOption[] = []; datasource.getVariablesRaw().forEach((v) => { if (get(v, 'query.queryType') !== queryType) { options.push({ label: v.label || v.name, value: `$${v.name}` }); } }); setVariableOptionGroup({ label: 'Template Variables', options, }); }, [datasource, queryType]); // Always retrieve subscriptions first as they're used in most template variable queries useEffectOnce(() => { datasource.getSubscriptions().then((subs) => { setSubscriptions(subs.map((s) => ({ label: s.text, value: s.value }))); }); }); const subscription = typeof query === 'object' && query.subscription; // When subscription is set, retrieve resource groups useEffect(() => { if (subscription) { datasource.getResourceGroups(subscription).then((rgs) => { setResourceGroups(rgs.map((s) => ({ label: s.resourceGroupName, value: s.resourceGroupName }))); }); } }, [datasource, subscription]); const resourceGroup = (typeof query === 'object' && query.resourceGroup) || ''; // When resource group is set, retrieve metric namespaces (aka resource types for a custom metric and custom metric namespace query) useEffect(() => { if (subscription && resourceGroup) { datasource.getMetricNamespaces(subscription, resourceGroup, undefined, false, true).then((rgs) => { setNamespaces(rgs.map((s) => ({ label: s.text, value: s.value }))); }); } }, [datasource, subscription, resourceGroup]); // When subscription is set also retrieve locations useEffect(() => { if (subscription) { datasource.azureMonitorDatasource.getLocations([subscription]).then((rgs) => { const regions: SelectableValue[] = []; rgs.forEach((r) => regions.push({ label: r.displayName, value: r.name })); setRegions(regions); }); } }, [datasource, subscription, resourceGroup]); const namespace = (typeof query === 'object' && query.namespace) || ''; // When subscription, resource group, and namespace are all set, retrieve resource names useEffect(() => { if (subscription && resourceGroup && namespace) { datasource.getResourceNames(subscription, resourceGroup, namespace).then((resources) => { setResources( resources.map((s) => { const parsedResource = parseResourceURI(s.id); return { label: s.name, value: parsedResource.resourceName }; }) ); }); } }, [datasource, subscription, resourceGroup, namespace]); const resource = (typeof query === 'object' && query.resource) || ''; // When subscription, resource group, namespace, and resource name are all set, retrieve custom metric namespaces useEffect(() => { if (subscription && resourceGroup && namespace && resource) { const resourceUri = UrlBuilder.buildResourceUri(getTemplateSrv(), { subscription, resourceGroup, metricNamespace: namespace, resourceName: resource, }); datasource.getMetricNamespaces(subscription, resourceGroup, resourceUri, true).then((rgs) => { setCustomNamespaces(rgs.map((s) => ({ label: s.text, value: s.value }))); }); } }, [datasource, subscription, resourceGroup, namespace, resource]); if (typeof query === 'string') { // still migrating the query return null; } const onQueryTypeChange = (selectableValue: SelectableValue) => { if (selectableValue.value) { onChange({ ...query, queryType: selectableValue.value, subscription: undefined, resourceGroup: undefined, namespace: undefined, resource: undefined, }); } }; const onChangeSubscription = (selectableValue: SelectableValue) => { if (selectableValue.value) { onChange({ ...query, subscription: selectableValue.value, resourceGroup: undefined, namespace: undefined, resource: undefined, }); } }; const onChangeResourceGroup = (selectableValue: SelectableValue) => { onChange({ ...query, resourceGroup: selectableValue.value, namespace: undefined, resource: undefined, }); }; const onChangeNamespace = (selectableValue: SelectableValue) => { onChange({ ...query, namespace: selectableValue.value, resource: undefined, }); }; const onChangeRegion = (selectableValue: SelectableValue) => { onChange({ ...query, region: selectableValue.value, }); }; const onChangeResource = (selectableValue: SelectableValue) => { onChange({ ...query, resource: selectableValue.value, }); }; const onQueryChange = (queryChange: AzureMonitorQuery) => { onChange(queryChange); }; const onChangeCustomNamespace = (selectableValue: SelectableValue) => { onChange({ ...query, customNamespace: selectableValue.value, }); }; return ( <> <Field label={t('components.variable-editor.label-query-type', 'Query Type')} data-testid={selectors.components.variableEditor.queryType.input} > <Select aria-label={t('components.variable-editor.aria-label-select-query-type', 'Select query type')} onChange={onQueryTypeChange} options={AZURE_QUERY_VARIABLE_TYPE_OPTIONS} width={25} value={queryType} /> </Field> {query.queryType === AzureQueryType.LogAnalytics && ( <> <LogsQueryEditor subscriptionId={query.subscription} query={query} datasource={datasource} onChange={onQueryChange} // Not applicable as the builder isn't available in the variable editor yet onQueryChange={onQueryChange} variableOptionGroup={variableOptionGroup} setError={setError} hideFormatAs={true} basicLogsEnabled={datasource.azureMonitorDatasource.basicLogsEnabled ?? false} /> {errorMessage && ( <> <Space v={2} /> <Alert severity="error" title={t( 'components.variable-editor.title-error-occurred', 'An error occurred while requesting metadata from Azure Monitor' )} > {errorMessage instanceof Error ? errorMessage.message : errorMessage} </Alert> </> )} </> )} {query.queryType === AzureQueryType.GrafanaTemplateVariableFn && ( <GrafanaTemplateVariableFnInput query={query} updateQuery={props.onChange} datasource={datasource} /> )} {requireSubscription && ( <Field label={t('components.variable-editor.label-subscription', 'Subscription')} data-testid={selectors.components.variableEditor.subscription.input} > <Select aria-label={t('components.variable-editor.aria-label-select-subscription', 'Select subscription')} onChange={onChangeSubscription} options={subscriptions.concat(variableOptionGroup)} width={25} value={query.subscription || null} /> </Field> )} {(requireResourceGroup || hasResourceGroup) && ( <Field label={t('components.variable-editor.label-resource-group', 'Resource Group')} data-testid={selectors.components.variableEditor.resourceGroup.input} > <Select aria-label={t('components.variable-editor.aria-label-select-resource-group', 'Select resource group')} onChange={onChangeResourceGroup} options={ requireResourceGroup ? resourceGroups.concat(variableOptionGroup) : resourceGroups.concat(variableOptionGroup, removeOption) } width={25} value={query.resourceGroup || null} placeholder={ requireResourceGroup ? undefined : t('components.variable-editor.placeholder-resource-group', 'Optional') } /> </Field> )} {(requireNamespace || hasNamespace) && ( <Field label={ queryType === AzureQueryType.CustomNamespacesQuery || queryType === AzureQueryType.CustomMetricNamesQuery ? t('components.variable-editor.label-resource-type', 'Resource Type') : t('components.variable-editor.label-namespace', 'Namespace') } data-testid={selectors.components.variableEditor.namespace.input} > <Select aria-label={t('components.variable-editor.aria-label-select-namespace', 'Select namespace')} onChange={onChangeNamespace} options={ requireNamespace ? namespaces.concat(variableOptionGroup) : namespaces.concat(variableOptionGroup, removeOption) } width={25} value={query.namespace || null} placeholder={ requireNamespace ? undefined : t('components.variable-editor.placeholder-namespace', 'Optional') } /> </Field> )} {hasRegion && ( <Field label={t('components.variable-editor.label-region', 'Region')} data-testid={selectors.components.variableEditor.region.input} > <Select aria-label={t('components.variable-editor.aria-label-select-region', 'Select region')} onChange={onChangeRegion} options={regions.concat(variableOptionGroup)} width={25} value={query.region || null} placeholder={t('components.variable-editor.placeholder-region', 'Optional')} /> </Field> )} {requireResource && ( <Field label={t('components.variable-editor.label-resource', 'Resource')} data-testid={selectors.components.variableEditor.resource.input} > <Select aria-label={t('components.variable-editor.aria-label-select-resource', 'Select resource')} onChange={onChangeResource} options={resources.concat(variableOptionGroup)} width={25} value={query.resource || null} /> </Field> )} {requireCustomNamespace && ( <Field label={t('components.variable-editor.label-custom-namespace', 'Custom Namespace')} data-testid={selectors.components.variableEditor.customNamespace.input} > <Select aria-label={t('components.variable-editor.aria-label-select-custom-namespace', 'Select custom namespace')} onChange={onChangeCustomNamespace} options={ requireCustomNamespace ? customNamespaces.concat(variableOptionGroup) : customNamespaces.concat(variableOptionGroup, removeOption) } width={25} value={query.customNamespace || null} placeholder={ requireCustomNamespace ? undefined : t('components.variable-editor.placeholder-custom-namespace', 'Optional') } /> </Field> )} {query.queryType === AzureQueryType.AzureResourceGraph && ( <> <ArgQueryEditor subscriptionId={datasource.azureLogAnalyticsDatasource.defaultSubscriptionId} query={query} datasource={datasource} onChange={onQueryChange} variableOptionGroup={variableOptionGroup} setError={setError} /> {errorMessage && ( <> <Space v={2} /> <Alert severity="error" title={t( 'components.variable-editor.title-error-occurred', 'An error occurred while requesting metadata from Azure Monitor' )} > {errorMessage instanceof Error ? errorMessage.message : errorMessage} </Alert> </> )} </> )} </> ); }; export default VariableEditor;
.
Edit
..
Edit
GrafanaTemplateVariableFn.tsx
Edit
VariableEditor.test.tsx
Edit
VariableEditor.tsx
Edit