Plugins: Extend panel menu with links from plugins (#63089)
* feat(plugins): introduce dashboard panel menu placement for adding menu items * test: add test for getPanelMenu() * added an unique identifier for each extension. * added context to getPluginExtensions. * wip * Wip * wiwip * Wip * feat: WWWIIIIPPPP 🧨 * Wip * Renamed some of the types to align a bit better. * added limit to how many extensions a plugin can register per placement. * decreased number of items to 2 * will trim the lenght of titles to max 25 chars. * wrapping configure function with error handling. * added error handling for all scenarios. * moved extension menu items to the bottom of the more sub menu. * added tests for configuring the title. * minor refactorings. * changed so you need to specify the full path in package.json. * wip * removed unused type. * big refactor to make things simpler and to centralize all configure error/validation handling. * added missing import. * fixed failing tests. * fixed tests. * revert(extensions): remove static extensions config in favour of registering via AppPlugin APIs * removed the compose that didn't work for some reason. * added tests just to verify that validation and error handling is tied together in configuration function. * adding some more values to the context. * draft validation. * added missing tests for getPanelMenu. * added more tests. * refactor(extensions): move logic for validating extension link config to function * Fixed ts errors. * Update packages/grafana-data/src/types/app.ts Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> * Update packages/grafana-runtime/src/services/pluginExtensions/extensions.test.ts Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> * refactor(extensions): rename limiter -> pluginPlacementCount * refactor(getpanelmenu): remove redundant continue statement --------- Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
co-authored by
Levente Balogh
Marcus Andersson
parent
5bd2fac9c8
commit
8c8f584b41
@@ -333,12 +333,6 @@ func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error)
|
||||
}
|
||||
}
|
||||
|
||||
for i, extension := range plugin.Extensions {
|
||||
if !filepath.IsAbs(extension.Path) {
|
||||
plugin.Extensions[i].Path = path.Join("/", extension.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -463,72 +462,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an app with link extensions",
|
||||
class: plugins.External,
|
||||
cfg: &config.Cfg{
|
||||
PluginsAllowUnsigned: []string{"test-app"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/test-app-with-link-extensions"},
|
||||
want: []*plugins.Plugin{
|
||||
{JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Type: "app",
|
||||
Name: "Test App",
|
||||
Info: plugins.Info{
|
||||
Author: plugins.InfoLink{
|
||||
Name: "Test Inc.",
|
||||
URL: "http://test.com",
|
||||
},
|
||||
Description: "Official Grafana Test App & Dashboard bundle",
|
||||
Version: "1.0.0",
|
||||
Links: []plugins.InfoLink{
|
||||
{Name: "Project site", URL: "http://project.com"},
|
||||
{Name: "License & Terms", URL: "http://license.com"},
|
||||
},
|
||||
Logos: plugins.Logos{
|
||||
Small: "public/img/icn-app.svg",
|
||||
Large: "public/img/icn-app.svg",
|
||||
},
|
||||
Updated: "2015-02-10",
|
||||
},
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaDependency: ">=8.0.0",
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Root Page (react)", Type: "page", Role: "Viewer", Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
||||
},
|
||||
Extensions: []*plugindef.ExtensionsLink{
|
||||
{
|
||||
Placement: "plugins/grafana-slo-app/slo-breach",
|
||||
Title: "Declare incident",
|
||||
Type: plugindef.ExtensionsLinkTypeLink,
|
||||
Description: "Declares a new incident",
|
||||
Path: "/incidents/declare",
|
||||
},
|
||||
{
|
||||
Placement: "plugins/grafana-slo-app/slo-breach",
|
||||
Title: "Declare incident",
|
||||
Type: plugindef.ExtensionsLinkTypeLink,
|
||||
Description: "Declares a new incident (path without backslash)",
|
||||
Path: "/incidents/declare",
|
||||
},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/test-app-with-link-extensions"),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"type": "app",
|
||||
"name": "Test App",
|
||||
"id": "test-app",
|
||||
"info": {
|
||||
"description": "Official Grafana Test App & Dashboard bundle",
|
||||
"author": {
|
||||
"name": "Test Inc.",
|
||||
"url": "http://test.com"
|
||||
},
|
||||
"keywords": [
|
||||
"test"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"name": "Project site",
|
||||
"url": "http://project.com"
|
||||
},
|
||||
{
|
||||
"name": "License & Terms",
|
||||
"url": "http://license.com"
|
||||
}
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"updated": "2015-02-10"
|
||||
},
|
||||
"includes": [
|
||||
{
|
||||
"type": "page",
|
||||
"name": "Root Page (react)",
|
||||
"path": "/a/my-simple-app",
|
||||
"role": "Viewer",
|
||||
"addToNav": true,
|
||||
"defaultNav": true
|
||||
}
|
||||
],
|
||||
"extensions": [
|
||||
{
|
||||
"placement": "plugins/grafana-slo-app/slo-breach",
|
||||
"type": "link",
|
||||
"title": "Declare incident",
|
||||
"description": "Declares a new incident",
|
||||
"path": "/incidents/declare"
|
||||
},
|
||||
{
|
||||
"placement": "plugins/grafana-slo-app/slo-breach",
|
||||
"type": "link",
|
||||
"title": "Declare incident",
|
||||
"description": "Declares a new incident (path without backslash)",
|
||||
"path": "incidents/declare"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"grafanaDependency": ">=8.0.0"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
)
|
||||
|
||||
@@ -255,11 +254,10 @@ type PanelDTO struct {
|
||||
}
|
||||
|
||||
type AppDTO struct {
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
Extensions []*plugindef.ExtensionsLink `json:"extensions,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Version string `json:"version"`
|
||||
Preload bool `json:"preload"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -84,10 +84,6 @@ func TestParsePluginTestdata(t *testing.T) {
|
||||
rootid: "test-app",
|
||||
skip: "has a 'page'-type include which isn't a known part of spec",
|
||||
},
|
||||
"test-app-with-link-extensions": {
|
||||
rootid: "test-app",
|
||||
skip: "has a 'page'-type include which isn't a known part of spec",
|
||||
},
|
||||
"test-app-with-roles": {
|
||||
rootid: "test-app",
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ package plugindef
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
||||
"github.com/grafana/thema"
|
||||
)
|
||||
|
||||
@@ -122,23 +122,6 @@ seqs: [
|
||||
...
|
||||
}
|
||||
|
||||
#ExtensionsLink: {
|
||||
// Target where the link will be rendered
|
||||
placement: =~"^(plugins|grafana)\/[a-z-/0-9]*$"
|
||||
// Type of extension
|
||||
type: "link"
|
||||
// Title that will be displayed for the rendered link
|
||||
title: string & strings.MinRunes(3) & strings.MaxRunes(22)
|
||||
// Description for the rendered link
|
||||
description: string & strings.MaxRunes(200)
|
||||
// Path relative to the extending plugin e.g. /incidents/declare
|
||||
path: =~"^\/.*"
|
||||
...
|
||||
}
|
||||
|
||||
// Extensions made by the current plugin.
|
||||
extensions?: [...#ExtensionsLink]
|
||||
|
||||
// For data source plugins, if the plugin supports logs.
|
||||
logs?: bool
|
||||
|
||||
|
||||
@@ -24,11 +24,6 @@ const (
|
||||
DependencyTypePanel DependencyType = "panel"
|
||||
)
|
||||
|
||||
// Defines values for ExtensionsLinkType.
|
||||
const (
|
||||
ExtensionsLinkTypeLink ExtensionsLinkType = "link"
|
||||
)
|
||||
|
||||
// Defines values for IncludeRole.
|
||||
const (
|
||||
IncludeRoleAdmin IncludeRole = "Admin"
|
||||
@@ -126,27 +121,6 @@ type Dependency struct {
|
||||
// DependencyType defines model for Dependency.Type.
|
||||
type DependencyType string
|
||||
|
||||
// ExtensionsLink defines model for ExtensionsLink.
|
||||
type ExtensionsLink struct {
|
||||
// Description for the rendered link
|
||||
Description string `json:"description"`
|
||||
|
||||
// Path relative to the extending plugin e.g. /incidents/declare
|
||||
Path string `json:"path"`
|
||||
|
||||
// Target where the link will be rendered
|
||||
Placement string `json:"placement"`
|
||||
|
||||
// Title that will be displayed for the rendered link
|
||||
Title string `json:"title"`
|
||||
|
||||
// Type of extension
|
||||
Type ExtensionsLinkType `json:"type"`
|
||||
}
|
||||
|
||||
// Type of extension
|
||||
type ExtensionsLinkType string
|
||||
|
||||
// Header describes an HTTP header that is forwarded with a proxied request for
|
||||
// a plugin route.
|
||||
type Header struct {
|
||||
@@ -313,9 +287,6 @@ type PluginDef struct {
|
||||
// https://golang.org/doc/install/source#environment.
|
||||
Executable *string `json:"executable,omitempty"`
|
||||
|
||||
// Extensions made by the current plugin.
|
||||
Extensions []ExtensionsLink `json:"extensions,omitempty"`
|
||||
|
||||
// For data source plugins, include hidden queries in the data
|
||||
// request.
|
||||
HiddenQueries *bool `json:"hiddenQueries,omitempty"`
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@@ -138,8 +137,7 @@ type JSONData struct {
|
||||
SkipDataQuery bool `json:"skipDataQuery"`
|
||||
|
||||
// App settings
|
||||
AutoEnabled bool `json:"autoEnabled"`
|
||||
Extensions []*plugindef.ExtensionsLink `json:"extensions"`
|
||||
AutoEnabled bool `json:"autoEnabled"`
|
||||
|
||||
// Datasource settings
|
||||
Annotations bool `json:"annotations"`
|
||||
|
||||
Reference in New Issue
Block a user