Plugins: Support for link extensions (#61663)

* added extensions to plugin.json and exposing it via frontend settings.

* added extensions to the plugin.json schema.

* changing the extensions in frontend settings to a map instead of an array.

* wip

* feat(pluginregistry): begin wiring up registry

* feat(pluginextensions): prevent duplicate links and clean up

* added test case for link extensions.

* added tests and implemented the getPluginLink function.

* wip

* feat(pluginextensions): expose plugin extension registry

* fix(pluginextensions): appease the typescript gods post rename

* renamed file and will throw error if trying to call setExtensionsRegistry if trying to call it twice.

* added reafactorings.

* fixed failing test.

* minor refactorings to make sure we only include extensions if the app is enabled.

* fixed some nits.

* Update public/app/features/plugins/extensions/registry.test.ts

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Update packages/grafana-runtime/src/services/pluginExtensions/registry.ts

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Update packages/grafana-runtime/src/services/pluginExtensions/registry.ts

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Update public/app/features/plugins/extensions/registry.test.ts

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Moved types for extensions from data to runtime.

* added a small example on how you could consume link extensions.

* renamed after feedback from levi.

* updated the plugindef.cue.

* using the generated plugin def.

* added tests for apps and extensions.

* fixed linting issues.

* wip

* wip

* wip

* wip

* test(extensions): fix up failing tests

* feat(extensions): freeze registry extension arrays, include type in registry items

* added restrictions in the pugindef cue schema.

* wip

* added required fields.

* added key to uniquely identify each item.

* test(pluginextensions): align tests with implementation

* chore(schema): refresh reference.md

---------

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
This commit is contained in:
Marcus Andersson
2023-02-07 17:20:05 +01:00
committed by GitHub
parent 8a94688114
commit 1cfd3f81fb
24 changed files with 812 additions and 98 deletions

View File

@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
@@ -15,20 +16,19 @@ import (
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/plugindef"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/licensing"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/updatechecker"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.FeatureManager) (*web.Mux, *HTTPServer) {
func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.FeatureManager, pstore plugins.Store, psettings pluginSettings.Service) (*web.Mux, *HTTPServer) {
t.Helper()
db.InitTestDB(t)
cfg.IsFeatureToggleEnabled = features.IsEnabled
@@ -44,8 +44,15 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.
})
}
sqlStore := db.InitTestDB(t)
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
var pluginStore = pstore
if pluginStore == nil {
pluginStore = &plugins.FakePluginStore{}
}
var pluginsSettings = psettings
if pluginsSettings == nil {
pluginsSettings = &pluginSettings.FakePluginSettings{}
}
hs := &HTTPServer{
Cfg: cfg,
@@ -55,12 +62,12 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features *featuremgmt.
Cfg: cfg,
RendererPluginManager: &fakeRendererManager{},
},
SQLStore: sqlStore,
SQLStore: db.InitTestDB(t),
SettingsProvider: setting.ProvideProvider(cfg),
pluginStore: &plugins.FakePluginStore{},
pluginStore: pluginStore,
grafanaUpdateChecker: &updatechecker.GrafanaService{},
AccessControl: accesscontrolmock.New().WithDisabled(),
PluginSettings: pluginSettings.ProvideService(sqlStore, secretsService),
PluginSettings: pluginsSettings,
pluginsCDNService: pluginscdn.ProvideService(&config.Cfg{
PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate,
PluginSettings: cfg.PluginSettings,
@@ -91,7 +98,7 @@ func TestHTTPServer_GetFrontendSettings_hideVersionAnonymous(t *testing.T) {
cfg.BuildVersion = "7.8.9"
cfg.BuildCommit = "01234567"
m, hs := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures())
m, hs := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil)
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
@@ -182,7 +189,7 @@ func TestHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
if test.mutateCfg != nil {
test.mutateCfg(cfg)
}
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures())
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), nil, nil)
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
recorder := httptest.NewRecorder()
@@ -195,3 +202,154 @@ func TestHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
})
}
}
func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
type settings struct {
Apps map[string]*plugins.AppDTO `json:"apps"`
}
tests := []struct {
desc string
pluginStore func() plugins.Store
pluginSettings func() pluginSettings.Service
expected settings
}{
{
desc: "app without extensions",
pluginStore: func() plugins.Store {
return &plugins.FakePluginStore{
PluginList: newPlugins("test-app", nil),
}
},
pluginSettings: func() pluginSettings.Service {
return &pluginSettings.FakePluginSettings{
Plugins: newAppSettings("test-app", true),
}
},
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
ID: "test-app",
Preload: false,
Path: "/test-app/module.js",
Version: "0.5.0",
Extensions: nil,
},
},
},
},
{
desc: "enabled app with link extensions",
pluginStore: func() plugins.Store {
return &plugins.FakePluginStore{
PluginList: newPlugins("test-app", []*plugindef.ExtensionsLink{
{
Target: "core/home/menu",
Type: plugindef.ExtensionsLinkTypeLink,
Title: "Title",
Description: "Home route of app",
Path: "/home",
},
}),
}
},
pluginSettings: func() pluginSettings.Service {
return &pluginSettings.FakePluginSettings{
Plugins: newAppSettings("test-app", true),
}
},
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
ID: "test-app",
Preload: false,
Path: "/test-app/module.js",
Version: "0.5.0",
Extensions: []*plugindef.ExtensionsLink{
{
Target: "core/home/menu",
Type: plugindef.ExtensionsLinkTypeLink,
Title: "Title",
Description: "Home route of app",
Path: "/home",
},
},
},
},
},
},
{
desc: "disabled app with link extensions",
pluginStore: func() plugins.Store {
return &plugins.FakePluginStore{
PluginList: newPlugins("test-app", []*plugindef.ExtensionsLink{
{
Target: "core/home/menu",
Type: plugindef.ExtensionsLinkTypeLink,
Title: "Title",
Description: "Home route of app",
Path: "/home",
},
}),
}
},
pluginSettings: func() pluginSettings.Service {
return &pluginSettings.FakePluginSettings{
Plugins: newAppSettings("test-app", false),
}
},
expected: settings{
Apps: map[string]*plugins.AppDTO{
"test-app": {
ID: "test-app",
Preload: false,
Path: "/test-app/module.js",
Version: "0.5.0",
Extensions: nil,
},
},
},
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
cfg := setting.NewCfg()
m, _ := setupTestEnvironment(t, cfg, featuremgmt.WithFeatures(), test.pluginStore(), test.pluginSettings())
req := httptest.NewRequest(http.MethodGet, "/api/frontend/settings", nil)
recorder := httptest.NewRecorder()
m.ServeHTTP(recorder, req)
var got settings
err := json.Unmarshal(recorder.Body.Bytes(), &got)
require.NoError(t, err)
require.Equal(t, http.StatusOK, recorder.Code)
require.EqualValues(t, test.expected, got)
})
}
}
func newAppSettings(id string, enabled bool) map[string]*pluginSettings.DTO {
return map[string]*pluginSettings.DTO{
id: {
ID: 0,
OrgID: 1,
PluginID: id,
Enabled: enabled,
},
}
}
func newPlugins(id string, extensions []*plugindef.ExtensionsLink) []plugins.PluginDTO {
return []plugins.PluginDTO{
{
Module: fmt.Sprintf("/%s/module.js", id),
JSONData: plugins.JSONData{
ID: id,
Info: plugins.Info{Version: "0.5.0"},
Type: plugins.App,
Extensions: extensions,
},
},
}
}