AzureMonitor: Fix Log Analytics portal links (#65482)
* Fix and update Log Analytics portal links - Build portal URL in backend - Correctly set multiple resource value - Move AddConfigLinks util function - Add necessary types - Remove unused functions * Fix lint issue * Remove unused cache variable
This commit is contained in:
@@ -4,10 +4,12 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -16,7 +18,6 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/macros"
|
||||
@@ -204,17 +205,16 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
return dataResponse
|
||||
}
|
||||
|
||||
model, err := simplejson.NewJson(query.JSON)
|
||||
azurePortalBaseUrl, err := GetAzurePortalUrl(dsInfo.Cloud)
|
||||
if err != nil {
|
||||
return dataResponseErrorWithExecuted(err)
|
||||
dataResponse.Error = err
|
||||
return dataResponse
|
||||
}
|
||||
|
||||
err = setAdditionalFrameMeta(frame,
|
||||
query.Query,
|
||||
model.Get("azureLogAnalytics").Get("resource").MustString())
|
||||
queryUrl, err := getQueryUrl(query.Query, query.Resources, azurePortalBaseUrl)
|
||||
if err != nil {
|
||||
frame.AppendNotices(data.Notice{Severity: data.NoticeSeverityWarning, Text: "could not add custom metadata: " + err.Error()})
|
||||
logger.Warn("failed to add custom metadata to azure log analytics response", err)
|
||||
dataResponse.Error = err
|
||||
return dataResponse
|
||||
}
|
||||
|
||||
if query.ResultFormat == types.TimeSeries {
|
||||
@@ -229,6 +229,8 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
|
||||
}
|
||||
}
|
||||
|
||||
AddConfigLinks(*frame, queryUrl)
|
||||
|
||||
dataResponse.Frames = data.Frames{frame}
|
||||
return dataResponse
|
||||
}
|
||||
@@ -268,6 +270,43 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, logger
|
||||
return req, nil
|
||||
}
|
||||
|
||||
type AzureLogAnalyticsURLResources struct {
|
||||
Resources []AzureLogAnalyticsURLResource `json:"resources"`
|
||||
}
|
||||
|
||||
type AzureLogAnalyticsURLResource struct {
|
||||
ResourceID string `json:"resourceId"`
|
||||
}
|
||||
|
||||
func getQueryUrl(query string, resources []string, azurePortalUrl string) (string, error) {
|
||||
encodedQuery, err := encodeQuery(query)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode the query: %s", err)
|
||||
}
|
||||
|
||||
portalUrl := azurePortalUrl
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse base portal URL: %s", err)
|
||||
}
|
||||
|
||||
portalUrl += "/#blade/Microsoft_OperationsManagementSuite_Workspace/AnalyticsBlade/initiator/AnalyticsShareLinkToQuery/isQueryEditorVisible/true/scope/"
|
||||
resourcesJson := AzureLogAnalyticsURLResources{
|
||||
Resources: make([]AzureLogAnalyticsURLResource, 0),
|
||||
}
|
||||
for _, resource := range resources {
|
||||
resourcesJson.Resources = append(resourcesJson.Resources, AzureLogAnalyticsURLResource{
|
||||
ResourceID: resource,
|
||||
})
|
||||
}
|
||||
resourcesMarshalled, err := json.Marshal(resourcesJson)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal log analytics resources: %s", err)
|
||||
}
|
||||
portalUrl += url.QueryEscape(string(resourcesMarshalled))
|
||||
portalUrl += "/query/" + url.PathEscape(encodedQuery) + "/isQueryBase64Compressed/true/timespanInIsoFormat/P1D"
|
||||
return portalUrl, nil
|
||||
}
|
||||
|
||||
// Error definition has been inferred from real data and other model definitions like
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/3640559afddbad452d265b54fb1c20b30be0b062/services/preview/virtualmachineimagebuilder/mgmt/2019-05-01-preview/virtualmachineimagebuilder/models.go
|
||||
type AzureLogAnalyticsAPIError struct {
|
||||
@@ -336,41 +375,20 @@ func (e *AzureLogAnalyticsDatasource) unmarshalResponse(logger log.Logger, res *
|
||||
|
||||
// LogAnalyticsMeta is a type for the a Frame's Meta's Custom property.
|
||||
type LogAnalyticsMeta struct {
|
||||
ColumnTypes []string `json:"azureColumnTypes"`
|
||||
EncodedQuery []byte `json:"encodedQuery"` // EncodedQuery is used for deep links.
|
||||
Resource string `json:"resource"`
|
||||
}
|
||||
|
||||
func setAdditionalFrameMeta(frame *data.Frame, query, resource string) error {
|
||||
if frame.Meta == nil || frame.Meta.Custom == nil {
|
||||
// empty response
|
||||
return nil
|
||||
}
|
||||
frame.Meta.ExecutedQueryString = query
|
||||
la, ok := frame.Meta.Custom.(*LogAnalyticsMeta)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type found for frame's custom metadata")
|
||||
}
|
||||
la.Resource = resource
|
||||
encodedQuery, err := encodeQuery(query)
|
||||
if err == nil {
|
||||
la.EncodedQuery = encodedQuery
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to encode the query into the encodedQuery property")
|
||||
ColumnTypes []string `json:"azureColumnTypes"`
|
||||
}
|
||||
|
||||
// encodeQuery encodes the query in gzip so the frontend can build links.
|
||||
func encodeQuery(rawQuery string) ([]byte, error) {
|
||||
func encodeQuery(rawQuery string) (string, error) {
|
||||
var b bytes.Buffer
|
||||
gz := gzip.NewWriter(&b)
|
||||
if _, err := gz.Write([]byte(rawQuery)); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := gz.Close(); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
return base64.StdEncoding.EncodeToString(b.Bytes()), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user