mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'origin/main' into drclau/unistor/replace-authenticators-3
This commit is contained in:
commit
36b3752490
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@ -76,6 +76,7 @@
|
||||
/pkg/api/ @grafana/grafana-backend-group
|
||||
/pkg/apis/ @grafana/grafana-app-platform-squad
|
||||
/pkg/apis/alerting_notifications @grafana/grafana-app-platform-squad @grafana/alerting-backend @grafana/alerting-frontend
|
||||
/pkg/apis/query @grafana/grafana-datasources-core-services
|
||||
/pkg/bus/ @grafana/grafana-search-and-storage
|
||||
/pkg/cmd/ @grafana/grafana-backend-group
|
||||
/pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad
|
||||
@ -403,6 +404,9 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
||||
/public/app/core/components/TimeSeries/ @grafana/dataviz-squad
|
||||
/public/app/core/components/TimelineChart/ @grafana/dataviz-squad
|
||||
/public/app/core/components/Form/ @grafana/grafana-frontend-platform
|
||||
/public/app/core/components/OptionsUI/ @grafana/dashboards-squad @grafana/dataviz-squad
|
||||
|
||||
|
||||
/public/app/core/history/ @grafana/explore-squad
|
||||
/public/app/features/admin/ @grafana/identity-access-team
|
||||
|
||||
@ -662,6 +666,7 @@ embed.go @grafana/grafana-as-code
|
||||
/pkg/registry/ @grafana/grafana-as-code
|
||||
/pkg/registry/apis/ @grafana/grafana-app-platform-squad
|
||||
/pkg/registry/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend
|
||||
/pkg/registry/apis/query @grafana/grafana-datasources-core-services
|
||||
/pkg/codegen/ @grafana/grafana-as-code
|
||||
/pkg/codegen/generators @grafana/grafana-as-code
|
||||
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
||||
|
@ -198,6 +198,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `exploreLogsShardSplitting` | Used in Explore Logs to split queries into multiple queries based on the number of shards |
|
||||
| `exploreLogsAggregatedMetrics` | Used in Explore Logs to query by aggregated metrics |
|
||||
| `exploreLogsLimitedTimeRange` | Used in Explore Logs to limit the time range |
|
||||
| `appSidecar` | Enable the app sidecar feature that allows rendering 2 apps at the same time |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
22
go.mod
22
go.mod
@ -75,7 +75,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||
github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e // @grafana/identity-access-team
|
||||
github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad
|
||||
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
|
||||
@ -85,11 +85,11 @@ require (
|
||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
||||
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e // @grafana/grafana-operator-experience-squad
|
||||
github.com/grafana/grafana-aws-sdk v0.30.0 // @grafana/aws-datasources
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.1 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.2 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-cloud-migration-snapshot v1.3.0 // @grafana/grafana-operator-experience-squad
|
||||
github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad
|
||||
@ -138,7 +138,7 @@ require (
|
||||
github.com/openfga/openfga v1.5.4 // @grafana/identity-access-team
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend
|
||||
github.com/prometheus/alertmanager v0.27.0 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_golang v1.20.2 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_golang v1.20.3 // @grafana/alerting-backend
|
||||
github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group
|
||||
github.com/prometheus/common v0.55.0 // @grafana/alerting-backend
|
||||
github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-backend
|
||||
@ -172,18 +172,18 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // @grafana/alerting-backend
|
||||
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
|
||||
gocloud.dev v0.39.0 // @grafana/grafana-app-platform-squad
|
||||
golang.org/x/crypto v0.26.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/crypto v0.27.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // @grafana/alerting-backend
|
||||
golang.org/x/mod v0.19.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/net v0.28.0 // @grafana/oss-big-tent @grafana/partner-datasources
|
||||
golang.org/x/net v0.29.0 // @grafana/oss-big-tent @grafana/partner-datasources
|
||||
golang.org/x/oauth2 v0.22.0 // @grafana/identity-access-team
|
||||
golang.org/x/sync v0.8.0 // @grafana/alerting-backend
|
||||
golang.org/x/text v0.17.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/text v0.18.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/time v0.6.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/tools v0.23.0 // @grafana/grafana-as-code
|
||||
gonum.org/v1/gonum v0.14.0 // @grafana/observability-metrics
|
||||
google.golang.org/api v0.191.0 // @grafana/grafana-backend-group
|
||||
google.golang.org/grpc v1.65.0 // @grafana/plugins-platform-backend
|
||||
google.golang.org/grpc v1.66.0 // @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.34.2 // @grafana/plugins-platform-backend
|
||||
gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend
|
||||
gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group
|
||||
@ -439,8 +439,8 @@ require (
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
@ -485,7 +485,7 @@ require (
|
||||
github.com/hairyhenderson/go-which v0.2.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.3.0 // indirect
|
||||
github.com/maypok86/otter v1.2.2 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.0 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b // indirect
|
||||
)
|
||||
|
||||
|
47
go.sum
47
go.sum
@ -1852,8 +1852,9 @@ github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJ
|
||||
github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
|
||||
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
|
||||
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
|
||||
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
||||
@ -2258,8 +2259,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41 h1:p+UsX43BoDH5YlG6xUd9xDS3M4sWouy8VJ+ODv5S6uE=
|
||||
github.com/grafana/alerting v0.0.0-20240822131459-9daa6239cc41/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e h1:FhuMCsOqm6wCzlikbmjSGJJ6pDrcC3FeFTQBCqvOfyk=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 h1:e+kFqd2sECBhbxOV1NoVxsudLygNQuu9bO+7FjNTkXo=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e h1:ng5SopWamGS0MHaCj2e5huWYxAfMeCrj1l/dbJnfiow=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
|
||||
@ -2282,8 +2283,8 @@ github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e h1:UlEET0InuoFa
|
||||
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||
github.com/grafana/grafana-aws-sdk v0.30.0 h1:6IIetM4s2NbvPOI4/fefsyN84BIb0/T09lHGF/pywo8=
|
||||
github.com/grafana/grafana-aws-sdk v0.30.0/go.mod h1:ZSVPU7IIJSi5lEg+K3Js+EUpZLXxUaBdaQWH+As1ihI=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.1 h1:90HjoS3kvCd6Thvcl29rtL2+AcSt9AAIDqgQAGk/6T8=
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.1/go.mod h1:yqaupYdH8i42m3VRrmVgNNLGvr4NVjoDmstgZzASAnc=
|
||||
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-cloud-migration-snapshot v1.3.0 h1:F0O9eTy4jHjEd1Z3/qIza2GdY7PYpTddUeaq9p3NKGU=
|
||||
github.com/grafana/grafana-cloud-migration-snapshot v1.3.0/go.mod h1:bd6Cm06EK0MzRO5ahUpbDz1SxNOKu+fzladbaRPHZPY=
|
||||
github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA=
|
||||
@ -2291,8 +2292,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0 h1:id7MPpONfZhSlhE5K4LU1rBqaVcpe2EDqqPdyb7UTfw=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0/go.mod h1:bK7/yelc8cANvNzXPbXk64I4qkqeyQfnyIx4U9y/ItY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0 h1:QOxVw3sgaPaxR3odu9DSRNGdCknyAuMWl0hDhaF91OA=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0/go.mod h1:sEi0wRVTvpxyB0KaMNbhPM74OnDMwVpah97usm6QXEM=
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 h1:2H9x4q53pkfUGtSNYD1qSBpNnxrFgylof/TYADb5xMI=
|
||||
github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2/go.mod h1:gBLBniiSUQvyt4LRrpIeysj8Many0DV+hdUKifRE0Ec=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o=
|
||||
@ -2942,8 +2943,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA=
|
||||
github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -2970,8 +2971,8 @@ github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -3478,8 +3479,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -3656,8 +3657,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -3867,8 +3868,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -3892,8 +3893,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -3915,8 +3916,8 @@ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -4453,8 +4454,8 @@ google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJai
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
16
go.work.sum
16
go.work.sum
@ -387,6 +387,7 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
||||
github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
@ -461,10 +462,13 @@ github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn
|
||||
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s=
|
||||
github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
|
||||
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
|
||||
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
|
||||
@ -475,6 +479,7 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
|
||||
github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
@ -494,6 +499,7 @@ github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs=
|
||||
github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM=
|
||||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
|
||||
github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@ -541,6 +547,7 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
@ -567,6 +574,7 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE=
|
||||
github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig=
|
||||
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
|
||||
github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
|
||||
@ -741,6 +749,8 @@ github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwy
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||
@ -945,6 +955,7 @@ go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||
@ -965,6 +976,7 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
@ -976,15 +988,18 @@ gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240730163845-b1a4ccb954bf h1:T4tsZBlZYXK3j40sQNP5MBO32I+rn6ypV1PpklsiV8k=
|
||||
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:5/MT647Cn/GGhwTpXC7QqcaR5Cnee4v4MKCU1/nwnIQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
|
||||
@ -998,6 +1013,7 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
|
@ -375,7 +375,7 @@
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"react-router": "5.3.3",
|
||||
"react-router-dom": "5.3.3",
|
||||
"react-router-dom-v5-compat": "^6.10.0",
|
||||
"react-router-dom-v5-compat": "^6.26.1",
|
||||
"react-select": "5.8.0",
|
||||
"react-split-pane": "0.1.92",
|
||||
"react-table": "7.8.0",
|
||||
|
@ -579,6 +579,7 @@ export {
|
||||
PluginSignatureType,
|
||||
PluginErrorCode,
|
||||
PluginIncludeType,
|
||||
PluginLoadingStrategy,
|
||||
GrafanaPlugin,
|
||||
type PluginError,
|
||||
type AngularMeta,
|
||||
|
@ -208,4 +208,5 @@ export interface FeatureToggles {
|
||||
exploreLogsAggregatedMetrics?: boolean;
|
||||
exploreLogsLimitedTimeRange?: boolean;
|
||||
appPlatformAccessTokens?: boolean;
|
||||
appSidecar?: boolean;
|
||||
}
|
||||
|
@ -59,6 +59,12 @@ export interface AngularMeta {
|
||||
hideDeprecation: boolean;
|
||||
}
|
||||
|
||||
// Signals to SystemJS how to load frontend js assets.
|
||||
export enum PluginLoadingStrategy {
|
||||
fetch = 'fetch',
|
||||
script = 'script',
|
||||
}
|
||||
|
||||
export interface PluginMeta<T extends KeyValue = {}> {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -91,6 +97,7 @@ export interface PluginMeta<T extends KeyValue = {}> {
|
||||
live?: boolean;
|
||||
angular?: AngularMeta;
|
||||
angularDetected?: boolean;
|
||||
loadingStrategy?: PluginLoadingStrategy;
|
||||
}
|
||||
|
||||
interface PluginDependencyInfo {
|
||||
|
@ -75,7 +75,10 @@ export type PluginExtensionAddedComponentConfig<Props = {}> = PluginExtensionCon
|
||||
component: React.ComponentType<Props>;
|
||||
};
|
||||
|
||||
export type PluginAddedLinksConfigureFunc<Context extends object> = (context?: Readonly<Context>) =>
|
||||
export type PluginAddedLinksConfigureFunc<Context extends object> = (
|
||||
context: Readonly<Context> | undefined,
|
||||
helpers: PluginExtensionHelpers
|
||||
) =>
|
||||
| Partial<{
|
||||
title: string;
|
||||
description: string;
|
||||
@ -142,11 +145,27 @@ export type PluginExtensionOpenModalOptions = {
|
||||
height?: string | number;
|
||||
};
|
||||
|
||||
type PluginExtensionHelpers = {
|
||||
/** Checks if the app plugin (that registers the extension) is currently visible (either in the main view or in the side view)
|
||||
* @experimental
|
||||
*/
|
||||
isAppOpened: () => boolean;
|
||||
};
|
||||
|
||||
export type PluginExtensionEventHelpers<Context extends object = object> = {
|
||||
context?: Readonly<Context>;
|
||||
// Opens a modal dialog and renders the provided React component inside it
|
||||
openModal: (options: PluginExtensionOpenModalOptions) => void;
|
||||
};
|
||||
|
||||
/** Opens the app plugin (that registers the extensions) in a side view
|
||||
* @experimental
|
||||
*/
|
||||
openAppInSideview: (context?: unknown) => void;
|
||||
/** Closes the side view for the app plugin (that registers the extensions) in case it was open
|
||||
* @experimental
|
||||
*/
|
||||
closeAppInSideview: () => void;
|
||||
} & PluginExtensionHelpers;
|
||||
|
||||
// Extension Points & Contexts
|
||||
// --------------------------------------------------------
|
||||
|
@ -17,18 +17,22 @@ export interface ScopeDashboardBinding {
|
||||
status: ScopeDashboardBindingStatus;
|
||||
}
|
||||
|
||||
export type ScopeFilterOperator = 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match';
|
||||
export type ScopeFilterOperator = 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match' | 'one-of' | 'not-one-of';
|
||||
|
||||
export const scopeFilterOperatorMap: Record<string, ScopeFilterOperator> = {
|
||||
'=': 'equals',
|
||||
'!=': 'not-equals',
|
||||
'=~': 'regex-match',
|
||||
'!~': 'regex-not-match',
|
||||
'=|': 'one-of',
|
||||
'!=|': 'not-one-of',
|
||||
};
|
||||
|
||||
export interface ScopeSpecFilter {
|
||||
key: string;
|
||||
value: string;
|
||||
// values is used for operators that support multiple values (e.g. one-of, not-one-of)
|
||||
values?: string[];
|
||||
operator: ScopeFilterOperator;
|
||||
}
|
||||
|
||||
|
@ -1275,6 +1275,8 @@ describe('modifyQuery', () => {
|
||||
},
|
||||
{ key: 'reg', value: 'regv', operator: '=~' },
|
||||
{ key: 'nreg', value: 'nregv', operator: '!~' },
|
||||
{ key: 'foo', value: 'bar', operator: '=|' },
|
||||
{ key: 'bar', value: 'baz', operator: '!=|' },
|
||||
];
|
||||
const expectedScopeFilter: ScopeSpecFilter[] = [
|
||||
{ key: 'eq', value: 'eqv', operator: 'equals' },
|
||||
@ -1285,6 +1287,8 @@ describe('modifyQuery', () => {
|
||||
},
|
||||
{ key: 'reg', value: 'regv', operator: 'regex-match' },
|
||||
{ key: 'nreg', value: 'nregv', operator: 'regex-not-match' },
|
||||
{ key: 'foo', value: 'bar', operator: 'one-of' },
|
||||
{ key: 'bar', value: 'baz', operator: 'not-one-of' },
|
||||
];
|
||||
const result = ds.generateScopeFilters(adhocFilter);
|
||||
result.forEach((r, i) => {
|
||||
|
@ -603,7 +603,7 @@ export class PrometheusDatasource
|
||||
return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k }));
|
||||
}
|
||||
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map(remapOneOf).map((f) => ({
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({
|
||||
label: f.key,
|
||||
value: f.value,
|
||||
op: f.operator,
|
||||
@ -620,7 +620,7 @@ export class PrometheusDatasource
|
||||
|
||||
// By implementing getTagKeys and getTagValues we add ad-hoc filters functionality
|
||||
async getTagValues(options: DataSourceGetTagValuesOptions<PromQuery>) {
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map(remapOneOf).map((f) => ({
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({
|
||||
label: f.key,
|
||||
value: f.value,
|
||||
op: f.operator,
|
||||
@ -822,10 +822,11 @@ export class PrometheusDatasource
|
||||
return [];
|
||||
}
|
||||
|
||||
return filters.map(remapOneOf).map((f) => ({
|
||||
...f,
|
||||
value: this.templateSrv.replace(f.value, {}, this.interpolateQueryExpr),
|
||||
return filters.map((f) => ({
|
||||
key: f.key,
|
||||
operator: scopeFilterOperatorMap[f.operator],
|
||||
value: this.templateSrv.replace(f.value, {}, this.interpolateQueryExpr),
|
||||
values: f.values?.map((v) => this.templateSrv.replace(v, {}, this.interpolateQueryExpr)),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -834,7 +835,7 @@ export class PrometheusDatasource
|
||||
return expr;
|
||||
}
|
||||
|
||||
const finalQuery = filters.map(remapOneOf).reduce((acc, filter) => {
|
||||
const finalQuery = filters.reduce((acc, filter) => {
|
||||
const { key, operator } = filter;
|
||||
let { value } = filter;
|
||||
if (operator === '=~' || operator === '!~') {
|
||||
@ -1001,19 +1002,3 @@ export function prometheusRegularEscape<T>(value: T) {
|
||||
export function prometheusSpecialRegexEscape<T>(value: T) {
|
||||
return typeof value === 'string' ? value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]\'+?.()|]/g, '\\\\$&') : value;
|
||||
}
|
||||
|
||||
export function remapOneOf(filter: AdHocVariableFilter) {
|
||||
let { operator, value, values } = filter;
|
||||
if (operator === '=|') {
|
||||
operator = '=~';
|
||||
value = values?.map(prometheusRegularEscape).join('|') ?? '';
|
||||
} else if (operator === '!=|') {
|
||||
operator = '!~';
|
||||
value = values?.map(prometheusRegularEscape).join('|') ?? '';
|
||||
}
|
||||
return {
|
||||
...filter,
|
||||
operator,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
SystemDateFormatSettings,
|
||||
getThemeById,
|
||||
AngularMeta,
|
||||
PluginLoadingStrategy,
|
||||
} from '@grafana/data';
|
||||
|
||||
export interface AzureSettings {
|
||||
@ -40,6 +41,7 @@ export type AppPluginConfig = {
|
||||
version: string;
|
||||
preload: boolean;
|
||||
angular: AngularMeta;
|
||||
loadingStrategy: PluginLoadingStrategy;
|
||||
};
|
||||
|
||||
export type PreinstalledPlugin = {
|
||||
|
@ -12,6 +12,7 @@ export type UsePluginExtensions<T = PluginExtension> = (
|
||||
|
||||
export type GetPluginExtensionsOptions = {
|
||||
extensionPointId: string;
|
||||
// Make sure this object is properly memoized and not mutated.
|
||||
context?: object | Record<string | symbol, unknown>;
|
||||
limitPerPlugin?: number;
|
||||
};
|
||||
|
@ -98,7 +98,7 @@
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-inlinesvg": "3.0.2",
|
||||
"react-loading-skeleton": "3.4.0",
|
||||
"react-router-dom": "5.3.3",
|
||||
"react-router-dom-v5-compat": "^6.26.1",
|
||||
"react-select": "5.8.0",
|
||||
"react-table": "7.8.0",
|
||||
"react-transition-group": "4.4.5",
|
||||
@ -151,7 +151,6 @@
|
||||
"@types/react-color": "3.0.12",
|
||||
"@types/react-dom": "18.2.25",
|
||||
"@types/react-highlight-words": "0.20.0",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/react-test-renderer": "18.3.0",
|
||||
"@types/react-transition-group": "4.4.11",
|
||||
"@types/react-window": "1.8.8",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { AnchorHTMLAttributes, forwardRef } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link as RouterLink } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { locationUtil, textUtil } from '@grafana/data';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MemoryRouter } from 'react-router-dom-v5-compat';
|
||||
|
||||
import { GrafanaConfig, locationUtil } from '@grafana/data';
|
||||
|
||||
|
@ -300,6 +300,16 @@ export function useSplitter(options: UseSplitterOptions) {
|
||||
const dragHandleStyle = direction === 'column' ? dragStyles.dragHandleHorizontal : dragStyles.dragHandleVertical;
|
||||
const id = useId();
|
||||
|
||||
const primaryStyles: React.CSSProperties = {
|
||||
flexGrow: clamp(initialSize ?? 0.5, 0, 1),
|
||||
[minDimProp]: 'min-content',
|
||||
};
|
||||
|
||||
const secondaryStyles: React.CSSProperties = {
|
||||
flexGrow: clamp(1 - initialSize, 0, 1),
|
||||
[minDimProp]: 'min-content',
|
||||
};
|
||||
|
||||
return {
|
||||
containerProps: {
|
||||
ref: containerRef,
|
||||
@ -308,18 +318,12 @@ export function useSplitter(options: UseSplitterOptions) {
|
||||
primaryProps: {
|
||||
ref: firstPaneRef,
|
||||
className: styles.panel,
|
||||
style: {
|
||||
[minDimProp]: 'min-content',
|
||||
flexGrow: clamp(initialSize ?? 0.5, 0, 1),
|
||||
},
|
||||
style: primaryStyles,
|
||||
},
|
||||
secondaryProps: {
|
||||
ref: secondPaneRef,
|
||||
className: styles.panel,
|
||||
style: {
|
||||
flexGrow: clamp(1 - initialSize, 0, 1),
|
||||
[minDimProp]: 'min-content',
|
||||
},
|
||||
style: secondaryStyles,
|
||||
},
|
||||
splitterProps: {
|
||||
onPointerUp,
|
||||
|
@ -4,7 +4,7 @@ go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/emicklei/go-restful/v3 v3.11.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435
|
||||
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38
|
||||
@ -98,7 +98,7 @@ require (
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.3 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
@ -131,15 +131,15 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
@ -147,7 +147,7 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/grpc v1.66.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
|
@ -130,8 +130,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0 h1:id7MPpONfZhSlhE5K4LU1rBqaVcpe2EDqqPdyb7UTfw=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0/go.mod h1:bK7/yelc8cANvNzXPbXk64I4qkqeyQfnyIx4U9y/ItY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0 h1:QOxVw3sgaPaxR3odu9DSRNGdCknyAuMWl0hDhaF91OA=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0/go.mod h1:sEi0wRVTvpxyB0KaMNbhPM74OnDMwVpah97usm6QXEM=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435 h1:lmw60EW7JWlAEvgggktOyVkH4hF1m/+LSF/Ap0NCyi8=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435/go.mod h1:ORVFiW/KNRY52lNjkGwnFWCxNVfE97bJG2jr2fetq0I=
|
||||
github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 h1:SNEeqY22DrGr5E9kGF1mKSqlOom14W9+b1u4XEGJowA=
|
||||
@ -247,8 +247,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -384,8 +384,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
@ -406,8 +406,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@ -434,14 +434,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -482,8 +482,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -28,6 +28,7 @@ type PluginSetting struct {
|
||||
SignatureType plugins.SignatureType `json:"signatureType"`
|
||||
SignatureOrg string `json:"signatureOrg"`
|
||||
AngularDetected bool `json:"angularDetected"`
|
||||
LoadingStrategy plugins.LoadingStrategy `json:"loadingStrategy"`
|
||||
}
|
||||
|
||||
type PluginListItem struct {
|
||||
|
@ -110,7 +110,8 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
|
||||
apps := make(map[string]*plugins.AppDTO, 0)
|
||||
for _, ap := range availablePlugins[plugins.TypeApp] {
|
||||
apps[ap.Plugin.ID] = newAppDTO(
|
||||
apps[ap.Plugin.ID] = hs.newAppDTO(
|
||||
c.Req.Context(),
|
||||
ap.Plugin,
|
||||
ap.Settings,
|
||||
)
|
||||
@ -140,18 +141,19 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
}
|
||||
|
||||
panels[panel.ID] = plugins.PanelDTO{
|
||||
ID: panel.ID,
|
||||
Name: panel.Name,
|
||||
AliasIDs: panel.AliasIDs,
|
||||
Info: panel.Info,
|
||||
Module: panel.Module,
|
||||
BaseURL: panel.BaseURL,
|
||||
SkipDataQuery: panel.SkipDataQuery,
|
||||
HideFromList: panel.HideFromList,
|
||||
ReleaseState: string(panel.State),
|
||||
Signature: string(panel.Signature),
|
||||
Sort: getPanelSort(panel.ID),
|
||||
Angular: panel.Angular,
|
||||
ID: panel.ID,
|
||||
Name: panel.Name,
|
||||
AliasIDs: panel.AliasIDs,
|
||||
Info: panel.Info,
|
||||
Module: panel.Module,
|
||||
BaseURL: panel.BaseURL,
|
||||
SkipDataQuery: panel.SkipDataQuery,
|
||||
HideFromList: panel.HideFromList,
|
||||
ReleaseState: string(panel.State),
|
||||
Signature: string(panel.Signature),
|
||||
Sort: getPanelSort(panel.ID),
|
||||
Angular: panel.Angular,
|
||||
LoadingStrategy: hs.pluginAssets.LoadingStrategy(c.Req.Context(), panel),
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,6 +457,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
BaseURL: plugin.BaseURL,
|
||||
Angular: plugin.Angular,
|
||||
MultiValueFilterOperators: plugin.MultiValueFilterOperators,
|
||||
LoadingStrategy: hs.pluginAssets.LoadingStrategy(c.Req.Context(), plugin),
|
||||
}
|
||||
|
||||
if ds.JsonData == nil {
|
||||
@ -551,13 +554,14 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
||||
return dataSources, nil
|
||||
}
|
||||
|
||||
func newAppDTO(plugin pluginstore.Plugin, settings pluginsettings.InfoDTO) *plugins.AppDTO {
|
||||
func (hs *HTTPServer) newAppDTO(ctx context.Context, plugin pluginstore.Plugin, settings pluginsettings.InfoDTO) *plugins.AppDTO {
|
||||
app := &plugins.AppDTO{
|
||||
ID: plugin.ID,
|
||||
Version: plugin.Info.Version,
|
||||
Path: plugin.Module,
|
||||
Preload: false,
|
||||
Angular: plugin.Angular,
|
||||
ID: plugin.ID,
|
||||
Version: plugin.Info.Version,
|
||||
Path: plugin.Module,
|
||||
Preload: false,
|
||||
Angular: plugin.Angular,
|
||||
LoadingStrategy: hs.pluginAssets.LoadingStrategy(ctx, plugin),
|
||||
}
|
||||
|
||||
if settings.Enabled {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
@ -35,7 +36,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.FeatureToggles, pstore pluginstore.Store, psettings pluginsettings.Service) (*web.Mux, *HTTPServer) {
|
||||
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.FeatureToggles, pstore pluginstore.Store, psettings pluginsettings.Service, passets *pluginassets.Service) (*web.Mux, *HTTPServer) {
|
||||
t.Helper()
|
||||
db.InitTestDB(t)
|
||||
// nolint:staticcheck
|
||||
@ -50,6 +51,11 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
||||
})
|
||||
}
|
||||
|
||||
pluginsCDN := pluginscdn.ProvideService(&config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate,
|
||||
PluginSettings: cfg.PluginSettings,
|
||||
})
|
||||
|
||||
var pluginStore = pstore
|
||||
if pluginStore == nil {
|
||||
pluginStore = &pluginstore.FakePluginStore{}
|
||||
@ -60,6 +66,11 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
||||
pluginsSettings = &pluginsettings.FakePluginSettings{}
|
||||
}
|
||||
|
||||
var pluginsAssets = passets
|
||||
if pluginsAssets == nil {
|
||||
pluginsAssets = pluginassets.ProvideService(cfg, pluginsCDN)
|
||||
}
|
||||
|
||||
hs := &HTTPServer{
|
||||
authnService: &authntest.FakeService{},
|
||||
Cfg: cfg,
|
||||
@ -69,16 +80,14 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
||||
Cfg: cfg,
|
||||
RendererPluginManager: &fakeRendererPluginManager{},
|
||||
},
|
||||
SQLStore: db.InitTestDB(t),
|
||||
SettingsProvider: setting.ProvideProvider(cfg),
|
||||
pluginStore: pluginStore,
|
||||
grafanaUpdateChecker: &updatechecker.GrafanaService{},
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
PluginSettings: pluginsSettings,
|
||||
pluginsCDNService: pluginscdn.ProvideService(&config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate,
|
||||
PluginSettings: cfg.PluginSettings,
|
||||
}),
|
||||
SQLStore: db.InitTestDB(t),
|
||||
SettingsProvider: setting.ProvideProvider(cfg),
|
||||
pluginStore: pluginStore,
|
||||
grafanaUpdateChecker: &updatechecker.GrafanaService{},
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
PluginSettings: pluginsSettings,
|
||||
pluginsCDNService: pluginsCDN,
|
||||
pluginAssets: pluginsAssets,
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
SocialService: socialimpl.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage(), nil, &ssosettingstests.MockService{}),
|
||||
managedPluginsService: managedplugins.NewNoop(),
|
||||
@ -108,7 +117,7 @@ func TestHTTPServer_GetFrontendSettings_hideVersionAnonymous(t *testing.T) {
|
||||
cfg.BuildVersion = "7.8.9"
|
||||
cfg.BuildCommit = "01234567"
|
||||
|
||||
m, hs := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil)
|
||||
m, hs := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
|
||||
|
||||
@ -198,7 +207,7 @@ func TestHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
|
||||
if test.mutateCfg != nil {
|
||||
test.mutateCfg(cfg)
|
||||
}
|
||||
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil)
|
||||
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
@ -221,6 +230,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
desc string
|
||||
pluginStore func() pluginstore.Store
|
||||
pluginSettings func() pluginsettings.Service
|
||||
pluginAssets func() *pluginassets.Service
|
||||
expected settings
|
||||
}{
|
||||
{
|
||||
@ -245,13 +255,17 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
Plugins: newAppSettings("test-app", false),
|
||||
}
|
||||
},
|
||||
pluginAssets: func() *pluginassets.Service {
|
||||
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: false,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
ID: "test-app",
|
||||
Preload: false,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
LoadingStrategy: plugins.LoadingStrategyScript,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -278,13 +292,17 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
Plugins: newAppSettings("test-app", true),
|
||||
}
|
||||
},
|
||||
pluginAssets: func() *pluginassets.Service {
|
||||
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
LoadingStrategy: plugins.LoadingStrategyScript,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -312,14 +330,99 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
Plugins: newAppSettings("test-app", true),
|
||||
}
|
||||
},
|
||||
pluginAssets: func() *pluginassets.Service {
|
||||
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
Angular: plugins.AngularMeta{Detected: true},
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
Angular: plugins.AngularMeta{Detected: true},
|
||||
LoadingStrategy: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "app plugin with create plugin version compatible with script loading strategy",
|
||||
pluginStore: func() pluginstore.Store {
|
||||
return &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
{
|
||||
Module: fmt.Sprintf("/%s/module.js", "test-app"),
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Info: plugins.Info{Version: "0.5.0"},
|
||||
Type: plugins.TypeApp,
|
||||
Preload: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
pluginSettings: func() pluginsettings.Service {
|
||||
return &pluginsettings.FakePluginSettings{
|
||||
Plugins: newAppSettings("test-app", true),
|
||||
}
|
||||
},
|
||||
pluginAssets: func() *pluginassets.Service {
|
||||
return pluginassets.ProvideService(&setting.Cfg{
|
||||
PluginSettings: map[string]map[string]string{
|
||||
"test-app": {
|
||||
pluginassets.CreatePluginVersionCfgKey: pluginassets.CreatePluginVersionScriptSupportEnabled,
|
||||
},
|
||||
},
|
||||
}, pluginscdn.ProvideService(&config.PluginManagementCfg{}))
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
LoadingStrategy: plugins.LoadingStrategyScript,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "app plugin with CDN class",
|
||||
pluginStore: func() pluginstore.Store {
|
||||
return &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
{
|
||||
Class: plugins.ClassCDN,
|
||||
Module: fmt.Sprintf("/%s/module.js", "test-app"),
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Info: plugins.Info{Version: "0.5.0"},
|
||||
Type: plugins.TypeApp,
|
||||
Preload: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
pluginSettings: func() pluginsettings.Service {
|
||||
return &pluginsettings.FakePluginSettings{
|
||||
Plugins: newAppSettings("test-app", true),
|
||||
}
|
||||
},
|
||||
pluginAssets: func() *pluginassets.Service {
|
||||
return pluginassets.ProvideService(setting.NewCfg(), pluginscdn.ProvideService(&config.PluginManagementCfg{}))
|
||||
},
|
||||
expected: settings{
|
||||
Apps: map[string]*plugins.AppDTO{
|
||||
"test-app": {
|
||||
ID: "test-app",
|
||||
Preload: true,
|
||||
Path: "/test-app/module.js",
|
||||
Version: "0.5.0",
|
||||
LoadingStrategy: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -329,7 +432,7 @@ func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), test.pluginStore(), test.pluginSettings())
|
||||
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), test.pluginStore(), test.pluginSettings(), test.pluginAssets())
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
@ -78,6 +78,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
@ -146,6 +147,7 @@ type HTTPServer struct {
|
||||
pluginDashboardService plugindashboards.Service
|
||||
pluginStaticRouteResolver plugins.StaticRouteResolver
|
||||
pluginErrorResolver plugins.ErrorResolver
|
||||
pluginAssets *pluginassets.Service
|
||||
SearchService search.Service
|
||||
ShortURLService shorturls.Service
|
||||
QueryHistoryService queryhistory.Service
|
||||
@ -247,7 +249,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService,
|
||||
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
|
||||
dataSourcesService datasources.DataSourceService, queryDataService query.Service, pluginFileStore plugins.FileStore,
|
||||
serviceaccountsService serviceaccounts.Service,
|
||||
serviceaccountsService serviceaccounts.Service, pluginAssets *pluginassets.Service,
|
||||
authInfoService login.AuthInfoService, storageService store.StorageService,
|
||||
notificationService notifications.Service, dashboardService dashboards.DashboardService,
|
||||
dashboardProvisioningService dashboards.DashboardProvisioningService, folderService folder.Service,
|
||||
@ -286,6 +288,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
pluginStore: pluginStore,
|
||||
pluginStaticRouteResolver: pluginStaticRouteResolver,
|
||||
pluginDashboardService: pluginDashboardService,
|
||||
pluginAssets: pluginAssets,
|
||||
pluginErrorResolver: pluginErrorResolver,
|
||||
pluginFileStore: pluginFileStore,
|
||||
grafanaUpdateChecker: grafanaUpdateChecker,
|
||||
|
@ -208,6 +208,7 @@ func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.
|
||||
SignatureOrg: plugin.SignatureOrg,
|
||||
SecureJsonFields: map[string]bool{},
|
||||
AngularDetected: plugin.Angular.Detected,
|
||||
LoadingStrategy: hs.pluginAssets.LoadingStrategy(c.Req.Context(), plugin),
|
||||
}
|
||||
|
||||
if plugin.IsApp() {
|
||||
|
@ -41,6 +41,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
@ -817,6 +818,7 @@ func Test_PluginsSettings(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
SecureJsonFields: map[string]bool{},
|
||||
LoadingStrategy: plugins.LoadingStrategyScript,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -841,6 +843,8 @@ func Test_PluginsSettings(t *testing.T) {
|
||||
ErrorCode: tc.errCode,
|
||||
})
|
||||
}
|
||||
pluginCDN := pluginscdn.ProvideService(&config.PluginManagementCfg{})
|
||||
hs.pluginAssets = pluginassets.ProvideService(hs.Cfg, pluginCDN)
|
||||
hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker)
|
||||
var err error
|
||||
hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest())
|
||||
|
@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e // @grafana/identity-access-team
|
||||
github.com/stretchr/testify v1.9.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
@ -32,13 +32,15 @@ require (
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/grpc v1.66.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
@ -28,8 +28,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e h1:FhuMCsOqm6wCzlikbmjSGJJ6pDrcC3FeFTQBCqvOfyk=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 h1:e+kFqd2sECBhbxOV1NoVxsudLygNQuu9bO+7FjNTkXo=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e h1:ng5SopWamGS0MHaCj2e5huWYxAfMeCrj1l/dbJnfiow=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
@ -68,13 +68,17 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@ -87,8 +91,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -106,8 +110,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -119,8 +123,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@ -133,8 +137,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -26,8 +26,10 @@ type ScopeSpec struct {
|
||||
}
|
||||
|
||||
type ScopeFilter struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
// Values is used for operators that require multiple values (e.g. one-of and not-one-of).
|
||||
Values []string `json:"values,omitempty"`
|
||||
Operator FilterOperator `json:"operator"`
|
||||
}
|
||||
|
||||
@ -41,6 +43,8 @@ const (
|
||||
FilterOperatorNotEquals FilterOperator = "not-equals"
|
||||
FilterOperatorRegexMatch FilterOperator = "regex-match"
|
||||
FilterOperatorRegexNotMatch FilterOperator = "regex-not-match"
|
||||
FilterOperatorOneOf FilterOperator = "one-of"
|
||||
FilterOperatorNotOneOf FilterOperator = "not-one-of"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@ -219,6 +219,11 @@ func (in *ScopeDashboardBindingStatus) DeepCopy() *ScopeDashboardBindingStatus {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ScopeFilter) DeepCopyInto(out *ScopeFilter) {
|
||||
*out = *in
|
||||
if in.Values != nil {
|
||||
in, out := &in.Values, &out.Values
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -347,7 +352,9 @@ func (in *ScopeSpec) DeepCopyInto(out *ScopeSpec) {
|
||||
if in.Filters != nil {
|
||||
in, out := &in.Filters, &out.Filters
|
||||
*out = make([]ScopeFilter, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -387,13 +387,28 @@ func schema_pkg_apis_scope_v0alpha1_ScopeFilter(ref common.ReferenceCallback) co
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"values": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"operator": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Possible enum values:\n - `\"equals\"`\n - `\"not-equals\"`\n - `\"regex-match\"`\n - `\"regex-not-match\"`",
|
||||
Description: "Possible enum values:\n - `\"equals\"`\n - `\"not-equals\"`\n - `\"not-one-of\"`\n - `\"one-of\"`\n - `\"regex-match\"`\n - `\"regex-not-match\"`",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"equals", "not-equals", "regex-match", "regex-not-match"},
|
||||
Enum: []interface{}{"equals", "not-equals", "not-one-of", "one-of", "regex-match", "regex-not-match"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,FindScopeDashboardBindingsResults,Items
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeDashboardBindingStatus,Groups
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeFilter,Values
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeNodeSpec,LinkID
|
||||
|
@ -6,7 +6,7 @@ require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.opentelemetry.io/otel/trace v1.29.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
@ -72,16 +72,16 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/grpc v1.66.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
@ -132,8 +132,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -219,8 +219,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
@ -239,8 +239,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@ -255,14 +255,14 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -298,8 +298,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -36,16 +36,16 @@ require (
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect; @grafana/grafana-backend-group
|
||||
go.opentelemetry.io/otel/sdk v1.29.0 // indirect; @grafana/grafana-backend-group
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/crypto v0.26.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/crypto v0.27.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/mod v0.19.0 // @grafana/grafana-backend-group
|
||||
golang.org/x/net v0.28.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources
|
||||
golang.org/x/net v0.29.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources
|
||||
golang.org/x/oauth2 v0.22.0 // @grafana/identity-access-team
|
||||
golang.org/x/sync v0.8.0 // indirect; @grafana/alerting-backend
|
||||
golang.org/x/text v0.17.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/text v0.18.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/time v0.6.0 // indirect; @grafana/grafana-backend-group
|
||||
golang.org/x/tools v0.23.0 // indirect; @grafana/grafana-as-code
|
||||
google.golang.org/api v0.191.0 // @grafana/grafana-backend-group
|
||||
google.golang.org/grpc v1.65.0 // indirect; @grafana/plugins-platform-backend
|
||||
google.golang.org/grpc v1.66.0 // indirect; @grafana/plugins-platform-backend
|
||||
google.golang.org/protobuf v1.34.2 // indirect; @grafana/plugins-platform-backend
|
||||
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend
|
||||
)
|
||||
@ -85,7 +85,7 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
|
@ -263,8 +263,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
@ -284,8 +284,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@ -306,13 +306,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
@ -349,8 +349,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -57,13 +57,14 @@ func (s *Service) Base(n PluginInfo) (string, error) {
|
||||
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "")
|
||||
}
|
||||
if n.parent != nil {
|
||||
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if s.cdn.PluginSupported(n.parent.pluginJSON.ID) {
|
||||
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, relPath)
|
||||
}
|
||||
return path.Join("public/plugins", n.parent.pluginJSON.ID, relPath), nil
|
||||
}
|
||||
|
||||
return path.Join("public/plugins", n.pluginJSON.ID), nil
|
||||
@ -87,13 +88,14 @@ func (s *Service) Module(n PluginInfo) (string, error) {
|
||||
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "module.js")
|
||||
}
|
||||
if n.parent != nil {
|
||||
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if s.cdn.PluginSupported(n.parent.pluginJSON.ID) {
|
||||
relPath, err := n.parent.fs.Rel(n.fs.Base())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, "module.js"))
|
||||
}
|
||||
return path.Join("public/plugins", n.parent.pluginJSON.ID, relPath, "module.js"), nil
|
||||
}
|
||||
|
||||
return path.Join("public/plugins", n.pluginJSON.ID, "module.js"), nil
|
||||
@ -117,10 +119,6 @@ func (s *Service) RelativeURL(n PluginInfo, pathStr string) (string, error) {
|
||||
return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, pathStr))
|
||||
}
|
||||
}
|
||||
|
||||
if s.cdn.PluginSupported(n.pluginJSON.ID) {
|
||||
return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr)
|
||||
}
|
||||
// Local
|
||||
u, err := url.Parse(pathStr)
|
||||
if err != nil {
|
||||
|
@ -187,3 +187,153 @@ func TestService(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ChildPlugins(t *testing.T) {
|
||||
type expected struct {
|
||||
module string
|
||||
baseURL string
|
||||
relURL string
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
name string
|
||||
cfg *config.PluginManagementCfg
|
||||
pluginInfo func() PluginInfo
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "Local FS external plugin",
|
||||
cfg: &config.PluginManagementCfg{},
|
||||
pluginInfo: func() PluginInfo {
|
||||
return NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil)
|
||||
},
|
||||
expected: expected{
|
||||
module: "public/plugins/parent/module.js",
|
||||
baseURL: "public/plugins/parent",
|
||||
relURL: "public/plugins/parent/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Local FS external plugin with child",
|
||||
cfg: &config.PluginManagementCfg{},
|
||||
pluginInfo: func() PluginInfo {
|
||||
parentInfo := NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil)
|
||||
childInfo := NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent/child"), &parentInfo)
|
||||
return childInfo
|
||||
},
|
||||
expected: expected{
|
||||
module: "public/plugins/parent/child/module.js",
|
||||
baseURL: "public/plugins/parent/child",
|
||||
relURL: "public/plugins/parent/child/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Local FS core plugin",
|
||||
cfg: &config.PluginManagementCfg{},
|
||||
pluginInfo: func() PluginInfo {
|
||||
return NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCore, plugins.NewLocalFS("/plugins/parent"), nil)
|
||||
},
|
||||
expected: expected{
|
||||
module: "core:plugin/parent",
|
||||
baseURL: "public/app/plugins/parent",
|
||||
relURL: "public/app/plugins/parent/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Externally-built Local FS core plugin",
|
||||
cfg: &config.PluginManagementCfg{},
|
||||
pluginInfo: func() PluginInfo {
|
||||
return NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCore, plugins.NewLocalFS("/plugins/parent/dist"), nil)
|
||||
},
|
||||
expected: expected{
|
||||
module: "public/plugins/parent/module.js",
|
||||
baseURL: "public/app/plugins/parent",
|
||||
relURL: "public/app/plugins/parent/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CDN Class plugin",
|
||||
cfg: &config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: "https://cdn.example.com",
|
||||
},
|
||||
pluginInfo: func() PluginInfo {
|
||||
return NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/plugins/parent"), nil)
|
||||
},
|
||||
expected: expected{
|
||||
module: "https://cdn.example.com/plugins/parent/module.js",
|
||||
baseURL: "https://cdn.example.com/plugins/parent",
|
||||
relURL: "https://cdn.example.com/plugins/parent/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CDN Class plugin with child",
|
||||
cfg: &config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: "https://cdn.example.com",
|
||||
},
|
||||
pluginInfo: func() PluginInfo {
|
||||
// Note: fake plugin FS is the most convenient way to mock the plugin FS for CDN plugins
|
||||
parentInfo := NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/parent"), nil)
|
||||
childInfo := NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassCDN, pluginFS("https://cdn.example.com/parent/some/other/dir/child"), &parentInfo)
|
||||
return childInfo
|
||||
},
|
||||
expected: expected{
|
||||
module: "https://cdn.example.com/parent/some/other/dir/child/module.js",
|
||||
baseURL: "https://cdn.example.com/parent/some/other/dir/child",
|
||||
relURL: "https://cdn.example.com/parent/some/other/dir/child/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CDN supported plugin",
|
||||
cfg: &config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: "https://cdn.example.com",
|
||||
PluginSettings: map[string]map[string]string{
|
||||
"parent": {"cdn": "true"},
|
||||
},
|
||||
},
|
||||
pluginInfo: func() PluginInfo {
|
||||
return NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil)
|
||||
},
|
||||
expected: expected{
|
||||
module: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/module.js",
|
||||
baseURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent",
|
||||
relURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CDN supported plugin with child",
|
||||
cfg: &config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: "https://cdn.example.com",
|
||||
PluginSettings: map[string]map[string]string{
|
||||
"parent": {"cdn": "true"},
|
||||
},
|
||||
},
|
||||
pluginInfo: func() PluginInfo {
|
||||
parentInfo := NewPluginInfo(plugins.JSONData{ID: "parent", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent"), nil)
|
||||
childInfo := NewPluginInfo(plugins.JSONData{ID: "child", Info: plugins.Info{Version: "1.0.0"}}, plugins.ClassExternal, plugins.NewLocalFS("/plugins/parent/child"), &parentInfo)
|
||||
return childInfo
|
||||
},
|
||||
expected: expected{
|
||||
module: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child/module.js",
|
||||
baseURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child",
|
||||
relURL: "https://cdn.example.com/parent/1.0.0/public/plugins/parent/child/path/to/file.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svc := ProvideService(tc.cfg, pluginscdn.ProvideService(tc.cfg))
|
||||
|
||||
module, err := svc.Module(tc.pluginInfo())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected.module, module)
|
||||
|
||||
baseURL, err := svc.Base(tc.pluginInfo())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected.baseURL, baseURL)
|
||||
|
||||
relURL, err := svc.RelativeURL(tc.pluginInfo(), "path/to/file.txt")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected.relURL, relURL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -176,6 +176,7 @@ type PluginMetaDTO struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Angular AngularMeta `json:"angular"`
|
||||
MultiValueFilterOperators bool `json:"multiValueFilterOperators"`
|
||||
LoadingStrategy LoadingStrategy `json:"loadingStrategy"`
|
||||
}
|
||||
|
||||
type DataSourceDTO struct {
|
||||
@ -211,28 +212,28 @@ type DataSourceDTO struct {
|
||||
}
|
||||
|
||||
type PanelDTO struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AliasIDs []string `json:"aliasIds,omitempty"`
|
||||
Info Info `json:"info"`
|
||||
HideFromList bool `json:"hideFromList"`
|
||||
Sort int `json:"sort"`
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
ReleaseState string `json:"state"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Signature string `json:"signature"`
|
||||
Module string `json:"module"`
|
||||
|
||||
Angular AngularMeta `json:"angular"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AliasIDs []string `json:"aliasIds,omitempty"`
|
||||
Info Info `json:"info"`
|
||||
HideFromList bool `json:"hideFromList"`
|
||||
Sort int `json:"sort"`
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
ReleaseState string `json:"state"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Signature string `json:"signature"`
|
||||
Module string `json:"module"`
|
||||
Angular AngularMeta `json:"angular"`
|
||||
LoadingStrategy LoadingStrategy `json:"loadingStrategy"`
|
||||
}
|
||||
|
||||
type AppDTO struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
|
||||
Angular AngularMeta `json:"angular"`
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
Angular AngularMeta `json:"angular"`
|
||||
LoadingStrategy LoadingStrategy `json:"loadingStrategy"`
|
||||
}
|
||||
|
||||
const (
|
||||
@ -252,6 +253,13 @@ type Error struct {
|
||||
message string `json:"-"`
|
||||
}
|
||||
|
||||
type LoadingStrategy string
|
||||
|
||||
const (
|
||||
LoadingStrategyFetch LoadingStrategy = "fetch"
|
||||
LoadingStrategyScript LoadingStrategy = "script"
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
if e.message != "" {
|
||||
return e.message
|
||||
|
@ -4,10 +4,10 @@ go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.3
|
||||
github.com/prometheus/common v0.55.0
|
||||
github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
@ -113,16 +113,16 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/goleak v1.3.0 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
gonum.org/v1/gonum v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/grpc v1.66.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
@ -98,8 +98,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3 h1:as4PmrFoYI1byS5JjsgPC7uSGTMh+SgS0ePv6hOyDGU=
|
||||
github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3/go.mod h1:lcjGB6SuaZ2o44A9nD6p/tR4QXSPbzViRY520Gy6pTQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0 h1:id7MPpONfZhSlhE5K4LU1rBqaVcpe2EDqqPdyb7UTfw=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.246.0/go.mod h1:bK7/yelc8cANvNzXPbXk64I4qkqeyQfnyIx4U9y/ItY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0 h1:QOxVw3sgaPaxR3odu9DSRNGdCknyAuMWl0hDhaF91OA=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.247.0/go.mod h1:sEi0wRVTvpxyB0KaMNbhPM74OnDMwVpah97usm6QXEM=
|
||||
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=
|
||||
@ -199,8 +199,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
@ -309,8 +309,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -331,12 +331,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@ -356,8 +356,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -89,8 +89,10 @@ type ScopeSpec struct {
|
||||
// ScopeFilter is a hand copy of the ScopeFilter struct from pkg/apis/scope/v0alpha1/types.go
|
||||
// to avoid import (temp fix)
|
||||
type ScopeFilter struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
// Values is used for operators that require multiple values (e.g. one-of and not-one-of).
|
||||
Values []string `json:"values,omitempty"`
|
||||
Operator FilterOperator `json:"operator"`
|
||||
}
|
||||
|
||||
@ -103,6 +105,8 @@ const (
|
||||
FilterOperatorNotEquals FilterOperator = "not-equals"
|
||||
FilterOperatorRegexMatch FilterOperator = "regex-match"
|
||||
FilterOperatorRegexNotMatch FilterOperator = "regex-not-match"
|
||||
FilterOperatorOneOf FilterOperator = "one-of"
|
||||
FilterOperatorNotOneOf FilterOperator = "not-one-of"
|
||||
)
|
||||
|
||||
// Internal interval and range variables
|
||||
|
@ -34,6 +34,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -213,6 +220,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -44,6 +44,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -223,6 +230,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -8,7 +8,7 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "default",
|
||||
"resourceVersion": "1715871691891",
|
||||
"resourceVersion": "1725885733879",
|
||||
"creationTimestamp": "2024-03-25T13:19:04Z"
|
||||
},
|
||||
"spec": {
|
||||
@ -31,6 +31,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@ -117,6 +124,13 @@
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"values": {
|
||||
"description": "Values is used for operators that require multiple values (e.g. one-of and not-one-of).",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -2,11 +2,13 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
)
|
||||
|
||||
// ApplyFiltersAndGroupBy takes a raw promQL expression, converts the filters into PromQL matchers, and applies these matchers to the parsed expression. It also applies the group by clause to any aggregate expressions in the parsed expression.
|
||||
func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []ScopeFilter, groupBy []string) (string, error) {
|
||||
expr, err := parser.ParseExpr(rawExpr)
|
||||
if err != nil {
|
||||
@ -98,8 +100,17 @@ func filterToMatcher(f ScopeFilter) (*labels.Matcher, error) {
|
||||
mt = labels.MatchRegexp
|
||||
case FilterOperatorRegexNotMatch:
|
||||
mt = labels.MatchNotRegexp
|
||||
case FilterOperatorOneOf:
|
||||
mt = labels.MatchRegexp
|
||||
case FilterOperatorNotOneOf:
|
||||
mt = labels.MatchNotRegexp
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown operator %q", f.Operator)
|
||||
}
|
||||
if f.Operator == FilterOperatorOneOf || f.Operator == FilterOperatorNotOneOf {
|
||||
if len(f.Values) > 0 {
|
||||
return labels.NewMatcher(mt, f.Key, strings.Join(f.Values, "|"))
|
||||
}
|
||||
}
|
||||
return labels.NewMatcher(mt, f.Key, f.Value)
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Adhoc and Scope filter conflict - adhoc wins",
|
||||
name: "Adhoc and Scope filter conflict - adhoc wins (if not oneOf or notOneOf)",
|
||||
query: `http_requests_total{job="prometheus"}`,
|
||||
scopeFilters: []ScopeFilter{
|
||||
{Key: "status", Value: "404", Operator: FilterOperatorEquals},
|
||||
@ -88,6 +88,15 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
|
||||
expected: `capacity_bytes{job="alloy"} + available_bytes{job="alloy"} / 1024`,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "OneOf Operator is combined into a single regex filter",
|
||||
query: `http_requests_total{job="prometheus"}`,
|
||||
scopeFilters: []ScopeFilter{
|
||||
{Key: "status", Values: []string{"404", "400"}, Operator: FilterOperatorOneOf},
|
||||
},
|
||||
expected: `http_requests_total{job="prometheus",status=~"404|400"}`,
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@ -98,7 +107,7 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expr, tt.expected)
|
||||
require.Equal(t, tt.expected, expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1434,6 +1434,12 @@ var (
|
||||
HideFromDocs: true,
|
||||
HideFromAdminPage: true,
|
||||
},
|
||||
{
|
||||
Name: "appSidecar",
|
||||
Description: "Enable the app sidecar feature that allows rendering 2 apps at the same time",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaExploreSquad,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -189,3 +189,4 @@ exploreLogsShardSplitting,experimental,@grafana/observability-logs,false,false,t
|
||||
exploreLogsAggregatedMetrics,experimental,@grafana/observability-logs,false,false,true
|
||||
exploreLogsLimitedTimeRange,experimental,@grafana/observability-logs,false,false,true
|
||||
appPlatformAccessTokens,experimental,@grafana/identity-access-team,false,false,false
|
||||
appSidecar,experimental,@grafana/explore-squad,false,false,false
|
||||
|
|
@ -766,4 +766,8 @@ const (
|
||||
// FlagAppPlatformAccessTokens
|
||||
// Enables the use of access tokens for the App Platform
|
||||
FlagAppPlatformAccessTokens = "appPlatformAccessTokens"
|
||||
|
||||
// FlagAppSidecar
|
||||
// Enable the app sidecar feature that allows rendering 2 apps at the same time
|
||||
FlagAppSidecar = "appSidecar"
|
||||
)
|
||||
|
@ -350,6 +350,18 @@
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "appSidecar",
|
||||
"resourceVersion": "1722872007883",
|
||||
"creationTimestamp": "2024-08-05T15:33:27Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enable the app sidecar feature that allows rendering 2 apps at the same time",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/explore-squad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "authAPIAccessTokenAuth",
|
||||
@ -2859,4 +2871,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1243,8 +1243,8 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
},
|
||||
Module: "public/plugins/test-panel/module.js",
|
||||
BaseURL: "public/plugins/test-panel",
|
||||
Module: "public/plugins/test-datasource/nested/module.js",
|
||||
BaseURL: "public/plugins/test-datasource/nested",
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "nested-plugins/parent/nested")),
|
||||
Signature: plugins.SignatureStatusValid,
|
||||
SignatureType: plugins.SignatureTypeGrafana,
|
||||
@ -1408,8 +1408,8 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
{Name: "License", URL: "https://github.com/grafana/grafana-starter-panel/blob/master/LICENSE"},
|
||||
},
|
||||
Logos: plugins.Logos{
|
||||
Small: "public/plugins/myorgid-simple-panel/img/logo.svg",
|
||||
Large: "public/plugins/myorgid-simple-panel/img/logo.svg",
|
||||
Small: "public/plugins/myorgid-simple-app/child/img/logo.svg",
|
||||
Large: "public/plugins/myorgid-simple-app/child/img/logo.svg",
|
||||
},
|
||||
Screenshots: []plugins.Screenshots{},
|
||||
Description: "Grafana Panel Plugin Template",
|
||||
@ -1423,8 +1423,8 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
},
|
||||
Module: "public/plugins/myorgid-simple-panel/module.js",
|
||||
BaseURL: "public/plugins/myorgid-simple-panel",
|
||||
Module: "public/plugins/myorgid-simple-app/child/module.js",
|
||||
BaseURL: "public/plugins/myorgid-simple-app/child",
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "app-with-child/dist/child")),
|
||||
IncludedInAppID: parent.ID,
|
||||
Signature: plugins.SignatureStatusValid,
|
||||
|
81
pkg/services/pluginsintegration/pluginassets/pluginassets.go
Normal file
81
pkg/services/pluginsintegration/pluginassets/pluginassets.go
Normal file
@ -0,0 +1,81 @@
|
||||
package pluginassets
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
CreatePluginVersionCfgKey = "create_plugin_version"
|
||||
CreatePluginVersionScriptSupportEnabled = "4.15.0"
|
||||
)
|
||||
|
||||
var (
|
||||
scriptLoadingMinSupportedVersion = semver.MustParse(CreatePluginVersionScriptSupportEnabled)
|
||||
)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, cdn *pluginscdn.Service) *Service {
|
||||
return &Service{
|
||||
cfg: cfg,
|
||||
cdn: cdn,
|
||||
log: log.New("pluginassets"),
|
||||
}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
cfg *setting.Cfg
|
||||
cdn *pluginscdn.Service
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// LoadingStrategy calculates the loading strategy for a plugin.
|
||||
// If a plugin has plugin setting `create_plugin_version` >= 4.15.0, set loadingStrategy to "script".
|
||||
// If a plugin is not loaded via the CDN and is not Angular, set loadingStrategy to "script".
|
||||
// Otherwise, set loadingStrategy to "fetch".
|
||||
func (s *Service) LoadingStrategy(_ context.Context, p pluginstore.Plugin) plugins.LoadingStrategy {
|
||||
if pCfg, ok := s.cfg.PluginSettings[p.ID]; ok {
|
||||
if s.compatibleCreatePluginVersion(pCfg) {
|
||||
return plugins.LoadingStrategyScript
|
||||
}
|
||||
}
|
||||
|
||||
// If the plugin has a parent, check the parent's create_plugin_version setting
|
||||
if p.Parent != nil {
|
||||
if pCfg, ok := s.cfg.PluginSettings[p.Parent.ID]; ok {
|
||||
if s.compatibleCreatePluginVersion(pCfg) {
|
||||
return plugins.LoadingStrategyScript
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !s.cndEnabled(p) && !p.Angular.Detected {
|
||||
return plugins.LoadingStrategyScript
|
||||
}
|
||||
|
||||
return plugins.LoadingStrategyFetch
|
||||
}
|
||||
|
||||
func (s *Service) compatibleCreatePluginVersion(ps map[string]string) bool {
|
||||
if cpv, ok := ps[CreatePluginVersionCfgKey]; ok {
|
||||
createPluginVer, err := semver.NewVersion(cpv)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to parse create plugin version setting as semver", "version", cpv, "error", err)
|
||||
} else {
|
||||
if !createPluginVer.LessThan(scriptLoadingMinSupportedVersion) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) cndEnabled(p pluginstore.Plugin) bool {
|
||||
return s.cdn.PluginSupported(p.ID) || p.Class == plugins.ClassCDN
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package pluginassets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestService_Calculate(t *testing.T) {
|
||||
const pluginID = "grafana-test-datasource"
|
||||
|
||||
const (
|
||||
incompatVersion = "4.14.0"
|
||||
compatVersion = CreatePluginVersionScriptSupportEnabled
|
||||
futureVersion = "5.0.0"
|
||||
)
|
||||
|
||||
tcs := []struct {
|
||||
name string
|
||||
pluginSettings setting.PluginSettings
|
||||
plugin pluginstore.Plugin
|
||||
expected plugins.LoadingStrategy
|
||||
}{
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when create-plugin version is compatible and plugin is not angular",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: compatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when parent create-plugin version is compatible and plugin is not angular",
|
||||
pluginSettings: newPluginSettings("parent-datasource", map[string]string{
|
||||
CreatePluginVersionCfgKey: compatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false, func(p pluginstore.Plugin) pluginstore.Plugin {
|
||||
p.Parent = &pluginstore.ParentPlugin{ID: "parent-datasource"}
|
||||
return p
|
||||
}),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when create-plugin version is future compatible and plugin is not angular",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: futureVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when create-plugin version is not provided, plugin is not angular and is not configured as CDN enabled",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
// NOTE: cdn key is not set
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when create-plugin version is not compatible, plugin is not angular, is not configured as CDN enabled and does not have the CDN class",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: incompatVersion,
|
||||
// NOTE: cdn key is not set
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false, func(p pluginstore.Plugin) pluginstore.Plugin {
|
||||
p.Class = plugins.ClassExternal
|
||||
return p
|
||||
}),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular, is configured as CDN enabled and does not have the CDN class",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
"cdn": "true",
|
||||
CreatePluginVersionCfgKey: incompatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false, func(p pluginstore.Plugin) pluginstore.Plugin {
|
||||
p.Class = plugins.ClassExternal
|
||||
return p
|
||||
}),
|
||||
expected: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible and plugin is angular",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: incompatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, true),
|
||||
expected: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular and plugin is configured as CDN enabled",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
"cdn": "true",
|
||||
CreatePluginVersionCfgKey: incompatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false),
|
||||
expected: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular and has the CDN class",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: incompatVersion,
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false, func(p pluginstore.Plugin) pluginstore.Plugin {
|
||||
p.Class = plugins.ClassCDN
|
||||
return p
|
||||
}),
|
||||
expected: plugins.LoadingStrategyFetch,
|
||||
},
|
||||
{
|
||||
name: "Expected LoadingStrategyScript when plugin setting create-plugin version is badly formatted, plugin is not configured as CDN enabled and does not have the CDN class",
|
||||
pluginSettings: newPluginSettings(pluginID, map[string]string{
|
||||
CreatePluginVersionCfgKey: "invalidSemver",
|
||||
}),
|
||||
plugin: newPlugin(pluginID, false),
|
||||
expected: plugins.LoadingStrategyScript,
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := &Service{
|
||||
cfg: newCfg(tc.pluginSettings),
|
||||
cdn: pluginscdn.ProvideService(&config.PluginManagementCfg{
|
||||
PluginsCDNURLTemplate: "http://cdn.example.com", // required for cdn.PluginSupported check
|
||||
PluginSettings: tc.pluginSettings,
|
||||
}),
|
||||
log: log.NewNopLogger(),
|
||||
}
|
||||
|
||||
got := s.LoadingStrategy(context.Background(), tc.plugin)
|
||||
assert.Equal(t, tc.expected, got, "unexpected loading strategy")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newPlugin(pluginID string, angular bool, cbs ...func(p pluginstore.Plugin) pluginstore.Plugin) pluginstore.Plugin {
|
||||
p := pluginstore.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: pluginID,
|
||||
},
|
||||
Angular: plugins.AngularMeta{Detected: angular},
|
||||
}
|
||||
for _, cb := range cbs {
|
||||
p = cb(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func newCfg(ps setting.PluginSettings) *setting.Cfg {
|
||||
return &setting.Cfg{
|
||||
PluginSettings: ps,
|
||||
}
|
||||
}
|
||||
|
||||
func newPluginSettings(pluginID string, kv map[string]string) setting.PluginSettings {
|
||||
return setting.PluginSettings{
|
||||
pluginID: kv,
|
||||
}
|
||||
}
|
@ -43,6 +43,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/loader"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||
@ -126,6 +127,7 @@ var WireSet = wire.NewSet(
|
||||
plugincontext.ProvideBaseService,
|
||||
wire.Bind(new(plugincontext.BasePluginContextProvider), new(*plugincontext.BaseProvider)),
|
||||
plugininstaller.ProvideService,
|
||||
pluginassets.ProvideService,
|
||||
)
|
||||
|
||||
// WireExtensionSet provides a wire.ProviderSet of plugin providers that can be
|
||||
|
@ -16,6 +16,7 @@ type Plugin struct {
|
||||
Class plugins.Class
|
||||
|
||||
// App fields
|
||||
Parent *ParentPlugin
|
||||
IncludedInAppID string
|
||||
DefaultNavURL string
|
||||
Pinned bool
|
||||
@ -59,7 +60,7 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin {
|
||||
supportsStreaming = true
|
||||
}
|
||||
|
||||
return Plugin{
|
||||
dto := Plugin{
|
||||
fs: p.FS,
|
||||
supportsStreaming: supportsStreaming,
|
||||
Class: p.Class,
|
||||
@ -76,4 +77,14 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin {
|
||||
ExternalService: p.ExternalService,
|
||||
Angular: p.Angular,
|
||||
}
|
||||
|
||||
if p.Parent != nil {
|
||||
dto.Parent = &ParentPlugin{ID: p.Parent.ID}
|
||||
}
|
||||
|
||||
return dto
|
||||
}
|
||||
|
||||
type ParentPlugin struct {
|
||||
ID string
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ require (
|
||||
github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gocloud.dev v0.39.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/grpc v1.66.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
k8s.io/apiserver v0.31.0
|
||||
k8s.io/client-go v0.31.0
|
||||
@ -43,7 +43,7 @@ require (
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e // indirect
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
@ -59,7 +59,7 @@ require (
|
||||
github.com/onsi/gomega v1.34.1 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.3 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
@ -80,13 +80,13 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/api v0.191.0 // indirect
|
||||
|
@ -161,8 +161,8 @@ github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDP
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e h1:FhuMCsOqm6wCzlikbmjSGJJ6pDrcC3FeFTQBCqvOfyk=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 h1:e+kFqd2sECBhbxOV1NoVxsudLygNQuu9bO+7FjNTkXo=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e h1:ng5SopWamGS0MHaCj2e5huWYxAfMeCrj1l/dbJnfiow=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o=
|
||||
@ -230,8 +230,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -320,8 +320,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
@ -347,8 +347,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@ -373,15 +373,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -389,8 +389,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -432,8 +432,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -4,15 +4,15 @@ go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/fullstorydev/grpchan v1.1.1
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.opentelemetry.io/otel/trace v1.29.0
|
||||
gocloud.dev v0.39.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/grpc v1.66.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
k8s.io/apimachinery v0.31.0
|
||||
)
|
||||
@ -43,11 +43,11 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/api v0.191.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
|
||||
|
@ -128,8 +128,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e h1:FhuMCsOqm6wCzlikbmjSGJJ6pDrcC3FeFTQBCqvOfyk=
|
||||
github.com/grafana/authlib v0.0.0-20240903121118-16441568af1e/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9 h1:e+kFqd2sECBhbxOV1NoVxsudLygNQuu9bO+7FjNTkXo=
|
||||
github.com/grafana/authlib v0.0.0-20240906122029-0100695765b9/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e h1:ng5SopWamGS0MHaCj2e5huWYxAfMeCrj1l/dbJnfiow=
|
||||
github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e h1:3vNpomyzv714Hgls5vn+fC0vgv8wUOSHepUl7PB5nUs=
|
||||
@ -169,8 +169,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
|
||||
github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
@ -217,8 +217,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@ -241,8 +241,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
@ -267,8 +267,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -281,8 +281,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -320,8 +320,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
|
||||
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -18,6 +19,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
@ -136,15 +138,16 @@ func (e *AzureLogAnalyticsDatasource) GetBasicLogsUsage(ctx context.Context, url
|
||||
// 3. parses the responses for each query into data frames
|
||||
func (e *AzureLogAnalyticsDatasource) ExecuteTimeSeriesQuery(ctx context.Context, originalQueries []backend.DataQuery, dsInfo types.DatasourceInfo, client *http.Client, url string, fromAlert bool) (*backend.QueryDataResponse, error) {
|
||||
result := backend.NewQueryDataResponse()
|
||||
queries, err := e.buildQueries(ctx, originalQueries, dsInfo, fromAlert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
res, err := e.executeQuery(ctx, query, dsInfo, client, url)
|
||||
for _, query := range originalQueries {
|
||||
logsQuery, err := e.buildQuery(ctx, query, dsInfo, fromAlert)
|
||||
if err != nil {
|
||||
result.Responses[query.RefID] = backend.DataResponse{Error: err}
|
||||
errorsource.AddErrorToResponse(query.RefID, result, err)
|
||||
continue
|
||||
}
|
||||
res, err := e.executeQuery(ctx, logsQuery, dsInfo, client, url)
|
||||
if err != nil {
|
||||
errorsource.AddErrorToResponse(query.RefID, result, err)
|
||||
continue
|
||||
}
|
||||
result.Responses[query.RefID] = *res
|
||||
@ -179,6 +182,7 @@ func buildLogAnalyticsQuery(query backend.DataQuery, dsInfo types.DatasourceInfo
|
||||
|
||||
if basicLogsQueryFlag {
|
||||
if meetsBasicLogsCriteria, meetsBasicLogsCriteriaErr := meetsBasicLogsCriteria(resources, fromAlert); meetsBasicLogsCriteriaErr != nil {
|
||||
// This error is a downstream error
|
||||
return nil, meetsBasicLogsCriteriaErr
|
||||
} else {
|
||||
basicLogsQuery = meetsBasicLogsCriteria
|
||||
@ -224,45 +228,52 @@ func buildLogAnalyticsQuery(query backend.DataQuery, dsInfo types.DatasourceInfo
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *AzureLogAnalyticsDatasource) buildQueries(ctx context.Context, queries []backend.DataQuery, dsInfo types.DatasourceInfo, fromAlert bool) ([]*AzureLogAnalyticsQuery, error) {
|
||||
azureLogAnalyticsQueries := []*AzureLogAnalyticsQuery{}
|
||||
func (e *AzureLogAnalyticsDatasource) buildQuery(ctx context.Context, query backend.DataQuery, dsInfo types.DatasourceInfo, fromAlert bool) (*AzureLogAnalyticsQuery, error) {
|
||||
var azureLogAnalyticsQuery *AzureLogAnalyticsQuery
|
||||
appInsightsRegExp, err := regexp.Compile("(?i)providers/microsoft.insights/components")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to compile Application Insights regex")
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) {
|
||||
azureLogAnalyticsQuery, err := buildLogAnalyticsQuery(query, dsInfo, appInsightsRegExp, fromAlert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build azure log analytics query: %w", err)
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeAzureLogAnalytics) {
|
||||
azureLogAnalyticsQuery, err = buildLogAnalyticsQuery(query, dsInfo, appInsightsRegExp, fromAlert)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Errorf("failed to build azure log analytics query: %w", err)
|
||||
var sourceError errorsource.Error
|
||||
if errors.As(err, &sourceError) {
|
||||
return nil, errorsource.SourceError(sourceError.Source(), errorMessage, false)
|
||||
}
|
||||
azureLogAnalyticsQueries = append(azureLogAnalyticsQueries, azureLogAnalyticsQuery)
|
||||
}
|
||||
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeAzureTraces) || query.QueryType == string(dataquery.AzureQueryTypeTraceql) {
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeTraceql) {
|
||||
cfg := backend.GrafanaConfigFromContext(ctx)
|
||||
hasPromExemplarsToggle := cfg.FeatureToggles().IsEnabled("azureMonitorPrometheusExemplars")
|
||||
if !hasPromExemplarsToggle {
|
||||
return nil, fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled")
|
||||
}
|
||||
}
|
||||
azureAppInsightsQuery, err := buildAppInsightsQuery(ctx, query, dsInfo, appInsightsRegExp, e.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build azure application insights query: %w", err)
|
||||
}
|
||||
azureLogAnalyticsQueries = append(azureLogAnalyticsQueries, azureAppInsightsQuery)
|
||||
return nil, errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
return azureLogAnalyticsQueries, nil
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeAzureTraces) || query.QueryType == string(dataquery.AzureQueryTypeTraceql) {
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeTraceql) {
|
||||
cfg := backend.GrafanaConfigFromContext(ctx)
|
||||
hasPromExemplarsToggle := cfg.FeatureToggles().IsEnabled("azureMonitorPrometheusExemplars")
|
||||
if !hasPromExemplarsToggle {
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled"), false)
|
||||
}
|
||||
}
|
||||
azureAppInsightsQuery, err := buildAppInsightsQuery(ctx, query, dsInfo, appInsightsRegExp, e.Logger)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Errorf("failed to build azure application insights query: %w", err)
|
||||
var sourceError errorsource.Error
|
||||
if errors.As(err, &sourceError) {
|
||||
return nil, errorsource.SourceError(sourceError.Source(), errorMessage, false)
|
||||
}
|
||||
return nil, errorMessage
|
||||
}
|
||||
azureLogAnalyticsQuery = azureAppInsightsQuery
|
||||
}
|
||||
|
||||
return azureLogAnalyticsQuery, nil
|
||||
}
|
||||
|
||||
func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery, dsInfo types.DatasourceInfo, client *http.Client, url string) (*backend.DataResponse, error) {
|
||||
// If azureLogAnalyticsSameAs is defined and set to false, return an error
|
||||
if sameAs, ok := dsInfo.JSONData["azureLogAnalyticsSameAs"]; ok && !sameAs.(bool) {
|
||||
return nil, fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials")
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"), false)
|
||||
}
|
||||
|
||||
queryJSONModel := dataquery.AzureMonitorQuery{}
|
||||
@ -273,7 +284,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
|
||||
|
||||
if query.QueryType == dataquery.AzureQueryTypeAzureTraces {
|
||||
if query.ResultFormat == dataquery.ResultFormatTrace && query.Query == "" {
|
||||
return nil, fmt.Errorf("cannot visualise trace events using the trace visualiser")
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("cannot visualise trace events using the trace visualiser"), false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +305,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errorsource.DownstreamError(err, false)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@ -611,7 +622,7 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource
|
||||
}()
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return AzureCorrelationAPIResponse{}, fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body))
|
||||
return AzureCorrelationAPIResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false)
|
||||
}
|
||||
var data AzureCorrelationAPIResponse
|
||||
d := json.NewDecoder(bytes.NewReader(body))
|
||||
@ -675,7 +686,7 @@ func (e *AzureLogAnalyticsDatasource) unmarshalResponse(res *http.Response) (Azu
|
||||
}()
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return AzureLogAnalyticsResponse{}, fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body))
|
||||
return AzureLogAnalyticsResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false)
|
||||
}
|
||||
|
||||
var data AzureLogAnalyticsResponse
|
||||
|
@ -741,7 +741,7 @@ func Test_exemplarsFeatureToggle(t *testing.T) {
|
||||
QueryType: string(dataquery.AzureQueryTypeTraceql),
|
||||
}
|
||||
|
||||
_, err := ds.buildQueries(ctx, []backend.DataQuery{query}, dsInfo, false)
|
||||
_, err := ds.buildQuery(ctx, query, dsInfo, false)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
@ -761,7 +761,7 @@ func Test_exemplarsFeatureToggle(t *testing.T) {
|
||||
QueryType: string(dataquery.AzureQueryTypeTraceql),
|
||||
}
|
||||
|
||||
_, err := ds.buildQueries(ctx, []backend.DataQuery{query}, dsInfo, false)
|
||||
_, err := ds.buildQuery(ctx, query, dsInfo, false)
|
||||
|
||||
require.Error(t, err, "query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled")
|
||||
})
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
|
||||
)
|
||||
|
||||
@ -45,14 +46,14 @@ func AddConfigLinks(frame data.Frame, dl string, title *string) data.Frame {
|
||||
// 4. number of selected resources is exactly one
|
||||
func meetsBasicLogsCriteria(resources []string, fromAlert bool) (bool, error) {
|
||||
if fromAlert {
|
||||
return false, fmt.Errorf("basic Logs queries cannot be used for alerts")
|
||||
return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"), false)
|
||||
}
|
||||
if len(resources) != 1 {
|
||||
return false, fmt.Errorf("basic logs queries cannot be run against multiple resources")
|
||||
return false, errorsource.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"), false)
|
||||
}
|
||||
|
||||
if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") {
|
||||
return false, fmt.Errorf("basic Logs queries may only be run against Log Analytics workspaces")
|
||||
return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries may only be run against Log Analytics workspaces"), false)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
@ -51,15 +52,15 @@ func (e *AzureMonitorDatasource) ResourceRequest(rw http.ResponseWriter, req *ht
|
||||
func (e *AzureMonitorDatasource) ExecuteTimeSeriesQuery(ctx context.Context, originalQueries []backend.DataQuery, dsInfo types.DatasourceInfo, client *http.Client, url string, fromAlert bool) (*backend.QueryDataResponse, error) {
|
||||
result := backend.NewQueryDataResponse()
|
||||
|
||||
queries, err := e.buildQueries(originalQueries, dsInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
res, err := e.executeQuery(ctx, query, dsInfo, client, url)
|
||||
for _, query := range originalQueries {
|
||||
azureQuery, err := e.buildQuery(query, dsInfo)
|
||||
if err != nil {
|
||||
result.Responses[query.RefID] = backend.DataResponse{Error: err}
|
||||
errorsource.AddErrorToResponse(query.RefID, result, err)
|
||||
continue
|
||||
}
|
||||
res, err := e.executeQuery(ctx, azureQuery, dsInfo, client, url)
|
||||
if err != nil {
|
||||
errorsource.AddErrorToResponse(query.RefID, result, err)
|
||||
continue
|
||||
}
|
||||
result.Responses[query.RefID] = *res
|
||||
@ -68,151 +69,146 @@ func (e *AzureMonitorDatasource) ExecuteTimeSeriesQuery(ctx context.Context, ori
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *AzureMonitorDatasource) buildQueries(queries []backend.DataQuery, dsInfo types.DatasourceInfo) ([]*types.AzureMonitorQuery, error) {
|
||||
azureMonitorQueries := []*types.AzureMonitorQuery{}
|
||||
func (e *AzureMonitorDatasource) buildQuery(query backend.DataQuery, dsInfo types.DatasourceInfo) (*types.AzureMonitorQuery, error) {
|
||||
var target string
|
||||
queryJSONModel := dataquery.AzureMonitorQuery{}
|
||||
err := json.Unmarshal(query.JSON, &queryJSONModel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the Azure Monitor query object from JSON: %w", err)
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
var target string
|
||||
queryJSONModel := dataquery.AzureMonitorQuery{}
|
||||
err := json.Unmarshal(query.JSON, &queryJSONModel)
|
||||
azJSONModel := queryJSONModel.AzureMonitor
|
||||
// Legacy: If only MetricDefinition is set, use it as namespace
|
||||
if azJSONModel.MetricDefinition != nil && *azJSONModel.MetricDefinition != "" &&
|
||||
azJSONModel.MetricNamespace != nil && *azJSONModel.MetricNamespace == "" {
|
||||
azJSONModel.MetricNamespace = azJSONModel.MetricDefinition
|
||||
}
|
||||
|
||||
azJSONModel.DimensionFilters = MigrateDimensionFilters(azJSONModel.DimensionFilters)
|
||||
|
||||
alias := ""
|
||||
if azJSONModel.Alias != nil {
|
||||
alias = *azJSONModel.Alias
|
||||
}
|
||||
azureURL := ""
|
||||
if queryJSONModel.Subscription != nil {
|
||||
azureURL = BuildSubscriptionMetricsURL(*queryJSONModel.Subscription)
|
||||
}
|
||||
filterInBody := true
|
||||
resourceIDs := []string{}
|
||||
resourceMap := map[string]dataquery.AzureMonitorResource{}
|
||||
if hasOne, resourceGroup, resourceName := hasOneResource(queryJSONModel); hasOne {
|
||||
ub := urlBuilder{
|
||||
ResourceURI: azJSONModel.ResourceUri,
|
||||
// Alternative, used to reconstruct resource URI if it's not present
|
||||
DefaultSubscription: &dsInfo.Settings.SubscriptionId,
|
||||
Subscription: queryJSONModel.Subscription,
|
||||
ResourceGroup: resourceGroup,
|
||||
MetricNamespace: azJSONModel.MetricNamespace,
|
||||
ResourceName: resourceName,
|
||||
}
|
||||
|
||||
// Construct the resourceURI (for legacy query objects pre Grafana 9)
|
||||
resourceUri, err := ub.buildResourceURI()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the Azure Monitor query object from JSON: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azJSONModel := queryJSONModel.AzureMonitor
|
||||
// Legacy: If only MetricDefinition is set, use it as namespace
|
||||
if azJSONModel.MetricDefinition != nil && *azJSONModel.MetricDefinition != "" &&
|
||||
azJSONModel.MetricNamespace != nil && *azJSONModel.MetricNamespace == "" {
|
||||
azJSONModel.MetricNamespace = azJSONModel.MetricDefinition
|
||||
// POST requests are only supported at the subscription level
|
||||
filterInBody = false
|
||||
if resourceUri != nil {
|
||||
azureURL = fmt.Sprintf("%s/providers/microsoft.insights/metrics", *resourceUri)
|
||||
resourceMap[*resourceUri] = dataquery.AzureMonitorResource{ResourceGroup: resourceGroup, ResourceName: resourceName}
|
||||
}
|
||||
|
||||
azJSONModel.DimensionFilters = MigrateDimensionFilters(azJSONModel.DimensionFilters)
|
||||
|
||||
alias := ""
|
||||
if azJSONModel.Alias != nil {
|
||||
alias = *azJSONModel.Alias
|
||||
}
|
||||
azureURL := ""
|
||||
if queryJSONModel.Subscription != nil {
|
||||
azureURL = BuildSubscriptionMetricsURL(*queryJSONModel.Subscription)
|
||||
}
|
||||
filterInBody := true
|
||||
resourceIDs := []string{}
|
||||
resourceMap := map[string]dataquery.AzureMonitorResource{}
|
||||
if hasOne, resourceGroup, resourceName := hasOneResource(queryJSONModel); hasOne {
|
||||
} else {
|
||||
for _, r := range azJSONModel.Resources {
|
||||
ub := urlBuilder{
|
||||
ResourceURI: azJSONModel.ResourceUri,
|
||||
// Alternative, used to reconstruct resource URI if it's not present
|
||||
DefaultSubscription: &dsInfo.Settings.SubscriptionId,
|
||||
Subscription: queryJSONModel.Subscription,
|
||||
ResourceGroup: resourceGroup,
|
||||
ResourceGroup: r.ResourceGroup,
|
||||
MetricNamespace: azJSONModel.MetricNamespace,
|
||||
ResourceName: resourceName,
|
||||
ResourceName: r.ResourceName,
|
||||
}
|
||||
|
||||
// Construct the resourceURI (for legacy query objects pre Grafana 9)
|
||||
resourceUri, err := ub.buildResourceURI()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// POST requests are only supported at the subscription level
|
||||
filterInBody = false
|
||||
if resourceUri != nil {
|
||||
azureURL = fmt.Sprintf("%s/providers/microsoft.insights/metrics", *resourceUri)
|
||||
resourceMap[*resourceUri] = dataquery.AzureMonitorResource{ResourceGroup: resourceGroup, ResourceName: resourceName}
|
||||
}
|
||||
} else {
|
||||
for _, r := range azJSONModel.Resources {
|
||||
ub := urlBuilder{
|
||||
DefaultSubscription: &dsInfo.Settings.SubscriptionId,
|
||||
Subscription: queryJSONModel.Subscription,
|
||||
ResourceGroup: r.ResourceGroup,
|
||||
MetricNamespace: azJSONModel.MetricNamespace,
|
||||
ResourceName: r.ResourceName,
|
||||
}
|
||||
resourceUri, err := ub.buildResourceURI()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resourceUri != nil {
|
||||
resourceMap[*resourceUri] = r
|
||||
}
|
||||
resourceIDs = append(resourceIDs, fmt.Sprintf("Microsoft.ResourceId eq '%s'", *resourceUri))
|
||||
resourceMap[*resourceUri] = r
|
||||
}
|
||||
resourceIDs = append(resourceIDs, fmt.Sprintf("Microsoft.ResourceId eq '%s'", *resourceUri))
|
||||
}
|
||||
|
||||
// old model
|
||||
dimension := ""
|
||||
if azJSONModel.Dimension != nil {
|
||||
dimension = strings.TrimSpace(*azJSONModel.Dimension)
|
||||
}
|
||||
dimensionFilter := ""
|
||||
if azJSONModel.DimensionFilter != nil {
|
||||
dimensionFilter = strings.TrimSpace(*azJSONModel.DimensionFilter)
|
||||
}
|
||||
|
||||
dimSB := strings.Builder{}
|
||||
|
||||
if dimension != "" && dimensionFilter != "" && dimension != "None" && len(azJSONModel.DimensionFilters) == 0 {
|
||||
dimSB.WriteString(fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
||||
} else {
|
||||
for i, filter := range azJSONModel.DimensionFilters {
|
||||
if len(filter.Filters) == 0 {
|
||||
dimSB.WriteString(fmt.Sprintf("%s eq '*'", *filter.Dimension))
|
||||
} else {
|
||||
dimSB.WriteString(types.ConstructFiltersString(filter))
|
||||
}
|
||||
if i != len(azJSONModel.DimensionFilters)-1 {
|
||||
dimSB.WriteString(" and ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterString := strings.Join(resourceIDs, " or ")
|
||||
|
||||
if dimSB.String() != "" {
|
||||
if filterString != "" {
|
||||
filterString = fmt.Sprintf("(%s) and (%s)", filterString, dimSB.String())
|
||||
} else {
|
||||
filterString = dimSB.String()
|
||||
}
|
||||
}
|
||||
|
||||
params, err := getParams(azJSONModel, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target = params.Encode()
|
||||
|
||||
sub := ""
|
||||
if queryJSONModel.Subscription != nil {
|
||||
sub = *queryJSONModel.Subscription
|
||||
}
|
||||
|
||||
query := &types.AzureMonitorQuery{
|
||||
URL: azureURL,
|
||||
Target: target,
|
||||
Params: params,
|
||||
RefID: query.RefID,
|
||||
Alias: alias,
|
||||
TimeRange: query.TimeRange,
|
||||
Dimensions: azJSONModel.DimensionFilters,
|
||||
Resources: resourceMap,
|
||||
Subscription: sub,
|
||||
}
|
||||
if filterString != "" {
|
||||
if filterInBody {
|
||||
query.BodyFilter = filterString
|
||||
} else {
|
||||
query.Params.Add("$filter", filterString)
|
||||
}
|
||||
}
|
||||
azureMonitorQueries = append(azureMonitorQueries, query)
|
||||
}
|
||||
|
||||
return azureMonitorQueries, nil
|
||||
// old model
|
||||
dimension := ""
|
||||
if azJSONModel.Dimension != nil {
|
||||
dimension = strings.TrimSpace(*azJSONModel.Dimension)
|
||||
}
|
||||
dimensionFilter := ""
|
||||
if azJSONModel.DimensionFilter != nil {
|
||||
dimensionFilter = strings.TrimSpace(*azJSONModel.DimensionFilter)
|
||||
}
|
||||
|
||||
dimSB := strings.Builder{}
|
||||
|
||||
if dimension != "" && dimensionFilter != "" && dimension != "None" && len(azJSONModel.DimensionFilters) == 0 {
|
||||
dimSB.WriteString(fmt.Sprintf("%s eq '%s'", dimension, dimensionFilter))
|
||||
} else {
|
||||
for i, filter := range azJSONModel.DimensionFilters {
|
||||
if len(filter.Filters) == 0 {
|
||||
dimSB.WriteString(fmt.Sprintf("%s eq '*'", *filter.Dimension))
|
||||
} else {
|
||||
dimSB.WriteString(types.ConstructFiltersString(filter))
|
||||
}
|
||||
if i != len(azJSONModel.DimensionFilters)-1 {
|
||||
dimSB.WriteString(" and ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterString := strings.Join(resourceIDs, " or ")
|
||||
|
||||
if dimSB.String() != "" {
|
||||
if filterString != "" {
|
||||
filterString = fmt.Sprintf("(%s) and (%s)", filterString, dimSB.String())
|
||||
} else {
|
||||
filterString = dimSB.String()
|
||||
}
|
||||
}
|
||||
|
||||
params, err := getParams(azJSONModel, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target = params.Encode()
|
||||
|
||||
sub := ""
|
||||
if queryJSONModel.Subscription != nil {
|
||||
sub = *queryJSONModel.Subscription
|
||||
}
|
||||
|
||||
azureQuery := &types.AzureMonitorQuery{
|
||||
URL: azureURL,
|
||||
Target: target,
|
||||
Params: params,
|
||||
RefID: query.RefID,
|
||||
Alias: alias,
|
||||
TimeRange: query.TimeRange,
|
||||
Dimensions: azJSONModel.DimensionFilters,
|
||||
Resources: resourceMap,
|
||||
Subscription: sub,
|
||||
}
|
||||
if filterString != "" {
|
||||
if filterInBody {
|
||||
azureQuery.BodyFilter = filterString
|
||||
} else {
|
||||
azureQuery.Params.Add("$filter", filterString)
|
||||
}
|
||||
}
|
||||
|
||||
return azureQuery, nil
|
||||
}
|
||||
|
||||
func getParams(azJSONModel *dataquery.AzureMetricQuery, query backend.DataQuery) (url.Values, error) {
|
||||
@ -288,7 +284,7 @@ func (e *AzureMonitorDatasource) retrieveSubscriptionDetails(cli *http.Client, c
|
||||
}
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return "", fmt.Errorf("request failed, status: %s, error: %s", res.Status, string(body))
|
||||
return "", errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, error: %s", res.Status, string(body)), false)
|
||||
}
|
||||
|
||||
var data types.SubscriptionsResponse
|
||||
@ -325,7 +321,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *types.
|
||||
|
||||
res, err := cli.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errorsource.DownstreamError(err, false)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@ -370,7 +366,7 @@ func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (types.Az
|
||||
}
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return types.AzureMonitorResponse{}, fmt.Errorf("request failed, status: %s, error: %s", res.Status, string(body))
|
||||
return types.AzureMonitorResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false)
|
||||
}
|
||||
|
||||
var data types.AzureMonitorResponse
|
||||
|
@ -294,7 +294,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
queries, err := datasource.buildQueries(tsdbQuery, dsInfo)
|
||||
query, err := datasource.buildQuery(tsdbQuery[0], dsInfo)
|
||||
require.NoError(t, err)
|
||||
|
||||
resources := map[string]dataquery.AzureMonitorResource{}
|
||||
@ -321,12 +321,12 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
Resources: resources,
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedParamFilter, queries[0].Params.Get("$filter"))
|
||||
assert.Equal(t, tt.expectedParamFilter, query.Params.Get("$filter"))
|
||||
if azureMonitorQuery.URL == "" {
|
||||
azureMonitorQuery.URL = "/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics"
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(azureMonitorQuery, queries[0], cmpopts.IgnoreUnexported(struct{}{}), cmpopts.IgnoreFields(types.AzureMonitorQuery{}, "Params", "Dimensions")); diff != "" {
|
||||
if diff := cmp.Diff(azureMonitorQuery, query, cmpopts.IgnoreUnexported(struct{}{}), cmpopts.IgnoreFields(types.AzureMonitorQuery{}, "Params", "Dimensions")); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
@ -338,7 +338,7 @@ func TestAzureMonitorBuildQueries(t *testing.T) {
|
||||
expectedPortalURL = *tt.expectedPortalURL
|
||||
}
|
||||
|
||||
actual, err := getQueryUrl(queries[0], "http://ds", "/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana", "grafana")
|
||||
actual, err := getQueryUrl(query, "http://ds", "/subscriptions/12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana", "grafana")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedPortalURL, actual)
|
||||
})
|
||||
@ -359,10 +359,10 @@ func TestCustomNamespace(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result, err := datasource.buildQueries(q, types.DatasourceInfo{})
|
||||
result, err := datasource.buildQuery(q[0], types.DatasourceInfo{})
|
||||
require.NoError(t, err)
|
||||
expected := "custom/namespace"
|
||||
require.Equal(t, expected, result[0].Params.Get("metricnamespace"))
|
||||
require.Equal(t, expected, result.Params.Get("metricnamespace"))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ package metrics
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
)
|
||||
|
||||
// urlBuilder builds the URL for calling the Azure Monitor API
|
||||
@ -33,7 +35,7 @@ func (params *urlBuilder) buildResourceURI() (*string, error) {
|
||||
|
||||
if metricNamespace == nil || *metricNamespace == "" {
|
||||
if params.MetricDefinition == nil || *params.MetricDefinition == "" {
|
||||
return nil, fmt.Errorf("no metricNamespace or metricDefiniton value provided")
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("no metricNamespace or metricDefiniton value provided"), false)
|
||||
}
|
||||
metricNamespace = params.MetricDefinition
|
||||
}
|
||||
@ -45,7 +47,7 @@ func (params *urlBuilder) buildResourceURI() (*string, error) {
|
||||
provider = metricNamespaceArray[0]
|
||||
metricNamespaceArray = metricNamespaceArray[1:]
|
||||
} else {
|
||||
return nil, fmt.Errorf("metricNamespace is not in the correct format")
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("metricNamespace is not in the correct format"), false)
|
||||
}
|
||||
|
||||
var resourceNameArray []string
|
||||
@ -76,7 +78,7 @@ func (params *urlBuilder) buildResourceURI() (*string, error) {
|
||||
if i < len(resourceNameArray) {
|
||||
urlArray = append(urlArray, namespace, resourceNameArray[i])
|
||||
} else {
|
||||
return nil, fmt.Errorf("resourceNameArray does not have enough elements")
|
||||
return nil, errorsource.DownstreamError(fmt.Errorf("resourceNameArray does not have enough elements"), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
@ -63,15 +64,14 @@ func (e *AzureResourceGraphDatasource) ExecuteTimeSeriesQuery(ctx context.Contex
|
||||
Responses: map[string]backend.DataResponse{},
|
||||
}
|
||||
|
||||
queries, err := e.buildQueries(originalQueries, dsInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
res, err := e.executeQuery(ctx, query, dsInfo, client, url)
|
||||
for _, query := range originalQueries {
|
||||
graphQuery, err := e.buildQuery(query, dsInfo)
|
||||
if err != nil {
|
||||
result.Responses[query.RefID] = backend.DataResponse{Error: err}
|
||||
return nil, err
|
||||
}
|
||||
res, err := e.executeQuery(ctx, graphQuery, dsInfo, client, url)
|
||||
if err != nil {
|
||||
errorsource.AddErrorToResponse(query.RefID, result, err)
|
||||
continue
|
||||
}
|
||||
result.Responses[query.RefID] = *res
|
||||
@ -87,38 +87,33 @@ type argJSONQuery struct {
|
||||
} `json:"azureResourceGraph"`
|
||||
}
|
||||
|
||||
func (e *AzureResourceGraphDatasource) buildQueries(queries []backend.DataQuery, dsInfo types.DatasourceInfo) ([]*AzureResourceGraphQuery, error) {
|
||||
azureResourceGraphQueries := make([]*AzureResourceGraphQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
queryJSONModel := argJSONQuery{}
|
||||
err := json.Unmarshal(query.JSON, &queryJSONModel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the Azure Resource Graph query object from JSON: %w", err)
|
||||
}
|
||||
|
||||
azureResourceGraphTarget := queryJSONModel.AzureResourceGraph
|
||||
|
||||
resultFormat := azureResourceGraphTarget.ResultFormat
|
||||
if resultFormat == "" {
|
||||
resultFormat = "table"
|
||||
}
|
||||
|
||||
interpolatedQuery, err := macros.KqlInterpolate(query, dsInfo, azureResourceGraphTarget.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azureResourceGraphQueries[i] = &AzureResourceGraphQuery{
|
||||
RefID: query.RefID,
|
||||
ResultFormat: resultFormat,
|
||||
JSON: query.JSON,
|
||||
InterpolatedQuery: interpolatedQuery,
|
||||
TimeRange: query.TimeRange,
|
||||
QueryType: query.QueryType,
|
||||
}
|
||||
func (e *AzureResourceGraphDatasource) buildQuery(query backend.DataQuery, dsInfo types.DatasourceInfo) (*AzureResourceGraphQuery, error) {
|
||||
queryJSONModel := argJSONQuery{}
|
||||
err := json.Unmarshal(query.JSON, &queryJSONModel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the Azure Resource Graph query object from JSON: %w", err)
|
||||
}
|
||||
|
||||
return azureResourceGraphQueries, nil
|
||||
azureResourceGraphTarget := queryJSONModel.AzureResourceGraph
|
||||
|
||||
resultFormat := azureResourceGraphTarget.ResultFormat
|
||||
if resultFormat == "" {
|
||||
resultFormat = "table"
|
||||
}
|
||||
|
||||
interpolatedQuery, err := macros.KqlInterpolate(query, dsInfo, azureResourceGraphTarget.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AzureResourceGraphQuery{
|
||||
RefID: query.RefID,
|
||||
ResultFormat: resultFormat,
|
||||
JSON: query.JSON,
|
||||
InterpolatedQuery: interpolatedQuery,
|
||||
TimeRange: query.TimeRange,
|
||||
QueryType: query.QueryType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, query *AzureResourceGraphQuery, dsInfo types.DatasourceInfo, client *http.Client, dsURL string) (*backend.DataResponse, error) {
|
||||
@ -164,7 +159,7 @@ func (e *AzureResourceGraphDatasource) executeQuery(ctx context.Context, query *
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errorsource.DownstreamError(err, false)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@ -224,7 +219,7 @@ func (e *AzureResourceGraphDatasource) unmarshalResponse(res *http.Response) (Az
|
||||
}()
|
||||
|
||||
if res.StatusCode/100 != 2 {
|
||||
return AzureResourceGraphResponse{}, fmt.Errorf("%s. Azure Resource Graph error: %s", res.Status, string(body))
|
||||
return AzureResourceGraphResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("%s. Azure Resource Graph error: %s", res.Status, string(body)), false)
|
||||
}
|
||||
|
||||
var data AzureResourceGraphResponse
|
||||
|
@ -25,11 +25,11 @@ func TestBuildingAzureResourceGraphQueries(t *testing.T) {
|
||||
fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryModel []backend.DataQuery
|
||||
timeRange backend.TimeRange
|
||||
azureResourceGraphQueries []*AzureResourceGraphQuery
|
||||
Err require.ErrorAssertionFunc
|
||||
name string
|
||||
queryModel []backend.DataQuery
|
||||
timeRange backend.TimeRange
|
||||
azureResourceGraphQuery AzureResourceGraphQuery
|
||||
Err require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "Query with macros should be interpolated",
|
||||
@ -49,20 +49,18 @@ func TestBuildingAzureResourceGraphQueries(t *testing.T) {
|
||||
RefID: "A",
|
||||
},
|
||||
},
|
||||
azureResourceGraphQueries: []*AzureResourceGraphQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
ResultFormat: "table",
|
||||
URL: "",
|
||||
JSON: []byte(`{
|
||||
azureResourceGraphQuery: AzureResourceGraphQuery{
|
||||
RefID: "A",
|
||||
ResultFormat: "table",
|
||||
URL: "",
|
||||
JSON: []byte(`{
|
||||
"queryType": "Azure Resource Graph",
|
||||
"azureResourceGraph": {
|
||||
"query": "resources | where $__contains(name,'res1','res2')",
|
||||
"resultFormat": "table"
|
||||
}
|
||||
}`),
|
||||
InterpolatedQuery: "resources | where ['name'] in ('res1','res2')",
|
||||
},
|
||||
InterpolatedQuery: "resources | where ['name'] in ('res1','res2')",
|
||||
},
|
||||
Err: require.NoError,
|
||||
},
|
||||
@ -70,9 +68,9 @@ func TestBuildingAzureResourceGraphQueries(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
queries, err := datasource.buildQueries(tt.queryModel, types.DatasourceInfo{})
|
||||
query, err := datasource.buildQuery(tt.queryModel[0], types.DatasourceInfo{})
|
||||
tt.Err(t, err)
|
||||
if diff := cmp.Diff(tt.azureResourceGraphQueries, queries, cmpopts.IgnoreUnexported(struct{}{})); diff != "" {
|
||||
if diff := cmp.Diff(&tt.azureResourceGraphQuery, query, cmpopts.IgnoreUnexported(struct{}{})); diff != "" {
|
||||
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource"
|
||||
)
|
||||
|
||||
// TimeGrain handles conversions between
|
||||
@ -26,7 +27,7 @@ func CreateISO8601DurationFromIntervalMS(it int64) (string, error) {
|
||||
timeValueString := formatted[0 : len(formatted)-1]
|
||||
timeValue, err := strconv.Atoi(timeValueString)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse interval %q to an ISO 8061 duration: %w", it, err)
|
||||
return "", errorsource.DownstreamError(fmt.Errorf("could not parse interval %q to an ISO 8061 duration: %w", it, err), false)
|
||||
}
|
||||
|
||||
unit := formatted[len(formatted)-1:]
|
||||
|
@ -217,10 +217,13 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
logger.Error("ExecuteQuery panic", "error", r, "stack", string(debug.Stack()))
|
||||
if theErr, ok := r.(error); ok {
|
||||
queryResult.dataResponse.Error = theErr
|
||||
queryResult.dataResponse.ErrorSource = backend.ErrorSourcePlugin
|
||||
} else if theErrString, ok := r.(string); ok {
|
||||
queryResult.dataResponse.Error = errors.New(theErrString)
|
||||
queryResult.dataResponse.ErrorSource = backend.ErrorSourcePlugin
|
||||
} else {
|
||||
queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError)
|
||||
queryResult.dataResponse.ErrorSource = backend.ErrorSourceDownstream
|
||||
}
|
||||
ch <- queryResult
|
||||
}
|
||||
@ -232,12 +235,13 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
|
||||
timeRange := query.TimeRange
|
||||
|
||||
errAppendDebug := func(frameErr string, err error, query string) {
|
||||
errAppendDebug := func(frameErr string, err error, query string, source backend.ErrorSource) {
|
||||
var emptyFrame data.Frame
|
||||
emptyFrame.SetMeta(&data.FrameMeta{
|
||||
ExecutedQueryString: query,
|
||||
})
|
||||
queryResult.dataResponse.Error = fmt.Errorf("%s: %w", frameErr, err)
|
||||
queryResult.dataResponse.ErrorSource = source
|
||||
queryResult.dataResponse.Frames = data.Frames{&emptyFrame}
|
||||
ch <- queryResult
|
||||
}
|
||||
@ -248,13 +252,13 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
// data source specific substitutions
|
||||
interpolatedQuery, err := e.macroEngine.Interpolate(&query, timeRange, interpolatedQuery)
|
||||
if err != nil {
|
||||
errAppendDebug("interpolation failed", e.TransformQueryError(logger, err), interpolatedQuery)
|
||||
errAppendDebug("interpolation failed", e.TransformQueryError(logger, err), interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := e.db.QueryContext(queryContext, interpolatedQuery)
|
||||
if err != nil {
|
||||
errAppendDebug("db query error", e.TransformQueryError(logger, err), interpolatedQuery)
|
||||
errAppendDebug("db query error", e.TransformQueryError(logger, err), interpolatedQuery, backend.ErrorSourceDownstream)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@ -265,7 +269,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
|
||||
qm, err := e.newProcessCfg(query, queryContext, rows, interpolatedQuery)
|
||||
if err != nil {
|
||||
errAppendDebug("failed to get configurations", err, interpolatedQuery)
|
||||
errAppendDebug("failed to get configurations", err, interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
|
||||
@ -273,7 +277,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
stringConverters := e.queryResultTransformer.GetConverterList()
|
||||
frame, err := sqlutil.FrameFromRows(rows, e.rowLimit, sqlutil.ToConverters(stringConverters...)...)
|
||||
if err != nil {
|
||||
errAppendDebug("convert frame from rows error", err, interpolatedQuery)
|
||||
errAppendDebug("convert frame from rows error", err, interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
|
||||
@ -295,14 +299,14 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
}
|
||||
|
||||
if err := convertSQLTimeColumnsToEpochMS(frame, qm); err != nil {
|
||||
errAppendDebug("converting time columns failed", err, interpolatedQuery)
|
||||
errAppendDebug("converting time columns failed", err, interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
|
||||
if qm.Format == dataQueryFormatSeries {
|
||||
// time series has to have time column
|
||||
if qm.timeIndex == -1 {
|
||||
errAppendDebug("db has no time column", errors.New("no time column found"), interpolatedQuery)
|
||||
errAppendDebug("db has no time column", errors.New("no time column found"), interpolatedQuery, backend.ErrorSourceDownstream)
|
||||
return
|
||||
}
|
||||
|
||||
@ -320,7 +324,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
|
||||
var err error
|
||||
if frame, err = convertSQLValueColumnToFloat(frame, i); err != nil {
|
||||
errAppendDebug("convert value to float failed", err, interpolatedQuery)
|
||||
errAppendDebug("convert value to float failed", err, interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -331,7 +335,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG
|
||||
originalData := frame
|
||||
frame, err = data.LongToWide(frame, qm.FillMissing)
|
||||
if err != nil {
|
||||
errAppendDebug("failed to convert long to wide series when converting from dataframe", err, interpolatedQuery)
|
||||
errAppendDebug("failed to convert long to wide series when converting from dataframe", err, interpolatedQuery, backend.ErrorSourcePlugin)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,32 +1,25 @@
|
||||
import { Action, KBarProvider } from 'kbar';
|
||||
import { Component, ComponentType } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router, Redirect, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
import { CompatRouter, CompatRoute } from 'react-router-dom-v5-compat';
|
||||
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
import { CompatRoute } from 'react-router-dom-v5-compat';
|
||||
|
||||
import {
|
||||
config,
|
||||
locationService,
|
||||
LocationServiceProvider,
|
||||
navigationLogger,
|
||||
reportInteraction,
|
||||
} from '@grafana/runtime';
|
||||
import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, PortalContainer, Stack } from '@grafana/ui';
|
||||
import { config, navigationLogger, reportInteraction } from '@grafana/runtime';
|
||||
import { ErrorBoundaryAlert, GlobalStyles, PortalContainer } from '@grafana/ui';
|
||||
import { getAppRoutes } from 'app/routes/routes';
|
||||
import { store } from 'app/store/store';
|
||||
|
||||
import { AngularRoot } from './angular/AngularRoot';
|
||||
import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled';
|
||||
import { GrafanaApp } from './app';
|
||||
import { AppChrome } from './core/components/AppChrome/AppChrome';
|
||||
import { AppNotificationList } from './core/components/AppNotifications/AppNotificationList';
|
||||
import { GrafanaContext } from './core/context/GrafanaContext';
|
||||
import { ModalsContextProvider } from './core/context/ModalsContextProvider';
|
||||
import { SidecarContext } from './core/context/SidecarContext';
|
||||
import { GrafanaRoute } from './core/navigation/GrafanaRoute';
|
||||
import { RouteDescriptor } from './core/navigation/types';
|
||||
import { sidecarService } from './core/services/SidecarService';
|
||||
import { contextSrv } from './core/services/context_srv';
|
||||
import { ThemeProvider } from './core/utils/ConfigProvider';
|
||||
import { LiveConnectionWarning } from './features/live/LiveConnectionWarning';
|
||||
import { ExperimentalSplitPaneRouterWrapper, RouterWrapper } from './routes/RoutesWrapper';
|
||||
|
||||
interface AppWrapperProps {
|
||||
app: GrafanaApp;
|
||||
@ -100,6 +93,12 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||
});
|
||||
};
|
||||
|
||||
const routerWrapperProps = {
|
||||
routes: ready && this.renderRoutes(),
|
||||
pageBanners,
|
||||
bodyRenderHooks,
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ErrorBoundaryAlert style="page">
|
||||
@ -109,33 +108,18 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
|
||||
actions={[]}
|
||||
options={{ enableHistory: true, callbacks: { onSelectAction: commandPaletteActionSelected } }}
|
||||
>
|
||||
<Router history={locationService.getHistory()}>
|
||||
<LocationServiceProvider service={locationService}>
|
||||
<CompatRouter>
|
||||
<ModalsContextProvider>
|
||||
<GlobalStyles />
|
||||
<div className="grafana-app">
|
||||
<AppChrome>
|
||||
<AngularRoot />
|
||||
<AppNotificationList />
|
||||
<Stack gap={0} grow={1} direction="column">
|
||||
{pageBanners.map((Banner, index) => (
|
||||
<Banner key={index.toString()} />
|
||||
))}
|
||||
{ready && this.renderRoutes()}
|
||||
</Stack>
|
||||
{bodyRenderHooks.map((Hook, index) => (
|
||||
<Hook key={index.toString()} />
|
||||
))}
|
||||
</AppChrome>
|
||||
</div>
|
||||
<LiveConnectionWarning />
|
||||
<ModalRoot />
|
||||
<PortalContainer />
|
||||
</ModalsContextProvider>
|
||||
</CompatRouter>
|
||||
</LocationServiceProvider>
|
||||
</Router>
|
||||
<GlobalStyles />
|
||||
<SidecarContext.Provider value={sidecarService}>
|
||||
<div className="grafana-app">
|
||||
{config.featureToggles.appSidecar ? (
|
||||
<ExperimentalSplitPaneRouterWrapper {...routerWrapperProps} />
|
||||
) : (
|
||||
<RouterWrapper {...routerWrapperProps} />
|
||||
)}
|
||||
<LiveConnectionWarning />
|
||||
<PortalContainer />
|
||||
</div>
|
||||
</SidecarContext.Provider>
|
||||
</KBarProvider>
|
||||
</ThemeProvider>
|
||||
</GrafanaContext.Provider>
|
||||
|
22
public/app/core/context/SidecarContext.ts
Normal file
22
public/app/core/context/SidecarContext.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
|
||||
import { SidecarService, sidecarService } from '../services/SidecarService';
|
||||
|
||||
export const SidecarContext = createContext<SidecarService>(sidecarService);
|
||||
|
||||
export function useSidecar() {
|
||||
const activePluginId = useObservable(sidecarService.activePluginId);
|
||||
const context = useContext(SidecarContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('No SidecarContext found');
|
||||
}
|
||||
|
||||
return {
|
||||
activePluginId,
|
||||
openApp: (pluginId: string) => context.openApp(pluginId),
|
||||
closeApp: (pluginId: string) => context.closeApp(pluginId),
|
||||
isAppOpened: (pluginId: string) => context.isAppOpened(pluginId),
|
||||
};
|
||||
}
|
@ -1,8 +1,37 @@
|
||||
import { uniqBy } from 'lodash';
|
||||
|
||||
import { LANGUAGES, VALID_LANGUAGES } from './constants';
|
||||
import {
|
||||
BRAZILIAN_PORTUGUESE,
|
||||
CHINESE_SIMPLIFIED,
|
||||
DEFAULT_LANGUAGE,
|
||||
ENGLISH_US,
|
||||
FRENCH_FRANCE,
|
||||
GERMAN_GERMANY,
|
||||
LANGUAGES,
|
||||
PSEUDO_LOCALE,
|
||||
SPANISH_SPAIN,
|
||||
VALID_LANGUAGES,
|
||||
} from './constants';
|
||||
|
||||
describe('internationalization constants', () => {
|
||||
it('should have set the constants correctly', () => {
|
||||
expect(ENGLISH_US).toBe('en-US');
|
||||
expect(FRENCH_FRANCE).toBe('fr-FR');
|
||||
expect(SPANISH_SPAIN).toBe('es-ES');
|
||||
expect(GERMAN_GERMANY).toBe('de-DE');
|
||||
expect(BRAZILIAN_PORTUGUESE).toBe('pt-BR');
|
||||
expect(CHINESE_SIMPLIFIED).toBe('zh-Hans');
|
||||
expect(PSEUDO_LOCALE).toBe('pseudo-LOCALE');
|
||||
expect(DEFAULT_LANGUAGE).toBe(ENGLISH_US);
|
||||
});
|
||||
|
||||
it('should match a canonical locale definition', () => {
|
||||
for (const lang of LANGUAGES) {
|
||||
const resolved = Intl.getCanonicalLocales(lang.code);
|
||||
expect(lang.code).toEqual(resolved[0]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not have duplicate languages codes', () => {
|
||||
const uniqLocales = uniqBy(LANGUAGES, (v) => v.code);
|
||||
expect(LANGUAGES).toHaveLength(uniqLocales.length);
|
||||
|
74
public/app/core/services/SidecarService.ts
Normal file
74
public/app/core/services/SidecarService.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
|
||||
export class SidecarService {
|
||||
// The ID of the app plugin that is currently opened in the sidecar view
|
||||
private _activePluginId: BehaviorSubject<string | undefined>;
|
||||
|
||||
constructor() {
|
||||
this._activePluginId = new BehaviorSubject<string | undefined>(undefined);
|
||||
}
|
||||
|
||||
private assertFeatureEnabled() {
|
||||
if (!config.featureToggles.appSidecar) {
|
||||
console.warn('The `appSidecar` feature toggle is not enabled, doing nothing.');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
get activePluginId() {
|
||||
return this._activePluginId.asObservable();
|
||||
}
|
||||
|
||||
openApp(pluginId: string) {
|
||||
if (!this.assertFeatureEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._activePluginId.next(pluginId);
|
||||
}
|
||||
|
||||
closeApp(pluginId: string) {
|
||||
if (!this.assertFeatureEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._activePluginId.getValue() === pluginId) {
|
||||
return this._activePluginId.next(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
isAppOpened(pluginId: string) {
|
||||
if (!this.assertFeatureEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._activePluginId.getValue() === pluginId || getMainAppPluginId() === pluginId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const sidecarService = new SidecarService();
|
||||
|
||||
// The app plugin that is "open" in the main Grafana view
|
||||
function getMainAppPluginId() {
|
||||
const { pathname } = locationService.getLocation();
|
||||
|
||||
// A naive way to sort of simulate core features being an app and having an appID
|
||||
let mainApp = pathname.match(/\/a\/([^/]+)/)?.[1];
|
||||
if (!mainApp && pathname.match(/\/explore/)) {
|
||||
mainApp = 'explore';
|
||||
}
|
||||
|
||||
if (!mainApp && pathname.match(/\/d\//)) {
|
||||
mainApp = 'dashboards';
|
||||
}
|
||||
|
||||
return mainApp || 'unknown';
|
||||
}
|
@ -15,6 +15,7 @@ import {
|
||||
} from '@grafana/data';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { GraphThresholdsStyleMode, Icon, InlineField, Input, Tooltip, useStyles2, Stack } from '@grafana/ui';
|
||||
import { logInfo } from 'app/features/alerting/unified/Analytics';
|
||||
import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow';
|
||||
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
@ -82,6 +83,19 @@ export const QueryWrapper = ({
|
||||
...cloneDeep(query.model),
|
||||
};
|
||||
|
||||
if (queryWithDefaults.datasource && queryWithDefaults.datasource?.uid !== query.datasourceUid) {
|
||||
logInfo('rule query datasource and datasourceUid mismatch', {
|
||||
queryModelDatasourceUid: queryWithDefaults.datasource?.uid || '',
|
||||
queryDatasourceUid: query.datasourceUid,
|
||||
datasourceType: query.model.datasource?.type || 'unknown type',
|
||||
});
|
||||
// There are occasions when the rule query model datasource UID and the datasourceUid do not match
|
||||
// It's unclear as to why this happens, but we need better visibility on why this happens,
|
||||
// so we log when it does, and make the query model datasource UID match the datasource UID
|
||||
// We already elsewhere work under the assumption that the datasource settings are fetched from the datasourceUid property
|
||||
queryWithDefaults.datasource.uid = query.datasourceUid;
|
||||
}
|
||||
|
||||
function SelectingDataSourceTooltip() {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
import { PluginLoadingStrategy, PluginMeta } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { plugins } from 'app/features/alerting/unified/testSetup/plugins';
|
||||
|
||||
@ -18,6 +18,7 @@ export const getPluginsHandler = (pluginsArray: PluginMeta[] = plugins) => {
|
||||
preload: true,
|
||||
version: info.version,
|
||||
angular: angular ?? { detected: false, hideDeprecation: false },
|
||||
loadingStrategy: PluginLoadingStrategy.script,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RuleGroupIdentifier } from 'app/types/unified-alerting';
|
||||
|
||||
@ -47,6 +48,7 @@ describe('getRuleOrigin', () => {
|
||||
path: '',
|
||||
preload: true,
|
||||
angular: { detected: false, hideDeprecation: false },
|
||||
loadingStrategy: PluginLoadingStrategy.script,
|
||||
},
|
||||
};
|
||||
const rule = mockCombinedRule({
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { contextSrv } from '../../../../core/services/context_srv';
|
||||
|
||||
import { ConnectionsRedirectNotice } from './ConnectionsRedirectNotice';
|
||||
|
||||
const setup = (hasAccessToDS: boolean) => {
|
||||
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(hasAccessToDS);
|
||||
|
||||
render(<ConnectionsRedirectNotice />);
|
||||
};
|
||||
|
||||
describe('ConnectionsRedirectNotice', () => {
|
||||
it('should render component when has access to data sources', () => {
|
||||
setup(true);
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render component when has no access to data sources', () => {
|
||||
setup(false);
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -4,6 +4,8 @@ import { useState } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { contextSrv } from '../../../../core/core';
|
||||
import { AccessControlAction } from '../../../../types';
|
||||
import { ROUTES } from '../../constants';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
@ -22,7 +24,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
|
||||
export function ConnectionsRedirectNotice() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [showNotice, setShowNotice] = useState(true);
|
||||
const canAccessDataSources =
|
||||
contextSrv.hasPermission(AccessControlAction.DataSourcesCreate) ||
|
||||
contextSrv.hasPermission(AccessControlAction.DataSourcesWrite);
|
||||
const [showNotice, setShowNotice] = useState(canAccessDataSources);
|
||||
|
||||
return showNotice ? (
|
||||
<Alert severity="info" title="" onRemove={() => setShowNotice(false)}>
|
||||
|
@ -12,17 +12,6 @@ import { activateFullSceneTree } from '../utils/test-utils';
|
||||
import { DashboardLinksEditView } from './DashboardLinksEditView';
|
||||
import { NEW_LINK } from './links/utils';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn().mockReturnValue({
|
||||
pathname: '/d/dash-1/settings/links',
|
||||
search: '',
|
||||
hash: '',
|
||||
state: null,
|
||||
key: '5nvxpbdafa',
|
||||
}),
|
||||
}));
|
||||
|
||||
function render(component: React.ReactNode) {
|
||||
return RTLRender(<TestProvider>{component}</TestProvider>);
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
import { render } from 'test/test-utils';
|
||||
import { byRole } from 'testing-library-selector';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
|
||||
import { configureStore } from '../../../../store/configureStore';
|
||||
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
|
||||
|
||||
import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings';
|
||||
@ -20,7 +16,6 @@ setBackendSrv({
|
||||
} as unknown as BackendSrv);
|
||||
|
||||
const setupTestContext = (options: Partial<Props>) => {
|
||||
const store = configureStore();
|
||||
const defaults: Props = {
|
||||
dashboard: createDashboardModelFixture(
|
||||
{
|
||||
@ -50,15 +45,7 @@ const setupTestContext = (options: Partial<Props>) => {
|
||||
|
||||
const props = { ...defaults, ...options };
|
||||
|
||||
const { rerender } = render(
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<GeneralSettings {...props} />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</GrafanaContext.Provider>
|
||||
);
|
||||
const { rerender } = render(<GeneralSettings {...props} />);
|
||||
|
||||
return { rerender, props };
|
||||
};
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { historySrv } from 'app/features/dashboard-scene/settings/version-history/HistorySrv';
|
||||
|
||||
import { configureStore } from '../../../../store/configureStore';
|
||||
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
|
||||
|
||||
import { VersionsSettings, VERSIONS_FETCH_LIMIT } from './VersionsSettings';
|
||||
@ -27,7 +23,6 @@ const queryByFullText = (text: string) =>
|
||||
});
|
||||
|
||||
function setup() {
|
||||
const store = configureStore();
|
||||
const dashboard = createDashboardModelFixture({
|
||||
id: 74,
|
||||
version: 11,
|
||||
@ -42,15 +37,7 @@ function setup() {
|
||||
},
|
||||
};
|
||||
|
||||
return render(
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<VersionsSettings sectionNav={sectionNav} dashboard={dashboard} />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</GrafanaContext.Provider>
|
||||
);
|
||||
return render(<VersionsSettings sectionNav={sectionNav} dashboard={dashboard} />);
|
||||
}
|
||||
|
||||
describe('VersionSettings', () => {
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { act, screen } from '@testing-library/react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { TextBoxVariableModel } from '@grafana/data';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { GetVariables } from 'app/features/variables/state/selectors';
|
||||
import { VariablesChanged } from 'app/features/variables/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { DashboardModel } from '../state';
|
||||
@ -41,18 +36,7 @@ jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||
});
|
||||
|
||||
function setup(props: Props) {
|
||||
const context = getGrafanaContextMock();
|
||||
const store = configureStore({});
|
||||
|
||||
return render(
|
||||
<GrafanaContext.Provider value={context}>
|
||||
<Provider store={store}>
|
||||
<Router history={locationService.getHistory()}>
|
||||
<DashboardGrid {...props} />
|
||||
</Router>
|
||||
</Provider>
|
||||
</GrafanaContext.Provider>
|
||||
);
|
||||
return render(<DashboardGrid {...props} />);
|
||||
}
|
||||
|
||||
function getTestDashboard(
|
||||
|
@ -43,7 +43,7 @@ describe('getExploreExtensionConfigs', () => {
|
||||
const extensions = getExploreExtensionConfigs();
|
||||
const [extension] = extensions;
|
||||
|
||||
expect(extension?.configure?.()).toBeUndefined();
|
||||
expect(extension?.configure?.(undefined, { isAppOpened: () => false })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return empty object if sufficient permissions', () => {
|
||||
@ -52,7 +52,7 @@ describe('getExploreExtensionConfigs', () => {
|
||||
const extensions = getExploreExtensionConfigs();
|
||||
const [extension] = extensions;
|
||||
|
||||
expect(extension?.configure?.()).toEqual({});
|
||||
expect(extension?.configure?.(undefined, { isAppOpened: () => false })).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { render, screen, waitForElementToBeRemoved, within } from '@testing-library/react';
|
||||
import { screen, waitForElementToBeRemoved, within } from '@testing-library/react';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
@ -69,15 +67,7 @@ afterEach(() => {
|
||||
const selectors = e2eSelectors.pages.PublicDashboards;
|
||||
|
||||
const renderPublicDashboardTable = async (waitForListRendering?: boolean) => {
|
||||
const context = getGrafanaContextMock();
|
||||
|
||||
render(
|
||||
<TestProvider grafanaContext={context}>
|
||||
<BrowserRouter>
|
||||
<PublicDashboardListTable />
|
||||
</BrowserRouter>
|
||||
</TestProvider>
|
||||
);
|
||||
render(<PublicDashboardListTable />);
|
||||
|
||||
waitForListRendering && (await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')[0], { timeout: 3000 }));
|
||||
};
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { History, Location } from 'history';
|
||||
import { type match } from 'react-router-dom';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { RouteDescriptor } from 'app/core/navigation/types';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
import { PlaylistEditPage, RouteParams } from './PlaylistEditPage';
|
||||
import { PlaylistEditPage } from './PlaylistEditPage';
|
||||
import { Playlist } from './types';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
@ -27,7 +26,7 @@ async function getTestContext({ name, interval, items, uid }: Partial<Playlist>
|
||||
const playlist = { name, items, interval, uid } as unknown as Playlist;
|
||||
const queryParams = {};
|
||||
const route = {} as RouteDescriptor;
|
||||
const match = { params: { uid: 'foo' } } as unknown as match<RouteParams>;
|
||||
const match = { isExact: false, path: '', url: '', params: { uid: 'foo' } };
|
||||
const location = {} as Location;
|
||||
const history = {} as History;
|
||||
const getMock = jest.spyOn(backendSrv, 'get');
|
||||
|
@ -1,18 +1,15 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { act, screen } from '@testing-library/react';
|
||||
import { Component } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { AppPlugin, PluginType, AppRootProps, NavModelItem, PluginIncludeType, OrgRole } from '@grafana/data';
|
||||
import { getMockPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { RouteDescriptor } from 'app/core/navigation/types';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { getPluginSettings } from '../pluginSettings';
|
||||
import { importAppPlugin } from '../plugin_loader';
|
||||
@ -89,7 +86,6 @@ async function renderUnderRouter(page = '') {
|
||||
appPluginNavItem.parentItem = appsSection;
|
||||
|
||||
const pagePath = page ? `/${page}` : '';
|
||||
const store = configureStore();
|
||||
const route = {
|
||||
component: () => <AppRootPage pluginId="my-awesome-plugin" pluginNavSection={appsSection} />,
|
||||
} as unknown as RouteDescriptor;
|
||||
@ -100,11 +96,7 @@ async function renderUnderRouter(page = '') {
|
||||
|
||||
render(
|
||||
<Router history={locationService.getHistory()}>
|
||||
<Provider store={store}>
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<Route path={`/a/:pluginId${pagePath}`} exact render={(props) => <GrafanaRoute {...props} route={route} />} />
|
||||
</GrafanaContext.Provider>
|
||||
</Provider>
|
||||
<Route path={`/a/:pluginId${pagePath}`} exact render={(props) => <GrafanaRoute {...props} route={route} />} />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
@ -33,8 +33,9 @@ import { buildPluginPageContext, PluginPageContext } from './PluginPageContext';
|
||||
interface Props {
|
||||
// The ID of the plugin we would like to load and display
|
||||
pluginId: string;
|
||||
// The root navModelItem for the plugin (root = lives directly under 'home')
|
||||
pluginNavSection: NavModelItem;
|
||||
// The root navModelItem for the plugin (root = lives directly under 'home'). In case app does not need a nva model,
|
||||
// for example it's in some way embedded or shown in a sideview this can be undefined.
|
||||
pluginNavSection?: NavModelItem;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -53,7 +54,7 @@ export function AppRootPage({ pluginId, pluginNavSection }: Props) {
|
||||
const [state, dispatch] = useReducer(stateSlice.reducer, initialState);
|
||||
const currentUrl = config.appSubUrl + location.pathname + location.search;
|
||||
const { plugin, loading, loadingError, pluginNav } = state;
|
||||
const navModel = buildPluginSectionNav(pluginNavSection, pluginNav, currentUrl);
|
||||
const navModel = buildPluginSectionNav(currentUrl, pluginNavSection);
|
||||
const queryParams = useMemo(() => locationSearchToObject(location.search), [location.search]);
|
||||
const context = useMemo(() => buildPluginPageContext(navModel), [navModel]);
|
||||
const grafanaContext = useGrafana();
|
||||
|
@ -174,7 +174,10 @@ describe('getPluginExtensions()', () => {
|
||||
getPluginExtensions({ ...registries, context, extensionPointId: extensionPoint2 });
|
||||
|
||||
expect(link2.configure).toHaveBeenCalledTimes(1);
|
||||
expect(link2.configure).toHaveBeenCalledWith(context);
|
||||
expect(link2.configure).toHaveBeenCalledWith(
|
||||
context,
|
||||
expect.objectContaining({ isAppOpened: expect.any(Function) })
|
||||
);
|
||||
});
|
||||
|
||||
test('should be possible to update the basic properties with the configure() function', async () => {
|
||||
|
@ -88,11 +88,7 @@ export const getPluginExtensions: GetExtensions = ({
|
||||
|
||||
const path = overrides?.path || addedLink.path;
|
||||
const extension: PluginExtensionLink = {
|
||||
id: generateExtensionId(pluginId, {
|
||||
...addedLink,
|
||||
extensionPointId,
|
||||
type: PluginExtensionTypes.link,
|
||||
}),
|
||||
id: generateExtensionId(pluginId, extensionPointId, addedLink.title),
|
||||
type: PluginExtensionTypes.link,
|
||||
pluginId: pluginId,
|
||||
onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext),
|
||||
@ -125,11 +121,7 @@ export const getPluginExtensions: GetExtensions = ({
|
||||
extensionsByPlugin[addedComponent.pluginId] = 0;
|
||||
}
|
||||
const extension: PluginExtensionComponent = {
|
||||
id: generateExtensionId(addedComponent.pluginId, {
|
||||
...addedComponent,
|
||||
extensionPointId,
|
||||
type: PluginExtensionTypes.component,
|
||||
}),
|
||||
id: generateExtensionId(addedComponent.pluginId, extensionPointId, addedComponent.title),
|
||||
type: PluginExtensionTypes.component,
|
||||
pluginId: addedComponent.pluginId,
|
||||
title: addedComponent.title,
|
||||
|
@ -3,6 +3,7 @@ import { useObservable } from 'react-use';
|
||||
|
||||
import { PluginExtension } from '@grafana/data';
|
||||
import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/runtime';
|
||||
import { useSidecar } from 'app/core/context/SidecarContext';
|
||||
|
||||
import { getPluginExtensions } from './getPluginExtensions';
|
||||
import { PluginExtensionRegistries } from './registry/types';
|
||||
@ -14,6 +15,7 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries)
|
||||
return function usePluginExtensions(options: GetPluginExtensionsOptions): UsePluginExtensionsResult<PluginExtension> {
|
||||
const addedComponentsRegistry = useObservable(observableAddedComponentsRegistry);
|
||||
const addedLinksRegistry = useObservable(observableAddedLinksRegistry);
|
||||
const { activePluginId } = useSidecar();
|
||||
|
||||
const { extensions } = useMemo(() => {
|
||||
if (!addedLinksRegistry && !addedComponentsRegistry) {
|
||||
@ -27,12 +29,17 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries)
|
||||
addedComponentsRegistry,
|
||||
addedLinksRegistry,
|
||||
});
|
||||
// Doing the deps like this instead of just `option` because users probably aren't going to memoize the
|
||||
// options object so we are checking it's simple value attributes.
|
||||
// The context though still has to be memoized though and not mutated.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: refactor `getPluginExtensions` to accept service dependencies as arguments instead of relying on the sidecar singleton under the hood
|
||||
}, [
|
||||
addedLinksRegistry,
|
||||
addedComponentsRegistry,
|
||||
options.extensionPointId,
|
||||
options.context,
|
||||
options.limitPerPlugin,
|
||||
activePluginId,
|
||||
]);
|
||||
|
||||
return { extensions, isLoading: false };
|
||||
|
@ -60,11 +60,7 @@ export function createUsePluginLinks(registry: AddedLinksRegistry) {
|
||||
|
||||
const path = overrides?.path || addedLink.path;
|
||||
const extension: PluginExtensionLink = {
|
||||
id: generateExtensionId(pluginId, {
|
||||
...addedLink,
|
||||
extensionPointId,
|
||||
type: PluginExtensionTypes.link,
|
||||
}),
|
||||
id: generateExtensionId(pluginId, extensionPointId, addedLink.title),
|
||||
type: PluginExtensionTypes.link,
|
||||
pluginId: pluginId,
|
||||
onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext),
|
||||
|
@ -5,7 +5,13 @@ import { dateTime, usePluginContext } from '@grafana/data';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import { deepFreeze, handleErrorsInFn, getReadOnlyProxy, getEventHelpers, wrapWithPluginContext } from './utils';
|
||||
import {
|
||||
deepFreeze,
|
||||
handleErrorsInFn,
|
||||
getReadOnlyProxy,
|
||||
createOpenModalFunction,
|
||||
wrapWithPluginContext,
|
||||
} from './utils';
|
||||
|
||||
jest.mock('app/features/plugins/pluginSettings', () => ({
|
||||
...jest.requireActual('app/features/plugins/pluginSettings'),
|
||||
@ -306,111 +312,100 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEventHelpers', () => {
|
||||
describe('openModal', () => {
|
||||
let renderModalSubscription: Unsubscribable | undefined;
|
||||
describe('createOpenModalFunction()', () => {
|
||||
let renderModalSubscription: Unsubscribable | undefined;
|
||||
|
||||
beforeAll(() => {
|
||||
renderModalSubscription = appEvents.subscribe(ShowModalReactEvent, (event) => {
|
||||
const { payload } = event;
|
||||
const Modal = payload.component;
|
||||
render(<Modal />);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
renderModalSubscription?.unsubscribe();
|
||||
});
|
||||
|
||||
it('should open modal with provided title and body', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const { openModal } = getEventHelpers(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
});
|
||||
|
||||
expect(await screen.findByRole('dialog')).toBeVisible();
|
||||
expect(screen.getByRole('heading')).toHaveTextContent('Title in modal');
|
||||
expect(screen.getByText('Text in body')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should open modal with default width if not specified', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const { openModal } = getEventHelpers(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.width).toBe('750px');
|
||||
expect(style.height).toBe('');
|
||||
});
|
||||
|
||||
it('should open modal with specified width', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const { openModal } = getEventHelpers(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
width: '70%',
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.width).toBe('70%');
|
||||
});
|
||||
|
||||
it('should open modal with specified height', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const { openModal } = getEventHelpers(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
height: 600,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.height).toBe('600px');
|
||||
});
|
||||
|
||||
it('should open modal with the plugin context being available', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const { openModal } = getEventHelpers(pluginId);
|
||||
|
||||
const ModalContent = () => {
|
||||
const context = usePluginContext();
|
||||
|
||||
return <div>Version: {context.meta.info.version}</div>;
|
||||
};
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: ModalContent,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
expect(modal).toHaveTextContent('Version: 1.0.0');
|
||||
beforeAll(() => {
|
||||
renderModalSubscription = appEvents.subscribe(ShowModalReactEvent, (event) => {
|
||||
const { payload } = event;
|
||||
const Modal = payload.component;
|
||||
render(<Modal />);
|
||||
});
|
||||
});
|
||||
|
||||
describe('context', () => {
|
||||
it('should return same object as passed to getEventHelpers', () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const source = {};
|
||||
const { context } = getEventHelpers(pluginId, source);
|
||||
expect(context).toBe(source);
|
||||
afterAll(() => {
|
||||
renderModalSubscription?.unsubscribe();
|
||||
});
|
||||
|
||||
it('should open modal with provided title and body', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const openModal = createOpenModalFunction(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
});
|
||||
|
||||
expect(await screen.findByRole('dialog')).toBeVisible();
|
||||
expect(screen.getByRole('heading')).toHaveTextContent('Title in modal');
|
||||
expect(screen.getByText('Text in body')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should open modal with default width if not specified', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const openModal = createOpenModalFunction(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.width).toBe('750px');
|
||||
expect(style.height).toBe('');
|
||||
});
|
||||
|
||||
it('should open modal with specified width', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const openModal = createOpenModalFunction(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
width: '70%',
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.width).toBe('70%');
|
||||
});
|
||||
|
||||
it('should open modal with specified height', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const openModal = createOpenModalFunction(pluginId);
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: () => <div>Text in body</div>,
|
||||
height: 600,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
const style = window.getComputedStyle(modal);
|
||||
|
||||
expect(style.height).toBe('600px');
|
||||
});
|
||||
|
||||
it('should open modal with the plugin context being available', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const openModal = createOpenModalFunction(pluginId);
|
||||
|
||||
const ModalContent = () => {
|
||||
const context = usePluginContext();
|
||||
|
||||
return <div>Version: {context.meta.info.version}</div>;
|
||||
};
|
||||
|
||||
openModal({
|
||||
title: 'Title in modal',
|
||||
body: ModalContent,
|
||||
});
|
||||
|
||||
const modal = await screen.findByRole('dialog');
|
||||
expect(modal).toHaveTextContent('Version: 1.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -20,6 +20,8 @@ import {
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Modal } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
// TODO: instead of depending on the service as a singleton, inject it as an argument from the React context
|
||||
import { sidecarService } from 'app/core/services/SidecarService';
|
||||
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
@ -48,9 +50,8 @@ export function handleErrorsInFn(fn: Function, errorMessagePrefix = '') {
|
||||
};
|
||||
}
|
||||
|
||||
// Event helpers are designed to make it easier to trigger "core actions" from an extension event handler, e.g. opening a modal or showing a notification.
|
||||
export function getEventHelpers(pluginId: string, context?: Readonly<object>): PluginExtensionEventHelpers {
|
||||
const openModal: PluginExtensionEventHelpers['openModal'] = async (options) => {
|
||||
export function createOpenModalFunction(pluginId: string): PluginExtensionEventHelpers['openModal'] {
|
||||
return async (options) => {
|
||||
const { title, body, width, height } = options;
|
||||
|
||||
appEvents.publish(
|
||||
@ -59,8 +60,6 @@ export function getEventHelpers(pluginId: string, context?: Readonly<object>): P
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return { openModal, context };
|
||||
}
|
||||
|
||||
type ModalWrapperProps = {
|
||||
@ -161,8 +160,8 @@ export function deepFreeze(value?: object | Record<string | symbol, unknown> | u
|
||||
return Object.freeze(clonedValue);
|
||||
}
|
||||
|
||||
export function generateExtensionId(pluginId: string, extensionConfig: PluginExtensionConfig): string {
|
||||
const str = `${pluginId}${extensionConfig.extensionPointId}${extensionConfig.title}`;
|
||||
export function generateExtensionId(pluginId: string, extensionPointId: string, title: string): string {
|
||||
const str = `${pluginId}${extensionPointId}${title}`;
|
||||
|
||||
return Array.from(str)
|
||||
.reduce((s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0, 0)
|
||||
@ -298,7 +297,7 @@ export function createExtensionSubMenu(extensions: PluginExtensionLink[]): Panel
|
||||
|
||||
export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkRegistryItem, context?: object) {
|
||||
try {
|
||||
const overrides = config.configure?.(context);
|
||||
const overrides = config.configure?.(context, { isAppOpened: () => isAppOpened(pluginId) });
|
||||
|
||||
// Hiding the extension
|
||||
if (overrides === undefined) {
|
||||
@ -369,7 +368,15 @@ export function getLinkExtensionOnClick(
|
||||
category: config.category,
|
||||
});
|
||||
|
||||
const result = onClick(event, getEventHelpers(pluginId, context));
|
||||
const helpers: PluginExtensionEventHelpers = {
|
||||
context,
|
||||
openModal: createOpenModalFunction(pluginId),
|
||||
isAppOpened: () => isAppOpened(pluginId),
|
||||
openAppInSideview: () => openAppInSideview(pluginId),
|
||||
closeAppInSideview: () => closeAppInSideview(pluginId),
|
||||
};
|
||||
|
||||
const result = onClick(event, helpers);
|
||||
|
||||
if (isPromise(result)) {
|
||||
result.catch((e) => {
|
||||
@ -395,3 +402,9 @@ export function getLinkExtensionPathWithTracking(pluginId: string, path: string,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export const openAppInSideview = (pluginId: string) => sidecarService.openApp(pluginId);
|
||||
|
||||
export const closeAppInSideview = (pluginId: string) => sidecarService.closeApp(pluginId);
|
||||
|
||||
export const isAppOpened = (pluginId: string) => sidecarService.isAppOpened(pluginId);
|
||||
|
@ -70,7 +70,7 @@ describe('Plugin Extension Validators', () => {
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
targets: 'grafana/some-page/extension-point-a',
|
||||
configure: () => {},
|
||||
configure: (_, {}) => {},
|
||||
} as PluginExtensionAddedLinkConfig);
|
||||
}).not.toThrowError();
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { PanelPlugin, PanelPluginMeta, PanelProps } from '@grafana/data';
|
||||
import { PanelPlugin, PanelPluginMeta, PanelProps, PluginLoadingStrategy } from '@grafana/data';
|
||||
import config from 'app/core/config';
|
||||
|
||||
import { getPanelPluginLoadError } from '../panel/components/PanelPluginError';
|
||||
@ -56,10 +56,12 @@ export function syncGetPanelPlugin(id: string): PanelPlugin | undefined {
|
||||
}
|
||||
|
||||
function getPanelPlugin(meta: PanelPluginMeta): Promise<PanelPlugin> {
|
||||
const fallbackLoadingStrategy = meta.loadingStrategy ?? PluginLoadingStrategy.fetch;
|
||||
return importPluginModule({
|
||||
path: meta.module,
|
||||
version: meta.info?.version,
|
||||
isAngular: meta.angular?.detected,
|
||||
loadingStrategy: fallbackLoadingStrategy,
|
||||
pluginId: meta.id,
|
||||
})
|
||||
.then((pluginExports) => {
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { registerPluginInCache, invalidatePluginInCache, resolveWithCache, getPluginFromCache } from './cache';
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
|
||||
import {
|
||||
registerPluginInCache,
|
||||
invalidatePluginInCache,
|
||||
resolveWithCache,
|
||||
getPluginFromCache,
|
||||
extractCacheKeyFromPath,
|
||||
} from './cache';
|
||||
|
||||
jest.mock('./constants', () => ({
|
||||
CACHE_INITIALISED_AT: 123456,
|
||||
@ -7,28 +15,28 @@ jest.mock('./constants', () => ({
|
||||
describe('Cache Functions', () => {
|
||||
describe('registerPluginInCache', () => {
|
||||
it('should register a plugin in the cache', () => {
|
||||
const plugin = { pluginId: 'plugin1', version: '1.0.0', isAngular: false };
|
||||
registerPluginInCache(plugin);
|
||||
const plugin = { version: '1.0.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache({ path: 'public/plugins/plugin1/module.js', ...plugin });
|
||||
expect(getPluginFromCache('plugin1')).toEqual(plugin);
|
||||
});
|
||||
|
||||
it('should not register a plugin if it already exists in the cache', () => {
|
||||
const pluginId = 'plugin2';
|
||||
const plugin = { pluginId, version: '2.0.0' };
|
||||
const path = 'public/plugins/plugin2/module.js';
|
||||
const plugin = { path, version: '2.0.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache(plugin);
|
||||
const plugin2 = { pluginId, version: '2.5.0' };
|
||||
const plugin2 = { path, version: '2.5.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache(plugin2);
|
||||
expect(getPluginFromCache(pluginId)?.version).toBe('2.0.0');
|
||||
expect(getPluginFromCache(path)?.version).toBe('2.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalidatePluginInCache', () => {
|
||||
it('should invalidate a plugin in the cache', () => {
|
||||
const pluginId = 'plugin3';
|
||||
const plugin = { pluginId, version: '3.0.0' };
|
||||
const path = 'public/plugins/plugin2/module.js';
|
||||
const plugin = { path, version: '3.0.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache(plugin);
|
||||
invalidatePluginInCache(pluginId);
|
||||
expect(getPluginFromCache(pluginId)).toBeUndefined();
|
||||
invalidatePluginInCache('plugin2');
|
||||
expect(getPluginFromCache('plugin2')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not throw an error if the plugin does not exist in the cache', () => {
|
||||
@ -43,17 +51,43 @@ describe('Cache Functions', () => {
|
||||
});
|
||||
|
||||
it('should resolve URL with plugin version as cache bust parameter if available', () => {
|
||||
const plugin = { pluginId: 'plugin5', version: '5.0.0' };
|
||||
registerPluginInCache(plugin);
|
||||
const url = 'http://localhost:3000/public/plugins/plugin5/module.js';
|
||||
const plugin = { path: url, version: '5.0.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache(plugin);
|
||||
expect(resolveWithCache(url)).toContain('_cache=5.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractCacheKeyFromPath', () => {
|
||||
it('should extract plugin ID from a path', () => {
|
||||
expect(extractCacheKeyFromPath('public/plugins/plugin6/module.js')).toBe('plugin6');
|
||||
});
|
||||
|
||||
it('should extract plugin ID from a path', () => {
|
||||
expect(extractCacheKeyFromPath('public/plugins/plugin6/datasource/module.js')).toBe('plugin6');
|
||||
});
|
||||
|
||||
it('should extract plugin ID from a url', () => {
|
||||
expect(extractCacheKeyFromPath('https://my-url.com/plugin6/1.0.0/public/plugins/plugin6/module.js')).toBe(
|
||||
'plugin6'
|
||||
);
|
||||
});
|
||||
|
||||
it('should extract plugin ID from a nested plugin url', () => {
|
||||
expect(
|
||||
extractCacheKeyFromPath('https://my-url.com/plugin6/1.0.0/public/plugins/plugin6/datasource/module.js')
|
||||
).toBe('plugin6');
|
||||
});
|
||||
|
||||
it('should return null if the path does not match the pattern', () => {
|
||||
expect(extractCacheKeyFromPath('public/plugins/plugin7')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPluginFromCache', () => {
|
||||
it('should return plugin from cache if exists', () => {
|
||||
const plugin = { pluginId: 'plugin6', version: '6.0.0' };
|
||||
registerPluginInCache(plugin);
|
||||
const plugin = { version: '6.0.0', loadingStrategy: PluginLoadingStrategy.script };
|
||||
registerPluginInCache({ path: 'public/plugins/plugin6/module.js', ...plugin });
|
||||
expect(getPluginFromCache('plugin6')).toEqual(plugin);
|
||||
});
|
||||
|
||||
|
@ -1,22 +1,26 @@
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
|
||||
import { clearPluginSettingsCache } from '../pluginSettings';
|
||||
|
||||
import { CACHE_INITIALISED_AT } from './constants';
|
||||
|
||||
const cache: Record<string, CacheablePlugin> = {};
|
||||
const cache: Record<string, CachedPlugin> = {};
|
||||
|
||||
type CacheablePlugin = {
|
||||
pluginId: string;
|
||||
path: string;
|
||||
version: string;
|
||||
isAngular?: boolean;
|
||||
loadingStrategy: PluginLoadingStrategy;
|
||||
};
|
||||
|
||||
export function registerPluginInCache({ pluginId, version, isAngular }: CacheablePlugin): void {
|
||||
const key = pluginId;
|
||||
type CachedPlugin = Omit<CacheablePlugin, 'path'>;
|
||||
|
||||
export function registerPluginInCache({ path, version, loadingStrategy }: CacheablePlugin): void {
|
||||
const key = extractCacheKeyFromPath(path);
|
||||
|
||||
if (key && !cache[key]) {
|
||||
cache[key] = {
|
||||
version: encodeURI(version),
|
||||
isAngular,
|
||||
pluginId,
|
||||
loadingStrategy,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -39,7 +43,7 @@ export function resolveWithCache(url: string, defaultBust = CACHE_INITIALISED_AT
|
||||
return `${url}?_cache=${bust}`;
|
||||
}
|
||||
|
||||
export function getPluginFromCache(path: string): CacheablePlugin | undefined {
|
||||
export function getPluginFromCache(path: string): CachedPlugin | undefined {
|
||||
const key = getCacheKey(path);
|
||||
if (!key) {
|
||||
return;
|
||||
@ -47,6 +51,12 @@ export function getPluginFromCache(path: string): CacheablePlugin | undefined {
|
||||
return cache[key];
|
||||
}
|
||||
|
||||
export function extractCacheKeyFromPath(path: string) {
|
||||
const regex = /\/?public\/plugins\/([^\/]+)\//;
|
||||
const match = path.match(regex);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function getCacheKey(address: string): string | undefined {
|
||||
const key = Object.keys(cache).find((key) => address.includes(key));
|
||||
if (!key) {
|
||||
|
@ -5,7 +5,7 @@ import { startMeasure, stopMeasure } from 'app/core/utils/metrics';
|
||||
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
|
||||
|
||||
import { PluginExtensionRegistries } from './extensions/registry/types';
|
||||
import * as pluginLoader from './plugin_loader';
|
||||
import { importPluginModule } from './plugin_loader';
|
||||
|
||||
export type PluginPreloadResult = {
|
||||
pluginId: string;
|
||||
@ -48,14 +48,15 @@ export async function preloadPlugins(
|
||||
}
|
||||
|
||||
async function preload(config: AppPluginConfig): Promise<PluginPreloadResult> {
|
||||
const { path, version, id: pluginId } = config;
|
||||
const { path, version, id: pluginId, loadingStrategy } = config;
|
||||
try {
|
||||
startMeasure(`frontend_plugin_preload_${pluginId}`);
|
||||
const { plugin } = await pluginLoader.importPluginModule({
|
||||
const { plugin } = await importPluginModule({
|
||||
path,
|
||||
version,
|
||||
isAngular: config.angular.detected,
|
||||
pluginId,
|
||||
loadingStrategy,
|
||||
});
|
||||
const { exposedComponentConfigs = [], addedComponentConfigs = [], addedLinkConfigs = [] } = plugin;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user