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:
Levente Balogh
2024-10-04 08:41:26 +02:00
committed by GitHub
parent 7188c13d22
commit 6096f46774
53 changed files with 3197 additions and 243 deletions

View File

@ -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 (