mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-11 03:10:32 +01:00
Compare commits
5 Commits
main
...
nv/list-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c20e8d5a8d | ||
|
|
979eb19050 | ||
|
|
e03a785015 | ||
|
|
a45c80cc1e | ||
|
|
d7bfeee72b |
@@ -254,12 +254,12 @@ func (module *module) PinV2(ctx context.Context, orgID valuer.UUID, userID value
|
||||
return module.pkgDashboardModule.PinV2(ctx, orgID, userID, id)
|
||||
}
|
||||
|
||||
func (module *module) UnpinV2(ctx context.Context, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.pkgDashboardModule.UnpinV2(ctx, userID, id)
|
||||
func (module *module) UnpinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.pkgDashboardModule.UnpinV2(ctx, orgID, userID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeletePreferencesForUser(ctx context.Context, userID valuer.UUID) error {
|
||||
return module.pkgDashboardModule.DeletePreferencesForUser(ctx, userID)
|
||||
func (module *module) DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
return module.pkgDashboardModule.DeletePreferencesForUser(ctx, orgID, userID)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
|
||||
@@ -73,11 +73,11 @@ type Module interface {
|
||||
|
||||
PinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
UnpinV2(ctx context.Context, userID valuer.UUID, id valuer.UUID) error
|
||||
UnpinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
DeleteV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
DeletePreferencesForUser(ctx context.Context, userID valuer.UUID) error
|
||||
DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
|
||||
@@ -383,15 +383,16 @@ func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) er
|
||||
// rows = 0 is the only signal of a real limit hit.
|
||||
func (store *store) PinForUser(ctx context.Context, preference *dashboardtypes.UserDashboardPreference) error {
|
||||
res, err := store.sqlstore.BunDBCtx(ctx).NewRaw(`
|
||||
INSERT INTO user_dashboard_preference (user_id, dashboard_id, is_pinned)
|
||||
SELECT ?, ?, true
|
||||
INSERT INTO user_dashboard_preference (id, user_id, dashboard_id, is_pinned, created_at, updated_at)
|
||||
SELECT ?, ?, ?, true, ?, ?
|
||||
WHERE (SELECT COUNT(*) FROM user_dashboard_preference WHERE user_id = ? AND is_pinned = true) < ?
|
||||
OR EXISTS (SELECT 1 FROM user_dashboard_preference WHERE user_id = ? AND dashboard_id = ? AND is_pinned = true)
|
||||
ON CONFLICT (user_id, dashboard_id) DO UPDATE SET is_pinned = true
|
||||
ON CONFLICT (user_id, dashboard_id) DO UPDATE SET is_pinned = true, updated_at = ?
|
||||
`,
|
||||
preference.UserID, preference.DashboardID,
|
||||
preference.ID, preference.UserID, preference.DashboardID, preference.CreatedAt, preference.UpdatedAt,
|
||||
preference.UserID, dashboardtypes.MaxPinnedDashboardsPerUser,
|
||||
preference.UserID, preference.DashboardID,
|
||||
preference.UpdatedAt,
|
||||
).Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't pin dashboard for user")
|
||||
@@ -410,12 +411,21 @@ func (store *store) PinForUser(ctx context.Context, preference *dashboardtypes.U
|
||||
// UnpinForUser deletes the user's preference row. This is fine while is_pinned
|
||||
// is the only preference stored; once the row carries other preferences this
|
||||
// must become an UPDATE that clears is_pinned instead of dropping the row.
|
||||
func (store *store) UnpinForUser(ctx context.Context, userID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
func (store *store) UnpinForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
// No org_id on the preference table, so scope by org via a subquery on the
|
||||
// parent (DELETE-with-JOIN isn't portable across Postgres/SQLite).
|
||||
dashboardIDsInOrgSubQuery := store.sqlstore.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("dashboard").
|
||||
Column("id").
|
||||
Where("org_id = ?", orgID)
|
||||
|
||||
_, err := store.sqlstore.BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model((*dashboardtypes.UserDashboardPreference)(nil)).
|
||||
Where("user_id = ?", userID).
|
||||
Where("dashboard_id = ?", dashboardID).
|
||||
Where("dashboard_id IN (?)", dashboardIDsInOrgSubQuery).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't unpin dashboard for user")
|
||||
@@ -423,11 +433,19 @@ func (store *store) UnpinForUser(ctx context.Context, userID valuer.UUID, dashbo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) DeletePreferencesForDashboard(ctx context.Context, dashboardID valuer.UUID) error {
|
||||
func (store *store) DeletePreferencesForDashboard(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
// No org_id on the preference table, so scope by org via a subquery on the
|
||||
// parent (DELETE-with-JOIN isn't portable across Postgres/SQLite).
|
||||
dashboardIDsInOrgSubQuery := store.sqlstore.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("dashboard").
|
||||
Column("id").
|
||||
Where("org_id = ?", orgID)
|
||||
_, err := store.sqlstore.BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model((*dashboardtypes.UserDashboardPreference)(nil)).
|
||||
Where("dashboard_id = ?", dashboardID).
|
||||
Where("dashboard_id IN (?)", dashboardIDsInOrgSubQuery).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't delete dashboard preferences")
|
||||
@@ -435,11 +453,19 @@ func (store *store) DeletePreferencesForDashboard(ctx context.Context, dashboard
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) DeletePreferencesForUser(ctx context.Context, userID valuer.UUID) error {
|
||||
func (store *store) DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
// No org_id on the preference table, so scope by org via a subquery on the
|
||||
// parent (DELETE-with-JOIN isn't portable across Postgres/SQLite).
|
||||
userIDsInOrgSubQuery := store.sqlstore.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("users").
|
||||
Column("id").
|
||||
Where("org_id = ?", orgID)
|
||||
_, err := store.sqlstore.BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model((*dashboardtypes.UserDashboardPreference)(nil)).
|
||||
Where("user_id = ?", userID).
|
||||
Where("user_id IN (?)", userIDsInOrgSubQuery).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.WrapInternalf(err, errors.CodeInternal, "couldn't delete dashboard preferences")
|
||||
|
||||
@@ -304,7 +304,7 @@ func (handler *handler) pinUnpinV2(rw http.ResponseWriter, r *http.Request, pin
|
||||
if pin {
|
||||
err = handler.module.PinV2(ctx, orgID, userID, dashboardID)
|
||||
} else {
|
||||
err = handler.module.UnpinV2(ctx, userID, dashboardID)
|
||||
err = handler.module.UnpinV2(ctx, orgID, userID, dashboardID)
|
||||
}
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
|
||||
@@ -119,7 +119,7 @@ func (module *module) UpdateV2(ctx context.Context, orgID valuer.UUID, id valuer
|
||||
return nil, err
|
||||
}
|
||||
// Locked-dashboard / state gate — independent of tags, so run it before the tx.
|
||||
if err := existing.CanUpdate(); err != nil {
|
||||
if err := existing.ErrIfNotUpdatable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func (module *module) PatchV2(ctx context.Context, orgID valuer.UUID, id valuer.
|
||||
return nil, err
|
||||
}
|
||||
// Locked-dashboard / state gate — independent of tags, so run it before the tx.
|
||||
if err := existing.CanUpdate(); err != nil {
|
||||
if err := existing.ErrIfNotUpdatable(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func (module *module) DeleteV2(ctx context.Context, orgID valuer.UUID, id valuer
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := existing.CanDelete(); err != nil {
|
||||
if err := existing.ErrIfNotDeletable(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ func (module *module) DeleteV2(ctx context.Context, orgID valuer.UUID, id valuer
|
||||
if _, err := module.tagModule.SyncTags(ctx, orgID, coretypes.KindDashboard, id, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := module.store.DeletePreferencesForDashboard(ctx, id); err != nil {
|
||||
if err := module.store.DeletePreferencesForDashboard(ctx, orgID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
@@ -231,10 +231,10 @@ func (module *module) PinV2(ctx context.Context, orgID valuer.UUID, userID value
|
||||
return module.store.PinForUser(ctx, dashboardtypes.NewUserDashboardPreference(userID, id))
|
||||
}
|
||||
|
||||
func (module *module) UnpinV2(ctx context.Context, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.UnpinForUser(ctx, userID, id)
|
||||
func (module *module) UnpinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.UnpinForUser(ctx, orgID, userID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeletePreferencesForUser(ctx context.Context, userID valuer.UUID) error {
|
||||
return module.store.DeletePreferencesForUser(ctx, userID)
|
||||
func (module *module) DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
return module.store.DeletePreferencesForUser(ctx, orgID, userID)
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ func (module *setter) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.dashboard.DeletePreferencesForUser(ctx, user.ID); err != nil {
|
||||
if err := module.dashboard.DeletePreferencesForUser(ctx, orgID, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewFixChangelogOperationTypeFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewCloudIntegrationRemoveCascadeDeleteFactory(sqlschema),
|
||||
sqlmigration.NewAddUserDashboardPreferenceFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewRecreateUserDashboardPreferenceFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
84
pkg/sqlmigration/093_recreate_user_dashboard_preference.go
Normal file
84
pkg/sqlmigration/093_recreate_user_dashboard_preference.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type recreateUserDashboardPreference struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewRecreateUserDashboardPreferenceFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("recreate_user_dashboard_pref"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &recreateUserDashboardPreference{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *recreateUserDashboardPreference) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
// Up replaces the composite (user_id, dashboard_id) primary key with a surrogate
|
||||
// id primary key, demotes the pair to a unique index, and adds created_at /
|
||||
// updated_at. The table is dropped and recreated since it carries no data yet.
|
||||
func (migration *recreateUserDashboardPreference) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
sqls := migration.sqlschema.Operator().DropTable(&sqlschema.Table{Name: "user_dashboard_preference"})
|
||||
|
||||
sqls = append(sqls, migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "user_dashboard_preference",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "user_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "dashboard_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "is_pinned", DataType: sqlschema.DataTypeBoolean, Nullable: false, Default: "false"},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{ColumnNames: []sqlschema.ColumnName{"id"}},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("user_id"),
|
||||
ReferencedTableName: sqlschema.TableName("users"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("dashboard_id"),
|
||||
ReferencedTableName: sqlschema.TableName("dashboard"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})...)
|
||||
|
||||
sqls = append(sqls, migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{
|
||||
TableName: "user_dashboard_preference",
|
||||
ColumnNames: []sqlschema.ColumnName{"user_id", "dashboard_id"},
|
||||
})...)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *recreateUserDashboardPreference) Down(_ context.Context, _ *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -62,10 +62,17 @@ type DashboardV2 struct {
|
||||
Spec DashboardSpec `json:"spec" required:"true"`
|
||||
}
|
||||
|
||||
func (d *DashboardV2) CanUpdate() error {
|
||||
func (d *DashboardV2) ErrIfNotMutable() error {
|
||||
if d.Source == SourceIntegration {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "integration dashboards cannot be modified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DashboardV2) ErrIfNotUpdatable() error {
|
||||
if err := d.ErrIfNotMutable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if d.Locked {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot update a locked dashboard, please unlock the dashboard to update")
|
||||
}
|
||||
@@ -73,7 +80,7 @@ func (d *DashboardV2) CanUpdate() error {
|
||||
}
|
||||
|
||||
func (d *DashboardV2) Update(updatable UpdatableDashboardV2, updatedBy string, resolvedTags []*tagtypes.Tag) error {
|
||||
if err := d.CanUpdate(); err != nil {
|
||||
if err := d.ErrIfNotUpdatable(); err != nil {
|
||||
return err
|
||||
}
|
||||
if updatable.Name != d.Name {
|
||||
@@ -87,7 +94,7 @@ func (d *DashboardV2) Update(updatable UpdatableDashboardV2, updatedBy string, r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DashboardV2) CanLockUnlock(isAdmin bool, updatedBy string) error {
|
||||
func (d *DashboardV2) ErrIfNotLockable(isAdmin bool, updatedBy string) error {
|
||||
if d.Source == SourceIntegration {
|
||||
return errors.Newf(errors.TypeInvalidInput, ErrCodeDashboardImmutable, "integration dashboards cannot be locked or unlocked")
|
||||
}
|
||||
@@ -101,7 +108,7 @@ func (d *DashboardV2) CanLockUnlock(isAdmin bool, updatedBy string) error {
|
||||
}
|
||||
|
||||
func (d *DashboardV2) LockUnlock(lock bool, isAdmin bool, updatedBy string) error {
|
||||
if err := d.CanLockUnlock(isAdmin, updatedBy); err != nil {
|
||||
if err := d.ErrIfNotLockable(isAdmin, updatedBy); err != nil {
|
||||
return err
|
||||
}
|
||||
d.Locked = lock
|
||||
@@ -110,7 +117,7 @@ func (d *DashboardV2) LockUnlock(lock bool, isAdmin bool, updatedBy string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DashboardV2) CanDelete() error {
|
||||
func (d *DashboardV2) ErrIfNotDeletable() error {
|
||||
if d.Locked {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot delete a locked dashboard, please unlock the dashboard to delete")
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ type Store interface {
|
||||
// Returns ErrCodePinnedDashboardLimitHit when the user is at MaxPinnedDashboardsPerUser.
|
||||
PinForUser(ctx context.Context, preference *UserDashboardPreference) error
|
||||
|
||||
UnpinForUser(ctx context.Context, userID valuer.UUID, dashboardID valuer.UUID) error
|
||||
UnpinForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, dashboardID valuer.UUID) error
|
||||
|
||||
DeletePreferencesForDashboard(ctx context.Context, dashboardID valuer.UUID) error
|
||||
DeletePreferencesForDashboard(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error
|
||||
|
||||
DeletePreferencesForUser(ctx context.Context, userID valuer.UUID) error
|
||||
DeletePreferencesForUser(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package dashboardtypes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
@@ -14,15 +17,20 @@ var ErrCodePinnedDashboardLimitHit = errors.MustNewCode("pinned_dashboard_limit_
|
||||
type UserDashboardPreference struct {
|
||||
bun.BaseModel `bun:"table:user_dashboard_preference,alias:user_dashboard_preference"`
|
||||
|
||||
UserID valuer.UUID `bun:"user_id,pk,type:text"`
|
||||
DashboardID valuer.UUID `bun:"dashboard_id,pk,type:text"`
|
||||
types.Identifiable
|
||||
types.TimeAuditable
|
||||
UserID valuer.UUID `bun:"user_id,type:text"`
|
||||
DashboardID valuer.UUID `bun:"dashboard_id,type:text"`
|
||||
IsPinned bool `bun:"is_pinned,notnull,default:false"`
|
||||
}
|
||||
|
||||
func NewUserDashboardPreference(userID, dashboardID valuer.UUID) *UserDashboardPreference {
|
||||
now := time.Now()
|
||||
return &UserDashboardPreference{
|
||||
UserID: userID,
|
||||
DashboardID: dashboardID,
|
||||
IsPinned: true,
|
||||
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: now, UpdatedAt: now},
|
||||
UserID: userID,
|
||||
DashboardID: dashboardID,
|
||||
IsPinned: true,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user