Skip to content

Commit

Permalink
AzureMonitor: Fix Log Analytics portal links (#65482)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
aangelisc committed Mar 29, 2023
1 parent 674144c commit e27cb67
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 166 deletions.
5 changes: 1 addition & 4 deletions .betterer.results
Original file line number Diff line number Diff line change
Expand Up @@ -3853,10 +3853,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"],
[0, 0, 0, "Unexpected any. Specify a different type.", "9"]
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
],
"public/app/plugins/datasource/azuremonitor/azure_log_analytics/response_parser.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"regexp"
"time"
Expand All @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -229,6 +229,8 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, logger l
}
}

AddConfigLinks(*frame, queryUrl)

dataResponse.Frames = data.Frames{frame}
return dataResponse
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"

"github.com/grafana/grafana/pkg/infra/log"
Expand Down Expand Up @@ -287,11 +286,3 @@ func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) {
t.Error("expecting the error to inform of bad credentials")
}
}

func Test_setAdditionalFrameMeta(t *testing.T) {
t.Run("it should not error with an empty response", func(t *testing.T) {
frame := data.NewFrame("test")
err := setAdditionalFrameMeta(frame, "", "")
require.NoError(t, err)
})
}
36 changes: 36 additions & 0 deletions pkg/tsdb/azuremonitor/loganalytics/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package loganalytics

import (
"fmt"

"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana-plugin-sdk-go/data"
)

func AddConfigLinks(frame data.Frame, dl string) data.Frame {
for i := range frame.Fields {
if frame.Fields[i].Config == nil {
frame.Fields[i].Config = &data.FieldConfig{}
}
deepLink := data.DataLink{
Title: "View in Azure Portal",
TargetBlank: true,
URL: dl,
}
frame.Fields[i].Config.Links = append(frame.Fields[i].Config.Links, deepLink)
}
return frame
}

func GetAzurePortalUrl(azureCloud string) (string, error) {
switch azureCloud {
case azsettings.AzurePublic:
return "https://portal.azure.com", nil
case azsettings.AzureChina:
return "https://portal.azure.cn", nil
case azsettings.AzureUSGovernment:
return "https://portal.azure.us", nil
default:
return "", fmt.Errorf("the cloud is not supported")
}
}
6 changes: 3 additions & 3 deletions pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/resourcegraph"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
)
Expand Down Expand Up @@ -246,7 +246,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, logger log.Lo
return dataResponse
}

azurePortalUrl, err := resourcegraph.GetAzurePortalUrl(dsInfo.Cloud)
azurePortalUrl, err := loganalytics.GetAzurePortalUrl(dsInfo.Cloud)
if err != nil {
dataResponse.Error = err
return dataResponse
Expand Down Expand Up @@ -379,7 +379,7 @@ func (e *AzureMonitorDatasource) parseResponse(amr types.AzureMonitorResponse, q
return nil, err
}

frameWithLink := resourcegraph.AddConfigLinks(*frame, queryUrl)
frameWithLink := loganalytics.AddConfigLinks(*frame, queryUrl)
frames = append(frames, &frameWithLink)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"path"
"time"

"github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -203,13 +202,13 @@ func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, logger
return dataResponse
}

azurePortalUrl, err := GetAzurePortalUrl(dsInfo.Cloud)
azurePortalUrl, err := loganalytics.GetAzurePortalUrl(dsInfo.Cloud)
if err != nil {
return dataResponseErrorWithExecuted(err)
}

url := azurePortalUrl + "/#blade/HubsExtension/ArgQueryBlade/query/" + url.PathEscape(query.InterpolatedQuery)
frameWithLink := AddConfigLinks(*frame, url)
frameWithLink := loganalytics.AddConfigLinks(*frame, url)
if frameWithLink.Meta == nil {
frameWithLink.Meta = &data.FrameMeta{}
}
Expand All @@ -219,21 +218,6 @@ func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, logger
return dataResponse
}

func AddConfigLinks(frame data.Frame, dl string) data.Frame {
for i := range frame.Fields {
if frame.Fields[i].Config == nil {
frame.Fields[i].Config = &data.FieldConfig{}
}
deepLink := data.DataLink{
Title: "View in Azure Portal",
TargetBlank: true,
URL: dl,
}
frame.Fields[i].Config.Links = append(frame.Fields[i].Config.Links, deepLink)
}
return frame
}

func (e *AzureResourceGraphDatasource) createRequest(ctx context.Context, logger log.Logger, reqBody []byte, url string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(reqBody))
if err != nil {
Expand Down Expand Up @@ -273,16 +257,3 @@ func (e *AzureResourceGraphDatasource) unmarshalResponse(logger log.Logger, res

return data, nil
}

func GetAzurePortalUrl(azureCloud string) (string, error) {
switch azureCloud {
case azsettings.AzurePublic:
return "https://portal.azure.com", nil
case azsettings.AzureChina:
return "https://portal.azure.cn", nil
case azsettings.AzureUSGovernment:
return "https://portal.azure.us", nil
default:
return "", fmt.Errorf("the cloud is not supported")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
)

Expand Down Expand Up @@ -124,7 +125,7 @@ func TestAddConfigData(t *testing.T) {
frame := data.Frame{
Fields: []*data.Field{&field},
}
frameWithLink := AddConfigLinks(frame, "http://ds")
frameWithLink := loganalytics.AddConfigLinks(frame, "http://ds")
expectedFrameWithLink := data.Frame{
Fields: []*data.Field{
{
Expand All @@ -148,7 +149,7 @@ func TestGetAzurePortalUrl(t *testing.T) {
}

for _, cloud := range clouds {
azurePortalUrl, err := GetAzurePortalUrl(cloud)
azurePortalUrl, err := loganalytics.GetAzurePortalUrl(cloud)
if err != nil {
t.Errorf("The cloud not supported")
}
Expand Down
Loading

0 comments on commit e27cb67

Please sign in to comment.