Compare commits

...

5 Commits

Author SHA1 Message Date
Naman Verma
a6e4294665 Merge branch 'nv/4172' into nv/4310 2026-04-06 22:18:48 +05:30
Naman Verma
66582e528f feat: update v2 dashboard handler 2026-04-06 22:17:18 +05:30
Naman Verma
9c3e8ce15d fix: remove err catch from call to NewDashboardV2 method 2026-04-06 21:25:37 +05:30
Naman Verma
50e96fc450 Merge branch 'nv/4172' into nv/4310 2026-04-06 21:19:41 +05:30
Naman Verma
a7f0df61d1 feat: create v2 dashboard handler 2026-04-06 18:21:43 +05:30
4 changed files with 283 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
package dashboardv2
import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboardV2) (*dashboardtypes.DashboardV2, error)
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboardV2, diff int) (*dashboardtypes.DashboardV2, error)
}
type Handler interface {
Create(http.ResponseWriter, *http.Request)
Update(http.ResponseWriter, *http.Request)
}

View File

@@ -0,0 +1,124 @@
package impldashboard
import (
"context"
"io"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/dashboardv2"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
type handler struct {
module dashboardv2.Module
}
func NewHandler(module dashboardv2.Module) dashboardv2.Handler {
return &handler{module: module}
}
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(rw, err)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
render.Error(rw, err)
return
}
req, err := dashboardtypes.UnmarshalAndValidateDashboardV2JSON(body)
if err != nil {
render.Error(rw, err)
return
}
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), *req)
if err != nil {
render.Error(rw, err)
return
}
gettable, err := dashboardtypes.NewGettableDashboardV2FromDashboard(dashboard)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusCreated, gettable)
}
func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(rw, err)
return
}
// TODO: reject cloud integration and installed integration dashboard IDs
// (prefixed with "cloud-integration--" and "integration--") with an explicit error,
// since those dashboards are read-only and cannot be updated.
id := mux.Vars(r)["id"]
if id == "" {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is missing in the path"))
return
}
dashboardID, err := valuer.NewUUID(id)
if err != nil {
render.Error(rw, err)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
render.Error(rw, err)
return
}
req, err := dashboardtypes.UnmarshalAndValidateDashboardV2JSON(body)
if err != nil {
render.Error(rw, err)
return
}
diff := 0
// Allow multiple deletions for API key requests; enforce for others
if claims.IdentNProvider == authtypes.IdentNProviderTokenizer {
diff = 1
}
dashboard, err := handler.module.Update(ctx, orgID, dashboardID, claims.Email, *req, diff)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, dashboard)
}

View File

@@ -0,0 +1,73 @@
package impldashboard
import (
"context"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/dashboardv2"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store *store
settings factory.ScopedProviderSettings
analytics analytics.Analytics
}
func NewModule(store *store, settings factory.ProviderSettings, analytics analytics.Analytics) dashboardv2.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/dashboardv2/impldashboard")
return &module{
store: store,
settings: scopedProviderSettings,
analytics: analytics,
}
}
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboardV2) (*dashboardtypes.DashboardV2, error) {
dashboard := dashboardtypes.NewDashboardV2(orgID, createdBy, data)
storable, err := dashboardtypes.NewStorableDashboardV2FromDashboardV2(dashboard)
if err != nil {
return nil, err
}
err = module.store.Create(ctx, storable)
if err != nil {
return nil, err
}
module.analytics.TrackUser(ctx, orgID.String(), creator.String(), "Dashboard Created", dashboardtypes.NewStatsFromStorableDashboardsV2([]*dashboardtypes.StorableDashboardV2{storable}))
return dashboard, nil
}
func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboardV2, diff int) (*dashboardtypes.DashboardV2, error) {
// Fetch current state to validate lock status and panel diff before updating.
// This lives in the module layer (not pushed into a conditional SQL update)
// to keep business logic out of the store.
storable, err := module.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
dashboard := dashboardtypes.NewDashboardV2FromStorableDashboard(storable)
err = dashboard.Update(ctx, data, updatedBy, diff)
if err != nil {
return nil, err
}
updatedStorable, err := dashboardtypes.NewStorableDashboardV2FromDashboardV2(dashboard)
if err != nil {
return nil, err
}
err = module.store.Update(ctx, orgID, updatedStorable)
if err != nil {
return nil, err
}
return dashboard, nil
}

View File

@@ -0,0 +1,65 @@
package impldashboard
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) *store {
return &store{sqlstore: sqlstore}
}
func (store *store) Create(ctx context.Context, storable *dashboardtypes.StorableDashboardV2) error {
_, err := store.
sqlstore.
BunDB().
NewInsert().
Model(storable).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "dashboard with id %s already exists", storable.ID)
}
return nil
}
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.StorableDashboardV2, error) {
storable := new(dashboardtypes.StorableDashboardV2)
err := store.
sqlstore.
BunDB().
NewSelect().
Model(storable).
Where("id = ?", id).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", id)
}
return storable, nil
}
func (store *store) Update(ctx context.Context, orgID valuer.UUID, storable *dashboardtypes.StorableDashboardV2) error {
_, err := store.
sqlstore.
BunDB().
NewUpdate().
Model(storable).
WherePK().
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with id %s doesn't exist", storable.ID)
}
return nil
}