move loading strategy into pkg/plugins

This commit is contained in:
Will Browne
2026-01-13 15:46:34 +00:00
parent f5aa39cc27
commit ae06690681
18 changed files with 208 additions and 247 deletions
@@ -0,0 +1,67 @@
package pluginassets
import (
"github.com/Masterminds/semver/v3"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
)
const (
CreatePluginVersionCfgKey = "create_plugin_version"
CreatePluginVersionScriptSupportEnabled = "4.15.0"
)
var (
scriptLoadingMinSupportedVersion = semver.MustParse(CreatePluginVersionScriptSupportEnabled)
)
// CalculateLoadingStrategy calculates the loading strategy for a plugin.
// If a plugin has plugin setting `create_plugin_version` >= 4.15.0, set loadingStrategy to "script".
// If a plugin is not loaded via the CDN and is not Angular, set loadingStrategy to "script".
// Otherwise, set loadingStrategy to "fetch".
func CalculateLoadingStrategy(p *plugins.Plugin, cfg *config.PluginManagementCfg, cdn *pluginscdn.Service) plugins.LoadingStrategy {
if cfg != nil && cfg.PluginSettings != nil {
if pCfg, ok := cfg.PluginSettings[p.ID]; ok {
if compatibleCreatePluginVersion(pCfg) {
return plugins.LoadingStrategyScript
}
}
// If the plugin has a parent
if p.Parent != nil {
// Check the parent's create_plugin_version setting
if pCfg, ok := cfg.PluginSettings[p.Parent.ID]; ok {
if compatibleCreatePluginVersion(pCfg) {
return plugins.LoadingStrategyScript
}
}
// Since the parent plugin is not explicitly configured as script loading compatible,
// If the plugin is either loaded from the CDN (via its parent) or contains Angular, we should use fetch
if cdnEnabled(p.Parent, cdn) || p.Angular.Detected {
return plugins.LoadingStrategyFetch
}
}
}
if !cdnEnabled(p, cdn) && !p.Angular.Detected {
return plugins.LoadingStrategyScript
}
return plugins.LoadingStrategyFetch
}
// compatibleCreatePluginVersion checks if the create_plugin_version setting is >= 4.15.0
func compatibleCreatePluginVersion(ps map[string]string) bool {
if cpv, ok := ps[CreatePluginVersionCfgKey]; ok {
createPluginVer, err := semver.NewVersion(cpv)
if err != nil {
// Invalid semver, treat as incompatible
return false
}
return !createPluginVer.LessThan(scriptLoadingMinSupportedVersion)
}
return false
}
@@ -0,0 +1,216 @@
package pluginassets
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
)
// cdnFS is a simple mock FS that returns CDN type
type cdnFS struct {
plugins.FS
}
func (f *cdnFS) Type() plugins.FSType {
return plugins.FSTypeCDN
}
func TestCalculateLoadingStrategy(t *testing.T) {
const pluginID = "grafana-test-datasource"
const (
incompatVersion = "4.14.0"
compatVersion = CreatePluginVersionScriptSupportEnabled
futureVersion = "5.0.0"
)
tcs := []struct {
name string
pluginSettings config.PluginSettings
plugin *plugins.Plugin
expected plugins.LoadingStrategy
}{
{
name: "Expected LoadingStrategyScript when create-plugin version is compatible and plugin is not angular",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: compatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false)),
expected: plugins.LoadingStrategyScript,
},
{
name: "Expected LoadingStrategyScript when parent create-plugin version is compatible and plugin is not angular",
pluginSettings: newPluginSettings("parent-datasource", map[string]string{
CreatePluginVersionCfgKey: compatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), func(p *plugins.Plugin) {
p.Parent = &plugins.Plugin{
JSONData: plugins.JSONData{ID: "parent-datasource"},
FS: plugins.NewFakeFS(),
}
}),
expected: plugins.LoadingStrategyScript,
},
{
name: "Expected LoadingStrategyScript when create-plugin version is future compatible and plugin is not angular",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: futureVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyScript,
},
{
name: "Expected LoadingStrategyScript when create-plugin version is not provided, plugin is not angular and is not configured as CDN enabled",
pluginSettings: newPluginSettings(pluginID, map[string]string{}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyScript,
},
{
name: "Expected LoadingStrategyScript when create-plugin version is not compatible, plugin is not angular, is not configured as CDN enabled and does not have a CDN fs",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: incompatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withClassForLoadingStrategy(plugins.ClassExternal), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyScript,
},
{
name: "Expected LoadingStrategyFetch when parent create-plugin version is not set, is configured as CDN enabled and plugin is not angular",
pluginSettings: config.PluginSettings{
"parent-datasource": {
"cdn": "true",
},
},
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), func(p *plugins.Plugin) {
p.Parent = &plugins.Plugin{
JSONData: plugins.JSONData{ID: "parent-datasource"},
FS: plugins.NewFakeFS(),
}
}),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when parent create-plugin version is not set, is configured as CDN enabled and plugin is angular",
pluginSettings: config.PluginSettings{
"parent-datasource": {
"cdn": "true",
},
},
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(true), func(p *plugins.Plugin) {
p.Parent = &plugins.Plugin{
JSONData: plugins.JSONData{ID: "parent-datasource"},
FS: plugins.NewFakeFS(),
}
}),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when parent create-plugin version is not set, is not configured as CDN enabled and plugin is angular",
pluginSettings: config.PluginSettings{},
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(true), withFSForLoadingStrategy(plugins.NewFakeFS()), func(p *plugins.Plugin) {
p.Parent = &plugins.Plugin{
JSONData: plugins.JSONData{ID: "parent-datasource"},
FS: plugins.NewFakeFS(),
}
}),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular, and plugin is configured as CDN enabled",
pluginSettings: newPluginSettings(pluginID, map[string]string{
"cdn": "true",
CreatePluginVersionCfgKey: incompatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withClassForLoadingStrategy(plugins.ClassExternal), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible and plugin is angular",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: incompatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(true), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular and plugin is configured as CDN enabled",
pluginSettings: newPluginSettings(pluginID, map[string]string{
"cdn": "true",
CreatePluginVersionCfgKey: incompatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyFetch when create-plugin version is not compatible, plugin is not angular and has a CDN fs",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: incompatVersion,
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withFSForLoadingStrategy(
&cdnFS{FS: plugins.NewFakeFS()},
)),
expected: plugins.LoadingStrategyFetch,
},
{
name: "Expected LoadingStrategyScript when plugin setting create-plugin version is badly formatted, plugin is not configured as CDN enabled and does not have a CDN fs",
pluginSettings: newPluginSettings(pluginID, map[string]string{
CreatePluginVersionCfgKey: "invalidSemver",
}),
plugin: newPluginForLoadingStrategy(pluginID, withAngularForLoadingStrategy(false), withFSForLoadingStrategy(plugins.NewFakeFS())),
expected: plugins.LoadingStrategyScript,
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
cfg := &config.PluginManagementCfg{
PluginSettings: tc.pluginSettings,
}
cdn := pluginscdn.ProvideService(&config.PluginManagementCfg{
PluginsCDNURLTemplate: "http://cdn.example.com", // required for cdn.PluginSupported check
PluginSettings: tc.pluginSettings,
})
got := CalculateLoadingStrategy(tc.plugin, cfg, cdn)
assert.Equal(t, tc.expected, got, "unexpected loading strategy")
})
}
}
func newPluginForLoadingStrategy(pluginID string, cbs ...func(*plugins.Plugin)) *plugins.Plugin {
p := &plugins.Plugin{
JSONData: plugins.JSONData{
ID: pluginID,
},
}
for _, cb := range cbs {
cb(p)
}
return p
}
func withAngularForLoadingStrategy(angular bool) func(*plugins.Plugin) {
return func(p *plugins.Plugin) {
p.Angular = plugins.AngularMeta{Detected: angular}
}
}
func withFSForLoadingStrategy(fs plugins.FS) func(*plugins.Plugin) {
return func(p *plugins.Plugin) {
p.FS = fs
}
}
func withClassForLoadingStrategy(class plugins.Class) func(*plugins.Plugin) {
return func(p *plugins.Plugin) {
p.Class = class
}
}
func newPluginSettings(pluginID string, kv map[string]string) config.PluginSettings {
return config.PluginSettings{
pluginID: kv,
}
}