mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 14:42:16 +08:00
Plugins: Support changing plugin IDs (hardcoded) (#67867)
This commit is contained in:
@ -58,6 +58,7 @@ export interface PluginMeta<T extends KeyValue = {}> {
|
|||||||
info: PluginMetaInfo;
|
info: PluginMetaInfo;
|
||||||
includes?: PluginInclude[];
|
includes?: PluginInclude[];
|
||||||
state?: PluginState;
|
state?: PluginState;
|
||||||
|
alias?: string;
|
||||||
|
|
||||||
// System.load & relative URLS
|
// System.load & relative URLS
|
||||||
module: string;
|
module: string;
|
||||||
|
@ -77,6 +77,7 @@ func (hs *HTTPServer) GetDataSources(c *contextmodel.ReqContext) response.Respon
|
|||||||
if plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type); exists {
|
if plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type); exists {
|
||||||
dsItem.TypeLogoUrl = plugin.Info.Logos.Small
|
dsItem.TypeLogoUrl = plugin.Info.Logos.Small
|
||||||
dsItem.TypeName = plugin.Name
|
dsItem.TypeName = plugin.Name
|
||||||
|
dsItem.Type = plugin.ID // may be from an alias
|
||||||
} else {
|
} else {
|
||||||
dsItem.TypeLogoUrl = "public/img/icn-datasource.svg"
|
dsItem.TypeLogoUrl = "public/img/icn-datasource.svg"
|
||||||
}
|
}
|
||||||
@ -730,6 +731,15 @@ func (hs *HTTPServer) convertModelToDtos(ctx context.Context, ds *datasources.Da
|
|||||||
ReadOnly: ds.ReadOnly,
|
ReadOnly: ds.ReadOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hs.pluginStore != nil {
|
||||||
|
if plugin, exists := hs.pluginStore.Plugin(ctx, ds.Type); exists {
|
||||||
|
dto.TypeLogoUrl = plugin.Info.Logos.Small
|
||||||
|
dto.Type = plugin.ID // may be from an alias
|
||||||
|
} else {
|
||||||
|
dto.TypeLogoUrl = "public/img/icn-datasource.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
secrets, err := hs.DataSourcesService.DecryptedValues(ctx, ds)
|
secrets, err := hs.DataSourcesService.DecryptedValues(ctx, ds)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for k, v := range secrets {
|
for k, v := range secrets {
|
||||||
|
@ -71,6 +71,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||||||
panels[panel.ID] = plugins.PanelDTO{
|
panels[panel.ID] = plugins.PanelDTO{
|
||||||
ID: panel.ID,
|
ID: panel.ID,
|
||||||
Name: panel.Name,
|
Name: panel.Name,
|
||||||
|
Alias: panel.Alias,
|
||||||
Info: panel.Info,
|
Info: panel.Info,
|
||||||
Module: panel.Module,
|
Module: panel.Module,
|
||||||
BaseURL: panel.BaseURL,
|
BaseURL: panel.BaseURL,
|
||||||
@ -318,6 +319,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
plugin := ap.Plugin
|
plugin := ap.Plugin
|
||||||
|
dsDTO.Type = plugin.ID
|
||||||
dsDTO.Preload = plugin.Preload
|
dsDTO.Preload = plugin.Preload
|
||||||
dsDTO.Module = plugin.Module
|
dsDTO.Module = plugin.Module
|
||||||
dsDTO.PluginMeta = &plugins.PluginMetaDTO{
|
dsDTO.PluginMeta = &plugins.PluginMetaDTO{
|
||||||
@ -479,10 +481,15 @@ type availablePluginDTO struct {
|
|||||||
type AvailablePlugins map[plugins.Type]map[string]*availablePluginDTO
|
type AvailablePlugins map[plugins.Type]map[string]*availablePluginDTO
|
||||||
|
|
||||||
func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*availablePluginDTO, bool) {
|
func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*availablePluginDTO, bool) {
|
||||||
if _, exists := ap[pluginType][pluginID]; exists {
|
p, exists := ap[pluginType][pluginID]
|
||||||
return ap[pluginType][pluginID], true
|
if exists {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
for _, p = range ap[pluginType] {
|
||||||
|
if p.Plugin.ID == pluginID || p.Plugin.Alias == pluginID {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +138,14 @@ func (l *Loader) loadPlugins(ctx context.Context, src plugins.PluginSource, foun
|
|||||||
// clear plugin error if a pre-existing error has since been resolved
|
// clear plugin error if a pre-existing error has since been resolved
|
||||||
delete(l.errs, plugin.ID)
|
delete(l.errs, plugin.ID)
|
||||||
|
|
||||||
|
// Hardcoded alias changes
|
||||||
|
switch plugin.ID {
|
||||||
|
case "grafana-pyroscope": // rebranding
|
||||||
|
plugin.Alias = "phlare"
|
||||||
|
case "debug": // panel plugin used for testing
|
||||||
|
plugin.Alias = "debugX"
|
||||||
|
}
|
||||||
|
|
||||||
// verify module.js exists for SystemJS to load.
|
// verify module.js exists for SystemJS to load.
|
||||||
// CDN plugins can be loaded with plugin.json only, so do not warn for those.
|
// CDN plugins can be loaded with plugin.json only, so do not warn for those.
|
||||||
if !plugin.IsRenderer() && !plugin.IsCorePlugin() {
|
if !plugin.IsRenderer() && !plugin.IsCorePlugin() {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type InMemory struct {
|
type InMemory struct {
|
||||||
store map[string]*plugins.Plugin
|
store map[string]*plugins.Plugin
|
||||||
|
alias map[string]*plugins.Plugin
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ func ProvideService() *InMemory {
|
|||||||
func NewInMemory() *InMemory {
|
func NewInMemory() *InMemory {
|
||||||
return &InMemory{
|
return &InMemory{
|
||||||
store: make(map[string]*plugins.Plugin),
|
store: make(map[string]*plugins.Plugin),
|
||||||
|
alias: make(map[string]*plugins.Plugin),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,18 +48,25 @@ func (i *InMemory) Add(_ context.Context, p *plugins.Plugin) error {
|
|||||||
|
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
i.store[p.ID] = p
|
i.store[p.ID] = p
|
||||||
|
if p.Alias != "" {
|
||||||
|
i.alias[p.Alias] = p
|
||||||
|
}
|
||||||
i.mu.Unlock()
|
i.mu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InMemory) Remove(_ context.Context, pluginID string) error {
|
func (i *InMemory) Remove(_ context.Context, pluginID string) error {
|
||||||
if !i.isRegistered(pluginID) {
|
p, ok := i.plugin(pluginID)
|
||||||
|
if !ok {
|
||||||
return fmt.Errorf("plugin %s is not registered", pluginID)
|
return fmt.Errorf("plugin %s is not registered", pluginID)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
delete(i.store, pluginID)
|
delete(i.store, pluginID)
|
||||||
|
if p != nil && p.Alias != "" {
|
||||||
|
delete(i.alias, p.Alias)
|
||||||
|
}
|
||||||
i.mu.Unlock()
|
i.mu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -69,13 +78,18 @@ func (i *InMemory) plugin(pluginID string) (*plugins.Plugin, bool) {
|
|||||||
p, exists := i.store[pluginID]
|
p, exists := i.store[pluginID]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, false
|
p, exists = i.alias[pluginID]
|
||||||
|
if !exists {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, true
|
return p, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InMemory) isRegistered(pluginID string) bool {
|
func (i *InMemory) isRegistered(pluginID string) bool {
|
||||||
_, exists := i.plugin(pluginID)
|
p, exists := i.plugin(pluginID)
|
||||||
return exists
|
|
||||||
|
// This may have matched based on an alias
|
||||||
|
return exists && p.ID == pluginID
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,7 @@ const pluginID = "test-ds"
|
|||||||
|
|
||||||
func TestInMemory(t *testing.T) {
|
func TestInMemory(t *testing.T) {
|
||||||
t.Run("Test mix of registry operations", func(t *testing.T) {
|
t.Run("Test mix of registry operations", func(t *testing.T) {
|
||||||
i := &InMemory{
|
i := NewInMemory()
|
||||||
store: map[string]*plugins.Plugin{},
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
p, exists := i.Plugin(ctx, pluginID)
|
p, exists := i.Plugin(ctx, pluginID)
|
||||||
@ -272,3 +270,45 @@ func TestInMemory_Remove(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAliasSupport(t *testing.T) {
|
||||||
|
t.Run("Test alias operations", func(t *testing.T) {
|
||||||
|
i := NewInMemory()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pluginIdNew := "plugin-new"
|
||||||
|
pluginIdOld := "plugin-old"
|
||||||
|
|
||||||
|
p, exists := i.Plugin(ctx, pluginIdNew)
|
||||||
|
require.False(t, exists)
|
||||||
|
require.Nil(t, p)
|
||||||
|
|
||||||
|
pluginNew := &plugins.Plugin{
|
||||||
|
JSONData: plugins.JSONData{
|
||||||
|
ID: pluginIdNew,
|
||||||
|
},
|
||||||
|
Alias: pluginIdOld, // TODO: move to JSONData
|
||||||
|
}
|
||||||
|
err := i.Add(ctx, pluginNew)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Can lookup by the new ID
|
||||||
|
found, exists := i.Plugin(ctx, pluginIdNew)
|
||||||
|
require.True(t, exists)
|
||||||
|
require.Equal(t, pluginNew, found)
|
||||||
|
|
||||||
|
// Can lookup by the old ID
|
||||||
|
found, exists = i.Plugin(ctx, pluginIdOld)
|
||||||
|
require.True(t, exists)
|
||||||
|
require.Equal(t, pluginNew, found)
|
||||||
|
|
||||||
|
// Register the old plugin and look it up
|
||||||
|
pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{
|
||||||
|
ID: pluginIdOld,
|
||||||
|
}}
|
||||||
|
require.NoError(t, i.Add(ctx, pluginOld))
|
||||||
|
found, exists = i.Plugin(ctx, pluginIdOld)
|
||||||
|
require.True(t, exists)
|
||||||
|
require.Equal(t, pluginOld, found)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -244,6 +244,7 @@ type DataSourceDTO struct {
|
|||||||
type PanelDTO struct {
|
type PanelDTO struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
Info Info `json:"info"`
|
Info Info `json:"info"`
|
||||||
HideFromList bool `json:"hideFromList"`
|
HideFromList bool `json:"hideFromList"`
|
||||||
Sort int `json:"sort"`
|
Sort int `json:"sort"`
|
||||||
|
@ -57,6 +57,9 @@ type Plugin struct {
|
|||||||
SecretsManager secretsmanagerplugin.SecretsManagerPlugin
|
SecretsManager secretsmanagerplugin.SecretsManagerPlugin
|
||||||
client backendplugin.Plugin
|
client backendplugin.Plugin
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
||||||
|
// This will be moved to plugin.json when we have general support in gcom
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginDTO struct {
|
type PluginDTO struct {
|
||||||
@ -84,6 +87,9 @@ type PluginDTO struct {
|
|||||||
BaseURL string
|
BaseURL string
|
||||||
|
|
||||||
AngularDetected bool
|
AngularDetected bool
|
||||||
|
|
||||||
|
// This will be moved to plugin.json when we have general support in gcom
|
||||||
|
Alias string `json:"alias,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PluginDTO) SupportsStreaming() bool {
|
func (p PluginDTO) SupportsStreaming() bool {
|
||||||
@ -155,7 +161,9 @@ func ReadPluginJSON(reader io.Reader) (JSONData, error) {
|
|||||||
return JSONData{}, err
|
return JSONData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin.ID == "grafana-piechart-panel" {
|
// Hardcoded changes
|
||||||
|
switch plugin.ID {
|
||||||
|
case "grafana-piechart-panel":
|
||||||
plugin.Name = "Pie Chart (old)"
|
plugin.Name = "Pie Chart (old)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,13 +14,16 @@ export function importPanelPlugin(id: string): Promise<PanelPlugin> {
|
|||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = config.panels[id];
|
const meta = config.panels[id] || Object.values(config.panels).find((p) => p.alias === id);
|
||||||
|
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
throw new Error(`Plugin ${id} not found`);
|
throw new Error(`Plugin ${id} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
promiseCache[id] = getPanelPlugin(meta);
|
promiseCache[id] = getPanelPlugin(meta);
|
||||||
|
if (id !== meta.type) {
|
||||||
|
promiseCache[meta.type] = promiseCache[id];
|
||||||
|
}
|
||||||
|
|
||||||
return promiseCache[id];
|
return promiseCache[id];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user