diff --git a/pkg/api/api.go b/pkg/api/api.go index 1381d8d7caa..81f7c964200 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -492,7 +492,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/render/*", reqSignedIn, hs.RenderToPng) // grafana.net proxy - r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest) + r.Any("/api/gnet/*", reqSignedIn, hs.ProxyGnetRequest) // Gravatar service. avatarCacheServer := avatar.NewCacheServer(hs.Cfg) diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index 074c8541a41..b7b3b2a6528 100644 --- a/pkg/api/dtos/plugins.go +++ b/pkg/api/dtos/plugins.go @@ -34,6 +34,7 @@ type PluginListItem struct { Enabled bool `json:"enabled"` Pinned bool `json:"pinned"` Info *plugins.Info `json:"info"` + Dependencies *plugins.Dependencies `json:"dependencies"` LatestVersion string `json:"latestVersion"` HasUpdate bool `json:"hasUpdate"` DefaultNavUrl string `json:"defaultNavUrl"` diff --git a/pkg/api/grafana_com_proxy.go b/pkg/api/grafana_com_proxy.go index 33f6146ca0b..343a5ba9ed0 100644 --- a/pkg/api/grafana_com_proxy.go +++ b/pkg/api/grafana_com_proxy.go @@ -22,7 +22,7 @@ var grafanaComProxyTransport = &http.Transport{ TLSHandshakeTimeout: 10 * time.Second, } -func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy { +func ReverseProxyGnetReq(proxyPath string, version string) *httputil.ReverseProxy { url, _ := url.Parse(setting.GrafanaComUrl) director := func(req *http.Request) { @@ -36,14 +36,17 @@ func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy { req.Header.Del("Cookie") req.Header.Del("Set-Cookie") req.Header.Del("Authorization") + + // send the current Grafana version for each request proxied to GCOM + req.Header.Add("grafana-version", version) } return &httputil.ReverseProxy{Director: director} } -func ProxyGnetRequest(c *models.ReqContext) { +func (hs *HTTPServer) ProxyGnetRequest(c *models.ReqContext) { proxyPath := web.Params(c.Req)["*"] - proxy := ReverseProxyGnetReq(proxyPath) + proxy := ReverseProxyGnetReq(proxyPath, hs.Cfg.BuildVersion) proxy.Transport = grafanaComProxyTransport proxy.ServeHTTP(c.Resp, c.Req) c.Resp.Header().Del("Set-Cookie") diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 8aed9a64a10..d56dedec246 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -67,6 +67,7 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response { Type: string(pluginDef.Type), Category: pluginDef.Category, Info: &pluginDef.Info, + Dependencies: &pluginDef.Dependencies, LatestVersion: pluginDef.GrafanaComVersion, HasUpdate: pluginDef.GrafanaComHasUpdate, DefaultNavUrl: pluginDef.DefaultNavURL, diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 89526e5e1ff..8faadc0a565 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -77,8 +77,9 @@ func (e SignatureError) AsErrorCode() ErrorCode { } type Dependencies struct { - GrafanaVersion string `json:"grafanaVersion"` - Plugins []Dependency `json:"plugins"` + GrafanaDependency string `json:"grafanaDependency"` + GrafanaVersion string `json:"grafanaVersion"` + Plugins []Dependency `json:"plugins"` } type Includes struct { diff --git a/pkg/tests/api/plugins/data/expectedListResp.json b/pkg/tests/api/plugins/data/expectedListResp.json index 6b34e72672b..65cc257c45f 100644 --- a/pkg/tests/api/plugins/data/expectedListResp.json +++ b/pkg/tests/api/plugins/data/expectedListResp.json @@ -1,1400 +1,1520 @@ [ { - "name":"Alert list", - "type":"panel", - "id":"alertlist", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Shows list of alerts and their current status", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/alertlist/img/icn-singlestat-panel.svg", - "large":"public/app/plugins/panel/alertlist/img/icn-singlestat-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/alertlist/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Alert list", + "type": "panel", + "id": "alertlist", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Shows list of alerts and their current status", + "links": null, + "logos": { + "small": "public/app/plugins/panel/alertlist/img/icn-singlestat-panel.svg", + "large": "public/app/plugins/panel/alertlist/img/icn-singlestat-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/alertlist/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Annotations list", - "type":"panel", - "id":"annolist", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"List annotations", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/annolist/img/icn-annolist-panel.svg", - "large":"public/app/plugins/panel/annolist/img/icn-annolist-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/annolist/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Annotations list", + "type": "panel", + "id": "annolist", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "List annotations", + "links": null, + "logos": { + "small": "public/app/plugins/panel/annolist/img/icn-annolist-panel.svg", + "large": "public/app/plugins/panel/annolist/img/icn-annolist-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/annolist/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Azure Monitor", - "type":"datasource", - "id":"grafana-azure-monitor-datasource", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for Microsoft Azure Monitor & Application Insights", - "links":[ + "name": "Azure Monitor", + "type": "datasource", + "id": "grafana-azure-monitor-datasource", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for Microsoft Azure Monitor \u0026 Application Insights", + "links": [ { - "name":"Learn more", - "url":"https://github.com/grafana/azure-monitor-datasource" + "name": "Learn more", + "url": "https://github.com/grafana/azure-monitor-datasource" }, { - "name":"Apache License", - "url":"https://github.com/grafana/azure-monitor-datasource/blob/master/LICENSE" + "name": "Apache License", + "url": "https://github.com/grafana/azure-monitor-datasource/blob/master/LICENSE" } ], - "logos":{ - "small":"public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg", - "large":"public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg" - }, - "build":{ - + "logos": { + "small": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg", + "large": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/logo.jpg" }, - "screenshots":[ + "build": {}, + "screenshots": [ { - "name":"Azure Contoso Loans", - "path":"public/app/plugins/datasource/grafana-azure-monitor-datasource/img/contoso_loans_grafana_dashboard.png" + "name": "Azure Contoso Loans", + "path": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/contoso_loans_grafana_dashboard.png" }, { - "name":"Azure Monitor Network", - "path":"public/app/plugins/datasource/grafana-azure-monitor-datasource/img/azure_monitor_network.png" + "name": "Azure Monitor Network", + "path": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/azure_monitor_network.png" }, { - "name":"Azure Monitor CPU", - "path":"public/app/plugins/datasource/grafana-azure-monitor-datasource/img/azure_monitor_cpu.png" + "name": "Azure Monitor CPU", + "path": "public/app/plugins/datasource/grafana-azure-monitor-datasource/img/azure_monitor_cpu.png" } ], - "version":"0.3.0", - "updated":"2018-12-06" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/grafana-azure-monitor-datasource/", - "category":"cloud", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "version": "0.3.0", + "updated": "2018-12-06" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "5.2.x", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/grafana-azure-monitor-datasource/", + "category": "cloud", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Bar chart", - "type":"panel", - "id":"barchart", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Categorical charts with group support", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/barchart/img/barchart.svg", - "large":"public/app/plugins/panel/barchart/img/barchart.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/barchart/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Bar chart", + "type": "panel", + "id": "barchart", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Categorical charts with group support", + "links": null, + "logos": { + "small": "public/app/plugins/panel/barchart/img/barchart.svg", + "large": "public/app/plugins/panel/barchart/img/barchart.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/barchart/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Bar gauge", - "type":"panel", - "id":"bargauge", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Horizontal and vertical gauges", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg", - "large":"public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/bargauge/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Bar gauge", + "type": "panel", + "id": "bargauge", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Horizontal and vertical gauges", + "links": null, + "logos": { + "small": "public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg", + "large": "public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/bargauge/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"CloudWatch", - "type":"datasource", - "id":"cloudwatch", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for Amazon AWS monitoring service", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png", - "large":"public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/cloudwatch/", - "category":"cloud", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "CloudWatch", + "type": "datasource", + "id": "cloudwatch", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for Amazon AWS monitoring service", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png", + "large": "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/cloudwatch/", + "category": "cloud", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Dashboard list", - "type":"panel", - "id":"dashlist", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"List of dynamic links to other dashboards", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/dashlist/img/icn-dashlist-panel.svg", - "large":"public/app/plugins/panel/dashlist/img/icn-dashlist-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/dashlist/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Dashboard list", + "type": "panel", + "id": "dashlist", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "List of dynamic links to other dashboards", + "links": null, + "logos": { + "small": "public/app/plugins/panel/dashlist/img/icn-dashlist-panel.svg", + "large": "public/app/plugins/panel/dashlist/img/icn-dashlist-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/dashlist/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Elasticsearch", - "type":"datasource", - "id":"elasticsearch", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source logging & analytics database", - "links":[ + "name": "Elasticsearch", + "type": "datasource", + "id": "elasticsearch", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source logging \u0026 analytics database", + "links": [ { - "name":"Learn more", - "url":"https://grafana.com/docs/features/datasources/elasticsearch/" + "name": "Learn more", + "url": "https://grafana.com/docs/features/datasources/elasticsearch/" } ], - "logos":{ - "small":"public/app/plugins/datasource/elasticsearch/img/elasticsearch.svg", - "large":"public/app/plugins/datasource/elasticsearch/img/elasticsearch.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/elasticsearch/", - "category":"logging", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/elasticsearch/img/elasticsearch.svg", + "large": "public/app/plugins/datasource/elasticsearch/img/elasticsearch.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/elasticsearch/", + "category": "logging", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Gauge", - "type":"panel", - "id":"gauge", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Standard gauge visualization", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/gauge/img/icon_gauge.svg", - "large":"public/app/plugins/panel/gauge/img/icon_gauge.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/gauge/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Gauge", + "type": "panel", + "id": "gauge", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Standard gauge visualization", + "links": null, + "logos": { + "small": "public/app/plugins/panel/gauge/img/icon_gauge.svg", + "large": "public/app/plugins/panel/gauge/img/icon_gauge.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/gauge/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Geomap", - "type":"panel", - "id":"geomap", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Geomap panel", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/geomap/img/icn-geomap.svg", - "large":"public/app/plugins/panel/geomap/img/icn-geomap.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/geomap/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Geomap", + "type": "panel", + "id": "geomap", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Geomap panel", + "links": null, + "logos": { + "small": "public/app/plugins/panel/geomap/img/icn-geomap.svg", + "large": "public/app/plugins/panel/geomap/img/icn-geomap.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/geomap/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Getting Started", - "type":"panel", - "id":"gettingstarted", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/gettingstarted/img/icn-dashlist-panel.svg", - "large":"public/app/plugins/panel/gettingstarted/img/icn-dashlist-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/gettingstarted/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Getting Started", + "type": "panel", + "id": "gettingstarted", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "", + "links": null, + "logos": { + "small": "public/app/plugins/panel/gettingstarted/img/icn-dashlist-panel.svg", + "large": "public/app/plugins/panel/gettingstarted/img/icn-dashlist-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/gettingstarted/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Google Cloud Monitoring", - "type":"datasource", - "id":"stackdriver", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for Google's monitoring service (formerly named Stackdriver)", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/cloud-monitoring/img/cloud_monitoring_logo.svg", - "large":"public/app/plugins/datasource/cloud-monitoring/img/cloud_monitoring_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"1.0.0", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/stackdriver/", - "category":"cloud", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Google Cloud Monitoring", + "type": "datasource", + "id": "stackdriver", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for Google's monitoring service (formerly named Stackdriver)", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/cloud-monitoring/img/cloud_monitoring_logo.svg", + "large": "public/app/plugins/datasource/cloud-monitoring/img/cloud_monitoring_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "1.0.0", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/stackdriver/", + "category": "cloud", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Graph (old)", - "type":"panel", - "id":"graph", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"The old default graph panel", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/graph/img/icn-graph-panel.svg", - "large":"public/app/plugins/panel/graph/img/icn-graph-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/graph/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Graph (old)", + "type": "panel", + "id": "graph", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "The old default graph panel", + "links": null, + "logos": { + "small": "public/app/plugins/panel/graph/img/icn-graph-panel.svg", + "large": "public/app/plugins/panel/graph/img/icn-graph-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/graph/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Graphite", - "type":"datasource", - "id":"graphite", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source time series database", - "links":[ + "name": "Graphite", + "type": "datasource", + "id": "graphite", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source time series database", + "links": [ { - "name":"Learn more", - "url":"https://graphiteapp.org/" + "name": "Learn more", + "url": "https://graphiteapp.org/" }, { - "name":"Graphite 1.1 Release", - "url":"https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/" + "name": "Graphite 1.1 Release", + "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/" } ], - "logos":{ - "small":"public/app/plugins/datasource/graphite/img/graphite_logo.png", - "large":"public/app/plugins/datasource/graphite/img/graphite_logo.png" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/graphite/", - "category":"tsdb", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/graphite/img/graphite_logo.png", + "large": "public/app/plugins/datasource/graphite/img/graphite_logo.png" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/graphite/", + "category": "tsdb", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Heatmap", - "type":"panel", - "id":"heatmap", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Like a histogram over time", - "links":[ + "name": "Heatmap", + "type": "panel", + "id": "heatmap", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Like a histogram over time", + "links": [ { - "name":"Brendan Gregg - Heatmaps", - "url":"http://www.brendangregg.com/heatmaps.html" + "name": "Brendan Gregg - Heatmaps", + "url": "http://www.brendangregg.com/heatmaps.html" }, { - "name":"Brendan Gregg - Latency Heatmaps", - "url":" http://www.brendangregg.com/HeatMaps/latency.html" + "name": "Brendan Gregg - Latency Heatmaps", + "url": " http://www.brendangregg.com/HeatMaps/latency.html" } ], - "logos":{ - "small":"public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg", - "large":"public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/heatmap/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg", + "large": "public/app/plugins/panel/heatmap/img/icn-heatmap-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/heatmap/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Histogram", - "type":"panel", - "id":"histogram", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/histogram/img/histogram.svg", - "large":"public/app/plugins/panel/histogram/img/histogram.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/histogram/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Histogram", + "type": "panel", + "id": "histogram", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "", + "links": null, + "logos": { + "small": "public/app/plugins/panel/histogram/img/histogram.svg", + "large": "public/app/plugins/panel/histogram/img/histogram.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/histogram/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"InfluxDB", - "type":"datasource", - "id":"influxdb", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source time series database", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/influxdb/img/influxdb_logo.svg", - "large":"public/app/plugins/datasource/influxdb/img/influxdb_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/influxdb/", - "category":"tsdb", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "InfluxDB", + "type": "datasource", + "id": "influxdb", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source time series database", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/influxdb/img/influxdb_logo.svg", + "large": "public/app/plugins/datasource/influxdb/img/influxdb_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/influxdb/", + "category": "tsdb", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Jaeger", - "type":"datasource", - "id":"jaeger", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source, end-to-end distributed tracing", - "links":[ + "name": "Jaeger", + "type": "datasource", + "id": "jaeger", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source, end-to-end distributed tracing", + "links": [ { - "name":"Learn more", - "url":"https://www.jaegertracing.io" + "name": "Learn more", + "url": "https://www.jaegertracing.io" }, { - "name":"GitHub Project", - "url":"https://github.com/jaegertracing/jaeger" + "name": "GitHub Project", + "url": "https://github.com/jaegertracing/jaeger" } ], - "logos":{ - "small":"public/app/plugins/datasource/jaeger/img/jaeger_logo.svg", - "large":"public/app/plugins/datasource/jaeger/img/jaeger_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/jaeger/", - "category":"tracing", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/jaeger/img/jaeger_logo.svg", + "large": "public/app/plugins/datasource/jaeger/img/jaeger_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/jaeger/", + "category": "tracing", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Logs", - "type":"panel", - "id":"logs", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/logs/img/icn-logs-panel.svg", - "large":"public/app/plugins/panel/logs/img/icn-logs-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/logs/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Logs", + "type": "panel", + "id": "logs", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "", + "links": null, + "logos": { + "small": "public/app/plugins/panel/logs/img/icn-logs-panel.svg", + "large": "public/app/plugins/panel/logs/img/icn-logs-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/logs/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Loki", - "type":"datasource", - "id":"loki", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Like Prometheus but for logs. OSS logging solution from Grafana Labs", - "links":[ + "name": "Loki", + "type": "datasource", + "id": "loki", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Like Prometheus but for logs. OSS logging solution from Grafana Labs", + "links": [ { - "name":"Learn more", - "url":"https://grafana.com/loki" + "name": "Learn more", + "url": "https://grafana.com/loki" }, { - "name":"GitHub Project", - "url":"https://github.com/grafana/loki" + "name": "GitHub Project", + "url": "https://github.com/grafana/loki" } ], - "logos":{ - "small":"public/app/plugins/datasource/loki/img/loki_icon.svg", - "large":"public/app/plugins/datasource/loki/img/loki_icon.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/loki/", - "category":"logging", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/loki/img/loki_icon.svg", + "large": "public/app/plugins/datasource/loki/img/loki_icon.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/loki/", + "category": "logging", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Microsoft SQL Server", - "type":"datasource", - "id":"mssql", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for Microsoft SQL Server compatible databases", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/mssql/img/sql_server_logo.svg", - "large":"public/app/plugins/datasource/mssql/img/sql_server_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/mssql/", - "category":"sql", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Microsoft SQL Server", + "type": "datasource", + "id": "mssql", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for Microsoft SQL Server compatible databases", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/mssql/img/sql_server_logo.svg", + "large": "public/app/plugins/datasource/mssql/img/sql_server_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/mssql/", + "category": "sql", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"MySQL", - "type":"datasource", - "id":"mysql", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for MySQL databases", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/mysql/img/mysql_logo.svg", - "large":"public/app/plugins/datasource/mysql/img/mysql_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/mysql/", - "category":"sql", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "MySQL", + "type": "datasource", + "id": "mysql", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for MySQL databases", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/mysql/img/mysql_logo.svg", + "large": "public/app/plugins/datasource/mysql/img/mysql_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/mysql/", + "category": "sql", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"News", - "type":"panel", - "id":"news", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"RSS feed reader", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/news/img/news.svg", - "large":"public/app/plugins/panel/news/img/news.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/news/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "News", + "type": "panel", + "id": "news", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "RSS feed reader", + "links": null, + "logos": { + "small": "public/app/plugins/panel/news/img/news.svg", + "large": "public/app/plugins/panel/news/img/news.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/news/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Node Graph", - "type":"panel", - "id":"nodeGraph", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/nodeGraph/img/icn-node-graph.svg", - "large":"public/app/plugins/panel/nodeGraph/img/icn-node-graph.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/nodeGraph/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Node Graph", + "type": "panel", + "id": "nodeGraph", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "", + "links": null, + "logos": { + "small": "public/app/plugins/panel/nodeGraph/img/icn-node-graph.svg", + "large": "public/app/plugins/panel/nodeGraph/img/icn-node-graph.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/nodeGraph/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"OpenTSDB", - "type":"datasource", - "id":"opentsdb", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source time series database", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/opentsdb/img/opentsdb_logo.png", - "large":"public/app/plugins/datasource/opentsdb/img/opentsdb_logo.png" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/opentsdb/", - "category":"tsdb", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "OpenTSDB", + "type": "datasource", + "id": "opentsdb", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source time series database", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/opentsdb/img/opentsdb_logo.png", + "large": "public/app/plugins/datasource/opentsdb/img/opentsdb_logo.png" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/opentsdb/", + "category": "tsdb", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Pie chart", - "type":"panel", - "id":"piechart", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"The new core pie chart visualization", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/piechart/img/icon_piechart.svg", - "large":"public/app/plugins/panel/piechart/img/icon_piechart.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/piechart/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Pie chart", + "type": "panel", + "id": "piechart", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "The new core pie chart visualization", + "links": null, + "logos": { + "small": "public/app/plugins/panel/piechart/img/icon_piechart.svg", + "large": "public/app/plugins/panel/piechart/img/icon_piechart.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/piechart/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Plugin list", - "type":"panel", - "id":"pluginlist", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Plugin List for Grafana", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg", - "large":"public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/pluginlist/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Plugin list", + "type": "panel", + "id": "pluginlist", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Plugin List for Grafana", + "links": null, + "logos": { + "small": "public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg", + "large": "public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/pluginlist/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"PostgreSQL", - "type":"datasource", - "id":"postgres", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Data source for PostgreSQL and compatible databases", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/postgres/img/postgresql_logo.svg", - "large":"public/app/plugins/datasource/postgres/img/postgresql_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/postgres/", - "category":"sql", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "PostgreSQL", + "type": "datasource", + "id": "postgres", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Data source for PostgreSQL and compatible databases", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/postgres/img/postgresql_logo.svg", + "large": "public/app/plugins/datasource/postgres/img/postgresql_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/postgres/", + "category": "sql", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Prometheus", - "type":"datasource", - "id":"prometheus", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Open source time series database & alerting", - "links":[ + "name": "Prometheus", + "type": "datasource", + "id": "prometheus", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Open source time series database \u0026 alerting", + "links": [ { - "name":"Learn more", - "url":"https://prometheus.io/" + "name": "Learn more", + "url": "https://prometheus.io/" } ], - "logos":{ - "small":"public/app/plugins/datasource/prometheus/img/prometheus_logo.svg", - "large":"public/app/plugins/datasource/prometheus/img/prometheus_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/prometheus/", - "category":"tsdb", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/prometheus/img/prometheus_logo.svg", + "large": "public/app/plugins/datasource/prometheus/img/prometheus_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/prometheus/", + "category": "tsdb", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Stat", - "type":"panel", - "id":"stat", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Big stat values & sparklines", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/stat/img/icn-singlestat-panel.svg", - "large":"public/app/plugins/panel/stat/img/icn-singlestat-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/stat/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Stat", + "type": "panel", + "id": "stat", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Big stat values \u0026 sparklines", + "links": null, + "logos": { + "small": "public/app/plugins/panel/stat/img/icn-singlestat-panel.svg", + "large": "public/app/plugins/panel/stat/img/icn-singlestat-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/stat/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"State timeline", - "type":"panel", - "id":"state-timeline", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"State changes and durations", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/state-timeline/img/timeline.svg", - "large":"public/app/plugins/panel/state-timeline/img/timeline.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/state-timeline/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "State timeline", + "type": "panel", + "id": "state-timeline", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "State changes and durations", + "links": null, + "logos": { + "small": "public/app/plugins/panel/state-timeline/img/timeline.svg", + "large": "public/app/plugins/panel/state-timeline/img/timeline.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/state-timeline/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Status history", - "type":"panel", - "id":"status-history", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Periodic status history", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/status-history/img/status.svg", - "large":"public/app/plugins/panel/status-history/img/status.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/status-history/", - "category":"", - "state":"beta", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Status history", + "type": "panel", + "id": "status-history", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Periodic status history", + "links": null, + "logos": { + "small": "public/app/plugins/panel/status-history/img/status.svg", + "large": "public/app/plugins/panel/status-history/img/status.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/status-history/", + "category": "", + "state": "beta", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Table", - "type":"panel", - "id":"table", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Supports many column styles", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/table/img/icn-table-panel.svg", - "large":"public/app/plugins/panel/table/img/icn-table-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/table/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Table", + "type": "panel", + "id": "table", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Supports many column styles", + "links": null, + "logos": { + "small": "public/app/plugins/panel/table/img/icn-table-panel.svg", + "large": "public/app/plugins/panel/table/img/icn-table-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/table/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Table (old)", - "type":"panel", - "id":"table-old", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Table Panel for Grafana", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/table-old/img/icn-table-panel.svg", - "large":"public/app/plugins/panel/table-old/img/icn-table-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/table-old/", - "category":"", - "state":"deprecated", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Table (old)", + "type": "panel", + "id": "table-old", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Table Panel for Grafana", + "links": null, + "logos": { + "small": "public/app/plugins/panel/table-old/img/icn-table-panel.svg", + "large": "public/app/plugins/panel/table-old/img/icn-table-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/table-old/", + "category": "", + "state": "deprecated", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Tempo", - "type":"datasource", - "id":"tempo", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"High volume, minimal dependency trace storage. OSS tracing solution from Grafana Labs.", - "links":[ + "name": "Tempo", + "type": "datasource", + "id": "tempo", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "High volume, minimal dependency trace storage. OSS tracing solution from Grafana Labs.", + "links": [ { - "name":"GitHub Project", - "url":"https://github.com/grafana/tempo" + "name": "GitHub Project", + "url": "https://github.com/grafana/tempo" } ], - "logos":{ - "small":"public/app/plugins/datasource/tempo/img/tempo_logo.svg", - "large":"public/app/plugins/datasource/tempo/img/tempo_logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/tempo/", - "category":"tracing", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/tempo/img/tempo_logo.svg", + "large": "public/app/plugins/datasource/tempo/img/tempo_logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/tempo/", + "category": "tracing", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"TestData DB", - "type":"datasource", - "id":"testdata", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Generates test data in different forms", - "links":null, - "logos":{ - "small":"public/app/plugins/datasource/testdata/img/testdata.svg", - "large":"public/app/plugins/datasource/testdata/img/testdata.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/testdata/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "TestData DB", + "type": "datasource", + "id": "testdata", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Generates test data in different forms", + "links": null, + "logos": { + "small": "public/app/plugins/datasource/testdata/img/testdata.svg", + "large": "public/app/plugins/datasource/testdata/img/testdata.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/testdata/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Text", - "type":"panel", - "id":"text", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Supports markdown and html content", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/text/img/icn-text-panel.svg", - "large":"public/app/plugins/panel/text/img/icn-text-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/text/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Text", + "type": "panel", + "id": "text", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Supports markdown and html content", + "links": null, + "logos": { + "small": "public/app/plugins/panel/text/img/icn-text-panel.svg", + "large": "public/app/plugins/panel/text/img/icn-text-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/text/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Time series", - "type":"panel", - "id":"timeseries", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Time based line, area and bar charts", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/timeseries/img/icn-timeseries-panel.svg", - "large":"public/app/plugins/panel/timeseries/img/icn-timeseries-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/timeseries/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Time series", + "type": "panel", + "id": "timeseries", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Time based line, area and bar charts", + "links": null, + "logos": { + "small": "public/app/plugins/panel/timeseries/img/icn-timeseries-panel.svg", + "large": "public/app/plugins/panel/timeseries/img/icn-timeseries-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/timeseries/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Welcome", - "type":"panel", - "id":"welcome", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"", - "links":null, - "logos":{ - "small":"public/app/plugins/panel/welcome/img/icn-dashlist-panel.svg", - "large":"public/app/plugins/panel/welcome/img/icn-dashlist-panel.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/welcome/", - "category":"", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "name": "Welcome", + "type": "panel", + "id": "welcome", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "", + "links": null, + "logos": { + "small": "public/app/plugins/panel/welcome/img/icn-dashlist-panel.svg", + "large": "public/app/plugins/panel/welcome/img/icn-dashlist-panel.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/welcome/", + "category": "", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" }, { - "name":"Zipkin", - "type":"datasource", - "id":"zipkin", - "enabled":true, - "pinned":false, - "info":{ - "author":{ - "name":"Grafana Labs", - "url":"https://grafana.com" - }, - "description":"Placeholder for the distributed tracing system.", - "links":[ + "name": "Zipkin", + "type": "datasource", + "id": "zipkin", + "enabled": true, + "pinned": false, + "info": { + "author": { + "name": "Grafana Labs", + "url": "https://grafana.com" + }, + "description": "Placeholder for the distributed tracing system.", + "links": [ { - "name":"Learn more", - "url":"https://zipkin.io" + "name": "Learn more", + "url": "https://zipkin.io" } ], - "logos":{ - "small":"public/app/plugins/datasource/zipkin/img/zipkin-logo.svg", - "large":"public/app/plugins/datasource/zipkin/img/zipkin-logo.svg" - }, - "build":{ - - }, - "screenshots":null, - "version":"", - "updated":"" - }, - "latestVersion":"", - "hasUpdate":false, - "defaultNavUrl":"/plugins/zipkin/", - "category":"tracing", - "state":"", - "signature":"internal", - "signatureType":"", - "signatureOrg":"" + "logos": { + "small": "public/app/plugins/datasource/zipkin/img/zipkin-logo.svg", + "large": "public/app/plugins/datasource/zipkin/img/zipkin-logo.svg" + }, + "build": {}, + "screenshots": null, + "version": "", + "updated": "" + }, + "dependencies": { + "grafanaDependency": "", + "grafanaVersion": "*", + "plugins": [] + }, + "latestVersion": "", + "hasUpdate": false, + "defaultNavUrl": "/plugins/zipkin/", + "category": "tracing", + "state": "", + "signature": "internal", + "signatureType": "", + "signatureOrg": "" } -] +] \ No newline at end of file diff --git a/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts b/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts index 2d0e1d73c9f..b49aad19f33 100644 --- a/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts +++ b/public/app/features/plugins/admin/__mocks__/catalogPlugin.mock.ts @@ -51,190 +51,284 @@ export default { { version: '4.2.2', createdAt: '2021-08-25T15:03:47.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.2.1', createdAt: '2021-08-10T19:59:28.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.2.0', createdAt: '2021-08-10T15:37:58.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.5', createdAt: '2021-05-18T14:52:59.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.4', createdAt: '2021-03-09T14:49:58.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.3', createdAt: '2021-03-05T08:54:12.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.2', createdAt: '2021-01-28T10:15:29.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.1', createdAt: '2020-12-30T11:51:47.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.1.0', createdAt: '2020-12-28T09:58:47.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.0.2', createdAt: '2020-11-13T14:34:08.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.0.1', createdAt: '2020-09-02T15:16:32.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '4.0.0', createdAt: '2020-08-26T10:36:59.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.12.4', createdAt: '2020-07-28T08:18:12.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.12.3', createdAt: '2020-07-17T14:24:28.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.12.2', createdAt: '2020-05-28T06:46:27.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.12.1', createdAt: '2020-05-25T07:26:13.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.12.0', createdAt: '2020-05-21T10:16:59.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.11.0', createdAt: '2020-03-23T13:29:01.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.5', createdAt: '2019-12-26T15:29:46.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.4', createdAt: '2019-08-08T10:11:23.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.3', createdAt: '2019-07-26T11:59:53.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.2', createdAt: '2019-04-23T17:23:44.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.1', createdAt: '2019-03-05T12:17:20.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.10.0', createdAt: '2019-02-15T11:20:40.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.9.1', createdAt: '2018-05-03T08:49:25.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.9.0', createdAt: '2018-03-23T16:37:53.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.8.1', createdAt: '2017-12-21T09:30:44.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.8.0', createdAt: '2017-12-20T14:23:50.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.7.0', createdAt: '2017-10-24T11:57:08.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.6.1', createdAt: '2017-07-26T16:23:09.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.6.0', createdAt: '2017-07-26T15:30:18.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.5.1', createdAt: '2017-07-10T09:47:25.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.5.0', createdAt: '2017-07-05T16:58:20.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.4.0', createdAt: '2017-05-17T13:48:12.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.3.0', createdAt: '2017-02-10T15:50:27.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.2.1', createdAt: '2017-02-02T14:20:53.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.2.0', createdAt: '2016-12-20T18:25:36.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.1.2', createdAt: '2016-11-09T19:12:05.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.1.1', createdAt: '2016-09-27T18:05:38.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.1.0', createdAt: '2016-09-26T19:31:45.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0', createdAt: '2016-07-04T21:17:55.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta8', createdAt: '2016-05-02T08:55:24.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta7', createdAt: '2016-04-14T18:58:43.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta6', createdAt: '2016-04-14T01:10:31.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta5', createdAt: '2016-04-12T14:55:31.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta4', createdAt: '2016-04-10T21:55:49.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, { version: '3.0.0-beta3', createdAt: '2016-04-06T20:23:41.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', }, ], }, diff --git a/public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts b/public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts index e1e148e0d5d..b0744b0a2d8 100644 --- a/public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts +++ b/public/app/features/plugins/admin/__mocks__/localPlugin.mock.ts @@ -61,7 +61,6 @@ export default { version: '4.2.2', updated: '2021-08-25', }, - latestVersion: '', hasUpdate: false, defaultNavUrl: '/plugins/alexanderzobnin-zabbix-app/', category: '', diff --git a/public/app/features/plugins/admin/__mocks__/mockHelpers.ts b/public/app/features/plugins/admin/__mocks__/mockHelpers.ts index 14e31fae777..04d1e227a80 100644 --- a/public/app/features/plugins/admin/__mocks__/mockHelpers.ts +++ b/public/app/features/plugins/admin/__mocks__/mockHelpers.ts @@ -1,6 +1,6 @@ import { mocked } from 'ts-jest/utils'; import { setBackendSrv } from '@grafana/runtime'; -import { API_ROOT, GRAFANA_API_ROOT } from '../constants'; +import { API_ROOT, GCOM_API_ROOT } from '../constants'; import { CatalogPlugin, LocalPlugin, @@ -71,17 +71,17 @@ export const mockPluginApis = ({ ...originalBackendSrv, get: (path: string) => { // Mock GCOM plugins (remote) if necessary - if (remote && path === `${GRAFANA_API_ROOT}/plugins`) { + if (remote && path === `${GCOM_API_ROOT}/plugins`) { return Promise.resolve({ items: [remote] }); } // Mock GCOM single plugin page (remote) if necessary - if (remote && path === `${GRAFANA_API_ROOT}/plugins/${remote.slug}`) { + if (remote && path === `${GCOM_API_ROOT}/plugins/${remote.slug}`) { return Promise.resolve(remote); } // Mock versions - if (versions && path === `${GRAFANA_API_ROOT}/plugins/${remote.slug}/versions`) { + if (versions && path === `${GCOM_API_ROOT}/plugins/${remote.slug}/versions`) { return Promise.resolve({ items: versions }); } diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index 20e41b9c325..d69f0626382 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -1,6 +1,6 @@ import { getBackendSrv } from '@grafana/runtime'; import { PluginError, renderMarkdown } from '@grafana/data'; -import { API_ROOT, GRAFANA_API_ROOT } from './constants'; +import { API_ROOT, GCOM_API_ROOT } from './constants'; import { mergeLocalAndRemote } from './helpers'; import { PluginDetails, @@ -28,26 +28,19 @@ export async function getPluginDetails(id: string): Promise=${dependencies?.grafanaVersion}` - : ''; + const dependencies = local?.dependencies || remote?.json?.dependencies; return { - grafanaDependency, + grafanaDependency: dependencies?.grafanaDependency ?? dependencies?.grafanaVersion ?? '', pluginDependencies: dependencies?.plugins || [], - links: remote?.json?.info.links || local?.info.links || [], + links: local?.info.links || remote?.json?.info.links || [], readme: localReadme || remote?.readme, versions, }; } export async function getRemotePlugins(): Promise { - const res = await getBackendSrv().get(`${GRAFANA_API_ROOT}/plugins`); + const res = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`); return res.items; } @@ -77,7 +70,7 @@ export async function getPluginErrors(): Promise { async function getRemotePlugin(id: string, isInstalled: boolean): Promise { try { - return await getBackendSrv().get(`${GRAFANA_API_ROOT}/plugins/${id}`, {}); + return await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}`, {}); } catch (error) { // It can happen that GCOM is not available, in that case we show a limited set of information to the user. error.isHandled = true; @@ -87,11 +80,14 @@ async function getRemotePlugin(id: string, isInstalled: boolean): Promise { try { - const versions: { items: PluginVersion[] } = await getBackendSrv().get( - `${GRAFANA_API_ROOT}/plugins/${id}/versions` - ); - - return (versions.items || []).map(({ version, createdAt }) => ({ version, createdAt })); + const versions: { items: PluginVersion[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}/versions`); + + return (versions.items || []).map((v) => ({ + version: v.version, + createdAt: v.createdAt, + isCompatible: v.isCompatible, + grafanaDependency: v.grafanaDependency, + })); } catch (error) { // It can happen that GCOM is not available, in that case we show a limited set of information to the user. error.isHandled = true; @@ -117,14 +113,14 @@ export async function getLocalPlugins(): Promise { } async function getOrg(slug: string): Promise { - const org = await getBackendSrv().get(`${GRAFANA_API_ROOT}/orgs/${slug}`); - return { ...org, avatarUrl: `${GRAFANA_API_ROOT}/orgs/${slug}/avatar` }; + const org = await getBackendSrv().get(`${GCOM_API_ROOT}/orgs/${slug}`); + return { ...org, avatarUrl: `${GCOM_API_ROOT}/orgs/${slug}/avatar` }; } -export async function installPlugin(id: string, version: string) { - return await getBackendSrv().post(`${API_ROOT}/${id}/install`, { - version, - }); +export async function installPlugin(id: string) { + // This will install the latest compatible version based on the logic + // on the backend. + return await getBackendSrv().post(`${API_ROOT}/${id}/install`); } export async function uninstallPlugin(id: string) { diff --git a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx index 3078c73d0b1..2c518c5179b 100644 --- a/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx +++ b/public/app/features/plugins/admin/components/Badges/PluginUpdateAvailableBadge.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { css } from '@emotion/css'; import { GrafanaTheme2, PluginType } from '@grafana/data'; -import { Tooltip, useStyles2 } from '@grafana/ui'; +import { useStyles2 } from '@grafana/ui'; import { CatalogPlugin } from '../../types'; type Props = { @@ -11,12 +11,9 @@ type Props = { export function PluginUpdateAvailableBadge({ plugin }: Props): React.ReactElement | null { const styles = useStyles2(getStyles); + // Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall. if (plugin.hasUpdate && !plugin.isCore && plugin.type !== PluginType.renderer) { - return ( - -

Update available!

-
- ); + return

Update available!

; } return null; diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx index fd8835b7827..ab18c18c9fc 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsButton.tsx @@ -3,15 +3,16 @@ import { AppEvents } from '@grafana/data'; import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui'; import appEvents from 'app/core/app_events'; -import { CatalogPlugin, PluginStatus } from '../../types'; +import { CatalogPlugin, PluginStatus, Version } from '../../types'; import { useInstallStatus, useUninstallStatus, useInstall, useUninstall } from '../../state/hooks'; type InstallControlsButtonProps = { plugin: CatalogPlugin; pluginStatus: PluginStatus; + latestCompatibleVersion?: Version; }; -export function InstallControlsButton({ plugin, pluginStatus }: InstallControlsButtonProps) { +export function InstallControlsButton({ plugin, pluginStatus, latestCompatibleVersion }: InstallControlsButtonProps) { const { isInstalling, error: errorInstalling } = useInstallStatus(); const { isUninstalling, error: errorUninstalling } = useUninstallStatus(); const install = useInstall(); @@ -22,7 +23,7 @@ export function InstallControlsButton({ plugin, pluginStatus }: InstallControlsB const uninstallBtnText = isUninstalling ? 'Uninstalling' : 'Uninstall'; const onInstall = async () => { - await install(plugin.id, plugin.version); + await install(plugin.id, latestCompatibleVersion?.version); if (!errorInstalling) { appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]); } @@ -37,7 +38,7 @@ export function InstallControlsButton({ plugin, pluginStatus }: InstallControlsB }; const onUpdate = async () => { - await install(plugin.id, plugin.version, true); + await install(plugin.id, latestCompatibleVersion?.version, true); if (!errorInstalling) { appEvents.emit(AppEvents.alertSuccess, [`Updated ${plugin.name}`]); } diff --git a/public/app/features/plugins/admin/components/InstallControls/index.tsx b/public/app/features/plugins/admin/components/InstallControls/index.tsx index d8435704b96..60a5960f955 100644 --- a/public/app/features/plugins/admin/components/InstallControls/index.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { css } from '@emotion/css'; -import { satisfies } from 'semver'; import { config } from '@grafana/runtime'; import { HorizontalGroup, Icon, LinkButton, useStyles2 } from '@grafana/ui'; @@ -8,27 +7,23 @@ import { GrafanaTheme2, PluginType } from '@grafana/data'; import { ExternallyManagedButton } from './ExternallyManagedButton'; import { InstallControlsButton } from './InstallControlsButton'; -import { CatalogPlugin, PluginStatus } from '../../types'; +import { CatalogPlugin, PluginStatus, Version } from '../../types'; import { getExternalManageLink } from '../../helpers'; import { useIsRemotePluginsAvailable } from '../../state/hooks'; import { isGrafanaAdmin } from '../../permissions'; interface Props { plugin: CatalogPlugin; + latestCompatibleVersion?: Version; } -export const InstallControls = ({ plugin }: Props) => { +export const InstallControls = ({ plugin, latestCompatibleVersion }: Props) => { const styles = useStyles2(getStyles); const isExternallyManaged = config.pluginAdminExternalManageEnabled; const hasPermission = isGrafanaAdmin(); - const grafanaDependency = plugin.details?.grafanaDependency; const isRemotePluginsAvailable = useIsRemotePluginsAvailable(); - const unsupportedGrafanaVersion = grafanaDependency - ? !satisfies(config.buildInfo.version, grafanaDependency, { - // needed for when running against main - includePrerelease: true, - }) - : false; + const isCompatible = Boolean(latestCompatibleVersion); + const pluginStatus = plugin.isInstalled ? plugin.hasUpdate ? PluginStatus.UPDATE @@ -68,7 +63,7 @@ export const InstallControls = ({ plugin }: Props) => { return
{message}
; } - if (unsupportedGrafanaVersion) { + if (!isCompatible) { return (
@@ -89,7 +84,13 @@ export const InstallControls = ({ plugin }: Props) => { ); } - return ; + return ( + + ); }; export const getStyles = (theme: GrafanaTheme2) => { diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index 0213808a8dc..9ebae16ed1d 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -34,7 +34,7 @@ export function PluginDetailsBody({ plugin, queryParams }: Props): JSX.Element { if (pageId === PluginTabIds.VERSIONS) { return (
- +
); } diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx index 01ac7f83972..51f161901e4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeader.tsx @@ -10,6 +10,7 @@ import { PluginLogo } from './PluginLogo'; import { CatalogPlugin } from '../types'; import { PluginDisabledBadge } from './Badges'; import { GetStartedWithPlugin } from './GetStartedWithPlugin'; +import { getLatestCompatibleVersion } from '../helpers'; type Props = { currentUrl: string; @@ -19,6 +20,8 @@ type Props = { export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): React.ReactElement { const styles = useStyles2(getStyles); + const latestCompatibleVersion = getLatestCompatibleVersion(plugin.details?.versions); + const version = plugin.installedVersion || latestCompatibleVersion?.version; return (
@@ -69,8 +72,8 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R )} - {/* Latest version */} - {plugin.version && {plugin.version}} + {/* Version */} + {Boolean(version) && {version}} {/* Signature information */} @@ -80,13 +83,14 @@ export function PluginDetailsHeader({ plugin, currentUrl, parentUrl }: Props): R

