AzureMonitor: Add switch to control time-range for Logs queries (#71278)

* Update types

* Update migration

- Default intersectTime property to false

* Update frontend components

- Add intersectTime field
- Update tests
- Update mocks
- Add onChange function

* Update backend

- Appropriately set intersectTime for logs queries
- intersectTime is always true for Traces queries
- Update tests

* Update docs

* Fix test and lint
This commit is contained in:
Andreas Christou 2023-07-17 12:02:16 +01:00 committed by GitHub
parent 24bcf9b3fd
commit 480ccf6e8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 195 additions and 18 deletions

View File

@ -141,10 +141,11 @@ The Azure documentation includes resources to help you learn KQL:
- [Tutorial: Use Kusto queries in Azure Monitor](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/tutorial?pivots=azuremonitor)
- [SQL to Kusto cheat sheet](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/sqlcheatsheet)
> **Implicit dashboard time range usage:** As of Grafana v9.4.12 and v10.0, all Azure Monitor Logs queries
> will use the specified dashboard or Explore time range by default.
> Any query making use of a time range explicitly specified in the query body will have their query
> executed against the intersection of the two time ranges. For more details on this change, refer to the [Azure Monitor Logs API documentation](https://learn.microsoft.com/en-us/rest/api/loganalytics/dataaccess/query/get?tabs=HTTP#uri-parameters).
> **Time-range:** The time-range that will be used for the query can be modified via the time-range switch. Selecting `Query` will only make use of time-ranges specified within the query.
> Specifying `Intersection` will make use of the intersection between the time-ranges within the query and the Grafana time-range.
> If there are no time-ranges specified within the query, the Grafana time-range will be used.
> For more details on this change, refer to the [Azure Monitor Logs API documentation](https://learn.microsoft.com/en-us/rest/api/loganalytics/dataaccess/query/get?tabs=HTTP#uri-parameters).
> Note: v9.4.12, v10.0, and v10.0.1 do not have this switch and will implicitly use the intersection of the Grafana and query time-ranges.
This example query returns a virtual machine's CPU performance, averaged over 5ms time grains:

View File

@ -164,6 +164,10 @@ export const defaultAzureMetricQuery: Partial<AzureMetricQuery> = {
* Azure Monitor Logs sub-query properties
*/
export interface AzureLogsQuery {
/**
* If set to true the intersection of time ranges specified in the query and Grafana will be used. Otherwise the query time ranges will be used. Defaults to false
*/
intersectTime?: boolean;
/**
* KQL query to be executed.
*/

View File

@ -118,6 +118,9 @@ type AppInsightsMetricNameQueryKind string
// Azure Monitor Logs sub-query properties
type AzureLogsQuery struct {
// If set to true the intersection of time ranges specified in the query and Grafana will be used. Otherwise the query time ranges will be used. Defaults to false
IntersectTime *bool `json:"intersectTime,omitempty"`
// KQL query to be executed.
Query *string `json:"query,omitempty"`

View File

@ -48,6 +48,7 @@ type AzureLogAnalyticsQuery struct {
Resources []string
QueryType string
AppInsightsQuery bool
IntersectTime bool
}
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) {
@ -103,6 +104,7 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l
traceExploreQuery := ""
traceParentExploreQuery := ""
traceLogsExploreQuery := ""
intersectTime := false
if query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) {
queryJSONModel := types.LogJSONQuery{}
err := json.Unmarshal(query.JSON, &queryJSONModel)
@ -138,6 +140,10 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l
if azureLogAnalyticsTarget.Query != nil {
queryString = *azureLogAnalyticsTarget.Query
}
if azureLogAnalyticsTarget.IntersectTime != nil {
intersectTime = *azureLogAnalyticsTarget.IntersectTime
}
}
if query.QueryType == string(dataquery.AzureQueryTypeAzureTraces) {
@ -210,6 +216,8 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l
if err != nil {
return nil, fmt.Errorf("failed to create traces logs explore query: %s", err)
}
intersectTime = true
}
apiURL := getApiURL(resourceOrWorkspace, appInsightsQuery)
@ -232,6 +240,7 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, logger l
TraceParentExploreQuery: traceParentExploreQuery,
TraceLogsExploreQuery: traceLogsExploreQuery,
AppInsightsQuery: appInsightsQuery,
IntersectTime: intersectTime,
})
}
@ -442,12 +451,15 @@ func appendErrorNotice(frame *data.Frame, err *AzureLogAnalyticsAPIError) *data.
}
func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, logger log.Logger, queryURL string, query *AzureLogAnalyticsQuery) (*http.Request, error) {
from := query.TimeRange.From.Format(time.RFC3339)
to := query.TimeRange.To.Format(time.RFC3339)
timespan := fmt.Sprintf("%s/%s", from, to)
body := map[string]interface{}{
"query": query.Query,
"timespan": timespan,
"query": query.Query,
}
if query.IntersectTime {
from := query.TimeRange.From.Format(time.RFC3339)
to := query.TimeRange.To.Format(time.RFC3339)
timespan := fmt.Sprintf("%s/%s", from, to)
body["timespan"] = timespan
}
if len(query.Resources) > 1 && query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) && !query.AppInsightsQuery {

View File

@ -109,7 +109,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
"query": "Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
RefID: "A",
@ -127,7 +128,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
"query": "Perf | where $__timeFilter() | where $__contains(Computer, 'comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
Query: "Perf | where ['TimeGenerated'] >= datetime('2018-03-15T13:00:00Z') and ['TimeGenerated'] <= datetime('2018-03-15T13:34:00Z') | where ['Computer'] in ('comp1','comp2') | summarize avg(CounterValue) by bin(TimeGenerated, 34000ms), Computer",
@ -135,6 +137,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TimeRange: timeRange,
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
},
},
Err: require.NoError,
@ -172,6 +175,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
Resources: []string{},
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
},
},
Err: require.NoError,
@ -209,6 +213,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
Resources: []string{},
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
},
},
Err: require.NoError,
@ -222,7 +227,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
"query": "Perf",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
RefID: "A",
@ -239,13 +245,15 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
"query": "Perf",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
Query: "Perf",
Resources: []string{"/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"},
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
},
},
Err: require.NoError,
@ -259,7 +267,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resources": ["/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace", "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace2"],
"query": "Perf",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
RefID: "A",
@ -277,7 +286,8 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"azureLogAnalytics": {
"resources": ["/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace", "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace2"],
"query": "Perf",
"resultFormat": "%s"
"resultFormat": "%s",
"intersectTime": false
}
}`, types.TimeSeries)),
Query: "Perf",
@ -285,6 +295,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TimeRange: timeRange,
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
},
},
Err: require.NoError,
@ -365,6 +376,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -442,6 +454,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -516,6 +529,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"${__data.fields.traceID}\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -593,6 +607,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -675,6 +690,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -757,6 +773,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -839,6 +856,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -913,6 +931,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"${__data.fields.traceID}\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -990,6 +1009,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1035,6 +1055,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" +
"| where operation_Id == \"test-op-id\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1116,6 +1137,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').traces\n" +
"| where operation_Id == \"op-id-multi\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1194,6 +1216,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').traces\n" +
"| where operation_Id == \"${__data.fields.traceID}\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1275,6 +1298,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').traces\n" +
"| where operation_Id == \"op-id-multi\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1363,6 +1387,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
"app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').traces\n" +
"| where operation_Id == \"op-id-non-overlapping\"",
AppInsightsQuery: true,
IntersectTime: true,
},
},
Err: require.NoError,
@ -1389,6 +1414,31 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{
Resources: []string{"r"},
Query: "Perf",
IntersectTime: false,
AppInsightsQuery: false,
})
require.NoError(t, err)
if req.URL.String() != url {
t.Errorf("Expecting %s, got %s", url, req.URL.String())
}
expectedHeaders := http.Header{"Content-Type": []string{"application/json"}}
if !cmp.Equal(req.Header, expectedHeaders) {
t.Errorf("Unexpected HTTP headers: %v", cmp.Diff(req.Header, expectedHeaders))
}
expectedBody := `{"query":"Perf"}`
body, err := io.ReadAll(req.Body)
require.NoError(t, err)
if !cmp.Equal(string(body), expectedBody) {
t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody))
}
})
t.Run("creates a request with timespan", func(t *testing.T) {
ds := AzureLogAnalyticsDatasource{}
req, err := ds.createRequest(ctx, logger, url, &AzureLogAnalyticsQuery{
Resources: []string{"r"},
Query: "Perf",
IntersectTime: true,
AppInsightsQuery: false,
})
require.NoError(t, err)
@ -1414,9 +1464,10 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
Query: "Perf",
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
AppInsightsQuery: false,
IntersectTime: false,
})
require.NoError(t, err)
expectedBody := `{"query":"Perf","timespan":"0001-01-01T00:00:00Z/0001-01-01T00:00:00Z","workspaces":["/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r1","/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r2"]}`
expectedBody := `{"query":"Perf","workspaces":["/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r1","/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r2"]}`
body, err := io.ReadAll(req.Body)
require.NoError(t, err)
if !cmp.Equal(string(body), expectedBody) {
@ -1437,6 +1488,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
To: to,
},
AppInsightsQuery: false,
IntersectTime: true,
})
require.NoError(t, err)
expectedBody := fmt.Sprintf(`{"query":"Perf","timespan":"%s/%s","workspaces":["/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r1","/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/r2"]}`, from.Format(time.RFC3339), to.Format(time.RFC3339))
@ -1459,6 +1511,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
To: to,
},
AppInsightsQuery: true,
IntersectTime: true,
})
require.NoError(t, err)
expectedBody := fmt.Sprintf(`{"applications":["/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1","/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"],"query":"","timespan":"%s/%s"}`, from.Format(time.RFC3339), to.Format(time.RFC3339))

View File

@ -18,6 +18,7 @@ export default function createMockQuery(overrides?: Partial<AzureMonitorQuery>):
resultFormat: ResultFormat.Table,
workspace: 'e3fe4fde-ad5e-4d60-9974-e2f3562ffdf2',
resources: ['test-resource'],
intersectTime: false,
...overrides?.azureLogAnalytics,
},

View File

@ -131,6 +131,7 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
resources,
// Workspace was removed in Grafana 8, but remains for backwards compat
workspace,
intersectTime: target.azureLogAnalytics.intersectTime,
},
};
}

View File

@ -184,4 +184,31 @@ describe('LogsQueryEditor', () => {
})
);
});
it('should update the intersectTime prop', async () => {
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
const query = createMockQuery();
const onChange = jest.fn();
render(
<LogsQueryEditor
query={query}
datasource={mockDatasource}
variableOptionGroup={variableOptionGroup}
onChange={onChange}
setError={() => {}}
/>
);
const intersectionOption = await screen.findByLabelText('Intersection');
await userEvent.click(intersectionOption);
expect(onChange).toBeCalledWith(
expect.objectContaining({
azureLogAnalytics: expect.objectContaining({
intersectTime: true,
}),
})
);
});
});

View File

@ -1,7 +1,7 @@
import React from 'react';
import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
import { Alert } from '@grafana/ui';
import { Alert, InlineField, RadioButtonGroup } from '@grafana/ui';
import Datasource from '../../datasource';
import { selectors } from '../../e2e/selectors';
@ -13,7 +13,7 @@ import { parseResourceDetails } from '../ResourcePicker/utils';
import AdvancedResourcePicker from './AdvancedResourcePicker';
import QueryField from './QueryField';
import { setFormatAs } from './setQueryValue';
import { setFormatAs, setIntersectTime } from './setQueryValue';
import useMigrations from './useMigrations';
interface LogsQueryEditorProps {
@ -81,6 +81,22 @@ const LogsQueryEditor = ({
)}
selectionNotice={() => 'You may only choose items of the same resource type.'}
/>
<InlineField
label="Time-range"
tooltip={
'Specifies the time-range used to query. The query option will only use time-ranges specified in the query. Intersection will combine query time-ranges with the Grafana time-range.'
}
>
<RadioButtonGroup
options={[
{ label: 'Query', value: false },
{ label: 'Intersection', value: true },
]}
value={query.azureLogAnalytics?.intersectTime ?? false}
size={'md'}
onChange={(val) => onChange(setIntersectTime(query, val))}
/>
</InlineField>
</EditorFieldGroup>
</EditorRow>
<QueryField

View File

@ -19,3 +19,13 @@ export function setFormatAs(query: AzureMonitorQuery, formatAs: ResultFormat): A
},
};
}
export function setIntersectTime(query: AzureMonitorQuery, intersectTime: boolean): AzureMonitorQuery {
return {
...query,
azureLogAnalytics: {
...query.azureLogAnalytics,
intersectTime,
},
};
}

View File

@ -115,6 +115,8 @@ composableKinds: DataQuery: {
resultFormat?: #ResultFormat
// Array of resource URIs to be queried.
resources?: [...string]
// If set to true the intersection of time ranges specified in the query and Grafana will be used. Otherwise the query time ranges will be used. Defaults to false
intersectTime?: bool
// Workspace ID. This was removed in Grafana 8, but remains for backwards compat
workspace?: string

View File

@ -161,6 +161,10 @@ export const defaultAzureMetricQuery: Partial<AzureMetricQuery> = {
* Azure Monitor Logs sub-query properties
*/
export interface AzureLogsQuery {
/**
* If set to true the intersection of time ranges specified in the query and Grafana will be used. Otherwise the query time ranges will be used. Defaults to false
*/
intersectTime?: boolean;
/**
* KQL query to be executed.
*/

View File

@ -12,6 +12,11 @@ const azureMonitorQueryV8 = {
resourceName: 'AppInsightsTestData',
timeGrain: 'auto',
},
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
resultFormat: ResultFormat.TimeSeries,
},
datasource: {
type: 'grafana-azure-monitor-datasource',
uid: 'sD-ZuB87k',
@ -33,6 +38,11 @@ const azureMonitorQueryV9_0 = {
'/subscriptions/44693801-6ee6-49de-9b2d-9106972f9572/resourceGroups/cloud-datasources/providers/microsoft.insights/components/AppInsightsTestData',
timeGrain: 'auto',
},
azureLogAnalytics: {
query:
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
resultFormat: ResultFormat.TimeSeries,
},
datasource: {
type: 'grafana-azure-monitor-datasource',
uid: 'sD-ZuB87k',
@ -47,6 +57,7 @@ const modernMetricsQuery: AzureMonitorQuery = {
'//change this example to create your own time series query\n<table name> //the table to query (e.g. Usage, Heartbeat, Perf)\n| where $__timeFilter(TimeGenerated) //this is a macro used to show the full charts time range, choose the datetime column here\n| summarize count() by <group by column>, bin(TimeGenerated, $__interval) //change “group by column” to a column in your table, such as “Computer”. The $__interval macro is used to auto-select the time grain. Can also use 1h, 5m etc.\n| order by TimeGenerated asc',
resultFormat: ResultFormat.TimeSeries,
workspace: 'mock-workspace-id',
intersectTime: false,
},
azureMonitor: {
aggregation: 'Average',
@ -190,6 +201,17 @@ describe('AzureMonitor: migrateQuery', () => {
})
);
});
it('correctly adds the intersectTime property', () => {
const result = migrateQuery({ ...azureMonitorQueryV8 });
expect(result).toMatchObject(
expect.objectContaining({
azureLogAnalytics: expect.objectContaining({
intersectTime: false,
}),
})
);
});
});
describe('migrating from a v9.0 query to the latest query version', () => {
@ -219,6 +241,17 @@ describe('AzureMonitor: migrateQuery', () => {
expect(result.azureMonitor).not.toHaveProperty('resourceGroup');
expect(result.azureMonitor).not.toHaveProperty('resourceName');
});
it('correctly adds the intersectTime property', () => {
const result = migrateQuery({ ...azureMonitorQueryV9_0 });
expect(result).toMatchObject(
expect.objectContaining({
azureLogAnalytics: expect.objectContaining({
intersectTime: false,
}),
})
);
});
});
it('should migrate a single resource for Logs', () => {

View File

@ -44,6 +44,16 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer
delete workingQuery.azureLogAnalytics?.resource;
}
if (workingQuery.azureLogAnalytics && workingQuery.azureLogAnalytics.intersectTime === undefined) {
workingQuery = {
...workingQuery,
azureLogAnalytics: {
...workingQuery.azureLogAnalytics,
intersectTime: false,
},
};
}
return workingQuery;
}