From c3a0f2ed0ee023d6593de99d095f7175c0d241f9 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:01:12 +0200 Subject: [PATCH 01/13] PR commit --- go.mod | 6 ++-- go.sum | 20 +++++++++---- package.json | 4 +-- pkg/azuredx/client/client.go | 10 +++++-- pkg/azuredx/client/client_test.go | 2 +- pkg/azuredx/datasource.go | 4 +-- pkg/azuredx/datasource_test.go | 3 +- pkg/azuredx/models/settings.go | 2 ++ pkg/azuredx/resource_handler.go | 8 ++--- .../ConfigEditor/ApplicationConfig.tsx | 30 +++++++++++++++++++ src/components/ConfigEditor/index.tsx | 3 ++ .../__fixtures__/ConfigEditor.fixtures.ts | 1 + src/components/__fixtures__/Datasource.ts | 1 + src/datasource.ts | 9 ++++-- src/test/selectors.ts | 5 ++++ src/types/index.ts | 1 + yarn.lock | 10 +++---- 17 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 src/components/ConfigEditor/ApplicationConfig.tsx diff --git a/go.mod b/go.mod index 49699802..73975eeb 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.5 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 - github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 + github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3 github.com/grafana/grafana-plugin-sdk-go v0.260.0 github.com/json-iterator/go v1.1.12 github.com/stretchr/testify v1.10.0 @@ -15,7 +15,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect @@ -105,4 +105,4 @@ require ( google.golang.org/protobuf v1.35.2 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 161b272b..eb826aea 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -30,6 +34,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMxETKpmQoWMBkeiuorElZIXoNmgiPE= github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= @@ -75,8 +81,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 h1:fV6IgVtViXcYZ4VqTAMuVBTLuGAnI27HhQkaLttzbPE= -github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2/go.mod h1:Cbh94bfL5o6mUSaHFiOkx4r4CRKlo/DJLx4dPL8XrE0= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3 h1:o1EMVnsvUIrJ2xrK6BFL6J61TzSAATqdPmnlC1FnwXk= +github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3/go.mod h1:fxwiI9U0OFYZp4Y+mdh4lRd0PoY/S1mYLzH4MKWJ2/Q= github.com/grafana/grafana-plugin-sdk-go v0.260.0 h1:Kq2z5pgOaS+URFd7Uk5pvhOCYxR4N+cvO5nqKxqj3Rg= github.com/grafana/grafana-plugin-sdk-go v0.260.0/go.mod h1:JriieK5Oc5v120QKhMs/LO55N0P3YI2ttEiVT1wfYsw= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= @@ -105,6 +111,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= @@ -165,6 +173,8 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= @@ -308,4 +318,4 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file diff --git a/package.json b/package.json index 7f69bb63..17122869 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "copy-webpack-plugin": "^12.0.2", "cspell": "8.16.1", "css-loader": "^6.7.3", - "cypress": "13.15.2", + "cypress": "13.16.0", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-deprecation": "^2.0.0", @@ -103,4 +103,4 @@ "**/@testing-library/dom": "7.31.2" }, "packageManager": "yarn@1.22.19" -} +} \ No newline at end of file diff --git a/pkg/azuredx/client/client.go b/pkg/azuredx/client/client.go index a7a455f9..4c2a87f8 100644 --- a/pkg/azuredx/client/client.go +++ b/pkg/azuredx/client/client.go @@ -26,7 +26,7 @@ import ( type AdxClient interface { TestKustoRequest(ctx context.Context, datasourceSettings *models.DatasourceSettings, properties *models.Properties, additionalHeaders map[string]string) error TestARGsRequest(ctx context.Context, datasourceSettings *models.DatasourceSettings, properties *models.Properties, additionalHeaders map[string]string) error - KustoRequest(ctx context.Context, cluster string, url string, payload models.RequestPayload, userTrackingEnabled bool) (*models.TableResponse, error) + KustoRequest(ctx context.Context, cluster string, url string, payload models.RequestPayload, userTrackingEnabled bool, application string) (*models.TableResponse, error) ARGClusterRequest(ctx context.Context, payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) } @@ -196,7 +196,7 @@ func (c *Client) testManagementClient(ctx context.Context, _ *models.DatasourceS // KustoRequest executes a Kusto Query language request to Azure's Data Explorer V1 REST API // and returns a TableResponse. If there is a query syntax error, the error message inside // the API's JSON error response is returned as well (if available). -func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path string, payload models.RequestPayload, userTrackingEnabled bool) (*models.TableResponse, error) { +func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path string, payload models.RequestPayload, userTrackingEnabled bool, application string) (*models.TableResponse, error) { buf, err := json.Marshal(payload) if err != nil { return nil, errorsource.DownstreamError(fmt.Errorf("no Azure request serial: %w", err), false) @@ -214,7 +214,11 @@ func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path strin req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - req.Header.Set("x-ms-app", "Grafana-ADX") + if application == "" { + application = "Grafana-ADX" + } + req.Header.Set("x-ms-app", application) + // req.Header.Set("x-ms-app", "Grafana-ADX") if payload.QuerySource == "" { payload.QuerySource = "unspecified" } diff --git a/pkg/azuredx/client/client_test.go b/pkg/azuredx/client/client_test.go index 56fa1d45..ce969a36 100644 --- a/pkg/azuredx/client/client_test.go +++ b/pkg/azuredx/client/client_test.go @@ -74,7 +74,7 @@ func TestClient(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { require.Equal(t, "application/json", req.Header.Get("Accept")) require.Equal(t, "application/json", req.Header.Get("Content-Type")) - require.Equal(t, "Grafana-ADX", req.Header.Get("x-ms-app")) + require.NotEmpty(t, req.Header.Get("x-ms-app"), "Header 'x-ms-app' should not be empty") })) defer server.Close() diff --git a/pkg/azuredx/datasource.go b/pkg/azuredx/datasource.go index 65d8bcf2..025a91c3 100644 --- a/pkg/azuredx/datasource.go +++ b/pkg/azuredx/datasource.go @@ -178,13 +178,13 @@ func (adx *AzureDataExplorer) modelQuery(ctx context.Context, q models.QueryMode // errorsource set in SanitizeClusterUri return backend.DataResponse{}, err } - + application := adx.settings.Application tableRes, err := adx.client.KustoRequest(ctx, sanitized, "/v1/rest/query", models.RequestPayload{ CSL: q.Query, DB: database, Properties: props, QuerySource: q.QuerySource, - }, adx.settings.EnableUserTracking) + }, adx.settings.EnableUserTracking, application) if err != nil { backend.Logger.Debug("error building kusto request", "error", err.Error()) // errorsource set in KustoRequest diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index ce9f7c21..ba859f6d 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -27,11 +27,12 @@ func TestDatasource(t *testing.T) { var adx AzureDataExplorer const UserLogin string = "user-login" const ClusterURL string = "base-url" + const Application string = "Grafana-ADX" t.Run("When running a query the right args should be passed to KustoRequest", func(t *testing.T) { adx = AzureDataExplorer{} adx.client = &fakeClient{} - adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL} + adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Applicaton: Application} query := backend.DataQuery{ RefID: "", QueryType: "", diff --git a/pkg/azuredx/models/settings.go b/pkg/azuredx/models/settings.go index 9359b2be..44e5b7e0 100644 --- a/pkg/azuredx/models/settings.go +++ b/pkg/azuredx/models/settings.go @@ -20,6 +20,8 @@ type DatasourceSettings struct { CacheMaxAge string `json:"cacheMaxAge"` DynamicCaching bool `json:"dynamicCaching"` EnableUserTracking bool `json:"enableUserTracking"` + Application string `json:"application"` + // QueryTimeoutRaw is a duration string set in the datasource settings and corresponds // to the server execution timeout. diff --git a/pkg/azuredx/resource_handler.go b/pkg/azuredx/resource_handler.go index f030b19f..9c306c55 100644 --- a/pkg/azuredx/resource_handler.go +++ b/pkg/azuredx/resource_handler.go @@ -147,9 +147,9 @@ func (adx *AzureDataExplorer) getSchema(rw http.ResponseWriter, req *http.Reques respondWithError(rw, http.StatusBadRequest, "Invalid clusterUri", err) return } - + application := adx.settings.Application // Default to not sending the user request headers for schema requests - response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false) + response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false, application) if err != nil { respondWithError(rw, http.StatusInternalServerError, "Azure query unsuccessful", err) return @@ -196,9 +196,9 @@ func (adx *AzureDataExplorer) getDatabases(rw http.ResponseWriter, req *http.Req respondWithError(rw, http.StatusBadRequest, "Invalid clusterUri", err) return } - + application := adx.settings.Application // Default to not sending the user request headers for schema requests - response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false) + response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false, application) if err != nil { respondWithError(rw, http.StatusInternalServerError, "Azure query unsuccessful", err) return diff --git a/src/components/ConfigEditor/ApplicationConfig.tsx b/src/components/ConfigEditor/ApplicationConfig.tsx new file mode 100644 index 00000000..361098e6 --- /dev/null +++ b/src/components/ConfigEditor/ApplicationConfig.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { Field, Input } from '@grafana/ui'; +import { AdxDataSourceOptions, AdxDataSourceSecureOptions } from 'types'; +import { selectors } from 'test/selectors'; + +interface ApplicationConfigProps + extends DataSourcePluginOptionsEditorProps<AdxDataSourceOptions, AdxDataSourceSecureOptions> { + updateJsonData: <T extends keyof AdxDataSourceOptions>(fieldName: T, value: AdxDataSourceOptions[T]) => void; +} + +const ApplicationConfig: React.FC<ApplicationConfigProps> = ({ options, updateJsonData }) => { + const { jsonData } = options; + + return ( + <Field label="Application name (Optional)" description="Application name to be displayed in ADX."> + <Input + aria-label="Application" + data-testid={selectors.components.applicationEditor.application.input} + value={jsonData.application} + id="adx-application" + placeholder="Grafana-ADX" + width={60} + onChange={(ev: React.ChangeEvent<HTMLInputElement>) => updateJsonData('application', ev.target.value)} + /> + </Field> + ); +}; + +export default ApplicationConfig; diff --git a/src/components/ConfigEditor/index.tsx b/src/components/ConfigEditor/index.tsx index 3a41a241..b7c6800d 100644 --- a/src/components/ConfigEditor/index.tsx +++ b/src/components/ConfigEditor/index.tsx @@ -10,6 +10,7 @@ import ConnectionConfig from './ConnectionConfig'; import DatabaseConfig from './DatabaseConfig'; import QueryConfig from './QueryConfig'; import TrackingConfig from './TrackingConfig'; +import ApplicationConfig from './ApplicationConfig'; import { getCredentials, getDefaultCredentials, @@ -52,6 +53,7 @@ const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { options.jsonData.cacheMaxAge || options.jsonData.useSchemaMapping || options.jsonData.enableUserTracking || + options.jsonData.application || options.secureJsonFields['OpenAIAPIKey'] ), [options] @@ -118,6 +120,7 @@ const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { > <QueryConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> <DatabaseConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> + <ApplicationConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> <TrackingConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> </ConfigSection> diff --git a/src/components/__fixtures__/ConfigEditor.fixtures.ts b/src/components/__fixtures__/ConfigEditor.fixtures.ts index 9b216654..00244d48 100644 --- a/src/components/__fixtures__/ConfigEditor.fixtures.ts +++ b/src/components/__fixtures__/ConfigEditor.fixtures.ts @@ -29,6 +29,7 @@ export const mockConfigEditorProps = (optionsOverrides?: Partial<ConfigEditorPro useSchemaMapping: false, enableUserTracking: true, clusterUrl: Chance().url(), + application: Chance().word(), }, readOnly: true, withCredentials: true, diff --git a/src/components/__fixtures__/Datasource.ts b/src/components/__fixtures__/Datasource.ts index 8fa036f5..0d6d5dbe 100644 --- a/src/components/__fixtures__/Datasource.ts +++ b/src/components/__fixtures__/Datasource.ts @@ -34,6 +34,7 @@ export const mockDatasourceOptions: DataSourcePluginOptionsEditorProps< useSchemaMapping: false, enableUserTracking: false, clusterUrl: 'clusterUrl', + application: '' }, secureJsonFields: {}, readOnly: false, diff --git a/src/datasource.ts b/src/datasource.ts index 5be29ab2..d04953c2 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -48,13 +48,14 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour private expressionParser: KustoExpressionParser; private defaultEditorMode: EditorMode; private schemaMapper: AdxSchemaMapper; + private application: string; constructor(private instanceSettings: DataSourceInstanceSettings<AdxDataSourceOptions>) { super(instanceSettings); const useSchemaMapping = instanceSettings.jsonData.useSchemaMapping ?? false; const schemaMapping = instanceSettings.jsonData.schemaMappings ?? []; - + const application = instanceSettings.jsonData.application ?? 'Grafana-ADX'; this.backendSrv = getBackendSrv(); this.templateSrv = getTemplateSrv(); this.defaultOrFirstDatabase = instanceSettings.jsonData.defaultDatabase; @@ -63,6 +64,7 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour this.defaultEditorMode = instanceSettings.jsonData.defaultEditorMode ?? EditorMode.Visual; this.schemaMapper = new AdxSchemaMapper(useSchemaMapping, schemaMapping); this.expressionParser = new KustoExpressionParser(this.templateSrv); + this.application = application; this.parseExpression = this.parseExpression.bind(this); this.autoCompleteQuery = this.autoCompleteQuery.bind(this); this.getSchemaMapper = this.getSchemaMapper.bind(this); @@ -313,6 +315,9 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour getDefaultEditorMode(): EditorMode { return this.defaultEditorMode; } + getApplication(): string { + return this.application + } async autoCompleteQuery(query: AutoCompleteQuery, columns: AdxColumnSchema[] | undefined): Promise<string[]> { const autoQuery = this.expressionParser.toAutoCompleteQuery(query, columns); @@ -329,7 +334,7 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour query: autoQuery, resultFormat: 'table', querySource: 'autocomplete', - clusterUri: query.clusterUri, + clusterUri: query.clusterUri }; const response = await lastValueFrom( diff --git a/src/test/selectors.ts b/src/test/selectors.ts index 3b8f5591..612ad265 100644 --- a/src/test/selectors.ts +++ b/src/test/selectors.ts @@ -1,6 +1,11 @@ import { E2ESelectors } from '@grafana/e2e-selectors'; export const components = { + applicationEditor: { + application : { + input: 'data-testid application' + } + }, configEditor: { authType: { input: 'data-testid azure-auth', diff --git a/src/types/index.ts b/src/types/index.ts index 0e4473dc..93b7f88c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -122,6 +122,7 @@ export interface AdxDataSourceOptions extends AzureDataSourceJsonData { schemaMappings?: Array<Partial<SchemaMapping>>; enableUserTracking: boolean; clusterUrl: string; + application: string; enableSecureSocksProxy?: boolean; // legacy options azureCloud?: string; diff --git a/yarn.lock b/yarn.lock index aa9dc8d3..860b6ab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5047,10 +5047,10 @@ cypress-file-upload@5.0.8: resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== -cypress@13.15.2: - version "13.15.2" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.15.2.tgz#ef19554c274bc4ff23802aeb5c52951677fa67f1" - integrity sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A== +cypress@13.16.0: + version "13.16.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.16.0.tgz#7674ca33941f9da58f15fd4e3456856d87730970" + integrity sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA== dependencies: "@cypress/request" "^3.0.6" "@cypress/xvfb" "^1.2.4" @@ -12041,4 +12041,4 @@ yocto-queue@^0.1.0: yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== \ No newline at end of file From 7333ab0a318bb89680f17a5be42b145eae24f446 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:10:20 +0200 Subject: [PATCH 02/13] Resolved conflicts in go.mod and go.sum --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 73975eeb..799a9388 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3 - github.com/grafana/grafana-plugin-sdk-go v0.260.0 + github.com/grafana/grafana-plugin-sdk-go v0.260.1 github.com/json-iterator/go v1.1.12 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.31.0 diff --git a/go.sum b/go.sum index eb826aea..9f7db4e1 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3 h1:o1EMVnsvUIrJ2xrK6BFL6J61TzSAATqdPmnlC1FnwXk= github.com/grafana/grafana-azure-sdk-go/v2 v2.1.3/go.mod h1:fxwiI9U0OFYZp4Y+mdh4lRd0PoY/S1mYLzH4MKWJ2/Q= -github.com/grafana/grafana-plugin-sdk-go v0.260.0 h1:Kq2z5pgOaS+URFd7Uk5pvhOCYxR4N+cvO5nqKxqj3Rg= -github.com/grafana/grafana-plugin-sdk-go v0.260.0/go.mod h1:JriieK5Oc5v120QKhMs/LO55N0P3YI2ttEiVT1wfYsw= +github.com/grafana/grafana-plugin-sdk-go v0.260.1 h1:KzbooQP9mv/9CPsn+SoUwGuomA8oUxO0iuIq6Rg/ekE= +github.com/grafana/grafana-plugin-sdk-go v0.260.1/go.mod h1:JriieK5Oc5v120QKhMs/LO55N0P3YI2ttEiVT1wfYsw= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= From 0ccb94e9e35983e245e405797acb9d7a9ae4740a Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:28:21 +0200 Subject: [PATCH 03/13] Fixed typo in datasource test file --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index ba859f6d..c4d6b304 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -32,7 +32,7 @@ func TestDatasource(t *testing.T) { t.Run("When running a query the right args should be passed to KustoRequest", func(t *testing.T) { adx = AzureDataExplorer{} adx.client = &fakeClient{} - adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Applicaton: Application} + adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Application: Application} query := backend.DataQuery{ RefID: "", QueryType: "", From 486b9258176a835ff33b2a5c1fb35a1855ccfab3 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:43:48 +0200 Subject: [PATCH 04/13] Readded blank line --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17122869..2d3d56b4 100644 --- a/package.json +++ b/package.json @@ -103,4 +103,4 @@ "**/@testing-library/dom": "7.31.2" }, "packageManager": "yarn@1.22.19" -} \ No newline at end of file +} From 119c37c5d15fea52f6444317de192a737e38b6c9 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Fri, 17 Jan 2025 19:05:19 +0200 Subject: [PATCH 05/13] Fix tests --- pkg/azuredx/client/client_test.go | 8 ++++---- pkg/azuredx/datasource_test.go | 6 +++--- pkg/azuredx/resource_handler_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/azuredx/client/client_test.go b/pkg/azuredx/client/client_test.go index ce969a36..dbca9882 100644 --- a/pkg/azuredx/client/client_test.go +++ b/pkg/azuredx/client/client_test.go @@ -37,7 +37,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.NoError(t, err) require.NotNil(t, table) }) @@ -64,7 +64,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) require.Contains(t, err.Error(), "Request is invalid and cannot be processed: Syntax error: SYN0002: A recognition error occurred. [line:position=1:9]. Query: 'PerfTest take 5'") @@ -85,7 +85,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) }) @@ -113,7 +113,7 @@ func TestClient(t *testing.T) { Login: "test-user", }, }) - table, err := client.KustoRequest(ctxWithUser, server.URL, "", payload, true) + table, err := client.KustoRequest(ctxWithUser, server.URL, "", payload, true, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) }) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index c4d6b304..41e61f5e 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -41,7 +41,7 @@ func TestDatasource(t *testing.T) { TimeRange: backend.TimeRange{}, JSON: []byte(`{"resultFormat": "table","querySource": "schema","database":"test-database"}`), } - kustoRequestMock = func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) { + kustoRequestMock = func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) { require.Equal(t, "/v1/rest/query", url) require.Equal(t, ClusterURL, cluster) require.Equal(t, payload.DB, "test-database") @@ -101,8 +101,8 @@ func (c *fakeClient) TestARGsRequest(_ context.Context, _ *models.DatasourceSett panic("not implemented") } -func (c *fakeClient) KustoRequest(_ context.Context, cluster string, url string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) { - return kustoRequestMock(url, cluster, payload, enableUserTracking) +func (c *fakeClient) KustoRequest(_ context.Context, cluster string, url string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) { + return kustoRequestMock(url, cluster, payload, enableUserTracking, application) } func (c *fakeClient) ARGClusterRequest(_ context.Context, payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) { diff --git a/pkg/azuredx/resource_handler_test.go b/pkg/azuredx/resource_handler_test.go index 638160ff..cc2b400e 100644 --- a/pkg/azuredx/resource_handler_test.go +++ b/pkg/azuredx/resource_handler_test.go @@ -82,7 +82,7 @@ func (c *failingClient) TestARGsRequest(_ context.Context, _ *models.DatasourceS panic("not implemented") } -func (c *failingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool) (*models.TableResponse, error) { +func (c *failingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { return nil, fmt.Errorf("HTTP error: %v - %v", http.StatusBadRequest, "") } @@ -100,7 +100,7 @@ func (c *workingClient) TestARGsRequest(_ context.Context, _ *models.DatasourceS panic("not implemented") } -func (c *workingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool) (*models.TableResponse, error) { +func (c *workingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { return &models.TableResponse{ Tables: []models.Table{ { From a5576680f277b177c4381e77c635bf82aa663488 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Mon, 20 Jan 2025 20:37:22 +0200 Subject: [PATCH 06/13] Fix kusto request mock --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index 41e61f5e..8b641424 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -10,7 +10,7 @@ import ( ) var ( - kustoRequestMock func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) + kustoRequestMock func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) ARGClusterRequestMock func(payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) table = &models.TableResponse{ Tables: []models.Table{ From ef857f20584f28f6e09c39278cc4baf657432a3c Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Mon, 20 Jan 2025 20:50:06 +0200 Subject: [PATCH 07/13] Fix kusto request mock --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index 8b641424..90e1928e 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -63,7 +63,7 @@ func TestDatasource(t *testing.T) { TimeRange: backend.TimeRange{}, JSON: []byte(`{"resultFormat": "table","querySource": "schema"}`), } - kustoRequestMock = func(_ string, _ string, payload models.RequestPayload, _ bool) (*models.TableResponse, error) { + kustoRequestMock = func(_ string, _ string, payload models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { require.Equal(t, payload.DB, "test-default-database") return table, nil } From b7de9aa720d0b7e1fa09469dc32cd9e202ec7392 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:01:12 +0200 Subject: [PATCH 08/13] PR commit --- go.mod | 2 +- go.sum | 2 +- package.json | 2 +- pkg/azuredx/client/client.go | 10 +++++-- pkg/azuredx/client/client_test.go | 2 +- pkg/azuredx/datasource.go | 4 +-- pkg/azuredx/datasource_test.go | 3 +- pkg/azuredx/models/settings.go | 2 ++ pkg/azuredx/resource_handler.go | 8 ++--- .../ConfigEditor/ApplicationConfig.tsx | 30 +++++++++++++++++++ src/components/ConfigEditor/index.tsx | 3 ++ .../__fixtures__/ConfigEditor.fixtures.ts | 1 + src/components/__fixtures__/Datasource.ts | 1 + src/datasource.ts | 9 ++++-- src/test/selectors.ts | 5 ++++ src/types/index.ts | 1 + yarn.lock | 2 +- 17 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/components/ConfigEditor/ApplicationConfig.tsx diff --git a/go.mod b/go.mod index 51368744..ded10542 100644 --- a/go.mod +++ b/go.mod @@ -106,4 +106,4 @@ require ( google.golang.org/protobuf v1.35.2 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 0796b9df..992f3a5b 100644 --- a/go.sum +++ b/go.sum @@ -329,4 +329,4 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file diff --git a/package.json b/package.json index 9928840f..b87f0028 100644 --- a/package.json +++ b/package.json @@ -113,4 +113,4 @@ "**/@testing-library/dom": "7.31.2" }, "packageManager": "yarn@1.22.19" -} +} \ No newline at end of file diff --git a/pkg/azuredx/client/client.go b/pkg/azuredx/client/client.go index a7a455f9..4c2a87f8 100644 --- a/pkg/azuredx/client/client.go +++ b/pkg/azuredx/client/client.go @@ -26,7 +26,7 @@ import ( type AdxClient interface { TestKustoRequest(ctx context.Context, datasourceSettings *models.DatasourceSettings, properties *models.Properties, additionalHeaders map[string]string) error TestARGsRequest(ctx context.Context, datasourceSettings *models.DatasourceSettings, properties *models.Properties, additionalHeaders map[string]string) error - KustoRequest(ctx context.Context, cluster string, url string, payload models.RequestPayload, userTrackingEnabled bool) (*models.TableResponse, error) + KustoRequest(ctx context.Context, cluster string, url string, payload models.RequestPayload, userTrackingEnabled bool, application string) (*models.TableResponse, error) ARGClusterRequest(ctx context.Context, payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) } @@ -196,7 +196,7 @@ func (c *Client) testManagementClient(ctx context.Context, _ *models.DatasourceS // KustoRequest executes a Kusto Query language request to Azure's Data Explorer V1 REST API // and returns a TableResponse. If there is a query syntax error, the error message inside // the API's JSON error response is returned as well (if available). -func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path string, payload models.RequestPayload, userTrackingEnabled bool) (*models.TableResponse, error) { +func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path string, payload models.RequestPayload, userTrackingEnabled bool, application string) (*models.TableResponse, error) { buf, err := json.Marshal(payload) if err != nil { return nil, errorsource.DownstreamError(fmt.Errorf("no Azure request serial: %w", err), false) @@ -214,7 +214,11 @@ func (c *Client) KustoRequest(ctx context.Context, clusterUrl string, path strin req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - req.Header.Set("x-ms-app", "Grafana-ADX") + if application == "" { + application = "Grafana-ADX" + } + req.Header.Set("x-ms-app", application) + // req.Header.Set("x-ms-app", "Grafana-ADX") if payload.QuerySource == "" { payload.QuerySource = "unspecified" } diff --git a/pkg/azuredx/client/client_test.go b/pkg/azuredx/client/client_test.go index 56fa1d45..ce969a36 100644 --- a/pkg/azuredx/client/client_test.go +++ b/pkg/azuredx/client/client_test.go @@ -74,7 +74,7 @@ func TestClient(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { require.Equal(t, "application/json", req.Header.Get("Accept")) require.Equal(t, "application/json", req.Header.Get("Content-Type")) - require.Equal(t, "Grafana-ADX", req.Header.Get("x-ms-app")) + require.NotEmpty(t, req.Header.Get("x-ms-app"), "Header 'x-ms-app' should not be empty") })) defer server.Close() diff --git a/pkg/azuredx/datasource.go b/pkg/azuredx/datasource.go index f7f2faa4..a2ae319c 100644 --- a/pkg/azuredx/datasource.go +++ b/pkg/azuredx/datasource.go @@ -178,13 +178,13 @@ func (adx *AzureDataExplorer) modelQuery(ctx context.Context, q models.QueryMode // errorsource set in SanitizeClusterUri return backend.DataResponse{}, err } - + application := adx.settings.Application tableRes, err := adx.client.KustoRequest(ctx, sanitized, "/v1/rest/query", models.RequestPayload{ CSL: q.Query, DB: database, Properties: props, QuerySource: q.QuerySource, - }, adx.settings.EnableUserTracking) + }, adx.settings.EnableUserTracking, application) if err != nil { backend.Logger.Debug("error building kusto request", "error", err.Error()) // errorsource set in KustoRequest diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index ce9f7c21..ba859f6d 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -27,11 +27,12 @@ func TestDatasource(t *testing.T) { var adx AzureDataExplorer const UserLogin string = "user-login" const ClusterURL string = "base-url" + const Application string = "Grafana-ADX" t.Run("When running a query the right args should be passed to KustoRequest", func(t *testing.T) { adx = AzureDataExplorer{} adx.client = &fakeClient{} - adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL} + adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Applicaton: Application} query := backend.DataQuery{ RefID: "", QueryType: "", diff --git a/pkg/azuredx/models/settings.go b/pkg/azuredx/models/settings.go index 9359b2be..44e5b7e0 100644 --- a/pkg/azuredx/models/settings.go +++ b/pkg/azuredx/models/settings.go @@ -20,6 +20,8 @@ type DatasourceSettings struct { CacheMaxAge string `json:"cacheMaxAge"` DynamicCaching bool `json:"dynamicCaching"` EnableUserTracking bool `json:"enableUserTracking"` + Application string `json:"application"` + // QueryTimeoutRaw is a duration string set in the datasource settings and corresponds // to the server execution timeout. diff --git a/pkg/azuredx/resource_handler.go b/pkg/azuredx/resource_handler.go index f030b19f..9c306c55 100644 --- a/pkg/azuredx/resource_handler.go +++ b/pkg/azuredx/resource_handler.go @@ -147,9 +147,9 @@ func (adx *AzureDataExplorer) getSchema(rw http.ResponseWriter, req *http.Reques respondWithError(rw, http.StatusBadRequest, "Invalid clusterUri", err) return } - + application := adx.settings.Application // Default to not sending the user request headers for schema requests - response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false) + response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false, application) if err != nil { respondWithError(rw, http.StatusInternalServerError, "Azure query unsuccessful", err) return @@ -196,9 +196,9 @@ func (adx *AzureDataExplorer) getDatabases(rw http.ResponseWriter, req *http.Req respondWithError(rw, http.StatusBadRequest, "Invalid clusterUri", err) return } - + application := adx.settings.Application // Default to not sending the user request headers for schema requests - response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false) + response, err := adx.client.KustoRequest(req.Context(), sanitized, ManagementApiPath, payload, false, application) if err != nil { respondWithError(rw, http.StatusInternalServerError, "Azure query unsuccessful", err) return diff --git a/src/components/ConfigEditor/ApplicationConfig.tsx b/src/components/ConfigEditor/ApplicationConfig.tsx new file mode 100644 index 00000000..361098e6 --- /dev/null +++ b/src/components/ConfigEditor/ApplicationConfig.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; +import { Field, Input } from '@grafana/ui'; +import { AdxDataSourceOptions, AdxDataSourceSecureOptions } from 'types'; +import { selectors } from 'test/selectors'; + +interface ApplicationConfigProps + extends DataSourcePluginOptionsEditorProps<AdxDataSourceOptions, AdxDataSourceSecureOptions> { + updateJsonData: <T extends keyof AdxDataSourceOptions>(fieldName: T, value: AdxDataSourceOptions[T]) => void; +} + +const ApplicationConfig: React.FC<ApplicationConfigProps> = ({ options, updateJsonData }) => { + const { jsonData } = options; + + return ( + <Field label="Application name (Optional)" description="Application name to be displayed in ADX."> + <Input + aria-label="Application" + data-testid={selectors.components.applicationEditor.application.input} + value={jsonData.application} + id="adx-application" + placeholder="Grafana-ADX" + width={60} + onChange={(ev: React.ChangeEvent<HTMLInputElement>) => updateJsonData('application', ev.target.value)} + /> + </Field> + ); +}; + +export default ApplicationConfig; diff --git a/src/components/ConfigEditor/index.tsx b/src/components/ConfigEditor/index.tsx index 3a41a241..b7c6800d 100644 --- a/src/components/ConfigEditor/index.tsx +++ b/src/components/ConfigEditor/index.tsx @@ -10,6 +10,7 @@ import ConnectionConfig from './ConnectionConfig'; import DatabaseConfig from './DatabaseConfig'; import QueryConfig from './QueryConfig'; import TrackingConfig from './TrackingConfig'; +import ApplicationConfig from './ApplicationConfig'; import { getCredentials, getDefaultCredentials, @@ -52,6 +53,7 @@ const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { options.jsonData.cacheMaxAge || options.jsonData.useSchemaMapping || options.jsonData.enableUserTracking || + options.jsonData.application || options.secureJsonFields['OpenAIAPIKey'] ), [options] @@ -118,6 +120,7 @@ const ConfigEditor: React.FC<ConfigEditorProps> = (props) => { > <QueryConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> <DatabaseConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> + <ApplicationConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> <TrackingConfig options={options} onOptionsChange={onOptionsChange} updateJsonData={updateJsonData} /> </ConfigSection> diff --git a/src/components/__fixtures__/ConfigEditor.fixtures.ts b/src/components/__fixtures__/ConfigEditor.fixtures.ts index 9b216654..00244d48 100644 --- a/src/components/__fixtures__/ConfigEditor.fixtures.ts +++ b/src/components/__fixtures__/ConfigEditor.fixtures.ts @@ -29,6 +29,7 @@ export const mockConfigEditorProps = (optionsOverrides?: Partial<ConfigEditorPro useSchemaMapping: false, enableUserTracking: true, clusterUrl: Chance().url(), + application: Chance().word(), }, readOnly: true, withCredentials: true, diff --git a/src/components/__fixtures__/Datasource.ts b/src/components/__fixtures__/Datasource.ts index 8fa036f5..0d6d5dbe 100644 --- a/src/components/__fixtures__/Datasource.ts +++ b/src/components/__fixtures__/Datasource.ts @@ -34,6 +34,7 @@ export const mockDatasourceOptions: DataSourcePluginOptionsEditorProps< useSchemaMapping: false, enableUserTracking: false, clusterUrl: 'clusterUrl', + application: '' }, secureJsonFields: {}, readOnly: false, diff --git a/src/datasource.ts b/src/datasource.ts index dd3bce71..04c2404a 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -48,13 +48,14 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour private expressionParser: KustoExpressionParser; private defaultEditorMode: EditorMode; private schemaMapper: AdxSchemaMapper; + private application: string; constructor(private instanceSettings: DataSourceInstanceSettings<AdxDataSourceOptions>) { super(instanceSettings); const useSchemaMapping = instanceSettings.jsonData.useSchemaMapping ?? false; const schemaMapping = instanceSettings.jsonData.schemaMappings ?? []; - + const application = instanceSettings.jsonData.application ?? 'Grafana-ADX'; this.backendSrv = getBackendSrv(); this.templateSrv = getTemplateSrv(); this.defaultOrFirstDatabase = instanceSettings.jsonData.defaultDatabase; @@ -63,6 +64,7 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour this.defaultEditorMode = instanceSettings.jsonData.defaultEditorMode ?? EditorMode.Visual; this.schemaMapper = new AdxSchemaMapper(useSchemaMapping, schemaMapping); this.expressionParser = new KustoExpressionParser(this.templateSrv); + this.application = application; this.parseExpression = this.parseExpression.bind(this); this.autoCompleteQuery = this.autoCompleteQuery.bind(this); this.getSchemaMapper = this.getSchemaMapper.bind(this); @@ -313,6 +315,9 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour getDefaultEditorMode(): EditorMode { return this.defaultEditorMode; } + getApplication(): string { + return this.application + } async autoCompleteQuery(query: AutoCompleteQuery, columns: AdxColumnSchema[] | undefined): Promise<string[]> { const autoQuery = this.expressionParser.toAutoCompleteQuery(query, columns); @@ -329,7 +334,7 @@ export class AdxDataSource extends DataSourceWithBackend<KustoQuery, AdxDataSour query: autoQuery, resultFormat: 'table', querySource: 'autocomplete', - clusterUri: query.clusterUri, + clusterUri: query.clusterUri }; const response = await lastValueFrom( diff --git a/src/test/selectors.ts b/src/test/selectors.ts index de611d2b..5ebf30c0 100644 --- a/src/test/selectors.ts +++ b/src/test/selectors.ts @@ -1,6 +1,11 @@ import { E2ESelectors } from '@grafana/e2e-selectors'; export const components = { + applicationEditor: { + application : { + input: 'data-testid application' + } + }, configEditor: { authType: { input: 'data-testid azure-auth', diff --git a/src/types/index.ts b/src/types/index.ts index 0e4473dc..93b7f88c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -122,6 +122,7 @@ export interface AdxDataSourceOptions extends AzureDataSourceJsonData { schemaMappings?: Array<Partial<SchemaMapping>>; enableUserTracking: boolean; clusterUrl: string; + application: string; enableSecureSocksProxy?: boolean; // legacy options azureCloud?: string; diff --git a/yarn.lock b/yarn.lock index 7d1803fd..dce9c167 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12663,4 +12663,4 @@ yocto-queue@^0.1.0: yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== \ No newline at end of file From 37b5946f2609410833ba2d1fa1dcb8c0a368371c Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:28:21 +0200 Subject: [PATCH 09/13] Fixed typo in datasource test file --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index ba859f6d..c4d6b304 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -32,7 +32,7 @@ func TestDatasource(t *testing.T) { t.Run("When running a query the right args should be passed to KustoRequest", func(t *testing.T) { adx = AzureDataExplorer{} adx.client = &fakeClient{} - adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Applicaton: Application} + adx.settings = &models.DatasourceSettings{EnableUserTracking: true, ClusterURL: ClusterURL, Application: Application} query := backend.DataQuery{ RefID: "", QueryType: "", From 031ed2ea0b233f1b4a56d31ea2acbb8f533d14b7 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Thu, 16 Jan 2025 20:43:48 +0200 Subject: [PATCH 10/13] Readded blank line --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b87f0028..9928840f 100644 --- a/package.json +++ b/package.json @@ -113,4 +113,4 @@ "**/@testing-library/dom": "7.31.2" }, "packageManager": "yarn@1.22.19" -} \ No newline at end of file +} From c5ecbe9573f78a94469dbb9be7640bb81bfad898 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Fri, 17 Jan 2025 19:05:19 +0200 Subject: [PATCH 11/13] Fix tests --- pkg/azuredx/client/client_test.go | 8 ++++---- pkg/azuredx/datasource_test.go | 6 +++--- pkg/azuredx/resource_handler_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/azuredx/client/client_test.go b/pkg/azuredx/client/client_test.go index ce969a36..dbca9882 100644 --- a/pkg/azuredx/client/client_test.go +++ b/pkg/azuredx/client/client_test.go @@ -37,7 +37,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.NoError(t, err) require.NotNil(t, table) }) @@ -64,7 +64,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) require.Contains(t, err.Error(), "Request is invalid and cannot be processed: Syntax error: SYN0002: A recognition error occurred. [line:position=1:9]. Query: 'PerfTest take 5'") @@ -85,7 +85,7 @@ func TestClient(t *testing.T) { } client := &Client{httpClientKusto: server.Client()} - table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false) + table, err := client.KustoRequest(context.Background(), server.URL, "", payload, false, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) }) @@ -113,7 +113,7 @@ func TestClient(t *testing.T) { Login: "test-user", }, }) - table, err := client.KustoRequest(ctxWithUser, server.URL, "", payload, true) + table, err := client.KustoRequest(ctxWithUser, server.URL, "", payload, true, "Grafana-ADX") require.Nil(t, table) require.NotNil(t, err) }) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index c4d6b304..41e61f5e 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -41,7 +41,7 @@ func TestDatasource(t *testing.T) { TimeRange: backend.TimeRange{}, JSON: []byte(`{"resultFormat": "table","querySource": "schema","database":"test-database"}`), } - kustoRequestMock = func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) { + kustoRequestMock = func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) { require.Equal(t, "/v1/rest/query", url) require.Equal(t, ClusterURL, cluster) require.Equal(t, payload.DB, "test-database") @@ -101,8 +101,8 @@ func (c *fakeClient) TestARGsRequest(_ context.Context, _ *models.DatasourceSett panic("not implemented") } -func (c *fakeClient) KustoRequest(_ context.Context, cluster string, url string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) { - return kustoRequestMock(url, cluster, payload, enableUserTracking) +func (c *fakeClient) KustoRequest(_ context.Context, cluster string, url string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) { + return kustoRequestMock(url, cluster, payload, enableUserTracking, application) } func (c *fakeClient) ARGClusterRequest(_ context.Context, payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) { diff --git a/pkg/azuredx/resource_handler_test.go b/pkg/azuredx/resource_handler_test.go index 638160ff..cc2b400e 100644 --- a/pkg/azuredx/resource_handler_test.go +++ b/pkg/azuredx/resource_handler_test.go @@ -82,7 +82,7 @@ func (c *failingClient) TestARGsRequest(_ context.Context, _ *models.DatasourceS panic("not implemented") } -func (c *failingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool) (*models.TableResponse, error) { +func (c *failingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { return nil, fmt.Errorf("HTTP error: %v - %v", http.StatusBadRequest, "") } @@ -100,7 +100,7 @@ func (c *workingClient) TestARGsRequest(_ context.Context, _ *models.DatasourceS panic("not implemented") } -func (c *workingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool) (*models.TableResponse, error) { +func (c *workingClient) KustoRequest(_ context.Context, _ string, _ string, _ models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { return &models.TableResponse{ Tables: []models.Table{ { From 3c0ffbd48699a73d2a8a0b35920cd251e464580a Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Mon, 20 Jan 2025 20:37:22 +0200 Subject: [PATCH 12/13] Fix kusto request mock --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index 41e61f5e..8b641424 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -10,7 +10,7 @@ import ( ) var ( - kustoRequestMock func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool) (*models.TableResponse, error) + kustoRequestMock func(url string, cluster string, payload models.RequestPayload, enableUserTracking bool, application string) (*models.TableResponse, error) ARGClusterRequestMock func(payload models.ARGRequestPayload, additionalHeaders map[string]string) ([]models.ClusterOption, error) table = &models.TableResponse{ Tables: []models.Table{ From f4192b407f8ed3df4d0d72451521479b574ec280 Mon Sep 17 00:00:00 2001 From: benmali <benm94.code@gmail.com> Date: Mon, 20 Jan 2025 20:50:06 +0200 Subject: [PATCH 13/13] Fix kusto request mock --- pkg/azuredx/datasource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/azuredx/datasource_test.go b/pkg/azuredx/datasource_test.go index 8b641424..90e1928e 100644 --- a/pkg/azuredx/datasource_test.go +++ b/pkg/azuredx/datasource_test.go @@ -63,7 +63,7 @@ func TestDatasource(t *testing.T) { TimeRange: backend.TimeRange{}, JSON: []byte(`{"resultFormat": "table","querySource": "schema"}`), } - kustoRequestMock = func(_ string, _ string, payload models.RequestPayload, _ bool) (*models.TableResponse, error) { + kustoRequestMock = func(_ string, _ string, payload models.RequestPayload, _ bool, _ string) (*models.TableResponse, error) { require.Equal(t, payload.DB, "test-default-database") return table, nil }