Files
grafana/pkg/services/dashboardimport/api/api.go
Alexa Vargas 62eef87208 Dashboard Library: Integrate community dashboards on Suggested Dashboards Flow (#112808)
* Extend interpolate endpoint to support community dashboard json interpolation
Added unit tests

* Implement Frontend Side
- Show tabs
- Fetch Community dashboads
- Use DashboardCard component
- Search grafana dashboard in the community tab
- Make Tabs and pagination sticky
- Adjust titles to be scoped by datasource name/type
- Add skeleton loading for community tabs and pagination
- Add dashboard details tooltip
- Bring old datasource-provisioned box back and rely on new feature toggle for community dashboards
- update i18n
- update swagger
---------

Co-authored-by: Juan Cabanas <juan.cabanas@grafana.com>
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
2025-11-11 10:40:39 +01:00

162 lines
5.3 KiB
Go

package api
import (
"errors"
"net/http"
"github.com/grafana/grafana/pkg/api/apierrors"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboardimport"
"github.com/grafana/grafana/pkg/services/dashboardimport/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/web"
)
type ImportDashboardAPI struct {
dashboardImportService dashboardimport.Service
quotaService QuotaService
pluginStore pluginstore.Store
ac accesscontrol.AccessControl
features featuremgmt.FeatureToggles
}
func New(dashboardImportService dashboardimport.Service, quotaService QuotaService,
pluginStore pluginstore.Store, ac accesscontrol.AccessControl, features featuremgmt.FeatureToggles) *ImportDashboardAPI {
return &ImportDashboardAPI{
dashboardImportService: dashboardImportService,
quotaService: quotaService,
pluginStore: pluginStore,
ac: ac,
features: features,
}
}
func (api *ImportDashboardAPI) RegisterAPIEndpoints(routeRegister routing.RouteRegister) {
authorize := accesscontrol.Middleware(api.ac)
routeRegister.Group("/api/dashboards", func(route routing.RouteRegister) {
route.Post(
"/import",
authorize(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)),
routing.Wrap(api.ImportDashboard),
)
//nolint:staticcheck // not yet migrated to OpenFeature
if api.features.IsEnabledGlobally(featuremgmt.FlagDashboardLibrary) || api.features.IsEnabledGlobally(featuremgmt.FlagSuggestedDashboards) {
route.Post(
"/interpolate",
authorize(accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate)),
routing.Wrap(api.InterpolateDashboard),
)
}
}, middleware.ReqSignedIn)
}
// swagger:route POST /dashboards/interpolate dashboards interpolateDashboard
//
// Interpolate dashboard. This is an experimental endpoint under dashboardLibrary or suggestedDashboards feature flags and is subject to change.
//
// Responses:
// 200: interpolateDashboardResponse
// 400: badRequestError
// 401: unauthorisedError
// 422: unprocessableEntityError
// 500: internalServerError
func (api *ImportDashboardAPI) InterpolateDashboard(c *contextmodel.ReqContext) response.Response {
req := dashboardimport.ImportDashboardRequest{}
if err := web.Bind(c.Req, &req); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
if req.PluginId == "" && req.Dashboard == nil {
return response.Error(http.StatusUnprocessableEntity, "pluginId or dashboard must be set", nil)
}
resp, err := api.dashboardImportService.InterpolateDashboard(c.Req.Context(), &req)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to interpolate dashboard", err)
}
resp.Del("__elements")
resp.Del("__inputs")
resp.Del("__requires")
return response.JSON(http.StatusOK, resp)
}
// swagger:route POST /dashboards/import dashboards importDashboard
//
// Import dashboard.
//
// Responses:
// 200: importDashboardResponse
// 400: badRequestError
// 401: unauthorisedError
// 412: preconditionFailedError
// 422: unprocessableEntityError
// 500: internalServerError
func (api *ImportDashboardAPI) ImportDashboard(c *contextmodel.ReqContext) response.Response {
req := dashboardimport.ImportDashboardRequest{}
if err := web.Bind(c.Req, &req); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
if req.PluginId == "" && req.Dashboard == nil {
return response.Error(http.StatusUnprocessableEntity, "Dashboard must be set", nil)
}
limitReached, err := api.quotaService.QuotaReached(c, dashboards.QuotaTargetSrv)
if err != nil {
return response.Err(err)
}
if limitReached {
return response.Error(http.StatusForbidden, "Quota reached", nil)
}
req.User = c.SignedInUser
resp, err := api.dashboardImportService.ImportDashboard(c.Req.Context(), &req)
if err != nil {
if errors.Is(err, utils.ErrDashboardInputMissing) {
return response.Error(http.StatusBadRequest, err.Error(), err)
}
return apierrors.ToDashboardErrorResponse(c.Req.Context(), api.pluginStore, err)
}
return response.JSON(http.StatusOK, resp)
}
type QuotaService interface {
QuotaReached(c *contextmodel.ReqContext, target quota.TargetSrv) (bool, error)
}
type quotaServiceFunc func(c *contextmodel.ReqContext, target quota.TargetSrv) (bool, error)
func (fn quotaServiceFunc) QuotaReached(c *contextmodel.ReqContext, target quota.TargetSrv) (bool, error) {
return fn(c, target)
}
// swagger:parameters importDashboard
type ImportDashboardParams struct {
// in:body
// required:true
Body dashboardimport.ImportDashboardRequest
}
// swagger:response importDashboardResponse
type ImportDashboardResponse struct {
// in: body
Body dashboardimport.ImportDashboardResponse `json:"body"`
}
// swagger:response interpolateDashboardResponse
type InterpolateDashboardResponse struct {
// in: body
Body interface{} `json:"body"`
}