Plugin Extensions: Require meta-data to be defined in plugin.json during development mode (#93429)
* feat: add extensions to the backend plugin model * feat: update the frontend plugin types * feat(pluginContext): return a `null` if there is no context found This will be necessary to understand if a certain hook is running inside a plugin context or not. * feat: add utility functions for checking extension configs * tests: fix failing tests due to the type updates * feat(AddedComponentsRegistry): validate plugin meta-info * feat(AddedLinksRegistry): validate meta-info * feat(ExposedComponentsRegistry): validate meta-info * feat(usePluginComponent): add meta-info validation * feat(usePluginComponents): add meta-info validation * feat(usePluginLinks): add meta-info validation * fix: only validate meta-info in registries if dev mode is enabled * tests: add unit tests for the restrictions functionality * tests: fix Go tests * fix(tests): revert accidental changes * fix: run goimports * fix: api tests * add nested app so that meta data can bested e2e tested * refactor(types): extract the ExtensionInfo into a separate type * refactor(extensions/utils): use Array.prototype.some() instead of .find() * refactor(usePluginLinks): update warning message * feat(usePluginExtensions()): validate plugin meta-info * Wip * fix(e2e): E2E tests for extensions * fix(extensions): allow multiple "/" slashes in the extension point id * fix(extensions/validators): stop validating the plugin id pattern --------- Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
This commit is contained in:
co-authored by
Erik Sundell
parent
7188c13d22
commit
6096f46774
@@ -50,6 +50,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
State: plugins.ReleaseStateAlpha,
|
||||
Backend: true,
|
||||
@@ -82,6 +91,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testData, "duplicate-plugins/nested")),
|
||||
@@ -104,6 +122,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testData, "duplicate-plugins/nested/nested")),
|
||||
@@ -150,6 +177,9 @@ func TestFinder_Find(t *testing.T) {
|
||||
{ID: "graphite", Type: "datasource", Name: "Graphite", Version: "1.0.0"},
|
||||
{ID: "graph", Type: "panel", Name: "Graph", Version: "1.0.0"},
|
||||
},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
@@ -169,6 +199,12 @@ func TestFinder_Find(t *testing.T) {
|
||||
{Name: "Nginx Panel", Type: "panel", Role: "Viewer", Action: "plugins.app:access"},
|
||||
{Name: "Nginx Datasource", Type: "datasource", Role: "Viewer", Action: "plugins.app:access"},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testData, "includes-symlinks")),
|
||||
},
|
||||
@@ -197,6 +233,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testData, "duplicate-plugins/nested")),
|
||||
@@ -219,6 +264,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
FS: mustNewStaticFSForTests(t, filepath.Join(testData, "duplicate-plugins/nested/nested")),
|
||||
@@ -241,6 +295,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
State: plugins.ReleaseStateAlpha,
|
||||
Backend: true,
|
||||
@@ -272,6 +335,15 @@ func TestFinder_Find(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
State: plugins.ReleaseStateAlpha,
|
||||
Backend: true,
|
||||
|
||||
@@ -98,6 +98,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
Category: "cloud",
|
||||
Annotations: true,
|
||||
@@ -141,6 +150,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
Executable: "test",
|
||||
Backend: true,
|
||||
@@ -195,6 +213,9 @@ func TestLoader_Load(t *testing.T) {
|
||||
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
|
||||
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
|
||||
},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
@@ -228,6 +249,12 @@ func TestLoader_Load(t *testing.T) {
|
||||
Slug: "nginx-datasource",
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
},
|
||||
Class: plugins.ClassExternal,
|
||||
Module: "public/plugins/test-app/module.js",
|
||||
@@ -266,6 +293,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
Backend: true,
|
||||
State: plugins.ReleaseStateAlpha,
|
||||
@@ -312,6 +348,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
Backend: true,
|
||||
State: plugins.ReleaseStateAlpha,
|
||||
@@ -393,11 +438,20 @@ func TestLoader_Load(t *testing.T) {
|
||||
GrafanaDependency: ">=8.0.0",
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
Extensions: plugins.ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer, Action: plugins.ActionAppAccess, Slug: "nginx-memory"},
|
||||
{Name: "Root Page (react)", Type: "page", Role: org.RoleViewer, Action: plugins.ActionAppAccess, Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
||||
},
|
||||
Extensions: plugins.Extensions{
|
||||
AddedLinks: []plugins.AddedLink{},
|
||||
AddedComponents: []plugins.AddedComponent{},
|
||||
ExposedComponents: []plugins.ExposedComponent{},
|
||||
ExtensionPoints: []plugins.ExtensionPoint{},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
||||
|
||||
+97
-3
@@ -1,6 +1,7 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -42,9 +43,100 @@ func (e DuplicateError) Is(err error) bool {
|
||||
}
|
||||
|
||||
type Dependencies struct {
|
||||
GrafanaDependency string `json:"grafanaDependency"`
|
||||
GrafanaVersion string `json:"grafanaVersion"`
|
||||
Plugins []Dependency `json:"plugins"`
|
||||
GrafanaDependency string `json:"grafanaDependency"`
|
||||
GrafanaVersion string `json:"grafanaVersion"`
|
||||
Plugins []Dependency `json:"plugins"`
|
||||
Extensions ExtensionsDependencies `json:"extensions"`
|
||||
}
|
||||
|
||||
// We need different versions for the Extensions struct because there is a now deprecated plugin.json schema out there, where the "extensions" prop
|
||||
// is in a different format (Extensions V1). In order to support those as well while reading the plugin.json, we need to add a custom unmarshaling logic for extensions.
|
||||
type ExtensionV1 struct {
|
||||
ExtensionPointID string `json:"extensionPointId"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ExtensionsV2 struct {
|
||||
AddedLinks []AddedLink `json:"addedLinks"`
|
||||
AddedComponents []AddedComponent `json:"addedComponents"`
|
||||
ExposedComponents []ExposedComponent `json:"exposedComponents"`
|
||||
ExtensionPoints []ExtensionPoint `json:"extensionPoints"`
|
||||
}
|
||||
|
||||
type Extensions ExtensionsV2
|
||||
|
||||
func (e *Extensions) UnmarshalJSON(data []byte) error {
|
||||
var err error
|
||||
var extensionsV2 ExtensionsV2
|
||||
|
||||
if err = json.Unmarshal(data, &extensionsV2); err == nil {
|
||||
e.AddedComponents = extensionsV2.AddedComponents
|
||||
e.AddedLinks = extensionsV2.AddedLinks
|
||||
e.ExposedComponents = extensionsV2.ExposedComponents
|
||||
e.ExtensionPoints = extensionsV2.ExtensionPoints
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fallback (V1)
|
||||
var extensionsV1 []ExtensionV1
|
||||
if err = json.Unmarshal(data, &extensionsV1); err == nil {
|
||||
// Trying to process old format and add them to `AddedLinks` and `AddedComponents`
|
||||
for _, extensionV1 := range extensionsV1 {
|
||||
if extensionV1.Type == "link" {
|
||||
extensionV2 := AddedLink{
|
||||
Targets: []string{extensionV1.ExtensionPointID},
|
||||
Title: extensionV1.Title,
|
||||
Description: extensionV1.Description,
|
||||
}
|
||||
e.AddedLinks = append(e.AddedLinks, extensionV2)
|
||||
}
|
||||
|
||||
if extensionV1.Type == "component" {
|
||||
extensionV2 := AddedComponent{
|
||||
Targets: []string{extensionV1.ExtensionPointID},
|
||||
Title: extensionV1.Title,
|
||||
Description: extensionV1.Description,
|
||||
}
|
||||
|
||||
e.AddedComponents = append(e.AddedComponents, extensionV2)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type AddedLink struct {
|
||||
Targets []string `json:"targets"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type AddedComponent struct {
|
||||
Targets []string `json:"targets"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ExposedComponent struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ExtensionPoint struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type ExtensionsDependencies struct {
|
||||
ExposedComponents []string `json:"exposedComponents"`
|
||||
}
|
||||
|
||||
type Includes struct {
|
||||
@@ -231,6 +323,8 @@ type AppDTO struct {
|
||||
Preload bool `json:"preload"`
|
||||
Angular AngularMeta `json:"angular"`
|
||||
LoadingStrategy LoadingStrategy `json:"loadingStrategy"`
|
||||
Extensions Extensions `json:"extensions"`
|
||||
Dependencies Dependencies `json:"dependencies"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
+22
-1
@@ -109,7 +109,8 @@ type JSONData struct {
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
|
||||
// App settings
|
||||
AutoEnabled bool `json:"autoEnabled"`
|
||||
AutoEnabled bool `json:"autoEnabled"`
|
||||
Extensions Extensions `json:"extensions"`
|
||||
|
||||
// Datasource settings
|
||||
Annotations bool `json:"annotations"`
|
||||
@@ -173,6 +174,26 @@ func ReadPluginJSON(reader io.Reader) (JSONData, error) {
|
||||
plugin.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
if len(plugin.Dependencies.Extensions.ExposedComponents) == 0 {
|
||||
plugin.Dependencies.Extensions.ExposedComponents = make([]string, 0)
|
||||
}
|
||||
|
||||
if plugin.Extensions.AddedLinks == nil {
|
||||
plugin.Extensions.AddedLinks = []AddedLink{}
|
||||
}
|
||||
|
||||
if plugin.Extensions.AddedComponents == nil {
|
||||
plugin.Extensions.AddedComponents = []AddedComponent{}
|
||||
}
|
||||
|
||||
if plugin.Extensions.ExposedComponents == nil {
|
||||
plugin.Extensions.ExposedComponents = []ExposedComponent{}
|
||||
}
|
||||
|
||||
if plugin.Extensions.ExtensionPoints == nil {
|
||||
plugin.Extensions.ExtensionPoints = []ExtensionPoint{}
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = org.RoleViewer
|
||||
|
||||
@@ -52,13 +52,25 @@ func Test_ReadPluginJSON(t *testing.T) {
|
||||
Updated: "2015-02-10",
|
||||
Keywords: []string{"test"},
|
||||
},
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "3.x.x",
|
||||
Plugins: []Dependency{
|
||||
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
|
||||
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
|
||||
},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
|
||||
Includes: []*Includes{
|
||||
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: org.RoleViewer, Action: ActionAppAccess},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer, Action: ActionAppAccess},
|
||||
@@ -94,10 +106,22 @@ func Test_ReadPluginJSON(t *testing.T) {
|
||||
ID: "grafana-piechart-panel",
|
||||
Type: TypePanel,
|
||||
Name: "Pie Chart (old)",
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
|
||||
Includes: []*Includes{
|
||||
{Name: "Pie Charts", Path: "dashboards/demo.json", Type: "dashboard", Role: org.RoleViewer},
|
||||
},
|
||||
@@ -117,10 +141,21 @@ func Test_ReadPluginJSON(t *testing.T) {
|
||||
ID: "grafana-pyroscope-datasource",
|
||||
AliasIDs: []string{"phlare"}, // Hardcoded from the parser
|
||||
Type: TypeDataSource,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaDependency: "",
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -142,18 +177,257 @@ func Test_ReadPluginJSON(t *testing.T) {
|
||||
Dependencies: Dependencies{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can read the latest versions of extensions information (v2)",
|
||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||
pJSON := `{
|
||||
"id": "myorg-extensions-app",
|
||||
"name": "Extensions App",
|
||||
"type": "app",
|
||||
"extensions": {
|
||||
"addedLinks": [
|
||||
{
|
||||
"title": "Added link 1",
|
||||
"description": "Added link 1 description",
|
||||
"targets": ["grafana/dashboard/panel/menu"]
|
||||
}
|
||||
],
|
||||
"addedComponents": [
|
||||
{
|
||||
"title": "Added component 1",
|
||||
"description": "Added component 1 description",
|
||||
"targets": ["grafana/user/profile/tab"]
|
||||
}
|
||||
],
|
||||
"exposedComponents": [
|
||||
{
|
||||
"title": "Exposed component 1",
|
||||
"description": "Exposed component 1 description",
|
||||
"id": "myorg-extensions-app/component-1/v1"
|
||||
}
|
||||
],
|
||||
"extensionPoints": [
|
||||
{
|
||||
"title": "Extension point 1",
|
||||
"description": "Extension points 1 description",
|
||||
"id": "myorg-extensions-app/extensions-point-1/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
return io.NopCloser(strings.NewReader(pJSON))
|
||||
},
|
||||
expected: JSONData{
|
||||
ID: "myorg-extensions-app",
|
||||
Name: "Extensions App",
|
||||
Type: TypeApp,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{
|
||||
{Title: "Added link 1", Description: "Added link 1 description", Targets: []string{"grafana/dashboard/panel/menu"}},
|
||||
},
|
||||
AddedComponents: []AddedComponent{
|
||||
{Title: "Added component 1", Description: "Added component 1 description", Targets: []string{"grafana/user/profile/tab"}},
|
||||
},
|
||||
ExposedComponents: []ExposedComponent{
|
||||
{Id: "myorg-extensions-app/component-1/v1", Title: "Exposed component 1", Description: "Exposed component 1 description"},
|
||||
},
|
||||
ExtensionPoints: []ExtensionPoint{
|
||||
{Id: "myorg-extensions-app/extensions-point-1/v1", Title: "Extension point 1", Description: "Extension points 1 description"},
|
||||
},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can read deprecated extensions info (v1) and parse it as v2",
|
||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||
pJSON := `{
|
||||
"id": "myorg-extensions-app",
|
||||
"name": "Extensions App",
|
||||
"type": "app",
|
||||
"extensions": [
|
||||
{
|
||||
"extensionPointId": "grafana/dashboard/panel/menu",
|
||||
"title": "Added link 1",
|
||||
"description": "Added link 1 description",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"extensionPointId": "grafana/dashboard/panel/menu",
|
||||
"title": "Added link 2",
|
||||
"description": "Added link 2 description",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"extensionPointId": "grafana/user/profile/tab",
|
||||
"title": "Added component 1",
|
||||
"description": "Added component 1 description",
|
||||
"type": "component"
|
||||
}
|
||||
]
|
||||
}`
|
||||
return io.NopCloser(strings.NewReader(pJSON))
|
||||
},
|
||||
expected: JSONData{
|
||||
ID: "myorg-extensions-app",
|
||||
Name: "Extensions App",
|
||||
Type: TypeApp,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{
|
||||
{Title: "Added link 1", Description: "Added link 1 description", Targets: []string{"grafana/dashboard/panel/menu"}},
|
||||
{Title: "Added link 2", Description: "Added link 2 description", Targets: []string{"grafana/dashboard/panel/menu"}},
|
||||
},
|
||||
AddedComponents: []AddedComponent{
|
||||
{Title: "Added component 1", Description: "Added component 1 description", Targets: []string{"grafana/user/profile/tab"}},
|
||||
},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "works if extensions info is empty",
|
||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||
pJSON := `{
|
||||
"id": "myorg-extensions-app",
|
||||
"name": "Extensions App",
|
||||
"type": "app",
|
||||
"extensions": []
|
||||
}`
|
||||
return io.NopCloser(strings.NewReader(pJSON))
|
||||
},
|
||||
expected: JSONData{
|
||||
ID: "myorg-extensions-app",
|
||||
Name: "Extensions App",
|
||||
Type: TypeApp,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "works if extensions info is completely missing",
|
||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||
pJSON := `{
|
||||
"id": "myorg-extensions-app",
|
||||
"name": "Extensions App",
|
||||
"type": "app"
|
||||
}`
|
||||
return io.NopCloser(strings.NewReader(pJSON))
|
||||
},
|
||||
expected: JSONData{
|
||||
ID: "myorg-extensions-app",
|
||||
Name: "Extensions App",
|
||||
Type: TypeApp,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can read extensions related dependencies",
|
||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||
pJSON := `{
|
||||
"id": "myorg-extensions-app",
|
||||
"name": "Extensions App",
|
||||
"type": "app",
|
||||
"dependencies": {
|
||||
"grafanaDependency": "10.0.0",
|
||||
"extensions": {
|
||||
"exposedComponents": ["myorg-extensions-app/component-1/v1"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
return io.NopCloser(strings.NewReader(pJSON))
|
||||
},
|
||||
expected: JSONData{
|
||||
ID: "myorg-extensions-app",
|
||||
Name: "Extensions App",
|
||||
Type: TypeApp,
|
||||
|
||||
Extensions: Extensions{
|
||||
AddedLinks: []AddedLink{},
|
||||
AddedComponents: []AddedComponent{},
|
||||
ExposedComponents: []ExposedComponent{},
|
||||
ExtensionPoints: []ExtensionPoint{},
|
||||
},
|
||||
|
||||
Dependencies: Dependencies{
|
||||
GrafanaVersion: "*",
|
||||
GrafanaDependency: "10.0.0",
|
||||
Plugins: []Dependency{},
|
||||
Extensions: ExtensionsDependencies{
|
||||
ExposedComponents: []string{"myorg-extensions-app/component-1/v1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := tt.pluginJSON(t)
|
||||
got, err := ReadPluginJSON(p)
|
||||
|
||||
// Check if the test returns the same error as expected
|
||||
// (unneccary to check further if there is an error at this point)
|
||||
if tt.err == nil && err != nil {
|
||||
t.Errorf("Error while reading pluginJSON: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the test returns the same error as expected
|
||||
if tt.err != nil {
|
||||
require.ErrorIs(t, err, tt.err)
|
||||
}
|
||||
|
||||
// Check if the test returns the expected pluginJSONData
|
||||
if !cmp.Equal(got, tt.expected) {
|
||||
t.Errorf("Unexpected pluginJSONData: %v", cmp.Diff(got, tt.expected))
|
||||
}
|
||||
|
||||
// Should be able to close the reader
|
||||
require.NoError(t, p.Close())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user