diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts index 4f0bbb9fd8e..cd8a21476c7 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/azure_monitor_datasource.ts @@ -7,23 +7,26 @@ import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/run import { getCredentials } from '../credentials'; import TimegrainConverter from '../time_grain_converter'; import { + AzureAPIResponse, + AzureMetricQuery, AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceJsonData, + AzureMonitorLocations, AzureMonitorMetricsMetadataResponse, + AzureMonitorProvidersResponse, AzureMonitorQuery, AzureQueryType, DatasourceValidationResult, + GetLogAnalyticsTableResponse, + GetMetricMetadataQuery, GetMetricNamespacesQuery, GetMetricNamesQuery, - GetMetricMetadataQuery, - AzureMetricQuery, - AzureMonitorLocations, - AzureMonitorProvidersResponse, - AzureAPIResponse, - Subscription, + instanceOfLogAnalyticsTableError, Location, Metric, MetricNamespace, + Subscription, + TablePlan, } from '../types'; import { replaceTemplateVariables, routeNames } from '../utils/common'; import migrateQuery from '../utils/migrateQuery'; @@ -274,6 +277,36 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend< return undefined; } + async getWorkspaceTablePlan(resources: string[], tableName: string): Promise { + let workspaceUri = ''; + + if (resources) { + workspaceUri = resources[0]; + } + + if (!workspaceUri) { + return TablePlan.Analytics; + } + + if (workspaceUri && !workspaceUri.toLowerCase().includes('microsoft.operationalinsights/workspaces')) { + // Not a Log Analytics workspace so default to Analytics + return TablePlan.Analytics; + } + + const url = UrlBuilder.buildAzureMonitorGetLogsTableUrl( + this.resourcePath, + this.templateSrv.replace(workspaceUri), + this.templateSrv.replace(tableName) + ); + const tableResult = await this.getResource(url); + + if (!tableResult || instanceOfLogAnalyticsTableError(tableResult)) { + return TablePlan.Analytics; + } + + return tableResult.properties.plan || TablePlan.Analytics; + } + private isValidConfigField(field?: string): boolean { return typeof field === 'string' && field.length > 0; } diff --git a/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts b/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts index 295fcb85ea3..148a8a94baf 100644 --- a/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts +++ b/public/app/plugins/datasource/azuremonitor/azure_monitor/url_builder.ts @@ -113,4 +113,13 @@ export default class UrlBuilder { return url; } + + static buildAzureMonitorGetLogsTableUrl( + baseUrl: string, + resourceUri: string, + tableName: string, + apiVersion = '2025-02-01' + ) { + return `${baseUrl}${resourceUri}/tables/${tableName}?api-version=${apiVersion}`; + } } diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx index fe9624ed5ef..1d08f93cc52 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/LogsQueryBuilder.tsx @@ -42,10 +42,11 @@ interface LogsQueryBuilderProps { templateVariableOptions: SelectableValue; datasource: Datasource; timeRange?: TimeRange; + isLoadingSchema: boolean; } export const LogsQueryBuilder: React.FC = (props) => { - const { query, onQueryChange, schema, datasource, timeRange } = props; + const { query, onQueryChange, schema, datasource, timeRange, isLoadingSchema } = props; const [isKQLPreviewHidden, setIsKQLPreviewHidden] = useState(true); const tables: AzureLogAnalyticsMetadataTable[] = useMemo(() => { @@ -70,6 +71,7 @@ export const LogsQueryBuilder: React.FC = (props) => { orderBy, columns, from, + basicLogsQuery, }: { limit?: number; reduce?: BuilderQueryEditorReduceExpression[]; @@ -79,6 +81,7 @@ export const LogsQueryBuilder: React.FC = (props) => { orderBy?: BuilderQueryEditorOrderByExpression[]; columns?: string[]; from?: BuilderQueryEditorPropertyExpression; + basicLogsQuery?: boolean; }) => { const datetimeColumn = allColumns.find((col) => col.type === 'datetime')?.name || 'TimeGenerated'; @@ -122,6 +125,7 @@ export const LogsQueryBuilder: React.FC = (props) => { ...query.azureLogAnalytics, builderQuery: updatedBuilderQuery, query: updatedQueryString, + basicLogsQuery: from ? basicLogsQuery : query.azureLogAnalytics?.basicLogsQuery, }, }); }, @@ -134,7 +138,13 @@ export const LogsQueryBuilder: React.FC = (props) => { {schema && tables.length === 0 && ( )} - + ) => void; templateVariableOptions?: SelectableValue; + onQueryChange: (newQuery: AzureMonitorQuery) => void; + isLoadingSchema: boolean; } export const TableSection: React.FC = (props) => { - const { allColumns, query, tables, buildAndUpdateQuery, templateVariableOptions } = props; + const { allColumns, query, tables, buildAndUpdateQuery, templateVariableOptions, isLoadingSchema } = props; const builderQuery = query.azureLogAnalytics?.builderQuery; const selectedColumns = query.azureLogAnalytics?.builderQuery?.columns?.columns || []; const tableOptions: Array> = tables.map((t) => ({ label: t.name, value: t.name, + description: t.plan === TablePlan.Basic ? 'Selecting this table will switch the query mode to Basic Logs' : '', })); const columnOptions: Array> = allColumns.map((col) => ({ @@ -68,6 +76,7 @@ export const TableSection: React.FC = (props) => { groupBy: [], orderBy: [], columns: [], + basicLogsQuery: selectedTable.plan === TablePlan.Basic, }); }; @@ -138,6 +147,7 @@ export const TableSection: React.FC = (props) => { placeholder="Select a table" onChange={handleTableChange} width={inputFieldSize} + isLoading={isLoadingSchema} /> diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts index c59771f71f3..3c72a880040 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryBuilder/utils.ts @@ -3,14 +3,14 @@ import { escapeRegExp } from 'lodash'; import { SelectableValue } from '@grafana/data'; import { - BuilderQueryExpression, BuilderQueryEditorExpressionType, - BuilderQueryEditorPropertyType, - BuilderQueryEditorReduceExpression, - BuilderQueryEditorWhereExpression, BuilderQueryEditorGroupByExpression, BuilderQueryEditorOrderByExpression, BuilderQueryEditorPropertyExpression, + BuilderQueryEditorPropertyType, + BuilderQueryEditorReduceExpression, + BuilderQueryEditorWhereExpression, + BuilderQueryExpression, } from '../../dataquery.gen'; import { AzureLogAnalyticsMetadataColumn, AzureMonitorQuery } from '../../types'; @@ -88,6 +88,7 @@ export interface BuildAndUpdateOptions { orderBy?: BuilderQueryEditorOrderByExpression[]; columns?: string[]; from?: BuilderQueryEditorPropertyExpression; + basicLogsQuery?: boolean; } export const aggregateOptions = [ diff --git a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx index 0970842e25d..655f96705d4 100644 --- a/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/LogsQueryEditor.tsx @@ -8,7 +8,14 @@ import { Alert, LinkButton, Space, Text, TextLink } from '@grafana/ui'; import { LogsEditorMode } from '../../dataquery.gen'; import Datasource from '../../datasource'; import { selectors } from '../../e2e/selectors'; -import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, ResultFormat, EngineSchema } from '../../types'; +import { + AzureMonitorErrorish, + AzureMonitorOption, + AzureMonitorQuery, + ResultFormat, + EngineSchema, + AzureLogAnalyticsMetadataTable, +} from '../../types'; import { LogsQueryBuilder } from '../LogsQueryBuilder/LogsQueryBuilder'; import ResourceField from '../ResourceField'; import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types'; @@ -60,6 +67,7 @@ const LogsQueryEditor = ({ const to = templateSrv?.replace('$__to'); const templateVariableOptions = templateSrv.getVariables(); const isBasicLogsQuery = (basicLogsEnabled && query.azureLogAnalytics?.basicLogsQuery) ?? false; + const [isLoadingSchema, setIsLoadingSchema] = useState(false); const disableRow = (row: ResourceRow, selectedRows: ResourceRowGroup) => { if (selectedRows.length === 0) { @@ -83,12 +91,34 @@ const LogsQueryEditor = ({ const [schema, setSchema] = useState(); useEffect(() => { - if (query.azureLogAnalytics?.resources && query.azureLogAnalytics.resources.length) { - datasource.azureLogAnalyticsDatasource.getKustoSchema(query.azureLogAnalytics.resources[0]).then((schema) => { - setSchema(schema); + const resources = query.azureLogAnalytics?.resources; + if (resources) { + setIsLoadingSchema(true); + const fetchAllPlans = async (tables: AzureLogAnalyticsMetadataTable[]) => { + const promises = []; + for (const table of tables) { + promises.push({ + ...table, + plan: await datasource.azureMonitorDatasource.getWorkspaceTablePlan(resources, table.name), + }); + } + + const tablesWithPlan = await Promise.all(promises); + return tablesWithPlan; + }; + datasource.azureLogAnalyticsDatasource.getKustoSchema(resources[0]).then((schema) => { + if (schema?.database?.tables) { + fetchAllPlans(schema?.database?.tables).then(async (t) => { + if (schema.database?.tables) { + schema.database.tables = t; + } + setSchema(schema); + }); + } + setIsLoadingSchema(false); }); } - }, [query.azureLogAnalytics?.resources, datasource.azureLogAnalyticsDatasource]); + }, [query.azureLogAnalytics?.resources, datasource.azureLogAnalyticsDatasource, datasource.azureMonitorDatasource]); useEffect(() => { if (shouldShowBasicLogsToggle(query.azureLogAnalytics?.resources || [], basicLogsEnabled)) { @@ -254,6 +284,7 @@ const LogsQueryEditor = ({ templateVariableOptions={templateVariableOptions} datasource={datasource} timeRange={timeRange} + isLoadingSchema={isLoadingSchema} /> ) : ( ; + retentionInDaysAsDefault: boolean; + totalRetentionInDaysAsDefault: boolean; + schema: { + tableSubType: string; + name: string; + tableType: string; + columns: Array>; + standardColumns: Array>; + solutions: string[]; + isTroubleshootingAllowed: boolean; + description?: string; + displayName?: string; + labels?: string[]; + source?: string; + }; + resultStatistics: Record; + provisioningState: string; + retentionInDays: number; + searchResults?: Record; + systemData?: Record; + }; + id: string; + name: string; + type?: string; +} + +export interface GetLogAnalyticsTableErrorResponse { + error: { + target: string; + message: string; + code: string; + }; +} + +export type GetLogAnalyticsTableResponse = GetLogAnalyticsTableSuccessResponse | GetLogAnalyticsTableErrorResponse; + +export function instanceOfLogAnalyticsTableError( + response: GetLogAnalyticsTableSuccessResponse | GetLogAnalyticsTableErrorResponse +): response is GetLogAnalyticsTableErrorResponse { + if (!response) { + return false; + } + return response.hasOwnProperty('error'); +}