{plugin.description}

- +
diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx index 2c15312a27a..a0c6ae173d4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderDependencies.tsx @@ -2,17 +2,24 @@ import React from 'react'; import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, Icon } from '@grafana/ui'; -import { CatalogPlugin, PluginIconName } from '../types'; +import { Version, CatalogPlugin, PluginIconName } from '../types'; type Props = { plugin: CatalogPlugin; + latestCompatibleVersion?: Version; className?: string; }; -export function PluginDetailsHeaderDependencies({ plugin, className }: Props): React.ReactElement | null { +export function PluginDetailsHeaderDependencies({ + plugin, + latestCompatibleVersion, + className, +}: Props): React.ReactElement | null { const styles = useStyles2(getStyles); const pluginDependencies = plugin.details?.pluginDependencies; - const grafanaDependency = plugin.details?.grafanaDependency; + const grafanaDependency = plugin.isInstalled + ? plugin.details?.grafanaDependency + : latestCompatibleVersion?.grafanaDependency || plugin.details?.grafanaDependency; const hasNoDependencyInfo = !grafanaDependency && (!pluginDependencies || !pluginDependencies.length); if (hasNoDependencyInfo) { diff --git a/public/app/features/plugins/admin/components/PluginListItem.test.tsx b/public/app/features/plugins/admin/components/PluginListItem.test.tsx index f23c2f9a3c2..0606ff0b3f4 100644 --- a/public/app/features/plugins/admin/components/PluginListItem.test.tsx +++ b/public/app/features/plugins/admin/components/PluginListItem.test.tsx @@ -46,7 +46,6 @@ describe('PluginListItem', () => { signature: PluginSignatureStatus.valid, publishedAt: '2020-09-01', updatedAt: '2021-06-28', - version: '1.0.0', hasUpdate: false, isInstalled: false, isCore: false, diff --git a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx index 24ade179cc5..ea92a11242a 100644 --- a/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx +++ b/public/app/features/plugins/admin/components/PluginListItemBadges.test.tsx @@ -22,7 +22,6 @@ describe('PluginListItemBadges', () => { signature: PluginSignatureStatus.valid, publishedAt: '2020-09-01', updatedAt: '2021-06-28', - version: '1.0.0', hasUpdate: false, isInstalled: false, isCore: false, @@ -63,8 +62,13 @@ describe('PluginListItemBadges', () => { expect(screen.getByRole('button', { name: /learn more/i })).toBeInTheDocument(); }); - it('renders a error badge (when plugin has an error', () => { + it('renders a error badge (when plugin has an error)', () => { render(); expect(screen.getByText(/disabled/i)).toBeVisible(); }); + + it('renders an upgrade badge (when plugin has an available update)', () => { + render(); + expect(screen.getByText(/update available/i)).toBeVisible(); + }); }); diff --git a/public/app/features/plugins/admin/components/VersionList.tsx b/public/app/features/plugins/admin/components/VersionList.tsx index ad20241c804..9942a873303 100644 --- a/public/app/features/plugins/admin/components/VersionList.tsx +++ b/public/app/features/plugins/admin/components/VersionList.tsx @@ -4,13 +4,16 @@ import { css } from '@emotion/css'; import { dateTimeFormatTimeAgo, GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { Version } from '../types'; +import { getLatestCompatibleVersion } from '../helpers'; interface Props { versions?: Version[]; + installedVersion?: string; } -export const VersionList = ({ versions = [] }: Props) => { +export const VersionList = ({ versions = [], installedVersion }: Props) => { const styles = useStyles2(getStyles); + const latestCompatibleVersion = getLatestCompatibleVersion(versions); if (versions.length === 0) { return

No version history was found.

; @@ -26,10 +29,22 @@ export const VersionList = ({ versions = [] }: Props) => { {versions.map((version) => { + const isInstalledVersion = installedVersion === version.version; return ( - {version.version} - {dateTimeFormatTimeAgo(version.createdAt)} + {/* Version number */} + {isInstalledVersion ? ( + {version.version} (installed version) + ) : version.version === latestCompatibleVersion?.version ? ( + {version.version} (latest compatible version) + ) : ( + {version.version} + )} + + {/* Last updated */} + + {dateTimeFormatTimeAgo(version.createdAt)} + ); })} @@ -43,6 +58,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ padding: ${theme.spacing(2, 4, 3)}; `, table: css` + table-layout: fixed; width: 100%; td, th { @@ -52,4 +68,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ font-size: ${theme.typography.h5.fontSize}; } `, + currentVersion: css` + font-weight: ${theme.typography.fontWeightBold}; + `, }); diff --git a/public/app/features/plugins/admin/constants.ts b/public/app/features/plugins/admin/constants.ts index df10b477e1c..66af4108726 100644 --- a/public/app/features/plugins/admin/constants.ts +++ b/public/app/features/plugins/admin/constants.ts @@ -1,5 +1,5 @@ export const API_ROOT = '/api/plugins'; -export const GRAFANA_API_ROOT = '/api/gnet'; +export const GCOM_API_ROOT = '/api/gnet'; // Used for prefixing the Redux actions export const STATE_PREFIX = 'plugins'; diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index 15f548f2029..44e8f550133 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -103,7 +103,6 @@ describe('Plugins/Helpers', () => { signature: 'valid', type: 'app', updatedAt: '2021-05-18T14:53:01.000Z', - version: '4.1.5', }); }); @@ -164,7 +163,7 @@ describe('Plugins/Helpers', () => { signatureType: 'community', type: 'app', updatedAt: '2021-08-25', - version: '4.2.2', + installedVersion: '4.2.2', }); }); @@ -211,18 +210,18 @@ describe('Plugins/Helpers', () => { signatureType: 'community', type: 'app', updatedAt: '2021-05-18T14:53:01.000Z', - version: '4.1.5', + installedVersion: '4.2.2', }); }); - test('`.description` - prefers the remote', () => { + test('`.description` - prefers the local', () => { // Local & Remote expect( mapToCatalogPlugin( { ...localPlugin, info: { ...localPlugin.info, description: 'Local description' } }, { ...remotePlugin, description: 'Remote description' } ) - ).toMatchObject({ description: 'Remote description' }); + ).toMatchObject({ description: 'Local description' }); // Remote only expect(mapToCatalogPlugin(undefined, { ...remotePlugin, description: 'Remote description' })).toMatchObject({ @@ -239,31 +238,9 @@ describe('Plugins/Helpers', () => { }); test('`.hasUpdate` - prefers the local', () => { - // Local & Remote (only if the remote version is greater than the local one) - expect( - mapToCatalogPlugin( - { ...localPlugin, info: { ...localPlugin.info, version: '2.0.0' } }, - { ...remotePlugin, version: '2.1.0' } - ) - ).toMatchObject({ hasUpdate: true }); - expect( - mapToCatalogPlugin( - { ...localPlugin, info: { ...localPlugin.info, version: '2.1.0' } }, - { ...remotePlugin, version: '2.1.0' } - ) - ).toMatchObject({ hasUpdate: false }); - - // Remote only - expect(mapToCatalogPlugin(undefined, { ...remotePlugin, version: '2.1.0' })).toMatchObject({ - hasUpdate: false, - }); - // Local only expect(mapToCatalogPlugin({ ...localPlugin })).toMatchObject({ hasUpdate: false }); expect(mapToCatalogPlugin({ ...localPlugin, hasUpdate: true })).toMatchObject({ hasUpdate: true }); - expect(mapToCatalogPlugin({ ...localPlugin, info: { ...localPlugin.info, version: '2.1.0' } })).toMatchObject({ - hasUpdate: false, - }); // No local or remote expect(mapToCatalogPlugin()).toMatchObject({ hasUpdate: false }); @@ -421,7 +398,7 @@ describe('Plugins/Helpers', () => { expect(mapToCatalogPlugin()).toMatchObject({ publishedAt: '' }); }); - test('`.type` - prefers the remote', () => { + test('`.type` - prefers the local', () => { // Local & Remote expect( mapToCatalogPlugin( @@ -429,7 +406,7 @@ describe('Plugins/Helpers', () => { { ...remotePlugin, typeCode: PluginType.datasource } ) ).toMatchObject({ - type: PluginType.datasource, + type: PluginType.app, }); // Remote only diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index e0fca9656d7..b36bb95d1f4 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -1,9 +1,8 @@ import { config } from '@grafana/runtime'; -import { gt } from 'semver'; import { PluginSignatureStatus, dateTimeParse, PluginError, PluginErrorCode } from '@grafana/data'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { Settings } from 'app/core/config'; -import { CatalogPlugin, LocalPlugin, RemotePlugin } from './types'; +import { CatalogPlugin, LocalPlugin, RemotePlugin, Version } from './types'; export function mergeLocalsAndRemotes( local: LocalPlugin[] = [], @@ -62,7 +61,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C } = plugin; const isDisabled = !!error; - const catalogPlugin = { + return { description, downloads, id, @@ -78,7 +77,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C publishedAt, signature: getPluginSignature({ remote: plugin, error }), updatedAt, - version, hasUpdate: false, isInstalled: isDisabled, isDisabled: isDisabled, @@ -88,7 +86,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C type: typeCode, error: error?.errorCode, }; - return catalogPlugin; } export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): CatalogPlugin { @@ -101,6 +98,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat type, signatureOrg, signatureType, + hasUpdate, } = plugin; return { @@ -116,8 +114,8 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat signatureOrg, signatureType, updatedAt: updated, - version, - hasUpdate: false, + installedVersion: version, + hasUpdate, isInstalled: true, isDisabled: !!error, isCore: signature === 'internal', @@ -128,31 +126,31 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat }; } +// TODO: change the signature by removing the optionals for local and remote. export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, error?: PluginError): CatalogPlugin { - const version = remote?.version || local?.info.version || ''; - const hasUpdate = - local?.hasUpdate || Boolean(remote?.version && local?.info.version && gt(remote?.version, local?.info.version)); + const installedVersion = local?.info.version; const id = remote?.slug || local?.id || ''; + const type = local?.type || remote?.typeCode; const isDisabled = !!error; let logos = { - small: 'https://grafana.com/api/plugins/404notfound/versions/none/logos/small', - large: 'https://grafana.com/api/plugins/404notfound/versions/none/logos/large', + small: `/public/img/icn-${type}.svg`, + large: `/public/img/icn-${type}.svg`, }; if (remote) { logos = { - small: `https://grafana.com/api/plugins/${id}/versions/${version}/logos/small`, - large: `https://grafana.com/api/plugins/${id}/versions/${version}/logos/large`, + small: `https://grafana.com/api/plugins/${id}/versions/${remote.version}/logos/small`, + large: `https://grafana.com/api/plugins/${id}/versions/${remote.version}/logos/large`, }; } else if (local && local.info.logos) { logos = local.info.logos; } return { - description: remote?.description || local?.info.description || '', + description: local?.info.description || remote?.description || '', downloads: remote?.downloads || 0, - hasUpdate, + hasUpdate: local?.hasUpdate || false, id, info: { logos, @@ -162,16 +160,19 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e isEnterprise: remote?.status === 'enterprise', isInstalled: Boolean(local) || isDisabled, isDisabled: isDisabled, + // TODO name: remote?.name || local?.name || '', + // TODO orgName: remote?.orgName || local?.info.author.name || '', popularity: remote?.popularity || 0, publishedAt: remote?.createdAt || '', - type: remote?.typeCode || local?.type, + type, signature: getPluginSignature({ local, remote, error }), signatureOrg: local?.signatureOrg || remote?.versionSignedByOrgName, signatureType: local?.signatureType || remote?.versionSignatureType || remote?.signatureType || undefined, + // TODO updatedAt: remote?.updatedAt || local?.info.updated || '', - version, + installedVersion, error: error?.errorCode, }; } @@ -247,3 +248,12 @@ export const updatePanels = () => .then((settings: Settings) => { config.panels = settings.panels; }); + +export function getLatestCompatibleVersion(versions: Version[] | undefined): Version | undefined { + if (!versions) { + return; + } + const [latest] = versions.filter((v) => Boolean(v.isCompatible)); + + return latest; +} diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index 9a3186b2bba..306af38271d 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -1,17 +1,24 @@ import React from 'react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; -import { render, RenderResult, waitFor } from '@testing-library/react'; +import { getDefaultNormalizer, render, RenderResult, SelectorMatcherOptions, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { config } from '@grafana/runtime'; import { mockPluginApis, getCatalogPluginMock, getPluginsStateMock, mockUserPermissions } from '../__mocks__'; import { configureStore } from 'app/store/configureStore'; import PluginDetailsPage from './PluginDetails'; import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps'; -import { CatalogPlugin, PluginTabIds, ReducerState, RequestStatus } from '../types'; +import { + CatalogPlugin, + CatalogPluginDetails, + PluginTabIds, + PluginTabLabels, + ReducerState, + RequestStatus, +} from '../types'; import * as api from '../api'; import { fetchRemotePlugins } from '../state/actions'; -import { PluginErrorCode, PluginSignatureStatus, PluginType } from '@grafana/data'; +import { PluginErrorCode, PluginSignatureStatus, PluginType, dateTimeFormatTimeAgo } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; jest.mock('@grafana/runtime', () => { @@ -122,16 +129,38 @@ describe('Plugin details page', () => { }); it('should display the number of downloads in the header', async () => { + // depending on what locale you have the Intl.NumberFormat will return a format that contains + // whitespaces. In that case we don't want testing library to remove whitespaces. const downloads = 24324; + const options: SelectorMatcherOptions = { normalizer: getDefaultNormalizer({ collapseWhitespace: false }) }; + const expected = new Intl.NumberFormat().format(downloads); + const { queryByText } = renderPluginDetails({ id, downloads }); - await waitFor(() => expect(queryByText(new Intl.NumberFormat().format(downloads))).toBeInTheDocument()); + await waitFor(() => expect(queryByText(expected, options)).toBeInTheDocument()); }); - it('should display the version in the header', async () => { - const version = '1.3.443'; - const { queryByText } = renderPluginDetails({ id, version }); + it('should display the installed version if a plugin is installed', async () => { + const installedVersion = '1.3.443'; + const { queryByText } = renderPluginDetails({ id, installedVersion }); + + await waitFor(() => expect(queryByText(installedVersion)).toBeInTheDocument()); + }); + + it('should display the latest compatible version in the header if a plugin is not installed', async () => { + const details: CatalogPluginDetails = { + links: [], + versions: [ + { version: '1.3.0', createdAt: '', isCompatible: false, grafanaDependency: '>=9.0.0' }, + { version: '1.2.0', createdAt: '', isCompatible: false, grafanaDependency: '>=8.3.0' }, + { version: '1.1.1', createdAt: '', isCompatible: true, grafanaDependency: '>=8.0.0' }, + { version: '1.1.0', createdAt: '', isCompatible: true, grafanaDependency: '>=8.0.0' }, + { version: '1.0.0', createdAt: '', isCompatible: true, grafanaDependency: '>=7.0.0' }, + ], + }; - await waitFor(() => expect(queryByText(version)).toBeInTheDocument()); + const { queryByText } = renderPluginDetails({ id, details }); + await waitFor(() => expect(queryByText('1.1.1')).toBeInTheDocument()); + await waitFor(() => expect(queryByText(/>=8.0.0/i)).toBeInTheDocument()); }); it('should display description in the header', async () => { @@ -166,17 +195,32 @@ describe('Plugin details page', () => { }); it('should display version history in case it is available', async () => { + const versions = [ + { + version: '1.2.0', + createdAt: '2018-04-06T20:23:41.000Z', + isCompatible: false, + grafanaDependency: '>=8.3.0', + }, + { + version: '1.1.0', + createdAt: '2017-04-06T20:23:41.000Z', + isCompatible: true, + grafanaDependency: '>=8.0.0', + }, + { + version: '1.0.0', + createdAt: '2016-04-06T20:23:41.000Z', + isCompatible: true, + grafanaDependency: '>=7.0.0', + }, + ]; const { queryByText, getByRole } = renderPluginDetails( { id, details: { links: [], - versions: [ - { - version: '1.0.0', - createdAt: '2016-04-06T20:23:41.000Z', - }, - ], + versions, }, }, { pageId: PluginTabIds.VERSIONS } @@ -185,32 +229,27 @@ describe('Plugin details page', () => { // Check if version information is available await waitFor(() => expect(queryByText(/version history/i)).toBeInTheDocument()); - expect( - getByRole('columnheader', { - name: /version/i, - }) - ).toBeInTheDocument(); - expect( - getByRole('columnheader', { - name: /last updated/i, - }) - ).toBeInTheDocument(); - expect( - getByRole('cell', { - name: /1\.0\.0/i, - }) - ).toBeInTheDocument(); - expect( - getByRole('cell', { - name: /5 years ago/i, - }) - ).toBeInTheDocument(); + // Check the column headers + expect(getByRole('columnheader', { name: /version/i })).toBeInTheDocument(); + expect(getByRole('columnheader', { name: /last updated/i })).toBeInTheDocument(); + + // Check the data + for (const version of versions) { + expect(getByRole('cell', { name: new RegExp(version.version, 'i') })).toBeInTheDocument(); + expect( + getByRole('cell', { name: new RegExp(dateTimeFormatTimeAgo(version.createdAt), 'i') }) + ).toBeInTheDocument(); + + // Check the latest compatible version + expect(queryByText('1.1.0 (latest compatible version)')).toBeInTheDocument(); + } }); it("should display an install button for a plugin that isn't installed", async () => { const { queryByRole } = renderPluginDetails({ id, isInstalled: false }); - await waitFor(() => expect(queryByRole('button', { name: /install/i })).toBeInTheDocument()); + await waitFor(() => expect(queryByRole('button', { name: /^install/i })).toBeInTheDocument()); + // Does not display "uninstall" button expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument(); }); @@ -218,6 +257,8 @@ describe('Plugin details page', () => { const { queryByRole } = renderPluginDetails({ id, isInstalled: true }); await waitFor(() => expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument()); + // Does not display "install" button + expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument(); }); it('should display update and uninstall buttons for a plugin with update', async () => { @@ -225,10 +266,10 @@ describe('Plugin details page', () => { // Displays an "update" button await waitFor(() => expect(queryByRole('button', { name: /update/i })).toBeInTheDocument()); - - // Does not display "install" and "uninstall" buttons - expect(queryByRole('button', { name: /install/i })).toBeInTheDocument(); expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument(); + + // Does not display "install" button + expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument(); }); it('should display an install button for enterprise plugins if license is valid', async () => { @@ -326,7 +367,7 @@ describe('Plugin details page', () => { // @ts-ignore api.uninstallPlugin = jest.fn(); - const { queryByText, queryByRole, getByRole } = renderPluginDetails({ + const { queryByText, getByRole } = renderPluginDetails({ id, name: 'Akumuli', isInstalled: true, @@ -334,11 +375,19 @@ describe('Plugin details page', () => { pluginDependencies: [], grafanaDependency: '>=8.0.0', links: [], + versions: [ + { + version: '1.0.0', + createdAt: '', + isCompatible: true, + grafanaDependency: '>=8.0.0', + }, + ], }, }); // Wait for the install controls to be loaded - await waitFor(() => expect(queryByRole('button', { name: /install/i })).toBeInTheDocument()); + await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); // Open the confirmation modal userEvent.click(getByRole('button', { name: /uninstall/i })); @@ -454,7 +503,7 @@ describe('Plugin details page', () => { it("should not display an install button for a plugin that isn't installed", async () => { const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: false }); - await waitFor(() => expect(queryByText('Overview')).toBeInTheDocument()); + await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); expect(queryByRole('button', { name: /install/i })).not.toBeInTheDocument(); }); @@ -462,7 +511,7 @@ describe('Plugin details page', () => { it('should not display an uninstall button for an already installed plugin', async () => { const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: true }); - await waitFor(() => expect(queryByText('Overview')).toBeInTheDocument()); + await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument(); }); @@ -470,7 +519,7 @@ describe('Plugin details page', () => { it('should not display update or uninstall buttons for a plugin with update', async () => { const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true }); - await waitFor(() => expect(queryByText('Overview')).toBeInTheDocument()); + await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); expect(queryByRole('button', { name: /update/i })).not.toBeInTheDocument(); expect(queryByRole('button', { name: /uninstall/i })).not.toBeInTheDocument(); @@ -480,7 +529,7 @@ describe('Plugin details page', () => { config.licenseInfo.hasValidLicense = true; const { queryByRole, queryByText } = renderPluginDetails({ id, isInstalled: false, isEnterprise: true }); - await waitFor(() => expect(queryByText('Overview')).toBeInTheDocument()); + await waitFor(() => expect(queryByText(PluginTabLabels.OVERVIEW)).toBeInTheDocument()); expect(queryByRole('button', { name: /install/i })).not.toBeInTheDocument(); }); diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index daa70e2e9f8..68a47c7ebad 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -59,10 +59,12 @@ export const fetchDetails = createAsyncThunk(`${STATE_PREFIX}/fetchDetails`, asy // We are also using the install API endpoint to update the plugin export const install = createAsyncThunk( `${STATE_PREFIX}/install`, - async ({ id, version, isUpdating = false }: { id: string; version: string; isUpdating?: boolean }, thunkApi) => { - const changes = isUpdating ? { isInstalled: true, hasUpdate: false } : { isInstalled: true }; + async ({ id, version, isUpdating = false }: { id: string; version?: string; isUpdating?: boolean }, thunkApi) => { + const changes = isUpdating + ? { isInstalled: true, installedVersion: version, hasUpdate: false } + : { isInstalled: true, installedVersion: version }; try { - await installPlugin(id, version); + await installPlugin(id); await updatePanels(); if (isUpdating) { @@ -85,7 +87,7 @@ export const uninstall = createAsyncThunk(`${STATE_PREFIX}/uninstall`, async (id return { id, - changes: { isInstalled: false }, + changes: { isInstalled: false, installedVersion: undefined }, } as Update; } catch (e) { return thunkApi.rejectWithValue('Unknown error.'); diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index 8eb340bd40b..0f7b6e2f01a 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -55,8 +55,7 @@ export const useGetSingle = (id: string): CatalogPlugin | undefined => { export const useInstall = () => { const dispatch = useDispatch(); - - return (id: string, version: string, isUpdating?: boolean) => dispatch(install({ id, version, isUpdating })); + return (id: string, version?: string, isUpdating?: boolean) => dispatch(install({ id, version, isUpdating })); }; export const useUninstall = () => { diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 9a4182ae060..9a2cace5735 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -52,7 +52,7 @@ export interface CatalogPlugin { publishedAt: string; type?: PluginType; updatedAt: string; - version: string; + installedVersion?: string; details?: CatalogPluginDetails; error?: PluginErrorCode; } @@ -146,7 +146,6 @@ export type LocalPlugin = { version: string; updated: string; }; - latestVersion: string; name: string; pinned: boolean; signature: PluginSignatureStatus; @@ -154,6 +153,7 @@ export type LocalPlugin = { signatureType: PluginSignatureType; state: string; type: PluginType; + dependencies: PluginDependencies; }; interface Rel { @@ -171,6 +171,8 @@ export interface Build { export interface Version { version: string; createdAt: string; + isCompatible: boolean; + grafanaDependency: string | null; } export interface PluginDetails { @@ -269,4 +271,6 @@ export type PluginVersion = { status: string; downloadSlug: string; links: Array<{ rel: string; href: string }>; + isCompatible: boolean; + grafanaDependency: string | null; };