Plugins: Refactor GetPluginDashboards/LoadPluginDashboard (#46316)

Refactors GetPluginDashboards/LoadPluginDashboard by moving database 
interaction from plugin management to the plugindashboards service.

Fixes #44553

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Marcus Efraimsson
2022-03-10 18:38:04 +01:00
committed by GitHub
co-authored by Will Browne
parent d076cabb60
commit 6c7d326499
32 changed files with 1434 additions and 579 deletions
+29
View File
@@ -40,6 +40,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
"github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/require"
)
@@ -446,3 +447,31 @@ func mockRequestBody(v interface{}) io.ReadCloser {
b, _ := json.Marshal(v)
return io.NopCloser(bytes.NewReader(b))
}
// APITestServerOption option func for customizing HTTPServer configuration
// when setting up an API test server via SetupAPITestServer.
type APITestServerOption func(hs *HTTPServer)
// SetupAPITestServer sets up a webtest.Server ready for testing all
// routes registered via HTTPServer.registerRoutes().
// Optionally customize HTTPServer configuration by providing APITestServerOption
// option(s).
func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Server {
t.Helper()
hs := &HTTPServer{
RouteRegister: routing.NewRouteRegister(),
Cfg: setting.NewCfg(),
AccessControl: accesscontrolmock.New().WithDisabled(),
Features: featuremgmt.WithFeatures(),
searchUsersService: &searchusers.OSSService{},
}
for _, opt := range opts {
opt(hs)
}
hs.registerRoutes()
s := webtest.NewServer(t, hs.RouteRegister)
return s
}
+4 -3
View File
@@ -48,6 +48,7 @@ import (
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/plugindashboards"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/query"
@@ -100,7 +101,7 @@ type HTTPServer struct {
PluginRequestValidator models.PluginRequestValidator
pluginClient plugins.Client
pluginStore plugins.Store
pluginDashboardManager plugins.PluginDashboardManager
pluginDashboardService plugindashboards.Service
pluginStaticRouteResolver plugins.StaticRouteResolver
pluginErrorResolver plugins.ErrorResolver
SearchService search.Service
@@ -152,7 +153,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
renderService rendering.Service, licensing models.Licensing, hooksService *hooks.HooksService,
cacheService *localcache.CacheService, sqlStore *sqlstore.SQLStore, alertEngine *alerting.AlertEngine,
pluginRequestValidator models.PluginRequestValidator, pluginStaticRouteResolver plugins.StaticRouteResolver,
pluginDashboardManager plugins.PluginDashboardManager, pluginStore plugins.Store, pluginClient plugins.Client,
pluginDashboardService plugindashboards.Service, pluginStore plugins.Store, pluginClient plugins.Client,
pluginErrorResolver plugins.ErrorResolver, settingsProvider setting.Provider,
dataSourceCache datasources.CacheService, userTokenService models.UserTokenService,
cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service,
@@ -191,7 +192,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
pluginClient: pluginClient,
pluginStore: pluginStore,
pluginStaticRouteResolver: pluginStaticRouteResolver,
pluginDashboardManager: pluginDashboardManager,
pluginDashboardService: pluginDashboardService,
pluginErrorResolver: pluginErrorResolver,
grafanaUpdateChecker: grafanaUpdateChecker,
pluginsUpdateChecker: pluginsUpdateChecker,
+35
View File
@@ -0,0 +1,35 @@
package api
import (
"errors"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/plugindashboards"
"github.com/grafana/grafana/pkg/web"
)
// GetPluginDashboards get plugin dashboards.
//
// /api/plugins/:pluginId/dashboards
func (hs *HTTPServer) GetPluginDashboards(c *models.ReqContext) response.Response {
pluginID := web.Params(c.Req)[":pluginId"]
listReq := &plugindashboards.ListPluginDashboardsRequest{
OrgID: c.OrgId,
PluginID: pluginID,
}
list, err := hs.pluginDashboardService.ListPluginDashboards(c.Req.Context(), listReq)
if err != nil {
var notFound plugins.NotFoundError
if errors.As(err, &notFound) {
return response.Error(http.StatusNotFound, notFound.Error(), nil)
}
return response.Error(http.StatusInternalServerError, "Failed to get plugin dashboards", err)
}
return response.JSON(http.StatusOK, list.Items)
}
+128
View File
@@ -0,0 +1,128 @@
package api
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/plugindashboards"
"github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/require"
)
func TestGetPluginDashboards(t *testing.T) {
const existingPluginID = "existing-plugin"
pluginDashboardService := &pluginDashboardServiceMock{
pluginDashboards: map[string][]*plugindashboards.PluginDashboard{
existingPluginID: {
{
PluginId: existingPluginID,
UID: "a",
Title: "A",
},
{
PluginId: existingPluginID,
UID: "b",
Title: "B",
},
},
},
unexpectedErrors: map[string]error{
"boom": fmt.Errorf("BOOM"),
},
}
s := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.pluginDashboardService = pluginDashboardService
})
t.Run("Not signed in should return 404 Not Found", func(t *testing.T) {
req := s.NewGetRequest("/api/plugins/test/dashboards")
resp, err := s.Send(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
})
t.Run("Signed in and not org admin should return 403 Forbidden", func(t *testing.T) {
user := &models.SignedInUser{
UserId: 1,
OrgRole: models.ROLE_EDITOR,
}
resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, existingPluginID, user)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusForbidden, resp.StatusCode)
})
t.Run("Signed in and org admin", func(t *testing.T) {
user := &models.SignedInUser{
UserId: 1,
OrgId: 1,
OrgRole: models.ROLE_ADMIN,
}
t.Run("When plugin doesn't exist should return 404 Not Found", func(t *testing.T) {
resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, "not-exists", user)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusNotFound, resp.StatusCode)
})
t.Run("When result is unexpected error should return 500 Internal Server Error", func(t *testing.T) {
resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, "boom", user)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
})
t.Run("When plugin exists should return 200 OK with expected payload", func(t *testing.T) {
resp, err := sendGetPluginDashboardsRequestForSignedInUser(t, s, existingPluginID, user)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
bytes, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
var listResp []*plugindashboards.PluginDashboard
err = json.Unmarshal(bytes, &listResp)
require.NoError(t, err)
require.NotNil(t, listResp)
require.Len(t, listResp, 2)
require.Equal(t, pluginDashboardService.pluginDashboards[existingPluginID], listResp)
})
})
}
func sendGetPluginDashboardsRequestForSignedInUser(t *testing.T, s *webtest.Server, pluginID string, user *models.SignedInUser) (*http.Response, error) {
t.Helper()
req := s.NewGetRequest(fmt.Sprintf("/api/plugins/%s/dashboards", pluginID))
webtest.RequestWithSignedInUser(req, user)
return s.Send(req)
}
type pluginDashboardServiceMock struct {
plugindashboards.Service
pluginDashboards map[string][]*plugindashboards.PluginDashboard
unexpectedErrors map[string]error
}
func (m *pluginDashboardServiceMock) ListPluginDashboards(ctx context.Context, req *plugindashboards.ListPluginDashboardsRequest) (*plugindashboards.ListPluginDashboardsResponse, error) {
if pluginDashboards, exists := m.pluginDashboards[req.PluginID]; exists {
return &plugindashboards.ListPluginDashboardsResponse{
Items: pluginDashboards,
}, nil
}
if err, exists := m.unexpectedErrors[req.PluginID]; exists {
return nil, err
}
return nil, plugins.NotFoundError{PluginID: req.PluginID}
}
-16
View File
@@ -182,22 +182,6 @@ func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext) response.Respons
return response.Success("Plugin settings updated")
}
func (hs *HTTPServer) GetPluginDashboards(c *models.ReqContext) response.Response {
pluginID := web.Params(c.Req)[":pluginId"]
list, err := hs.pluginDashboardManager.GetPluginDashboards(c.Req.Context(), c.OrgId, pluginID)
if err != nil {
var notFound plugins.NotFoundError
if errors.As(err, &notFound) {
return response.Error(404, notFound.Error(), nil)
}
return response.Error(500, "Failed to get plugin dashboards", err)
}
return response.JSON(200, list)
}
func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response {
pluginID := web.Params(c.Req)[":pluginId"]
name := web.Params(c.Req)[":name"]