mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 22:22:25 +08:00
Plugins: Move alias support to plugin json (but still hardcoded) (#75129)
This commit is contained in:
@ -58,7 +58,7 @@ export interface PluginMeta<T extends KeyValue = {}> {
|
|||||||
info: PluginMetaInfo;
|
info: PluginMetaInfo;
|
||||||
includes?: PluginInclude[];
|
includes?: PluginInclude[];
|
||||||
state?: PluginState;
|
state?: PluginState;
|
||||||
alias?: string;
|
aliasIDs?: string[];
|
||||||
|
|
||||||
// System.load & relative URLS
|
// System.load & relative URLS
|
||||||
module: string;
|
module: string;
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) {
|
func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) {
|
||||||
@ -72,7 +73,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,
|
AliasIDs: panel.AliasIDs,
|
||||||
Info: panel.Info,
|
Info: panel.Info,
|
||||||
Module: panel.Module,
|
Module: panel.Module,
|
||||||
BaseURL: panel.BaseURL,
|
BaseURL: panel.BaseURL,
|
||||||
@ -500,7 +501,7 @@ func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*avail
|
|||||||
return p, true
|
return p, true
|
||||||
}
|
}
|
||||||
for _, p = range ap[pluginType] {
|
for _, p = range ap[pluginType] {
|
||||||
if p.Plugin.ID == pluginID || p.Plugin.Alias == pluginID {
|
if p.Plugin.ID == pluginID || slices.Contains(p.Plugin.AliasIDs, pluginID) {
|
||||||
return p, true
|
return p, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ func DefaultConstructFunc(signatureCalculator plugins.SignatureCalculator, asset
|
|||||||
// DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage.
|
// DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage.
|
||||||
func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc {
|
func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc {
|
||||||
return []DecorateFunc{
|
return []DecorateFunc{
|
||||||
AliasDecorateFunc,
|
|
||||||
AppDefaultNavURLDecorateFunc,
|
AppDefaultNavURLDecorateFunc,
|
||||||
AppChildDecorateFunc(cfg),
|
AppChildDecorateFunc(cfg),
|
||||||
}
|
}
|
||||||
@ -79,19 +78,6 @@ func (c *DefaultConstructor) Construct(ctx context.Context, src plugins.PluginSo
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AliasDecorateFunc is a DecorateFunc that sets the alias for the plugin.
|
|
||||||
func AliasDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
|
||||||
switch p.ID {
|
|
||||||
case "grafana-pyroscope-datasource": // rebranding
|
|
||||||
p.Alias = "phlare"
|
|
||||||
case "grafana-testdata-datasource":
|
|
||||||
p.Alias = "testdata"
|
|
||||||
case "debug": // panel plugin used for testing
|
|
||||||
p.Alias = "debugX"
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppDefaultNavURLDecorateFunc is a DecorateFunc that sets the default nav URL for app plugins.
|
// AppDefaultNavURLDecorateFunc is a DecorateFunc that sets the default nav URL for app plugins.
|
||||||
func AppDefaultNavURLDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
func AppDefaultNavURLDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
||||||
if p.IsApp() {
|
if p.IsApp() {
|
||||||
|
@ -48,8 +48,8 @@ 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 != "" {
|
for _, a := range p.AliasIDs {
|
||||||
i.alias[p.Alias] = p
|
i.alias[a] = p
|
||||||
}
|
}
|
||||||
i.mu.Unlock()
|
i.mu.Unlock()
|
||||||
|
|
||||||
@ -64,8 +64,10 @@ func (i *InMemory) Remove(_ context.Context, pluginID string) error {
|
|||||||
|
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
delete(i.store, pluginID)
|
delete(i.store, pluginID)
|
||||||
if p != nil && p.Alias != "" {
|
if p != nil {
|
||||||
delete(i.alias, p.Alias)
|
for _, a := range p.AliasIDs {
|
||||||
|
delete(i.alias, a)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i.mu.Unlock()
|
i.mu.Unlock()
|
||||||
|
|
||||||
|
@ -278,6 +278,7 @@ func TestAliasSupport(t *testing.T) {
|
|||||||
|
|
||||||
pluginIdNew := "plugin-new"
|
pluginIdNew := "plugin-new"
|
||||||
pluginIdOld := "plugin-old"
|
pluginIdOld := "plugin-old"
|
||||||
|
pluginIdOld2 := "plugin-old2"
|
||||||
|
|
||||||
p, exists := i.Plugin(ctx, pluginIdNew)
|
p, exists := i.Plugin(ctx, pluginIdNew)
|
||||||
require.False(t, exists)
|
require.False(t, exists)
|
||||||
@ -285,9 +286,9 @@ func TestAliasSupport(t *testing.T) {
|
|||||||
|
|
||||||
pluginNew := &plugins.Plugin{
|
pluginNew := &plugins.Plugin{
|
||||||
JSONData: plugins.JSONData{
|
JSONData: plugins.JSONData{
|
||||||
ID: pluginIdNew,
|
ID: pluginIdNew,
|
||||||
|
AliasIDs: []string{pluginIdOld, pluginIdOld2},
|
||||||
},
|
},
|
||||||
Alias: pluginIdOld, // TODO: move to JSONData
|
|
||||||
}
|
}
|
||||||
err := i.Add(ctx, pluginNew)
|
err := i.Add(ctx, pluginNew)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -302,6 +303,11 @@ func TestAliasSupport(t *testing.T) {
|
|||||||
require.True(t, exists)
|
require.True(t, exists)
|
||||||
require.Equal(t, pluginNew, found)
|
require.Equal(t, pluginNew, found)
|
||||||
|
|
||||||
|
// Can lookup by the other old ID
|
||||||
|
found, exists = i.Plugin(ctx, pluginIdOld2)
|
||||||
|
require.True(t, exists)
|
||||||
|
require.Equal(t, pluginNew, found)
|
||||||
|
|
||||||
// Register the old plugin and look it up
|
// Register the old plugin and look it up
|
||||||
pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{
|
pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{
|
||||||
ID: pluginIdOld,
|
ID: pluginIdOld,
|
||||||
|
@ -243,18 +243,18 @@ 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"`
|
AliasIDs []string `json:"aliasIds,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"`
|
||||||
SkipDataQuery bool `json:"skipDataQuery"`
|
SkipDataQuery bool `json:"skipDataQuery"`
|
||||||
ReleaseState string `json:"state"`
|
ReleaseState string `json:"state"`
|
||||||
BaseURL string `json:"baseUrl"`
|
BaseURL string `json:"baseUrl"`
|
||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
Module string `json:"module"`
|
Module string `json:"module"`
|
||||||
AngularDetected bool `json:"angularDetected"`
|
AngularDetected bool `json:"angularDetected"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppDTO struct {
|
type AppDTO struct {
|
||||||
|
@ -18,6 +18,10 @@ schemas: [{
|
|||||||
id: string & strings.MinRunes(1)
|
id: string & strings.MinRunes(1)
|
||||||
id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([ for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|datagrid|gauge|geomap|gettingstarted|graph|heatmap|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|trend|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|grafana-testdata-datasource|zipkin|phlare|parca)$"
|
id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([ for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|datagrid|gauge|geomap|gettingstarted|graph|heatmap|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|trend|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|grafana-testdata-datasource|zipkin|phlare|parca)$"
|
||||||
|
|
||||||
|
// An alias is useful when migrating from one plugin id to another (rebranding etc)
|
||||||
|
// This should be used sparingly, and is currently only supported though a hardcoded checklist
|
||||||
|
aliasIDs?: [...string]
|
||||||
|
|
||||||
// Human-readable name of the plugin that is shown to the user in
|
// Human-readable name of the plugin that is shown to the user in
|
||||||
// the UI.
|
// the UI.
|
||||||
name: string
|
name: string
|
||||||
|
@ -278,6 +278,10 @@ type PluginDef struct {
|
|||||||
// For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`.
|
// For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`.
|
||||||
Alerting *bool `json:"alerting,omitempty"`
|
Alerting *bool `json:"alerting,omitempty"`
|
||||||
|
|
||||||
|
// An alias is useful when migrating from one plugin id to another (rebranding etc)
|
||||||
|
// This should be used sparingly, and is currently only supported though a hardcoded checklist
|
||||||
|
AliasIDs []string `json:"aliasIDs,omitempty"`
|
||||||
|
|
||||||
// For data source plugins, if the plugin supports annotation
|
// For data source plugins, if the plugin supports annotation
|
||||||
// queries.
|
// queries.
|
||||||
Annotations *bool `json:"annotations,omitempty"`
|
Annotations *bool `json:"annotations,omitempty"`
|
||||||
|
@ -29,6 +29,7 @@ var (
|
|||||||
ErrPluginFileRead = errors.New("file could not be read")
|
ErrPluginFileRead = errors.New("file could not be read")
|
||||||
ErrUninstallInvalidPluginDir = errors.New("cannot recognize as plugin folder")
|
ErrUninstallInvalidPluginDir = errors.New("cannot recognize as plugin folder")
|
||||||
ErrInvalidPluginJSON = errors.New("did not find valid type or id properties in plugin.json")
|
ErrInvalidPluginJSON = errors.New("did not find valid type or id properties in plugin.json")
|
||||||
|
ErrUnsupportedAlias = errors.New("can not set alias in plugin.json")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
@ -64,9 +65,6 @@ type Plugin struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
// This will be moved to plugin.json when we have general support in gcom
|
|
||||||
Alias string `json:"alias,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONData represents the plugin's plugin.json
|
// JSONData represents the plugin's plugin.json
|
||||||
@ -75,7 +73,7 @@ type JSONData struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type Type `json:"type"`
|
Type Type `json:"type"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Alias string `json:"alias,omitempty"`
|
AliasIDs []string `json:"aliasIDs,omitempty"`
|
||||||
Info Info `json:"info"`
|
Info Info `json:"info"`
|
||||||
Dependencies Dependencies `json:"dependencies"`
|
Dependencies Dependencies `json:"dependencies"`
|
||||||
Includes []*Includes `json:"includes"`
|
Includes []*Includes `json:"includes"`
|
||||||
@ -130,12 +128,20 @@ func ReadPluginJSON(reader io.Reader) (JSONData, error) {
|
|||||||
switch plugin.ID {
|
switch plugin.ID {
|
||||||
case "grafana-piechart-panel":
|
case "grafana-piechart-panel":
|
||||||
plugin.Name = "Pie Chart (old)"
|
plugin.Name = "Pie Chart (old)"
|
||||||
case "grafana-pyroscope-datasource": // rebranding
|
case "grafana-pyroscope-datasource":
|
||||||
plugin.Alias = "phlare"
|
fallthrough
|
||||||
case "grafana-testdata-datasource":
|
case "grafana-testdata-datasource":
|
||||||
plugin.Alias = "testdata"
|
fallthrough
|
||||||
case "debug": // panel plugin used for testing
|
case "annolist":
|
||||||
plugin.Alias = "debugX"
|
fallthrough
|
||||||
|
case "debug":
|
||||||
|
if len(plugin.AliasIDs) == 0 {
|
||||||
|
return plugin, fmt.Errorf("expected alias to be set")
|
||||||
|
}
|
||||||
|
default: // TODO: when gcom validates the alias, this condition can be removed
|
||||||
|
if len(plugin.AliasIDs) > 0 {
|
||||||
|
return plugin, ErrUnsupportedAlias
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(plugin.Dependencies.Plugins) == 0 {
|
if len(plugin.Dependencies.Plugins) == 0 {
|
||||||
|
@ -107,14 +107,15 @@ func Test_ReadPluginJSON(t *testing.T) {
|
|||||||
pluginJSON: func(t *testing.T) io.ReadCloser {
|
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||||
pJSON := `{
|
pJSON := `{
|
||||||
"id": "grafana-pyroscope-datasource",
|
"id": "grafana-pyroscope-datasource",
|
||||||
"type": "datasource"
|
"type": "datasource",
|
||||||
|
"aliasIDs": ["phlare"]
|
||||||
}`
|
}`
|
||||||
return io.NopCloser(strings.NewReader(pJSON))
|
return io.NopCloser(strings.NewReader(pJSON))
|
||||||
},
|
},
|
||||||
expected: JSONData{
|
expected: JSONData{
|
||||||
ID: "grafana-pyroscope-datasource",
|
ID: "grafana-pyroscope-datasource",
|
||||||
Alias: "phlare", // Hardcoded from the parser
|
AliasIDs: []string{"phlare"}, // Hardcoded from the parser
|
||||||
Type: TypeDataSource,
|
Type: TypeDataSource,
|
||||||
Dependencies: Dependencies{
|
Dependencies: Dependencies{
|
||||||
GrafanaDependency: "",
|
GrafanaDependency: "",
|
||||||
GrafanaVersion: "*",
|
GrafanaVersion: "*",
|
||||||
@ -122,6 +123,24 @@ func Test_ReadPluginJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "do not allow alias except for our hardcoded set",
|
||||||
|
pluginJSON: func(t *testing.T) io.ReadCloser {
|
||||||
|
pJSON := `{
|
||||||
|
"id": "my-custom-app",
|
||||||
|
"type": "app",
|
||||||
|
"aliasIDs": ["phlare"]
|
||||||
|
}`
|
||||||
|
return io.NopCloser(strings.NewReader(pJSON))
|
||||||
|
},
|
||||||
|
err: ErrUnsupportedAlias,
|
||||||
|
expected: JSONData{
|
||||||
|
ID: "my-custom-app",
|
||||||
|
AliasIDs: []string{"phlare"}, // Hardcoded from the parser
|
||||||
|
Type: "app",
|
||||||
|
Dependencies: Dependencies{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -32,9 +32,6 @@ type Plugin struct {
|
|||||||
|
|
||||||
AngularDetected bool
|
AngularDetected bool
|
||||||
|
|
||||||
// This will be moved to plugin.json when we have general support in gcom
|
|
||||||
Alias string
|
|
||||||
|
|
||||||
ExternalService *auth.ExternalService
|
ExternalService *auth.ExternalService
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +73,6 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin {
|
|||||||
Module: p.Module,
|
Module: p.Module,
|
||||||
BaseURL: p.BaseURL,
|
BaseURL: p.BaseURL,
|
||||||
AngularDetected: p.AngularDetected,
|
AngularDetected: p.AngularDetected,
|
||||||
Alias: p.Alias,
|
|
||||||
ExternalService: p.ExternalService,
|
ExternalService: p.ExternalService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,16 @@ export function hasPanelPlugin(id: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getPanelPluginMeta(id: string): PanelPluginMeta {
|
export function getPanelPluginMeta(id: string): PanelPluginMeta {
|
||||||
return config.panels[id] || Object.values(config.panels).find((p) => p.alias === id);
|
const v = config.panels[id];
|
||||||
|
if (!v) {
|
||||||
|
// Check alias values before failing
|
||||||
|
for (const p of Object.values(config.panels)) {
|
||||||
|
if (p.aliasIDs?.includes(id)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importPanelPluginFromMeta(meta: PanelPluginMeta): Promise<PanelPlugin> {
|
export function importPanelPluginFromMeta(meta: PanelPluginMeta): Promise<PanelPlugin> {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
"name": "Grafana Pyroscope",
|
"name": "Grafana Pyroscope",
|
||||||
"id": "grafana-pyroscope-datasource",
|
"id": "grafana-pyroscope-datasource",
|
||||||
|
"aliasIDs": ["phlare"],
|
||||||
"category": "profiling",
|
"category": "profiling",
|
||||||
|
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
"name": "TestData",
|
"name": "TestData",
|
||||||
"id": "grafana-testdata-datasource",
|
"id": "grafana-testdata-datasource",
|
||||||
|
"aliasIDs": ["testdata"],
|
||||||
|
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"logs": true,
|
"logs": true,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"type": "panel",
|
"type": "panel",
|
||||||
"name": "Annotations list",
|
"name": "Annotations list",
|
||||||
"id": "annolist",
|
"id": "annolist",
|
||||||
|
"aliasIDs": ["ryantxu-annolist-panel"],
|
||||||
|
|
||||||
"skipDataQuery": true,
|
"skipDataQuery": true,
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"type": "panel",
|
"type": "panel",
|
||||||
"name": "Debug",
|
"name": "Debug",
|
||||||
"id": "debug",
|
"id": "debug",
|
||||||
|
"aliasIDs": ["debugX"],
|
||||||
"state": "alpha",
|
"state": "alpha",
|
||||||
|
|
||||||
"info": {
|
"info": {
|
||||||
|
Reference in New Issue
Block a user