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:
Jack Westbrook
2023-03-02 15:42:00 +01:00
committed by GitHub
co-authored by Levente Balogh Marcus Andersson
parent 5bd2fac9c8
commit 8c8f584b41
36 changed files with 1382 additions and 713 deletions
-6
View File
@@ -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
}
-66
View File
@@ -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 -6
View File
@@ -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 (
-4
View File
@@ -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",
},
+1 -18
View File
@@ -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"`
+1 -3
View File
@@ -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"`