mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AzureMonitor: Improve Log Analytics query efficiency (#74675)
* Promisify loading schema - Move schema loading to LogsQueryEditor - Improve typing - Switch callbacks to promises * Update types * Refactor backend for new props - Rename intersectTime - Support setting timeColumn - Add additional properties to logs request body * Update applyTemplateVariables * Update set functions * Add new TimeManagement component * Update LogsQueryEditor * Hardcode timestamp column for traces queries * Ensure timeColumn is always set for log queries * Update tests * Update frontend tests * Readd type to make migration easier * Add migration * Add fake schema * Use predefined type * Update checks and defaults * Add tests * README updates * README update * Type update * Lint * More linting and type fixing * Error silently * More linting and typing * Update betterer * Update test * Simplify default column setting * Fix default column setting * Add tracking * Review - Fix typo on comment - Destructure and remove type assertion - Break out await into two variables - Remove lets and rename variable for clarity
This commit is contained in:
parent
025979df75
commit
b779ce5687
@ -3163,9 +3163,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/azuremonitor/components/LogsQueryEditor/QueryField.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"]
|
||||
],
|
||||
"public/app/plugins/datasource/azuremonitor/components/MonitorConfig.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -147,10 +147,10 @@ The Azure documentation includes resources to help you learn KQL:
|
||||
- [SQL to Kusto cheat sheet](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/sqlcheatsheet)
|
||||
|
||||
> **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.
|
||||
> Specifying `Dashboard` will only make use of the Grafana time-range.
|
||||
> If there are no time-ranges specified within the query, the default Log Analytics time-range will apply.
|
||||
> 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.
|
||||
> If the `Intersection` option was previously chosen it will be migrated by default to `Dashboard`.
|
||||
|
||||
This example query returns a virtual machine's CPU performance, averaged over 5ms time grains:
|
||||
|
||||
|
@ -165,7 +165,11 @@ export const defaultAzureMetricQuery: Partial<AzureMetricQuery> = {
|
||||
*/
|
||||
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
|
||||
* If set to true the dashboard time range will be used as a filter for the query. Otherwise the query time ranges will be used. Defaults to false.
|
||||
*/
|
||||
dashboardTime?: boolean;
|
||||
/**
|
||||
* @deprecated Use dashboardTime instead
|
||||
*/
|
||||
intersectTime?: boolean;
|
||||
/**
|
||||
@ -185,7 +189,11 @@ export interface AzureLogsQuery {
|
||||
*/
|
||||
resultFormat?: ResultFormat;
|
||||
/**
|
||||
* Workspace ID. This was removed in Grafana 8, but remains for backwards compat
|
||||
* If dashboardTime is set to true this value dictates which column the time filter will be applied to. Defaults to the first tables timeSpan column, the first datetime column found, or TimeGenerated
|
||||
*/
|
||||
timeColumn?: string;
|
||||
/**
|
||||
* Workspace ID. This was removed in Grafana 8, but remains for backwards compat.
|
||||
*/
|
||||
workspace?: string;
|
||||
}
|
||||
|
@ -118,7 +118,10 @@ 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
|
||||
// If set to true the dashboard time range will be used as a filter for the query. Otherwise the query time ranges will be used. Defaults to false.
|
||||
DashboardTime *bool `json:"dashboardTime,omitempty"`
|
||||
|
||||
// @deprecated Use dashboardTime instead
|
||||
IntersectTime *bool `json:"intersectTime,omitempty"`
|
||||
|
||||
// KQL query to be executed.
|
||||
@ -131,7 +134,10 @@ type AzureLogsQuery struct {
|
||||
Resources []string `json:"resources,omitempty"`
|
||||
ResultFormat *ResultFormat `json:"resultFormat,omitempty"`
|
||||
|
||||
// Workspace ID. This was removed in Grafana 8, but remains for backwards compat
|
||||
// If dashboardTime is set to true this value dictates which column the time filter will be applied to. Defaults to the first tables timeSpan column, the first datetime column found, or TimeGenerated
|
||||
TimeColumn *string `json:"timeColumn,omitempty"`
|
||||
|
||||
// Workspace ID. This was removed in Grafana 8, but remains for backwards compat.
|
||||
Workspace *string `json:"workspace,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,8 @@ type AzureLogAnalyticsQuery struct {
|
||||
Resources []string
|
||||
QueryType string
|
||||
AppInsightsQuery bool
|
||||
IntersectTime bool
|
||||
DashboardTime bool
|
||||
TimeColumn string
|
||||
}
|
||||
|
||||
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) (http.ResponseWriter, error) {
|
||||
@ -107,7 +108,8 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, queries
|
||||
traceExploreQuery := ""
|
||||
traceParentExploreQuery := ""
|
||||
traceLogsExploreQuery := ""
|
||||
intersectTime := false
|
||||
dashboardTime := false
|
||||
timeColumn := ""
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) {
|
||||
queryJSONModel := types.LogJSONQuery{}
|
||||
err := json.Unmarshal(query.JSON, &queryJSONModel)
|
||||
@ -143,8 +145,16 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, queries
|
||||
queryString = *azureLogAnalyticsTarget.Query
|
||||
}
|
||||
|
||||
if azureLogAnalyticsTarget.IntersectTime != nil {
|
||||
intersectTime = *azureLogAnalyticsTarget.IntersectTime
|
||||
if azureLogAnalyticsTarget.DashboardTime != nil {
|
||||
dashboardTime = *azureLogAnalyticsTarget.DashboardTime
|
||||
if dashboardTime {
|
||||
if azureLogAnalyticsTarget.TimeColumn != nil {
|
||||
timeColumn = *azureLogAnalyticsTarget.TimeColumn
|
||||
} else {
|
||||
// Final fallback to TimeGenerated if no column is provided
|
||||
timeColumn = "TimeGenerated"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +228,8 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, queries
|
||||
return nil, fmt.Errorf("failed to create traces logs explore query: %s", err)
|
||||
}
|
||||
|
||||
intersectTime = true
|
||||
dashboardTime = true
|
||||
timeColumn = "timestamp"
|
||||
}
|
||||
|
||||
apiURL := getApiURL(resourceOrWorkspace, appInsightsQuery)
|
||||
@ -241,7 +252,8 @@ func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, queries
|
||||
TraceParentExploreQuery: traceParentExploreQuery,
|
||||
TraceLogsExploreQuery: traceLogsExploreQuery,
|
||||
AppInsightsQuery: appInsightsQuery,
|
||||
IntersectTime: intersectTime,
|
||||
DashboardTime: dashboardTime,
|
||||
TimeColumn: timeColumn,
|
||||
})
|
||||
}
|
||||
|
||||
@ -432,11 +444,14 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, queryUR
|
||||
"query": query.Query,
|
||||
}
|
||||
|
||||
if query.IntersectTime {
|
||||
if query.DashboardTime {
|
||||
from := query.TimeRange.From.Format(time.RFC3339)
|
||||
to := query.TimeRange.To.Format(time.RFC3339)
|
||||
timespan := fmt.Sprintf("%s/%s", from, to)
|
||||
body["timespan"] = timespan
|
||||
body["query_datetimescope_from"] = from
|
||||
body["query_datetimescope_to"] = to
|
||||
body["query_datetimescope_column"] = query.TimeColumn
|
||||
}
|
||||
|
||||
if len(query.Resources) > 1 && query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) && !query.AppInsightsQuery {
|
||||
|
@ -107,7 +107,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"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",
|
||||
"intersectTime": false
|
||||
"dashboardTime": false
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
RefID: "A",
|
||||
@ -126,7 +126,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"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",
|
||||
"intersectTime": false
|
||||
"dashboardTime": 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",
|
||||
@ -134,7 +134,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -172,7 +172,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
Resources: []string{},
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -210,7 +210,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
Resources: []string{},
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -225,7 +225,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s",
|
||||
"intersectTime": false
|
||||
"dashboardTime": false
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
RefID: "A",
|
||||
@ -243,14 +243,14 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"resource": "/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace",
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s",
|
||||
"intersectTime": false
|
||||
"dashboardTime": 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,
|
||||
DashboardTime: false,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -265,7 +265,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"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",
|
||||
"intersectTime": false
|
||||
"dashboardTime": false
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
RefID: "A",
|
||||
@ -284,7 +284,7 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
"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",
|
||||
"intersectTime": false
|
||||
"dashboardTime": false
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Query: "Perf",
|
||||
@ -292,7 +292,52 @@ func TestBuildingAzureLogAnalyticsQueries(t *testing.T) {
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "Query that uses dashboard time",
|
||||
queryModel: []backend.DataQuery{
|
||||
{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resources": ["/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"],
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s",
|
||||
"dashboardTime": true,
|
||||
"timeColumn": "TimeGenerated"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
RefID: "A",
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
},
|
||||
},
|
||||
azureLogAnalyticsQueries: []*AzureLogAnalyticsQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
ResultFormat: types.TimeSeries,
|
||||
URL: "v1/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace/query",
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Log Analytics",
|
||||
"azureLogAnalytics": {
|
||||
"resources": ["/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"],
|
||||
"query": "Perf",
|
||||
"resultFormat": "%s",
|
||||
"dashboardTime": true,
|
||||
"timeColumn": "TimeGenerated"
|
||||
}
|
||||
}`, types.TimeSeries)),
|
||||
Query: "Perf",
|
||||
Resources: []string{"/subscriptions/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/resourceGroups/cloud-datasources/providers/Microsoft.OperationalInsights/workspaces/AppInsightsTestDataWorkspace"},
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "TimeGenerated",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -373,7 +418,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -451,7 +497,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -526,7 +573,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -604,7 +652,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -687,7 +736,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -770,7 +820,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -853,7 +904,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -928,7 +980,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1006,7 +1059,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1052,7 +1106,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1134,7 +1189,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1213,7 +1269,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1295,7 +1352,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1384,7 +1442,8 @@ 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,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
},
|
||||
},
|
||||
Err: require.NoError,
|
||||
@ -1411,7 +1470,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
req, err := ds.createRequest(ctx, url, &AzureLogAnalyticsQuery{
|
||||
Resources: []string{"r"},
|
||||
Query: "Perf",
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
AppInsightsQuery: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@ -1430,30 +1489,6 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creates a request with timespan", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
req, err := ds.createRequest(ctx, url, &AzureLogAnalyticsQuery{
|
||||
Resources: []string{"r"},
|
||||
Query: "Perf",
|
||||
IntersectTime: true,
|
||||
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","timespan":"0001-01-01T00:00:00Z/0001-01-01T00:00:00Z"}`
|
||||
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 multiple resources", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
req, err := ds.createRequest(ctx, url, &AzureLogAnalyticsQuery{
|
||||
@ -1461,7 +1496,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
Query: "Perf",
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureLogAnalytics),
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: false,
|
||||
DashboardTime: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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"]}`
|
||||
@ -1472,7 +1507,7 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creates a request with timerange from query", func(t *testing.T) {
|
||||
t.Run("creates a request with timerange from dashboard", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
from := time.Now()
|
||||
to := from.Add(3 * time.Hour)
|
||||
@ -1485,10 +1520,11 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
To: to,
|
||||
},
|
||||
AppInsightsQuery: false,
|
||||
IntersectTime: true,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "TimeGenerated",
|
||||
})
|
||||
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))
|
||||
expectedBody := fmt.Sprintf(`{"query":"Perf","query_datetimescope_column":"TimeGenerated","query_datetimescope_from":"%s","query_datetimescope_to":"%s","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), from.Format(time.RFC3339), to.Format(time.RFC3339))
|
||||
body, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(string(body), expectedBody) {
|
||||
@ -1508,10 +1544,11 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
To: to,
|
||||
},
|
||||
AppInsightsQuery: true,
|
||||
IntersectTime: true,
|
||||
DashboardTime: true,
|
||||
TimeColumn: "timestamp",
|
||||
})
|
||||
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))
|
||||
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":"","query_datetimescope_column":"timestamp","query_datetimescope_from":"%s","query_datetimescope_to":"%s","timespan":"%s/%s"}`, from.Format(time.RFC3339), to.Format(time.RFC3339), from.Format(time.RFC3339), to.Format(time.RFC3339))
|
||||
body, err := io.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(string(body), expectedBody) {
|
||||
|
@ -18,7 +18,8 @@ export default function createMockQuery(overrides?: Partial<AzureMonitorQuery>):
|
||||
resultFormat: ResultFormat.Table,
|
||||
workspace: 'e3fe4fde-ad5e-4d60-9974-e2f3562ffdf2',
|
||||
resources: ['test-resource'],
|
||||
intersectTime: false,
|
||||
dashboardTime: false,
|
||||
timeColumn: 'TimeGenerated',
|
||||
...overrides?.azureLogAnalytics,
|
||||
},
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { AzureLogAnalyticsMetadataTable, EngineSchema } from '../../types';
|
||||
|
||||
export default class FakeSchemaData {
|
||||
static getLogAnalyticsFakeSchema() {
|
||||
return {
|
||||
@ -317,4 +319,68 @@ export default class FakeSchemaData {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static getLogAnalyticsFakeEngineSchema(tableOverride?: AzureLogAnalyticsMetadataTable[]): EngineSchema {
|
||||
const database = {
|
||||
name: 'test',
|
||||
tables: tableOverride ?? [
|
||||
{
|
||||
id: 't/Alert',
|
||||
name: 'Alert',
|
||||
timespanColumn: 'TimeGenerated',
|
||||
columns: [
|
||||
{ name: 'TimeGenerated', type: 'datetime' },
|
||||
{ name: 'AlertSeverity', type: 'string' },
|
||||
{ name: 'SourceDisplayName', type: 'string' },
|
||||
{ name: 'AlertName', type: 'string' },
|
||||
{ name: 'AlertDescription', type: 'string' },
|
||||
{ name: 'SourceSystem', type: 'string' },
|
||||
{ name: 'QueryExecutionStartTime', type: 'datetime' },
|
||||
{ name: 'QueryExecutionEndTime', type: 'datetime' },
|
||||
{ name: 'Query', type: 'string' },
|
||||
{ name: 'RemediationJobId', type: 'string' },
|
||||
{ name: 'RemediationRunbookName', type: 'string' },
|
||||
{ name: 'AlertRuleId', type: 'string' },
|
||||
{ name: 'AlertRuleInstanceId', type: 'string' },
|
||||
{ name: 'ThresholdOperator', type: 'string' },
|
||||
{ name: 'ThresholdValue', type: 'int' },
|
||||
{ name: 'LinkToSearchResults', type: 'string' },
|
||||
{ name: 'ServiceDeskConnectionName', type: 'string' },
|
||||
{ name: 'ServiceDeskId', type: 'string' },
|
||||
{ name: 'ServiceDeskWorkItemLink', type: 'string' },
|
||||
{ name: 'ServiceDeskWorkItemType', type: 'string' },
|
||||
{ name: 'ResourceId', type: 'string' },
|
||||
{ name: 'ResourceType', type: 'string' },
|
||||
{ name: 'ResourceValue', type: 'string' },
|
||||
{ name: 'RootObjectName', type: 'string' },
|
||||
{ name: 'ObjectDisplayName', type: 'string' },
|
||||
{ name: 'Computer', type: 'string' },
|
||||
{ name: 'AlertPriority', type: 'string' },
|
||||
{ name: 'SourceFullName', type: 'string' },
|
||||
{ name: 'AlertId', type: 'string' },
|
||||
{ name: 'RepeatCount', type: 'int' },
|
||||
{ name: 'AlertState', type: 'string' },
|
||||
{ name: 'ResolvedBy', type: 'string' },
|
||||
{ name: 'LastModifiedBy', type: 'string' },
|
||||
{ name: 'TimeRaised', type: 'datetime' },
|
||||
{ name: 'TimeResolved', type: 'datetime' },
|
||||
{ name: 'TimeLastModified', type: 'datetime' },
|
||||
],
|
||||
related: { solutions: [] },
|
||||
},
|
||||
],
|
||||
functions: [],
|
||||
majorVersion: 0,
|
||||
minorVersion: 0,
|
||||
};
|
||||
return {
|
||||
clusterType: 'Engine',
|
||||
cluster: {
|
||||
connectionString: 'test',
|
||||
databases: [database],
|
||||
},
|
||||
database: database,
|
||||
globalScalarParameters: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -40,15 +40,15 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
});
|
||||
|
||||
it('should return a schema to use with monaco-kusto', async () => {
|
||||
const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace');
|
||||
const { database } = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace');
|
||||
|
||||
expect(result.database.tables).toHaveLength(2);
|
||||
expect(result.database.tables[0].name).toBe('Alert');
|
||||
expect(result.database.tables[0].timespanColumn).toBe('TimeGenerated');
|
||||
expect(result.database.tables[1].name).toBe('AzureActivity');
|
||||
expect(result.database.tables[0].columns).toHaveLength(69);
|
||||
expect(database?.tables).toHaveLength(2);
|
||||
expect(database?.tables[0].name).toBe('Alert');
|
||||
expect(database?.tables[0].timespanColumn).toBe('TimeGenerated');
|
||||
expect(database?.tables[1].name).toBe('AzureActivity');
|
||||
expect(database?.tables[0].columns).toHaveLength(69);
|
||||
|
||||
expect(result.database.functions[1].inputParameters).toEqual([
|
||||
expect(database?.functions[1].inputParameters).toEqual([
|
||||
{
|
||||
name: 'RangeStart',
|
||||
type: 'datetime',
|
||||
@ -77,7 +77,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
|
||||
it('should include macros as suggested functions', async () => {
|
||||
const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace');
|
||||
expect(result.database.functions.map((f: { name: string }) => f.name)).toEqual([
|
||||
expect(result.database?.functions.map((f: { name: string }) => f.name)).toEqual([
|
||||
'Func1',
|
||||
'_AzureBackup_GetVaults',
|
||||
'$__timeFilter',
|
||||
@ -90,7 +90,7 @@ describe('AzureLogAnalyticsDatasource', () => {
|
||||
|
||||
it('should include template variables as global parameters', async () => {
|
||||
const result = await ctx.datasource.azureLogAnalyticsDatasource.getKustoSchema('myWorkspace');
|
||||
expect(result.globalParameters.map((f: { name: string }) => f.name)).toEqual([`$${singleVariable.name}`]);
|
||||
expect(result.globalScalarParameters?.map((f: { name: string }) => f.name)).toEqual([`$${singleVariable.name}`]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -131,7 +131,8 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
resources,
|
||||
// Workspace was removed in Grafana 8, but remains for backwards compat
|
||||
workspace,
|
||||
intersectTime: target.azureLogAnalytics.intersectTime,
|
||||
dashboardTime: item.dashboardTime,
|
||||
timeColumn: templateSrv.replace(item.timeColumn, scopedVars),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { VariableModel } from '@grafana/data';
|
||||
|
||||
import { EngineSchema } from '../types';
|
||||
import { AzureLogAnalyticsMetadata } from '../types/logAnalyticsMetadata';
|
||||
|
||||
// matches (name):(type) = (defaultValue)
|
||||
@ -48,7 +49,7 @@ export function transformMetadataToKustoSchema(
|
||||
sourceSchema: AzureLogAnalyticsMetadata,
|
||||
nameOrIdOrSomething: string,
|
||||
templateVariables: VariableModel[]
|
||||
) {
|
||||
): EngineSchema {
|
||||
const database = {
|
||||
name: nameOrIdOrSomething,
|
||||
tables: sourceSchema.tables,
|
||||
@ -114,7 +115,7 @@ export function transformMetadataToKustoSchema(
|
||||
);
|
||||
|
||||
// Adding macros as global parameters
|
||||
const globalParameters = templateVariables.map((v) => {
|
||||
const globalScalarParameters = templateVariables.map((v) => {
|
||||
return {
|
||||
name: `$${v.name}`,
|
||||
type: 'dynamic',
|
||||
@ -128,6 +129,6 @@ export function transformMetadataToKustoSchema(
|
||||
databases: [database],
|
||||
},
|
||||
database: database,
|
||||
globalParameters,
|
||||
globalScalarParameters,
|
||||
};
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ describe('LogsQueryEditor', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should update the intersectTime prop', async () => {
|
||||
it('should update the dashboardTime prop', async () => {
|
||||
const mockDatasource = createMockDatasource({ resourcePickerData: createMockResourcePickerData() });
|
||||
const query = createMockQuery();
|
||||
const onChange = jest.fn();
|
||||
@ -200,13 +200,13 @@ describe('LogsQueryEditor', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const intersectionOption = await screen.findByLabelText('Intersection');
|
||||
await userEvent.click(intersectionOption);
|
||||
const dashboardTimeOption = await screen.findByLabelText('Dashboard');
|
||||
await userEvent.click(dashboardTimeOption);
|
||||
|
||||
expect(onChange).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
intersectTime: true,
|
||||
dashboardTime: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { EditorFieldGroup, EditorRow, EditorRows } from '@grafana/experimental';
|
||||
import { Alert, InlineField, RadioButtonGroup } from '@grafana/ui';
|
||||
import { Alert } from '@grafana/ui';
|
||||
|
||||
import Datasource from '../../datasource';
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, ResultFormat } from '../../types';
|
||||
import { AzureMonitorErrorish, AzureMonitorOption, AzureMonitorQuery, ResultFormat, EngineSchema } from '../../types';
|
||||
import FormatAsField from '../FormatAsField';
|
||||
import ResourceField from '../ResourceField';
|
||||
import { ResourceRow, ResourceRowGroup, ResourceRowType } from '../ResourcePicker/types';
|
||||
@ -13,7 +13,8 @@ import { parseResourceDetails } from '../ResourcePicker/utils';
|
||||
|
||||
import AdvancedResourcePicker from './AdvancedResourcePicker';
|
||||
import QueryField from './QueryField';
|
||||
import { setFormatAs, setIntersectTime } from './setQueryValue';
|
||||
import { TimeManagement } from './TimeManagement';
|
||||
import { setFormatAs } from './setQueryValue';
|
||||
import useMigrations from './useMigrations';
|
||||
|
||||
interface LogsQueryEditorProps {
|
||||
@ -49,6 +50,15 @@ const LogsQueryEditor = ({
|
||||
// Only resources with the same metricNamespace can be selected
|
||||
return rowResourceNS !== selectedRowSampleNs;
|
||||
};
|
||||
const [schema, setSchema] = useState<EngineSchema | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (query.azureLogAnalytics?.resources && query.azureLogAnalytics.resources.length) {
|
||||
datasource.azureLogAnalyticsDatasource.getKustoSchema(query.azureLogAnalytics.resources[0]).then((schema) => {
|
||||
setSchema(schema);
|
||||
});
|
||||
}
|
||||
}, [query.azureLogAnalytics?.resources, datasource.azureLogAnalyticsDatasource]);
|
||||
|
||||
return (
|
||||
<span data-testid={selectors.components.queryEditor.logsQueryEditor.container.input}>
|
||||
@ -81,22 +91,14 @@ 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))}
|
||||
<TimeManagement
|
||||
query={query}
|
||||
datasource={datasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
schema={schema}
|
||||
/>
|
||||
</InlineField>
|
||||
</EditorFieldGroup>
|
||||
</EditorRow>
|
||||
<QueryField
|
||||
@ -106,6 +108,7 @@ const LogsQueryEditor = ({
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={setError}
|
||||
schema={schema}
|
||||
/>
|
||||
<EditorRow>
|
||||
<EditorFieldGroup>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { EngineSchema, Schema } from '@kusto/monaco-kusto';
|
||||
import { Uri } from 'monaco-editor';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { CodeEditor, Monaco, MonacoEditor } from '@grafana/ui';
|
||||
import { Deferred } from 'app/core/utils/deferred';
|
||||
|
||||
import { AzureQueryEditorFieldProps } from '../../types';
|
||||
|
||||
import { setKustoQuery } from './setQueryValue';
|
||||
|
||||
interface MonacoPromise {
|
||||
interface MonacoEditorValues {
|
||||
editor: MonacoEditor;
|
||||
monaco: Monaco;
|
||||
}
|
||||
@ -17,50 +17,39 @@ interface MonacoLanguages {
|
||||
kusto: {
|
||||
getKustoWorker: () => Promise<
|
||||
(url: Uri) => Promise<{
|
||||
setSchema: (schema: any, clusterUrl: string, name: string) => void;
|
||||
setSchema: (schema: Schema) => void;
|
||||
}>
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
const QueryField = ({ query, datasource, onQueryChange }: AzureQueryEditorFieldProps) => {
|
||||
const monacoPromiseRef = useRef<Deferred<MonacoPromise>>();
|
||||
function getPromise() {
|
||||
if (!monacoPromiseRef.current) {
|
||||
monacoPromiseRef.current = new Deferred<MonacoPromise>();
|
||||
}
|
||||
|
||||
return monacoPromiseRef.current.promise;
|
||||
}
|
||||
const QueryField = ({ query, onQueryChange, schema }: AzureQueryEditorFieldProps) => {
|
||||
const [monaco, setMonaco] = useState<MonacoEditorValues | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!query.azureLogAnalytics?.resources || !query.azureLogAnalytics.resources.length) {
|
||||
if (!schema || !monaco) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promises = [
|
||||
datasource.azureLogAnalyticsDatasource.getKustoSchema(query.azureLogAnalytics.resources[0]),
|
||||
getPromise(),
|
||||
] as const;
|
||||
|
||||
// the kusto schema call might fail, but it's okay for that to happen silently
|
||||
Promise.all(promises).then(([schema, { monaco, editor }]) => {
|
||||
const setupEditor = async ({ monaco, editor }: MonacoEditorValues, schema: EngineSchema) => {
|
||||
try {
|
||||
const languages = monaco.languages as unknown as MonacoLanguages;
|
||||
|
||||
languages.kusto
|
||||
.getKustoWorker()
|
||||
.then((kusto) => {
|
||||
const model = editor.getModel();
|
||||
return model && kusto(model.uri);
|
||||
})
|
||||
.then((worker) => {
|
||||
worker?.setSchema(schema, 'https://help.kusto.windows.net', 'Samples');
|
||||
});
|
||||
});
|
||||
}, [datasource.azureLogAnalyticsDatasource, query.azureLogAnalytics?.resources]);
|
||||
if (model) {
|
||||
const kustoWorker = await languages.kusto.getKustoWorker();
|
||||
const kustoMode = await kustoWorker(model?.uri);
|
||||
await kustoMode.setSchema(schema);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
setupEditor(monaco, schema).catch((err) => console.error(err));
|
||||
}, [schema, monaco]);
|
||||
|
||||
const handleEditorMount = useCallback((editor: MonacoEditor, monaco: Monaco) => {
|
||||
monacoPromiseRef.current?.resolve?.({ editor, monaco });
|
||||
setMonaco({ monaco, editor });
|
||||
}, []);
|
||||
|
||||
const onChange = useCallback(
|
||||
|
@ -0,0 +1,172 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import createMockDatasource from '../../__mocks__/datasource';
|
||||
import createMockQuery from '../../__mocks__/query';
|
||||
import FakeSchemaData from '../../azure_log_analytics/__mocks__/schema';
|
||||
|
||||
import { TimeManagement } from './TimeManagement';
|
||||
|
||||
const variableOptionGroup = {
|
||||
label: 'Template variables',
|
||||
options: [],
|
||||
};
|
||||
|
||||
describe('LogsQueryEditor.TimeManagement', () => {
|
||||
it('should render the column picker if Dashboard is chosen', async () => {
|
||||
const mockDatasource = createMockDatasource();
|
||||
const query = createMockQuery({ azureLogAnalytics: { timeColumn: undefined } });
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { rerender } = render(
|
||||
<TimeManagement
|
||||
query={query}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={() => {}}
|
||||
schema={FakeSchemaData.getLogAnalyticsFakeEngineSchema()}
|
||||
/>
|
||||
);
|
||||
|
||||
const dashboardTimeOption = await screen.findByLabelText('Dashboard');
|
||||
await userEvent.click(dashboardTimeOption);
|
||||
|
||||
expect(onChange).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
dashboardTime: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
rerender(
|
||||
<TimeManagement
|
||||
query={{ ...query, azureLogAnalytics: { ...query.azureLogAnalytics, dashboardTime: true } }}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={() => {}}
|
||||
schema={FakeSchemaData.getLogAnalyticsFakeEngineSchema()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onChange).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
timeColumn: 'TimeGenerated',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the default value if no time columns exist', async () => {
|
||||
const mockDatasource = createMockDatasource();
|
||||
const query = createMockQuery();
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TimeManagement
|
||||
query={{
|
||||
...query,
|
||||
azureLogAnalytics: { ...query.azureLogAnalytics, dashboardTime: true, timeColumn: undefined },
|
||||
}}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={() => {}}
|
||||
schema={FakeSchemaData.getLogAnalyticsFakeEngineSchema([
|
||||
{
|
||||
id: 't/Alert',
|
||||
name: 'Alert',
|
||||
timespanColumn: 'TimeGenerated',
|
||||
columns: [],
|
||||
related: {
|
||||
solutions: [],
|
||||
},
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onChange).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
timeColumn: 'TimeGenerated',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the first time column if no default exists', async () => {
|
||||
const mockDatasource = createMockDatasource();
|
||||
const query = createMockQuery();
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TimeManagement
|
||||
query={{
|
||||
...query,
|
||||
azureLogAnalytics: { ...query.azureLogAnalytics, dashboardTime: true, timeColumn: undefined },
|
||||
}}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={() => {}}
|
||||
schema={FakeSchemaData.getLogAnalyticsFakeEngineSchema([
|
||||
{
|
||||
id: 't/Alert',
|
||||
name: 'Alert',
|
||||
timespanColumn: '',
|
||||
columns: [{ name: 'Timespan', type: 'datetime' }],
|
||||
related: {
|
||||
solutions: [],
|
||||
},
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onChange).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
timeColumn: 'Timespan',
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the query time column if it exists', async () => {
|
||||
const mockDatasource = createMockDatasource();
|
||||
const query = createMockQuery();
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TimeManagement
|
||||
query={{
|
||||
...query,
|
||||
azureLogAnalytics: { ...query.azureLogAnalytics, dashboardTime: true, timeColumn: 'TestTimeColumn' },
|
||||
}}
|
||||
datasource={mockDatasource}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
onQueryChange={onChange}
|
||||
setError={() => {}}
|
||||
schema={FakeSchemaData.getLogAnalyticsFakeEngineSchema([
|
||||
{
|
||||
id: 't/Alert',
|
||||
name: 'Alert',
|
||||
timespanColumn: '',
|
||||
columns: [{ name: 'TestTimeColumn', type: 'datetime' }],
|
||||
related: {
|
||||
solutions: [],
|
||||
},
|
||||
},
|
||||
])}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(onChange).not.toBeCalled();
|
||||
expect(screen.getByText('Alert > TestTimeColumn')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,137 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineField, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
|
||||
import { AzureQueryEditorFieldProps } from '../../types';
|
||||
|
||||
import { setDashboardTime, setTimeColumn } from './setQueryValue';
|
||||
|
||||
export function TimeManagement({ query, onQueryChange: onChange, schema }: AzureQueryEditorFieldProps) {
|
||||
const [defaultTimeColumns, setDefaultTimeColumns] = useState<SelectableValue[] | undefined>();
|
||||
const [timeColumns, setTimeColumns] = useState<SelectableValue[] | undefined>();
|
||||
|
||||
const setDefaultColumn = useCallback((column: string) => onChange(setTimeColumn(query, column)), [query, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (schema && query.azureLogAnalytics?.dashboardTime) {
|
||||
const timeColumnOptions: SelectableValue[] = [];
|
||||
const timeColumnsSet: Set<string> = new Set();
|
||||
const defaultColumnsMap: Map<string, SelectableValue> = new Map();
|
||||
const db = schema.database;
|
||||
if (db) {
|
||||
for (const table of db.tables) {
|
||||
const cols = table.columns.reduce<SelectableValue[]>((prev, curr, i) => {
|
||||
if (curr.type === 'datetime') {
|
||||
if (!table.timespanColumn || table.timespanColumn !== curr.name) {
|
||||
prev.push({ value: curr.name, label: `${table.name} > ${curr.name}` });
|
||||
timeColumnsSet.add(curr.name);
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
timeColumnOptions.push(...cols);
|
||||
if (table.timespanColumn && !defaultColumnsMap.has(table.timespanColumn)) {
|
||||
defaultColumnsMap.set(table.timespanColumn, {
|
||||
value: table.timespanColumn,
|
||||
label: table.timespanColumn,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeColumns(timeColumnOptions);
|
||||
const defaultColumns = Array.from(defaultColumnsMap.values());
|
||||
setDefaultTimeColumns(defaultColumns);
|
||||
|
||||
// Set default value
|
||||
if (
|
||||
!query.azureLogAnalytics.timeColumn ||
|
||||
(query.azureLogAnalytics.timeColumn &&
|
||||
!timeColumnsSet.has(query.azureLogAnalytics.timeColumn) &&
|
||||
!defaultColumnsMap.has(query.azureLogAnalytics.timeColumn))
|
||||
) {
|
||||
if (defaultColumns && defaultColumns.length) {
|
||||
setDefaultColumn(defaultColumns[0].value);
|
||||
setDefaultColumn(defaultColumns[0].value);
|
||||
return;
|
||||
} else if (timeColumnOptions && timeColumnOptions.length) {
|
||||
setDefaultColumn(timeColumnOptions[0].value);
|
||||
return;
|
||||
} else {
|
||||
setDefaultColumn('TimeGenerated');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [schema, query.azureLogAnalytics?.dashboardTime, query.azureLogAnalytics?.timeColumn, setDefaultColumn]);
|
||||
|
||||
const handleTimeColumnChange = useCallback(
|
||||
(change: SelectableValue<string>) => {
|
||||
if (!change.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newQuery = setTimeColumn(query, change.value);
|
||||
onChange(newQuery);
|
||||
},
|
||||
[onChange, query]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<InlineField
|
||||
label="Time-range"
|
||||
tooltip={
|
||||
<span>
|
||||
Specifies the time-range used to query. The <code>Query</code> option will only use time-ranges specified in
|
||||
the query. <code>Dashboard</code> will only use the Grafana time-range.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'Query', value: false },
|
||||
{ label: 'Dashboard', value: true },
|
||||
]}
|
||||
value={query.azureLogAnalytics?.dashboardTime ?? false}
|
||||
size={'md'}
|
||||
onChange={(val) => onChange(setDashboardTime(query, val))}
|
||||
/>
|
||||
</InlineField>
|
||||
{query.azureLogAnalytics?.dashboardTime && (
|
||||
<InlineField
|
||||
label="Time Column"
|
||||
tooltip={
|
||||
<span>
|
||||
Specifies the time column used for filtering. Defaults to the first tables <code>timeSpan</code> column,
|
||||
the first <code>datetime</code> column found or <code>TimeGenerated</code>.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'Default time columns',
|
||||
options: defaultTimeColumns ?? [{ value: 'TimeGenerated', label: 'TimeGenerated' }],
|
||||
},
|
||||
{
|
||||
label: 'Other time columns',
|
||||
options: timeColumns ?? [],
|
||||
},
|
||||
]}
|
||||
onChange={handleTimeColumnChange}
|
||||
value={
|
||||
query.azureLogAnalytics?.timeColumn
|
||||
? query.azureLogAnalytics?.timeColumn
|
||||
: defaultTimeColumns
|
||||
? defaultTimeColumns[0]
|
||||
: timeColumns
|
||||
? timeColumns[0]
|
||||
: { value: 'TimeGenerated', label: 'TimeGenerated' }
|
||||
}
|
||||
allowCustomValue
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -20,12 +20,22 @@ export function setFormatAs(query: AzureMonitorQuery, formatAs: ResultFormat): A
|
||||
};
|
||||
}
|
||||
|
||||
export function setIntersectTime(query: AzureMonitorQuery, intersectTime: boolean): AzureMonitorQuery {
|
||||
export function setDashboardTime(query: AzureMonitorQuery, dashboardTime: boolean): AzureMonitorQuery {
|
||||
return {
|
||||
...query,
|
||||
azureLogAnalytics: {
|
||||
...query.azureLogAnalytics,
|
||||
intersectTime,
|
||||
dashboardTime,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function setTimeColumn(query: AzureMonitorQuery, timeColumn: string): AzureMonitorQuery {
|
||||
return {
|
||||
...query,
|
||||
azureLogAnalytics: {
|
||||
...query.azureLogAnalytics,
|
||||
timeColumn,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -111,13 +111,17 @@ 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
|
||||
// If set to true the dashboard time range will be used as a filter for the query. Otherwise the query time ranges will be used. Defaults to false.
|
||||
dashboardTime?: bool
|
||||
// If dashboardTime is set to true this value dictates which column the time filter will be applied to. Defaults to the first tables timeSpan column, the first datetime column found, or TimeGenerated
|
||||
timeColumn?: string
|
||||
// Workspace ID. This was removed in Grafana 8, but remains for backwards compat.
|
||||
workspace?: string
|
||||
|
||||
// @deprecated Use resources instead
|
||||
resource?: string
|
||||
// @deprecated Use dashboardTime instead
|
||||
intersectTime?: bool
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
// Application Insights Traces sub-query properties
|
||||
|
@ -162,7 +162,11 @@ export const defaultAzureMetricQuery: Partial<AzureMetricQuery> = {
|
||||
*/
|
||||
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
|
||||
* If set to true the dashboard time range will be used as a filter for the query. Otherwise the query time ranges will be used. Defaults to false.
|
||||
*/
|
||||
dashboardTime?: boolean;
|
||||
/**
|
||||
* @deprecated Use dashboardTime instead
|
||||
*/
|
||||
intersectTime?: boolean;
|
||||
/**
|
||||
@ -182,7 +186,11 @@ export interface AzureLogsQuery {
|
||||
*/
|
||||
resultFormat?: ResultFormat;
|
||||
/**
|
||||
* Workspace ID. This was removed in Grafana 8, but remains for backwards compat
|
||||
* If dashboardTime is set to true this value dictates which column the time filter will be applied to. Defaults to the first tables timeSpan column, the first datetime column found, or TimeGenerated
|
||||
*/
|
||||
timeColumn?: string;
|
||||
/**
|
||||
* Workspace ID. This was removed in Grafana 8, but remains for backwards compat.
|
||||
*/
|
||||
workspace?: string;
|
||||
}
|
||||
|
@ -57,6 +57,14 @@ jest.mock('@grafana/runtime', () => {
|
||||
{ queryType: 'Azure Regions' },
|
||||
{ queryType: 'Grafana Template Variable Function' },
|
||||
{ queryType: 'unknown' },
|
||||
{
|
||||
queryType: 'Azure Log Analytics',
|
||||
azureLogAnalytics: { dashboardTime: true },
|
||||
},
|
||||
{
|
||||
queryType: 'Azure Log Analytics',
|
||||
azureLogAnalytics: { dashboardTime: false },
|
||||
},
|
||||
] as AzureMonitorQuery[],
|
||||
},
|
||||
})
|
||||
@ -78,10 +86,12 @@ describe('queriesOnInitDashboard', () => {
|
||||
azure_monitor_multiple_resource: 1,
|
||||
azure_monitor_query: 2,
|
||||
|
||||
azure_log_analytics_queries: 1,
|
||||
azure_log_analytics_queries: 3,
|
||||
azure_log_analytics_queries_hidden: 1,
|
||||
azure_log_analytics_queries_grafana_time: 1,
|
||||
azure_log_analytics_queries_query_time: 3,
|
||||
azure_log_multiple_resource: 1,
|
||||
azure_log_query: 2,
|
||||
azure_log_query: 4,
|
||||
|
||||
azure_resource_graph_queries: 1,
|
||||
azure_resource_graph_queries_hidden: 1,
|
||||
|
@ -37,6 +37,8 @@ getAppEvents().subscribe<DashboardLoadedEvent<AzureMonitorQuery>>(
|
||||
},
|
||||
[AzureQueryType.LogAnalytics]: {
|
||||
...common,
|
||||
grafanaTime: 0,
|
||||
queryTime: 0,
|
||||
},
|
||||
[AzureQueryType.AzureResourceGraph]: {
|
||||
...common,
|
||||
@ -69,6 +71,7 @@ getAppEvents().subscribe<DashboardLoadedEvent<AzureMonitorQuery>>(
|
||||
}
|
||||
if (query.queryType === AzureQueryType.LogAnalytics) {
|
||||
stats[AzureQueryType.LogAnalytics][query.hide ? 'hidden' : 'visible']++;
|
||||
stats[AzureQueryType.LogAnalytics][query.azureLogAnalytics?.dashboardTime ? 'grafanaTime' : 'queryTime']++;
|
||||
if (query.azureLogAnalytics?.resources && query.azureLogAnalytics.resources.length > 1) {
|
||||
stats[AzureQueryType.LogAnalytics].multiResource++;
|
||||
}
|
||||
@ -137,6 +140,8 @@ getAppEvents().subscribe<DashboardLoadedEvent<AzureMonitorQuery>>(
|
||||
azure_log_analytics_queries: stats[AzureQueryType.LogAnalytics].visible,
|
||||
azure_log_analytics_queries_hidden: stats[AzureQueryType.LogAnalytics].hidden,
|
||||
azure_log_multiple_resource: stats[AzureQueryType.LogAnalytics].multiResource,
|
||||
azure_log_analytics_queries_grafana_time: stats[AzureQueryType.LogAnalytics].grafanaTime,
|
||||
azure_log_analytics_queries_query_time: stats[AzureQueryType.LogAnalytics].queryTime,
|
||||
azure_log_query: stats[AzureQueryType.LogAnalytics].count,
|
||||
|
||||
// ARG queries stats
|
||||
|
@ -38,6 +38,10 @@ export type AzureMonitorDashboardLoadedProps = {
|
||||
azure_log_analytics_queries_hidden: number;
|
||||
/** number of Azure Log Analytics queries using multiple resources */
|
||||
azure_log_multiple_resource: number;
|
||||
/** number of Azure Log Analytics queries using time-range defined explicitly in query */
|
||||
azure_log_analytics_queries_query_time: number;
|
||||
/** number of Azure Log Analytics queries using Grafana time-range */
|
||||
azure_log_analytics_queries_grafana_time: number;
|
||||
/** number of Azure Log Analytics queries */
|
||||
azure_log_query: number;
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ScalarParameter, TabularParameter, Function } from '@kusto/monaco-kusto';
|
||||
|
||||
import {
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceJsonData,
|
||||
@ -8,6 +10,7 @@ import {
|
||||
|
||||
import Datasource from '../datasource';
|
||||
|
||||
import { AzureLogAnalyticsMetadataTable } from './logAnalyticsMetadata';
|
||||
import { AzureMonitorQuery, ResultFormat } from './query';
|
||||
|
||||
export type AzureDataSourceSettings = DataSourceSettings<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
|
||||
@ -131,11 +134,32 @@ export interface AzureQueryEditorFieldProps {
|
||||
datasource: Datasource;
|
||||
subscriptionId?: string;
|
||||
variableOptionGroup: VariableOptionGroup;
|
||||
schema?: EngineSchema;
|
||||
|
||||
onQueryChange: (newQuery: AzureMonitorQuery) => void;
|
||||
setError: (source: string, error: AzureMonitorErrorish | undefined) => void;
|
||||
}
|
||||
|
||||
// To avoid a type issue we redeclare the EngineSchema type from @kusto/monaco-kusto
|
||||
export interface EngineSchema {
|
||||
clusterType: 'Engine';
|
||||
cluster: {
|
||||
connectionString: string;
|
||||
databases: Database[];
|
||||
};
|
||||
database: Database | undefined;
|
||||
globalScalarParameters?: ScalarParameter[];
|
||||
globalTabularParameters?: TabularParameter[];
|
||||
}
|
||||
|
||||
export interface Database {
|
||||
name: string;
|
||||
tables: AzureLogAnalyticsMetadataTable[];
|
||||
functions: Function[];
|
||||
majorVersion: number;
|
||||
minorVersion: number;
|
||||
}
|
||||
|
||||
export interface FormatAsFieldProps extends AzureQueryEditorFieldProps {
|
||||
inputId: string;
|
||||
options: Array<SelectableValue<ResultFormat>>;
|
||||
|
@ -57,7 +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 chart’s 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,
|
||||
dashboardTime: false,
|
||||
},
|
||||
azureMonitor: {
|
||||
aggregation: 'Average',
|
||||
@ -202,12 +202,12 @@ describe('AzureMonitor: migrateQuery', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('correctly adds the intersectTime property', () => {
|
||||
it('correctly adds the dashboardTime property', () => {
|
||||
const result = migrateQuery({ ...azureMonitorQueryV8 });
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
intersectTime: false,
|
||||
dashboardTime: false,
|
||||
}),
|
||||
})
|
||||
);
|
||||
@ -242,12 +242,12 @@ describe('AzureMonitor: migrateQuery', () => {
|
||||
expect(result.azureMonitor).not.toHaveProperty('resourceName');
|
||||
});
|
||||
|
||||
it('correctly adds the intersectTime property', () => {
|
||||
it('correctly adds the dashboardTime property', () => {
|
||||
const result = migrateQuery({ ...azureMonitorQueryV9_0 });
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
intersectTime: false,
|
||||
dashboardTime: false,
|
||||
}),
|
||||
})
|
||||
);
|
||||
@ -265,4 +265,20 @@ describe('AzureMonitor: migrateQuery', () => {
|
||||
const result = migrateQuery(q);
|
||||
expect(result.azureLogAnalytics?.resources).toEqual(['foo']);
|
||||
});
|
||||
|
||||
it('correctly migrates intersectTime to dashboardTime', () => {
|
||||
const query = modernMetricsQuery;
|
||||
delete query.azureLogAnalytics?.dashboardTime;
|
||||
const result = migrateQuery({
|
||||
...query,
|
||||
azureLogAnalytics: { ...query.azureLogAnalytics, intersectTime: true },
|
||||
});
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
azureLogAnalytics: expect.objectContaining({
|
||||
dashboardTime: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -44,14 +44,24 @@ export default function migrateQuery(query: AzureMonitorQuery): AzureMonitorQuer
|
||||
delete workingQuery.azureLogAnalytics?.resource;
|
||||
}
|
||||
|
||||
if (workingQuery.azureLogAnalytics && workingQuery.azureLogAnalytics.intersectTime === undefined) {
|
||||
if (workingQuery.azureLogAnalytics && workingQuery.azureLogAnalytics.dashboardTime === undefined) {
|
||||
if (workingQuery.azureLogAnalytics.intersectTime) {
|
||||
workingQuery = {
|
||||
...workingQuery,
|
||||
azureLogAnalytics: {
|
||||
...workingQuery.azureLogAnalytics,
|
||||
intersectTime: false,
|
||||
dashboardTime: true,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
workingQuery = {
|
||||
...workingQuery,
|
||||
azureLogAnalytics: {
|
||||
...workingQuery.azureLogAnalytics,
|
||||
dashboardTime: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return workingQuery;
|
||||
|
Loading…
Reference in New Issue
Block a user