Compare commits

...

12 Commits

Author SHA1 Message Date
Grot (@grafanabot)
2386d78193 [v9.4.x] Alerting: Fix boolean default in migration from false to 0 (#63963)
Alerting: Fix boolean default in migration from false to 0 (#63952)

Fix boolean default in migration from false to 0

(cherry picked from commit a05bf41ff9)

Co-authored-by: Alex Moreno <alexander.moreno@grafana.com>
2023-03-01 16:08:07 -06:00
Grot (@grafanabot)
fac483c393 [v9.4.x] Alerting: Fix migration pauses all alert rules on PostgreSQL (#63968)
Alerting: Fix migration pauses all alert rules on PostgreSQL (#63951)

This commit fixes a serious bug in Grafana 9.4.1 where on upgrade
a migration would pause all existing alert rules and change the
default value of the column to true.

(cherry picked from commit 030f6c948f)

Co-authored-by: George Robinson <george.robinson@grafana.com>
2023-03-01 13:48:07 -06:00
Timur Olzhabayev
fd67ab151d fix(dashboard version service): add DashboardUID to query and respons… (#63821)
fix(dashboard version service): add DashboardUID to query and responses (#60800)

* fix(dashboard version service): add DashboardUID to query and responses

The DashboardUID was not populated in the response from Get and ListDashboardVersions. This adds the DashboardUID to the Get query (it was already in List) and populated the DashboardUID in the returned DashboardVersionDTOs.

(cherry picked from commit 42be0e106f)

Co-authored-by: Kristin Laemmert <mildwonkey@users.noreply.github.com>
2023-02-27 16:07:24 -06:00
Grot (@grafanabot)
27a6d700f8 [v9.4.x] InfluxDB: Fix getting empty response when querying fields with retention policy (#63671)
InfluxDB: Fix getting empty response when querying fields with retention policy (#63669)

Revert "InfluxDB: Send retention policy with InfluxQL queries if its been specified. (#62149)"

This reverts commit a5a85e0398.

(cherry picked from commit ffed779879)

Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
(cherry picked from commit 3e30c2024c)
2023-02-23 13:58:53 -06:00
Grot (@grafanabot)
fcd658359c [v9.4.x] InfluxDB datasource: Query variable breaks trying to interpolate __interval (#63685)
InfluxDB ds: Query variable breaks trying to interpolate `__interval` (#63682)

(cherry picked from commit ca4cd85504)

Co-authored-by: Ivan Ortega Alba <ivanortegaalba@gmail.com>
(cherry picked from commit 30f3f63bd6)
2023-02-23 13:57:45 -06:00
Grot (@grafanabot)
3d99babd0f Release: Bump version to 9.4.1 (#773)
"Release: Updated versions in package to 9.4.1"
2023-02-23 07:56:57 +01:00
Grot (@grafanabot)
1ffc7860e6 [v9.4.x] MSSQL Datasource: Revert functions within macros change (#63598)
MSSQL Datasource: Revert functions within macros change (#63592)

* Revert functions within macros change
* Add tests for function and macro for mssql
* Remove macro support tests

---------

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
(cherry picked from commit 356e2e1933)

Co-authored-by: Kyle Cunningham <codeincarnate@users.noreply.github.com>
2023-02-23 07:43:27 +01:00
Horst Gutmann
955b931756 [9.4.x] CI: Running Redis integration tests without grabpl (#63028) (#63074)
This restores some changes from
https://github.com/grafana/grafana/pull/61920 that were accidentally
deleted.

(cherry picked from commit 2804acd264)
2023-02-21 07:56:25 +01:00
Grot (@grafanabot)
afb449e8a1 [v9.4.x] Fix MSSQL queries failing because of bad interpolation (#63172)
Fix MSSQL queries failing because of bad interpolation (#63167)

fix failing mssql queries

(cherry picked from commit 62b078e4e4)

Co-authored-by: Victor Marin <36818606+mdvictor@users.noreply.github.com>
2023-02-21 07:23:25 +01:00
Grot (@grafanabot)
3bf22fcb21 [v9.4.x] SAML: Update library (fix single logout) (#62933)
SAML: Update library (fix single logout) (#62880)

* SAML: update library (fix single logout)

* Run go mod tidy

(cherry picked from commit 5205cfb11c)

Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com>
(cherry picked from commit 8e9fb8cf93)
2023-02-06 10:41:47 +02:00
Grot (@grafanabot)
8ad6df8266 [v9.4.x] TraceView: Add key and url escaping of json tag values (#761)
TraceView: Add key and url escaping of json tag values (#751)

(cherry picked from commit 8b53b448bc24fbfc4492ce1f4e2b1708218f70b7)

Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com>
2023-02-03 11:53:22 -06:00
Grot (@grafanabot)
a6370342b0 [v9.4.x] Geomap: Sanitize the attribution string (#755)
Geomap: Sanitize the attribution string (#745)

* SAML: Update grafana/saml library (#691)

Co-authored-by: jguer <joao.guerreiro@grafana.com>

* SVG: Add dompurify preprocessor step (#698)

* add sanitized SVG component

* add sanitize

* Fix frontend build

* Remove unnecessary yarn.lock changes

* Fix formatting

* Re-add yarn.lock message as I guess it is needed

---------

Co-authored-by: dsotirakis <dimitrios.sotirakis@grafana.com>
Co-authored-by: jguer <joao.guerreiro@grafana.com>
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
(cherry picked from commit 37b4af7ffacfc24d5f24d190356a9ef32d99aa6f)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2023-02-03 11:53:22 -06:00
32 changed files with 432 additions and 255 deletions

View File

@@ -3238,7 +3238,9 @@ steps:
name: mysql-integration-tests
- commands:
- dockerize -wait tcp://redis:6379/0 -timeout 120s
- ./bin/grabpl integration-tests
- go clean -testcache
- go list './pkg/...' | xargs -I {} sh -c 'go test -run Integration -covermode=atomic
-timeout=5m {}'
depends_on:
- wire-install
environment:
@@ -5935,7 +5937,9 @@ steps:
name: mysql-integration-tests
- commands:
- dockerize -wait tcp://redis:6379/0 -timeout 120s
- ./bin/grabpl integration-tests
- go clean -testcache
- go list './pkg/...' | xargs -I {} sh -c 'go test -run Integration -covermode=atomic
-timeout=5m {}'
depends_on:
- wire-install
environment:
@@ -6549,6 +6553,6 @@ kind: secret
name: aws_secret_access_key
---
kind: signature
hmac: a9051a8245848db0d2bd763f1f80d4103e093053093cf370792c18855d745602
hmac: 934e2dbe22f80a17ebb301d68fdac9d91544dbb92486f798300bb39436405bff
...

4
go.mod
View File

@@ -126,7 +126,7 @@ require (
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1
xorm.io/builder v0.3.6 // indirect
xorm.io/builder v0.3.6
xorm.io/core v0.7.3
xorm.io/xorm v0.8.2
)
@@ -408,7 +408,7 @@ require (
)
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a
// Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0.
// Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593

4
go.sum
View File

@@ -1418,8 +1418,8 @@ github.com/grafana/phlare/api v0.1.2 h1:1jrwd3KnsXMzj/tJih9likx5EvbY3pbvLbDqAAYe
github.com/grafana/phlare/api v0.1.2/go.mod h1:29vcLwFDmZBDce2jwFIMtzvof7fzPadT8VMKw9ks7FU=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230119183635-ec19b0a443b7 h1:ma1CfisUaAXQzL24tCao9yMleZYsFJ853m2l0rgahyE=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230119183635-ec19b0a443b7/go.mod h1:MnBfDPXJqXmmfPwQlCLvVUdqfnvrAw+hSPtDeaaFwj4=
github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb h1:9PLj02xp4DeLTM2+ZyBMcN1sh0ir8GuF/1xXKyF+yws=
github.com/grafana/saml v0.4.13-0.20230123091136-3b6b1ec6c3cb/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a h1:aWSTt/pTOI4uGY9DhBMG1l0GOnGjIYtaqxzYR3/q82o=
github.com/grafana/saml v0.4.13-0.20230203140620-5f476db5c00a/go.mod h1:igEejV+fihTIlHXYP8zOec3V5A8y3lws5bQBFsTm4gA=
github.com/grafana/sqlds/v2 v2.3.10 h1:HWKhE0vR6LoEiE+Is8CSZOgaB//D1yqb2ntkass9Fd4=
github.com/grafana/sqlds/v2 v2.3.10/go.mod h1:c6ibxnxRVGxV/0YkEgvy7QpQH/lyifFyV7K/14xvdIs=
github.com/grafana/thema v0.0.0-20230122235053-b4b6714dd1c9 h1:nAdsZkvPYNH6wDPkAi9JaDSIf5i2iVz4+Rqk4AOt6sE=

View File

@@ -4,5 +4,5 @@
"packages": [
"packages/*"
],
"version": "9.4.0"
"version": "9.4.1"
}

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "9.4.0",
"version": "9.4.1",
"repository": "github:grafana/grafana",
"scripts": {
"build": "yarn i18n:compile && NODE_ENV=production webpack --progress --config scripts/webpack/webpack.prod.js",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/data",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana Data Library",
"keywords": [
"typescript"
@@ -36,7 +36,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "6.0.1",
"@grafana/schema": "9.4.0",
"@grafana/schema": "9.4.1",
"@types/d3-interpolate": "^3.0.0",
"d3-interpolate": "3.0.1",
"date-fns": "2.29.3",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e-selectors",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana End-to-End Test Selectors Library",
"keywords": [
"cli",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/e2e",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana End-to-End Test Library",
"keywords": [
"cli",
@@ -63,7 +63,7 @@
"@babel/core": "7.20.5",
"@babel/preset-env": "7.20.2",
"@cypress/webpack-preprocessor": "5.16.0",
"@grafana/e2e-selectors": "9.4.0",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/tsconfig": "^1.2.0-rc1",
"@mochajs/json-file-reporter": "^1.2.0",
"babel-loader": "9.1.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@grafana/eslint-plugin",
"description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.",
"version": "9.4.0",
"version": "9.4.1",
"main": "./index.cjs",
"author": "Grafana Labs",
"license": "Apache-2.0",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/runtime",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana Runtime Library",
"keywords": [
"grafana",
@@ -37,10 +37,10 @@
"postpack": "mv package.json.bak package.json"
},
"dependencies": {
"@grafana/data": "9.4.0",
"@grafana/e2e-selectors": "9.4.0",
"@grafana/data": "9.4.1",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/faro-web-sdk": "1.0.0-beta2",
"@grafana/ui": "9.4.0",
"@grafana/ui": "9.4.1",
"@sentry/browser": "6.19.7",
"history": "4.10.1",
"lodash": "4.17.21",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/schema",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana Schema Library",
"keywords": [
"typescript"

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/toolkit",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana Toolkit",
"keywords": [
"grafana",
@@ -51,10 +51,10 @@
"@babel/preset-env": "7.18.9",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.18.6",
"@grafana/data": "9.4.0",
"@grafana/data": "9.4.1",
"@grafana/eslint-config": "5.0.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "9.4.0",
"@grafana/ui": "9.4.1",
"@jest/core": "27.5.1",
"@types/command-exists": "^1.2.0",
"@types/eslint": "8.4.1",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/ui",
"version": "9.4.0",
"version": "9.4.1",
"description": "Grafana Components Library",
"keywords": [
"grafana",
@@ -49,9 +49,9 @@
"dependencies": {
"@emotion/css": "11.10.5",
"@emotion/react": "11.10.5",
"@grafana/data": "9.4.0",
"@grafana/e2e-selectors": "9.4.0",
"@grafana/schema": "9.4.0",
"@grafana/data": "9.4.1",
"@grafana/e2e-selectors": "9.4.1",
"@grafana/schema": "9.4.1",
"@leeoniya/ufuzzy": "0.9.0",
"@monaco-editor/react": "4.4.6",
"@popperjs/core": "2.11.6",

View File

@@ -739,9 +739,10 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
version, _ := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 32)
query := dashver.GetDashboardVersionQuery{
OrgID: c.OrgID,
DashboardID: dash.ID,
Version: int(version),
OrgID: c.OrgID,
DashboardID: dash.ID,
DashboardUID: dash.UID,
Version: int(version),
}
res, err := hs.dashboardVersionService.Get(c.Req.Context(), &query)
@@ -757,7 +758,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *contextmodel.ReqContext) response.R
dashVersionMeta := &dashver.DashboardVersionMeta{
ID: res.ID,
DashboardID: res.DashboardID,
DashboardUID: dashUID,
DashboardUID: dash.UID,
Data: res.Data,
ParentVersion: res.ParentVersion,
RestoredFrom: res.RestoredFrom,
@@ -989,7 +990,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *contextmodel.ReqContext) respon
return dashboardGuardianResponse(err)
}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, Version: apiCmd.Version, OrgID: c.OrgID}
versionQuery := dashver.GetDashboardVersionQuery{DashboardID: dashID, DashboardUID: dash.UID, Version: apiCmd.Version, OrgID: c.OrgID}
version, err := hs.dashboardVersionService.Get(c.Req.Context(), &versionQuery)
if err != nil {
return response.Error(404, "Dashboard version not found", nil)

View File

@@ -5,9 +5,8 @@ package dashboards
import (
context "context"
mock "github.com/stretchr/testify/mock"
folder "github.com/grafana/grafana/pkg/services/folder"
mock "github.com/stretchr/testify/mock"
)
// FakeDashboardService is an autogenerated mock type for the DashboardService type

View File

@@ -5,10 +5,11 @@ package dashboards
import (
context "context"
folder "github.com/grafana/grafana/pkg/services/folder"
mock "github.com/stretchr/testify/mock"
models "github.com/grafana/grafana/pkg/services/alerting/models"
folder "github.com/grafana/grafana/pkg/services/folder"
quota "github.com/grafana/grafana/pkg/services/quota"
)

View File

@@ -2,8 +2,11 @@ package dashverimpl
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/setting"
)
@@ -14,27 +17,49 @@ const (
)
type Service struct {
store store
store store
dashSvc dashboards.DashboardService
log log.Logger
}
func ProvideService(db db.DB) dashver.Service {
func ProvideService(db db.DB, dashboardService dashboards.DashboardService) dashver.Service {
return &Service{
store: &sqlStore{
db: db,
dialect: db.GetDialect(),
},
dashSvc: dashboardService,
log: log.New("dashboard-version"),
}
}
func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQuery) (*dashver.DashboardVersionDTO, error) {
// Get the DashboardUID if not populated
if query.DashboardUID == "" {
u, err := s.getDashUIDMaybeEmpty(ctx, query.DashboardID)
if err != nil {
return nil, err
}
query.DashboardUID = u
}
// The store methods require the dashboard ID (uid is not in the dashboard
// versions table, at time of this writing), so get the DashboardID if it
// was not populated.
if query.DashboardID == 0 {
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID)
if err != nil {
return nil, err
}
query.DashboardID = id
}
version, err := s.store.Get(ctx, query)
if err != nil {
return nil, err
}
version.Data.Set("id", version.DashboardID)
// FIXME: the next PR will add the dashboardService so we can grab the DashboardUID
return version.ToDTO(""), nil
return version.ToDTO(query.DashboardUID), nil
}
func (s *Service) DeleteExpired(ctx context.Context, cmd *dashver.DeleteExpiredVersionsCommand) error {
@@ -69,6 +94,25 @@ func (s *Service) DeleteExpired(ctx context.Context, cmd *dashver.DeleteExpiredV
// List all dashboard versions for the given dashboard ID.
func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersionsQuery) ([]*dashver.DashboardVersionDTO, error) {
// Get the DashboardUID if not populated
if query.DashboardUID == "" {
u, err := s.getDashUIDMaybeEmpty(ctx, query.DashboardID)
if err != nil {
return nil, err
}
query.DashboardUID = u
}
// The store methods require the dashboard ID (uid is not in the dashboard
// versions table, at time of this writing), so get the DashboardID if it
// was not populated.
if query.DashboardID == 0 {
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID)
if err != nil {
return nil, err
}
query.DashboardID = id
}
if query.Limit == 0 {
query.Limit = 1000
}
@@ -78,8 +122,42 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
}
dtos := make([]*dashver.DashboardVersionDTO, len(dvs))
for i, v := range dvs {
// FIXME: the next PR will add the dashboardService so we can grab the DashboardUID
dtos[i] = v.ToDTO("")
dtos[i] = v.ToDTO(query.DashboardUID)
}
return dtos, nil
}
// getDashUIDMaybeEmpty is a helper function which takes a dashboardID and
// returns the UID. If the dashboard is not found, it will return an empty
// string.
func (s *Service) getDashUIDMaybeEmpty(ctx context.Context, id int64) (string, error) {
q := dashboards.GetDashboardRefByIDQuery{ID: id}
result, err := s.dashSvc.GetDashboardUIDByID(ctx, &q)
if err != nil {
if errors.Is(err, dashboards.ErrDashboardNotFound) {
s.log.Debug("dashboard not found")
return "", nil
} else {
s.log.Error("error getting dashboard", err)
return "", err
}
}
return result.UID, nil
}
// getDashIDMaybeEmpty is a helper function which takes a dashboardUID and
// returns the ID. If the dashboard is not found, it will return -1.
func (s *Service) getDashIDMaybeEmpty(ctx context.Context, uid string) (int64, error) {
q := dashboards.GetDashboardQuery{UID: uid}
result, err := s.dashSvc.GetDashboard(ctx, &q)
if err != nil {
if errors.Is(err, dashboards.ErrDashboardNotFound) {
s.log.Debug("dashboard not found")
return -1, nil
} else {
s.log.Error("error getting dashboard", err)
return -1, err
}
}
return result.ID, nil
}

View File

@@ -5,16 +5,20 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/setting"
)
func TestDashboardVersionService(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardVersionService := Service{store: dashboardVersionStore}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService}
t.Run("Get dashboard version", func(t *testing.T) {
dashboard := &dashver.DashboardVersion{
@@ -22,9 +26,11 @@ func TestDashboardVersionService(t *testing.T) {
Data: &simplejson.Json{},
}
dashboardVersionStore.ExpectedDashboardVersion = dashboard
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).Return(&dashboards.DashboardRef{UID: "uid"}, nil)
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(&dashboards.Dashboard{ID: 42}, nil)
dashboardVersion, err := dashboardVersionService.Get(context.Background(), &dashver.GetDashboardVersionQuery{})
require.NoError(t, err)
require.Equal(t, dashboard.ToDTO(""), dashboardVersion)
require.Equal(t, dashboard.ToDTO("uid"), dashboardVersion)
})
}
@@ -33,7 +39,8 @@ func TestDeleteExpiredVersions(t *testing.T) {
setting.DashboardVersionsToKeep = versionsToKeep
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardVersionService := Service{store: dashboardVersionStore}
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService}
t.Run("Don't delete anything if there are no expired versions", func(t *testing.T) {
err := dashboardVersionService.DeleteExpired(context.Background(), &dashver.DeleteExpiredVersionsCommand{DeletedRows: 4})
@@ -55,15 +62,85 @@ func TestDeleteExpiredVersions(t *testing.T) {
}
func TestListDashboardVersions(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardVersionService := Service{store: dashboardVersionStore}
t.Run("List all versions for a given Dashboard ID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{
{ID: 1, DashboardID: 42},
}
dashboardService.On("GetDashboardUIDByID", mock.Anything,
mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
Return(&dashboards.DashboardRef{UID: "uid"}, nil)
t.Run("Get all versions for a given Dashboard ID", func(t *testing.T) {
query := dashver.ListDashboardVersionsQuery{}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{{}}
query := dashver.ListDashboardVersionsQuery{DashboardID: 42}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res))
// validate that the UID was populated
require.EqualValues(t, []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}, res)
})
t.Run("List all versions for a non-existent DashboardID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{
{ID: 1, DashboardID: 42},
}
dashboardService.On("GetDashboardUIDByID", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardRefByIDQuery")).
Return(nil, dashboards.ErrDashboardNotFound).Once()
query := dashver.ListDashboardVersionsQuery{DashboardID: 42}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res))
// The DashboardID remains populated with the given value, even though the dash was not found
require.EqualValues(t, []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42}}, res)
})
t.Run("List all versions for a given DashboardUID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{{DashboardID: 42, ID: 1}}
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).
Return(&dashboards.Dashboard{ID: 42}, nil)
query := dashver.ListDashboardVersionsQuery{DashboardUID: "uid"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res))
// validate that the dashboardID was populated from the GetDashboard method call.
require.EqualValues(t, []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}, res)
})
t.Run("List all versions for a given non-existent DashboardUID", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger()}
dashboardVersionStore.ExpectedListVersions = []*dashver.DashboardVersion{{DashboardID: 42, ID: 1}}
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).
Return(nil, dashboards.ErrDashboardNotFound)
query := dashver.ListDashboardVersionsQuery{DashboardUID: "uid"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, 1, len(res))
// validate that the dashboardUID & ID are populated, even though the dash was not found
require.EqualValues(t, []*dashver.DashboardVersionDTO{{ID: 1, DashboardID: 42, DashboardUID: "uid"}}, res)
})
t.Run("List Dashboard versions - error from store", func(t *testing.T) {
dashboardVersionStore := newDashboardVersionStoreFake()
dashboardService := dashboards.NewFakeDashboardService(t)
dashboardVersionService := Service{store: dashboardVersionStore, dashSvc: dashboardService, log: log.NewNopLogger()}
dashboardVersionStore.ExpectedError = dashver.ErrDashboardVersionNotFound
query := dashver.ListDashboardVersionsQuery{DashboardID: 42, DashboardUID: "42"}
res, err := dashboardVersionService.List(context.Background(), &query)
require.Nil(t, res)
require.ErrorIs(t, err, dashver.ErrDashboardVersionNotFound)
})
}

View File

@@ -34,8 +34,8 @@ func testIntegrationGetDashboardVersion(t *testing.T, fn getStore) {
res, err := dashVerStore.Get(context.Background(), &query)
require.Nil(t, err)
assert.Equal(t, query.DashboardID, savedDash.ID)
assert.Equal(t, query.Version, savedDash.Version)
assert.Equal(t, savedDash.ID, res.ID)
assert.Equal(t, savedDash.Version, res.Version)
assert.Equal(t, createdById, res.CreatedBy)
dashCmd := &dashboards.Dashboard{

View File

@@ -46,10 +46,13 @@ func (v *DashboardVersion) ToDTO(dashUid string) *DashboardVersionDTO {
}
}
// GetDashboardVersionQuery is used to Get a dashboard version. Only one of
// DashboardID and DashboardUID are required.
type GetDashboardVersionQuery struct {
DashboardID int64
OrgID int64
Version int
DashboardID int64
DashboardUID string
OrgID int64
Version int
}
type DeleteExpiredVersionsCommand struct {

View File

@@ -283,9 +283,14 @@ func addAlertRuleMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64)
Name: "is_paused",
Type: migrator.DB_Bool,
Nullable: false,
Default: "false",
Default: "0",
},
))
// This migration fixes a bug where "false" for the default value created a column with default "true" in PostgreSQL databases
mg.AddMigration("fix is_paused column for alert_rule table", migrator.NewRawSQLMigration("").
Postgres(`ALTER TABLE alert_rule ALTER COLUMN is_paused SET DEFAULT false;
UPDATE alert_rule SET is_paused = false;`))
}
func addAlertRuleVersionMigrations(mg *migrator.Migrator) {
@@ -347,9 +352,14 @@ func addAlertRuleVersionMigrations(mg *migrator.Migrator) {
Name: "is_paused",
Type: migrator.DB_Bool,
Nullable: false,
Default: "false",
Default: "0",
},
))
// This migration fixes a bug where "false" for the default value created a column with default "true" in PostgreSQL databases
mg.AddMigration("fix is_paused column for alert_rule_version table", migrator.NewRawSQLMigration("").
Postgres(`ALTER TABLE alert_rule_version ALTER COLUMN is_paused SET DEFAULT false;
UPDATE alert_rule_version SET is_paused = false;`))
}
func addAlertmanagerConfigMigrations(mg *migrator.Migrator) {

View File

@@ -13,7 +13,6 @@ import (
const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
const argFuncExpr = `[\(|,|\s]([_a-zA-Z0-9]+\([^\(]*?\))`
type msSQLMacroEngine struct {
*sqleng.SQLMacroEngineBase
@@ -23,41 +22,16 @@ func newMssqlMacroEngine() sqleng.SQLMacroEngine {
return &msSQLMacroEngine{SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase()}
}
func replaceFunctionCallArgsWithPlaceholders(sql string) (string, map[string]string) {
rExp, _ := regexp.Compile(argFuncExpr)
functionCalls := rExp.FindAllString(sql, -1)
replacedArgs := make(map[string]string)
if len(functionCalls) > 0 {
sql = strings.Replace(sql, "\n", "", -1)
for i, functionCall := range functionCalls {
key := fmt.Sprintf("$%d", i)
formattedVal := functionCall[1:]
sql = strings.Replace(sql, formattedVal, key, 1)
replacedArgs[key] = formattedVal
}
return sql, replacedArgs
}
return sql, nil
}
func (m *msSQLMacroEngine) Interpolate(query *backend.DataQuery, timeRange backend.TimeRange,
sql string) (string, error) {
// TODO: Return any error
rExp, _ := regexp.Compile(sExpr)
var macroError error
formattedSql, fctCallArgsMap := replaceFunctionCallArgsWithPlaceholders(sql)
sql = m.ReplaceAllStringSubmatchFunc(rExp, formattedSql, func(groups []string) string {
sql = m.ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
args := strings.Split(groups[2], ",")
for i, arg := range args {
args[i] = strings.Trim(arg, " ")
if fctCallArgsMap != nil && fctCallArgsMap[args[i]] != "" {
args[i] = strings.Replace(args[i], args[i], fctCallArgsMap[args[i]], 1)
}
}
res, err := m.evaluateMacro(timeRange, query, groups[1], args)
if err != nil && macroError == nil {

View File

@@ -208,145 +208,17 @@ func TestMacroEngine(t *testing.T) {
require.Equal(t, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.UnixNano(), to.UnixNano()), sql)
})
t.Run("interpolate __timeGroup with function calls as params successfully", func(t *testing.T) {
//various queries with function calls as params tested with different spacings
sqls := []struct {
query string
expected string
}{
{
query: "select $__timeGroup(test, 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', test)/300)*300",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup(func(), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', func())/300)*300",
},
{
query: "select $__timeGroup( try_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup( \n try_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup( \n\n\ntry_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup( try_convert(timestamp, time) , 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup( try_convert(timestamp, time) ,5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup(try_convert(timestamp, time),5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300",
},
}
for _, sql := range sqls {
actual, err := engine.Interpolate(query, timeRange, sql.query)
require.Nil(t, err)
require.Equal(t, sql.expected, actual)
}
})
t.Run("interpolate macros with function calls as params successfully", func(t *testing.T) {
filterInterpolated := fmt.Sprintf("BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))
unixEpochFilter := fmt.Sprintf("select try_convert(timestamp, time) >= %d AND try_convert(timestamp, time) <= %d", from.Unix(), to.Unix())
unixEphocNanoFilter := fmt.Sprintf("select try_convert(timestamp, time) >= %d AND try_convert(timestamp, time) <= %d", from.UnixNano(), to.UnixNano())
//queries with macros and fct calls as params. fct calls are tested with various params and spaces
sqls := []struct {
query string
expected string
}{
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(time)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where time " + filterInterpolated,
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time))",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time) " + filterInterpolated,
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time));",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time) " + filterInterpolated + ";",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time, var2));",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time, var2) " + filterInterpolated + ";",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 5m), $__timeGroup(func2( var1 , var2, var3 ), 15m) from test where $__timeFilter(func(time, var2));",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300, FLOOR(DATEDIFF(second, '1970-01-01', func2( var1 , var2, var3 ))/900)*900 from test where func(time, var2) " + filterInterpolated + ";",
},
{
query: "select $__timeGroupAlias(try_convert(), 5m)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert())/300)*300 AS [time]",
},
{
query: "select $__timeEpoch(try_convert(timestamp, time))",
expected: "select DATEDIFF(second, '1970-01-01', try_convert(timestamp, time)) AS time",
},
{
query: "select $__time(try_convert(timestamp, time))",
expected: "select try_convert(timestamp, time) AS time",
},
{
query: "select $__timeGroup(try_convert(timestamp, time), 15m, NULL)",
expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/900)*900",
},
{
query: "select $__unixEpochFilter(try_convert(timestamp, time))",
expected: unixEpochFilter,
},
{
query: "select $__unixEpochNanoFilter(try_convert(timestamp, time))",
expected: unixEphocNanoFilter,
},
{
query: "select $__unixEpochGroup(try_convert(timestamp, time), 15m)",
expected: "select FLOOR(try_convert(timestamp, time)/900)*900",
},
{
query: "select $__unixEpochGroupAlias(try_convert(timestamp, time), 15m)",
expected: "select FLOOR(try_convert(timestamp, time)/900)*900 AS [time]",
},
}
for _, sql := range sqls {
actual, err := engine.Interpolate(query, timeRange, sql.query)
require.Nil(t, err)
require.Equal(t, sql.expected, actual)
}
})
t.Run("interpolate macros with function calls as params unsuccessfully", func(t *testing.T) {
t.Run("should return unmodified sql if there are no macros present", func(t *testing.T) {
sqls := []string{
"select $__timeGroup(func1(func2()), 5m)", //cannot go beyond 1st level of nested function calls
"select $__timeGroup(func1(), func2())", //second param must be interval
"select * from table",
"select count(val) from table",
"select col1, col2,col3, col4 from table where col1 = 'val1' and col2 = 'val2' order by col1 asc",
}
for _, sql := range sqls {
_, err := engine.Interpolate(query, timeRange, sql)
require.NotNil(t, err)
actual, err := engine.Interpolate(query, timeRange, sql)
require.Nil(t, err)
require.Equal(t, sql, actual)
}
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "@grafana-plugins/input-datasource",
"version": "9.4.0",
"version": "9.4.1",
"description": "Input Datasource",
"private": true,
"repository": {
@@ -15,15 +15,15 @@
},
"author": "Grafana Labs",
"devDependencies": {
"@grafana/toolkit": "9.4.0",
"@grafana/toolkit": "9.4.1",
"@types/jest": "26.0.15",
"@types/lodash": "4.14.149",
"@types/react": "17.0.30",
"lodash": "4.17.21"
},
"dependencies": {
"@grafana/data": "9.4.0",
"@grafana/ui": "9.4.0",
"@grafana/data": "9.4.1",
"@grafana/ui": "9.4.1",
"jquery": "3.5.1",
"react": "17.0.1",
"react-dom": "17.0.1",

View File

@@ -24,7 +24,7 @@ const data = [
{ key: 'jsonkey', value: JSON.stringify({ hello: 'world' }) },
];
const setup = (propOverrides?: KeyValuesTableProps) => {
const setup = (propOverrides?: Partial<KeyValuesTableProps>) => {
const props = {
data: data,
...propOverrides,
@@ -89,4 +89,28 @@ describe('KeyValuesTable tests', () => {
expect(screen.getAllByRole('button')).toHaveLength(4);
});
it('renders a link in json and properly escapes it', () => {
setup({
data: [
{ key: 'jsonkey', value: JSON.stringify({ hello: 'https://example.com"id=x tabindex=1 onfocus=alert(1)' }) },
],
});
const link = screen.getByText(/https:\/\/example.com/);
expect(link.tagName).toBe('A');
expect(link.attributes.getNamedItem('href')?.value).toBe(
'https://example.com%22id=x%20tabindex=1%20onfocus=alert(1)'
);
});
it('properly escapes json values', () => {
setup({
data: [
{ key: 'jsonkey', value: JSON.stringify({ '<img src=x onerror=alert(1)>': '<img src=x onerror=alert(1)>' }) },
],
});
const values = screen.getAllByText(/onerror=alert/);
expect(values[0].innerHTML).toBe('"&lt;img src=x onerror=alert(1)&gt;":');
expect(values[1].innerHTML).toBe('"&lt;img src=x onerror=alert(1)&gt;"');
});
});

View File

@@ -14,7 +14,6 @@
import { css } from '@emotion/css';
import cx from 'classnames';
import jsonMarkup from 'json-markup';
import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
@@ -25,6 +24,8 @@ import CopyIcon from '../../common/CopyIcon';
import { TraceKeyValuePair, TraceLink, TNil } from '../../types';
import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles';
import jsonMarkup from './jsonMarkup';
const copyIconClassName = 'copyIcon';
export const getStyles = (theme: GrafanaTheme2) => {

View File

@@ -0,0 +1,133 @@
// The MIT License (MIT)
//
// Copyright (c) 2014 Mathias Buus
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
const INDENT = ' ';
function inlineRule(objRule) {
let str = '';
objRule &&
Object.keys(objRule).forEach(function (rule) {
str += rule + ':' + objRule[rule] + ';';
});
return str;
}
function Stylize(styleFile) {
function styleClass(cssClass) {
return 'class="' + cssClass + '"';
}
function styleInline(cssClass) {
return 'style="' + inlineRule(styleFile['.' + cssClass]) + '"';
}
if (!styleFile) {
return styleClass;
}
return styleInline;
}
function type(doc) {
if (doc === null) {
return 'null';
}
if (Array.isArray(doc)) {
return 'array';
}
if (typeof doc === 'string' && /^https?:/.test(doc)) {
return 'link';
}
if (typeof doc === 'object' && typeof doc.toISOString === 'function') {
return 'date';
}
return typeof doc;
}
function escape(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
module.exports = function (doc, styleFile) {
let indent = '';
const style = Stylize(styleFile);
let forEach = function (list, start, end, fn) {
if (!list.length) {
return start + ' ' + end;
}
let out = start + '\n';
indent += INDENT;
list.forEach(function (key, i) {
out += indent + fn(key) + (i < list.length - 1 ? ',' : '') + '\n';
});
indent = indent.slice(0, -INDENT.length);
return out + indent + end;
};
function visit(obj) {
if (obj === undefined) {
return '';
}
switch (type(obj)) {
case 'boolean':
return '<span ' + style('json-markup-bool') + '>' + obj + '</span>';
case 'number':
return '<span ' + style('json-markup-number') + '>' + obj + '</span>';
case 'date':
return '<span class="json-markup-string">"' + escape(obj.toISOString()) + '"</span>';
case 'null':
return '<span ' + style('json-markup-null') + '>null</span>';
case 'string':
return '<span ' + style('json-markup-string') + '>"' + escape(obj.replace(/\n/g, '\n' + indent)) + '"</span>';
case 'link':
return (
'<span ' + style('json-markup-string') + '>"<a href="' + encodeURI(obj) + '">' + escape(obj) + '</a>"</span>'
);
case 'array':
return forEach(obj, '[', ']', visit);
case 'object':
const keys = Object.keys(obj).filter(function (key) {
return obj[key] !== undefined;
});
return forEach(keys, '{', '}', function (key) {
return '<span ' + style('json-markup-key') + '>"' + escape(key) + '":</span> ' + visit(obj[key]);
});
}
return '';
}
return '<div ' + style('json-markup') + '>' + visit(doc) + '</div>';
};

View File

@@ -180,7 +180,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): Record<string, any> {
// We want to interpolate these variables on backend
const { __interval, __interval_ms, ...rest } = scopedVars;
const { __interval, __interval_ms, ...rest } = scopedVars || {};
if (this.isFlux) {
return {
@@ -403,10 +403,6 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
params.db = this.database;
}
if (options?.policy) {
params.rp = options.policy;
}
const { q } = data;
if (method === 'POST' && has(data, 'q')) {

View File

@@ -11,8 +11,7 @@ const runExploreQuery = (
): Promise<Array<{ text: string }>> => {
const builder = new InfluxQueryBuilder(target, datasource.database);
const q = builder.buildExploreQuery(type, withKey, withMeasurementFilter);
const options = { policy: target.policy };
return datasource.metricFindQuery(q, options);
return datasource.metricFindQuery(q);
};
export async function getAllPolicies(datasource: InfluxDatasource): Promise<string[]> {

View File

@@ -2,7 +2,7 @@ import { Map as OpenLayersMap } from 'ol';
import { FeatureLike } from 'ol/Feature';
import { Subject } from 'rxjs';
import { getFrameMatchers, MapLayerHandler, MapLayerOptions, PanelData } from '@grafana/data/src';
import { getFrameMatchers, MapLayerHandler, MapLayerOptions, PanelData, textUtil } from '@grafana/data';
import { config } from '@grafana/runtime/src';
import { GeomapPanel } from '../GeomapPanel';
@@ -114,6 +114,10 @@ export async function initLayer(
return Promise.reject('unknown layer: ' + options.type);
}
if (options.config?.attribution) {
options.config.attribution = textUtil.sanitizeTextPanelContent(options.config.attribution);
}
const handler = await item.create(map, options, panel.props.eventBus, config.theme2);
const layer = handler.init(); // eslint-disable-line
if (options.opacity != null) {

View File

@@ -1106,7 +1106,8 @@ def redis_integration_tests_step():
},
"commands": [
"dockerize -wait tcp://redis:6379/0 -timeout 120s",
"./bin/grabpl integration-tests",
"go clean -testcache",
"go list './pkg/...' | xargs -I {} sh -c 'go test -run Integration -covermode=atomic -timeout=5m {}'",
],
}

View File

@@ -4815,9 +4815,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana-plugins/input-datasource@workspace:plugins-bundled/internal/input-datasource"
dependencies:
"@grafana/data": 9.4.0
"@grafana/toolkit": 9.4.0
"@grafana/ui": 9.4.0
"@grafana/data": 9.4.1
"@grafana/toolkit": 9.4.1
"@grafana/ui": 9.4.1
"@types/jest": 26.0.15
"@types/lodash": 4.14.149
"@types/react": 17.0.30
@@ -4838,12 +4838,12 @@ __metadata:
languageName: node
linkType: hard
"@grafana/data@9.4.0, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
"@grafana/data@9.4.1, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
version: 0.0.0-use.local
resolution: "@grafana/data@workspace:packages/grafana-data"
dependencies:
"@braintree/sanitize-url": 6.0.1
"@grafana/schema": 9.4.0
"@grafana/schema": 9.4.1
"@grafana/tsconfig": ^1.2.0-rc1
"@rollup/plugin-commonjs": 23.0.2
"@rollup/plugin-json": 5.0.1
@@ -4901,7 +4901,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/e2e-selectors@9.4.0, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
"@grafana/e2e-selectors@9.4.1, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
version: 0.0.0-use.local
resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors"
dependencies:
@@ -4938,7 +4938,7 @@ __metadata:
"@babel/core": 7.20.5
"@babel/preset-env": 7.20.2
"@cypress/webpack-preprocessor": 5.16.0
"@grafana/e2e-selectors": 9.4.0
"@grafana/e2e-selectors": 9.4.1
"@grafana/tsconfig": ^1.2.0-rc1
"@mochajs/json-file-reporter": ^1.2.0
"@rollup/plugin-node-resolve": 15.0.1
@@ -5110,11 +5110,11 @@ __metadata:
version: 0.0.0-use.local
resolution: "@grafana/runtime@workspace:packages/grafana-runtime"
dependencies:
"@grafana/data": 9.4.0
"@grafana/e2e-selectors": 9.4.0
"@grafana/data": 9.4.1
"@grafana/e2e-selectors": 9.4.1
"@grafana/faro-web-sdk": 1.0.0-beta2
"@grafana/tsconfig": ^1.2.0-rc1
"@grafana/ui": 9.4.0
"@grafana/ui": 9.4.1
"@rollup/plugin-commonjs": 23.0.2
"@rollup/plugin-node-resolve": 15.0.1
"@sentry/browser": 6.19.7
@@ -5165,7 +5165,7 @@ __metadata:
languageName: node
linkType: hard
"@grafana/schema@9.4.0, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
"@grafana/schema@9.4.1, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
version: 0.0.0-use.local
resolution: "@grafana/schema@workspace:packages/grafana-schema"
dependencies:
@@ -5184,7 +5184,7 @@ __metadata:
languageName: unknown
linkType: soft
"@grafana/toolkit@9.4.0, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
"@grafana/toolkit@9.4.1, @grafana/toolkit@workspace:*, @grafana/toolkit@workspace:packages/grafana-toolkit":
version: 0.0.0-use.local
resolution: "@grafana/toolkit@workspace:packages/grafana-toolkit"
dependencies:
@@ -5200,10 +5200,10 @@ __metadata:
"@babel/preset-env": 7.18.9
"@babel/preset-react": 7.18.6
"@babel/preset-typescript": 7.18.6
"@grafana/data": 9.4.0
"@grafana/data": 9.4.1
"@grafana/eslint-config": 5.0.0
"@grafana/tsconfig": ^1.2.0-rc1
"@grafana/ui": 9.4.0
"@grafana/ui": 9.4.1
"@jest/core": 27.5.1
"@types/command-exists": ^1.2.0
"@types/eslint": 8.4.1
@@ -5284,16 +5284,16 @@ __metadata:
languageName: node
linkType: hard
"@grafana/ui@9.4.0, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
"@grafana/ui@9.4.1, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
version: 0.0.0-use.local
resolution: "@grafana/ui@workspace:packages/grafana-ui"
dependencies:
"@babel/core": 7.20.5
"@emotion/css": 11.10.5
"@emotion/react": 11.10.5
"@grafana/data": 9.4.0
"@grafana/e2e-selectors": 9.4.0
"@grafana/schema": 9.4.0
"@grafana/data": 9.4.1
"@grafana/e2e-selectors": 9.4.1
"@grafana/schema": 9.4.1
"@grafana/tsconfig": ^1.2.0-rc1
"@leeoniya/ufuzzy": 0.9.0
"@mdx-js/react": 1.6.22
@@ -18444,9 +18444,9 @@ __metadata:
linkType: hard
"dompurify@npm:^2.4.1":
version: 2.4.3
resolution: "dompurify@npm:2.4.3"
checksum: b440981f2a38cada2085759cc3d1e2f94571afc34343d011a8a6aa1ad91ae6abf651adbfa4994b0e2283f0ce81f7891cdb04b67d0b234c8d190cb70e9691f026
version: 2.4.1
resolution: "dompurify@npm:2.4.1"
checksum: 1169177465b3cbb25a44322937fba549f6c4e1a91b83245d144471be26619c835cccf0f8e20aa78c25ac11a06efd17cc1b9db9cacadceb78a4c08a1029eafee5
languageName: node
linkType: hard