mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 02:48:59 +08:00
fix: support multitenancy in dashboards & savedviews (#7237)
* fix: support multitenancy in dashboards * fix: support multitenancy in saved views * fix: move migrations to provider file * fix: remove getUserFromClaims and use new errors * fix: remove getUserFromClaims and use new errors * fix: use new errors in dashboards.go * Update ee/query-service/app/api/dashboard.go Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * fix: minor changes --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
parent
d576dec156
commit
a9618886b9
@ -1,15 +1,15 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"go.signoz.io/signoz/pkg/errors"
|
||||||
|
"go.signoz.io/signoz/pkg/http/render"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/common"
|
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ah *APIHandler) lockDashboard(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) lockDashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -31,26 +31,31 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// Get the dashboard UUID from the request
|
// Get the dashboard UUID from the request
|
||||||
uuid := mux.Vars(r)["uuid"]
|
uuid := mux.Vars(r)["uuid"]
|
||||||
if strings.HasPrefix(uuid,"integration") {
|
if strings.HasPrefix(uuid, "integration") {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: errors.New("dashboards created by integrations cannot be unlocked")}, "You are not authorized to lock/unlock this dashboard")
|
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "dashboards created by integrations cannot be modified"))
|
||||||
return
|
|
||||||
}
|
|
||||||
dashboard, err := dashboards.GetDashboard(r.Context(), uuid)
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
if !auth.IsAdmin(user) && (dashboard.CreateBy != nil && *dashboard.CreateBy != user.Email) {
|
if !ok {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: err}, "You are not authorized to lock/unlock this dashboard")
|
render.Error(w, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard, err := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get dashboard"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !auth.IsAdminV2(claims) && (dashboard.CreatedBy != claims.Email) {
|
||||||
|
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "You are not authorized to lock/unlock this dashboard"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock/Unlock the dashboard
|
// Lock/Unlock the dashboard
|
||||||
err = dashboards.LockUnlockDashboard(r.Context(), uuid, lock)
|
err = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
|
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to lock/unlock dashboard"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := baseexplorer.InitWithDSN(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
if err := baseexplorer.InitWithDSN(serverOptions.SigNoz.SQLStore.BunDB()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore.BunDB()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ export function DashboardProvider({
|
|||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const updatedDashboardData = transformDashboardVariables(data);
|
const updatedDashboardData = transformDashboardVariables(data);
|
||||||
const updatedDate = dayjs(updatedDashboardData.updated_at);
|
const updatedDate = dayjs(updatedDashboardData.updatedAt);
|
||||||
|
|
||||||
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
|
setIsDashboardLocked(updatedDashboardData?.isLocked || false);
|
||||||
|
|
||||||
@ -321,7 +321,7 @@ export function DashboardProvider({
|
|||||||
|
|
||||||
dashboardRef.current = updatedDashboardData;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
updatedTimeRef.current = dayjs(updatedDashboardData.updatedAt);
|
||||||
|
|
||||||
setLayouts(
|
setLayouts(
|
||||||
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
|
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
|
||||||
@ -334,7 +334,7 @@ export function DashboardProvider({
|
|||||||
modalRef.current = modal;
|
modalRef.current = modal;
|
||||||
} else {
|
} else {
|
||||||
// normal flow
|
// normal flow
|
||||||
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
updatedTimeRef.current = dayjs(updatedDashboardData.updatedAt);
|
||||||
|
|
||||||
dashboardRef.current = updatedDashboardData;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
|
@ -46,10 +46,10 @@ export interface IDashboardVariable {
|
|||||||
export interface Dashboard {
|
export interface Dashboard {
|
||||||
id: number;
|
id: number;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
created_at: string;
|
createdAt: string;
|
||||||
updated_at: string;
|
updatedAt: string;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
updated_by: string;
|
updatedBy: string;
|
||||||
data: DashboardData;
|
data: DashboardData;
|
||||||
isLocked?: boolean;
|
isLocked?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ var (
|
|||||||
CodeMethodNotAllowed = code{"method_not_allowed"}
|
CodeMethodNotAllowed = code{"method_not_allowed"}
|
||||||
CodeAlreadyExists = code{"already_exists"}
|
CodeAlreadyExists = code{"already_exists"}
|
||||||
CodeUnauthenticated = code{"unauthenticated"}
|
CodeUnauthenticated = code{"unauthenticated"}
|
||||||
|
CodeForbidden = code{"forbidden"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -8,6 +8,7 @@ var (
|
|||||||
TypeMethodNotAllowed = typ{"method-not-allowed"}
|
TypeMethodNotAllowed = typ{"method-not-allowed"}
|
||||||
TypeAlreadyExists = typ{"already-exists"}
|
TypeAlreadyExists = typ{"already-exists"}
|
||||||
TypeUnauthenticated = typ{"unauthenticated"}
|
TypeUnauthenticated = typ{"unauthenticated"}
|
||||||
|
TypeForbidden = typ{"forbidden"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines custom error types
|
// Defines custom error types
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/signoz/pkg/sqlstore"
|
"go.signoz.io/signoz/pkg/sqlstore"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -493,9 +493,9 @@ func (c *Controller) UpdateServiceConfig(
|
|||||||
// All dashboards that are available based on cloud integrations configuration
|
// All dashboards that are available based on cloud integrations configuration
|
||||||
// across all cloud providers
|
// across all cloud providers
|
||||||
func (c *Controller) AvailableDashboards(ctx context.Context) (
|
func (c *Controller) AvailableDashboards(ctx context.Context) (
|
||||||
[]dashboards.Dashboard, *model.ApiError,
|
[]types.Dashboard, *model.ApiError,
|
||||||
) {
|
) {
|
||||||
allDashboards := []dashboards.Dashboard{}
|
allDashboards := []types.Dashboard{}
|
||||||
|
|
||||||
for _, provider := range []string{"aws"} {
|
for _, provider := range []string{"aws"} {
|
||||||
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, provider)
|
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, provider)
|
||||||
@ -513,7 +513,7 @@ func (c *Controller) AvailableDashboards(ctx context.Context) (
|
|||||||
|
|
||||||
func (c *Controller) AvailableDashboardsForCloudProvider(
|
func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||||
ctx context.Context, cloudProvider string,
|
ctx context.Context, cloudProvider string,
|
||||||
) ([]dashboards.Dashboard, *model.ApiError) {
|
) ([]types.Dashboard, *model.ApiError) {
|
||||||
|
|
||||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, cloudProvider)
|
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, cloudProvider)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
@ -545,21 +545,25 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
svcDashboards := []dashboards.Dashboard{}
|
svcDashboards := []types.Dashboard{}
|
||||||
for _, svc := range allServices {
|
for _, svc := range allServices {
|
||||||
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
|
||||||
if serviceDashboardsCreatedAt != nil {
|
if serviceDashboardsCreatedAt != nil {
|
||||||
for _, d := range svc.Assets.Dashboards {
|
for _, d := range svc.Assets.Dashboards {
|
||||||
isLocked := 1
|
isLocked := 1
|
||||||
author := fmt.Sprintf("%s-integration", cloudProvider)
|
author := fmt.Sprintf("%s-integration", cloudProvider)
|
||||||
svcDashboards = append(svcDashboards, dashboards.Dashboard{
|
svcDashboards = append(svcDashboards, types.Dashboard{
|
||||||
Uuid: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
UUID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
||||||
Locked: &isLocked,
|
Locked: &isLocked,
|
||||||
Data: *d.Definition,
|
Data: *d.Definition,
|
||||||
CreatedAt: *serviceDashboardsCreatedAt,
|
TimeAuditable: types.TimeAuditable{
|
||||||
CreateBy: &author,
|
CreatedAt: *serviceDashboardsCreatedAt,
|
||||||
UpdatedAt: *serviceDashboardsCreatedAt,
|
UpdatedAt: *serviceDashboardsCreatedAt,
|
||||||
UpdateBy: &author,
|
},
|
||||||
|
UserAuditable: types.UserAuditable{
|
||||||
|
CreatedBy: author,
|
||||||
|
UpdatedBy: author,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
servicesWithAvailableMetrics[svc.Id] = nil
|
servicesWithAvailableMetrics[svc.Id] = nil
|
||||||
@ -571,7 +575,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
|||||||
func (c *Controller) GetDashboardById(
|
func (c *Controller) GetDashboardById(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dashboardUuid string,
|
dashboardUuid string,
|
||||||
) (*dashboards.Dashboard, *model.ApiError) {
|
) (*types.Dashboard, *model.ApiError) {
|
||||||
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
@ -585,7 +589,7 @@ func (c *Controller) GetDashboardById(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range allDashboards {
|
for _, d := range allDashboards {
|
||||||
if d.Uuid == dashboardUuid {
|
if d.UUID == dashboardUuid {
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
"go.signoz.io/signoz/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents a cloud provider account for cloud integrations
|
// Represents a cloud provider account for cloud integrations
|
||||||
@ -187,12 +187,12 @@ type CloudServiceAssets struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CloudServiceDashboard struct {
|
type CloudServiceDashboard struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
Definition *dashboards.Data `json:"definition,omitempty"`
|
Definition *types.DashboardData `json:"definition,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedSignals struct {
|
type SupportedSignals struct {
|
||||||
|
@ -2,7 +2,6 @@ package dashboards
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -11,18 +10,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gosimple/slug"
|
"github.com/uptrace/bun"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/common"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This time the global variable is unexported.
|
// This time the global variable is unexported.
|
||||||
var db *sqlx.DB
|
var db *bun.DB
|
||||||
|
|
||||||
// User for mapping job,instance from grafana
|
// User for mapping job,instance from grafana
|
||||||
var (
|
var (
|
||||||
@ -35,96 +33,43 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// InitDB sets up setting up the connection pool global variable.
|
// InitDB sets up setting up the connection pool global variable.
|
||||||
func InitDB(inputDB *sqlx.DB) error {
|
func InitDB(inputDB *bun.DB) error {
|
||||||
db = inputDB
|
db = inputDB
|
||||||
telemetry.GetInstance().SetDashboardsInfoCallback(GetDashboardsInfo)
|
telemetry.GetInstance().SetDashboardsInfoCallback(GetDashboardsInfo)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dashboard struct {
|
|
||||||
Id int `json:"id" db:"id"`
|
|
||||||
Uuid string `json:"uuid" db:"uuid"`
|
|
||||||
Slug string `json:"-" db:"-"`
|
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
||||||
CreateBy *string `json:"created_by" db:"created_by"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
||||||
UpdateBy *string `json:"updated_by" db:"updated_by"`
|
|
||||||
Title string `json:"-" db:"-"`
|
|
||||||
Data Data `json:"data" db:"data"`
|
|
||||||
Locked *int `json:"isLocked" db:"locked"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Data map[string]interface{}
|
|
||||||
|
|
||||||
// func (c *Data) Value() (driver.Value, error) {
|
|
||||||
// if c != nil {
|
|
||||||
// b, err := json.Marshal(c)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// return string(b), nil
|
|
||||||
// }
|
|
||||||
// return nil, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (c *Data) Scan(src interface{}) error {
|
|
||||||
var data []byte
|
|
||||||
if b, ok := src.([]byte); ok {
|
|
||||||
data = b
|
|
||||||
} else if s, ok := src.(string); ok {
|
|
||||||
data = []byte(s)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(data, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDashboard creates a new dashboard
|
// CreateDashboard creates a new dashboard
|
||||||
func CreateDashboard(ctx context.Context, data map[string]interface{}, fm interfaces.FeatureLookup) (*Dashboard, *model.ApiError) {
|
func CreateDashboard(ctx context.Context, orgID string, email string, data map[string]interface{}, fm interfaces.FeatureLookup) (*types.Dashboard, *model.ApiError) {
|
||||||
dash := &Dashboard{
|
dash := &types.Dashboard{
|
||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
var userEmail string
|
|
||||||
if user := common.GetUserFromContext(ctx); user != nil {
|
dash.OrgID = orgID
|
||||||
userEmail = user.Email
|
|
||||||
}
|
|
||||||
dash.CreatedAt = time.Now()
|
dash.CreatedAt = time.Now()
|
||||||
dash.CreateBy = &userEmail
|
dash.CreatedBy = email
|
||||||
dash.UpdatedAt = time.Now()
|
dash.UpdatedAt = time.Now()
|
||||||
dash.UpdateBy = &userEmail
|
dash.UpdatedBy = email
|
||||||
dash.UpdateSlug()
|
dash.UpdateSlug()
|
||||||
dash.Uuid = uuid.New().String()
|
dash.UUID = uuid.New().String()
|
||||||
if data["uuid"] != nil {
|
if data["uuid"] != nil {
|
||||||
dash.Uuid = data["uuid"].(string)
|
dash.UUID = data["uuid"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapData, err := json.Marshal(dash.Data)
|
err := db.NewInsert().Model(dash).Returning("id").Scan(ctx, &dash.ID)
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("Error in marshalling data field in dashboard: ", zap.Any("dashboard", dash), zap.Error(err))
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.Exec("INSERT INTO dashboards (uuid, created_at, created_by, updated_at, updated_by, data) VALUES ($1, $2, $3, $4, $5, $6)",
|
|
||||||
dash.Uuid, dash.CreatedAt, userEmail, dash.UpdatedAt, userEmail, mapData)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in inserting dashboard data: ", zap.Any("dashboard", dash), zap.Error(err))
|
zap.L().Error("Error in inserting dashboard data: ", zap.Any("dashboard", dash), zap.Error(err))
|
||||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
}
|
}
|
||||||
lastInsertId, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
|
||||||
}
|
|
||||||
dash.Id = int(lastInsertId)
|
|
||||||
|
|
||||||
return dash, nil
|
return dash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboards(ctx context.Context) ([]Dashboard, *model.ApiError) {
|
func GetDashboards(ctx context.Context, orgID string) ([]types.Dashboard, *model.ApiError) {
|
||||||
|
dashboards := []types.Dashboard{}
|
||||||
|
|
||||||
dashboards := []Dashboard{}
|
err := db.NewSelect().Model(&dashboards).Where("org_id = ?", orgID).Scan(ctx)
|
||||||
query := `SELECT * FROM dashboards`
|
|
||||||
|
|
||||||
err := db.Select(&dashboards, query)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
}
|
}
|
||||||
@ -132,23 +77,19 @@ func GetDashboards(ctx context.Context) ([]Dashboard, *model.ApiError) {
|
|||||||
return dashboards, nil
|
return dashboards, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteDashboard(ctx context.Context, uuid string, fm interfaces.FeatureLookup) *model.ApiError {
|
func DeleteDashboard(ctx context.Context, orgID, uuid string, fm interfaces.FeatureLookup) *model.ApiError {
|
||||||
|
|
||||||
dashboard, dErr := GetDashboard(ctx, uuid)
|
dashboard, dErr := GetDashboard(ctx, orgID, uuid)
|
||||||
if dErr != nil {
|
if dErr != nil {
|
||||||
zap.L().Error("Error in getting dashboard: ", zap.String("uuid", uuid), zap.Any("error", dErr))
|
zap.L().Error("Error in getting dashboard: ", zap.String("uuid", uuid), zap.Any("error", dErr))
|
||||||
return dErr
|
return dErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if user := common.GetUserFromContext(ctx); user != nil {
|
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
||||||
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
return model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to delete it"))
|
||||||
return model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to delete it"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `DELETE FROM dashboards WHERE uuid=?`
|
result, err := db.NewDelete().Model(&types.Dashboard{}).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Exec(ctx)
|
||||||
|
|
||||||
result, err := db.Exec(query, uuid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &model.ApiError{Typ: model.ErrorExec, Err: err}
|
return &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
}
|
}
|
||||||
@ -164,12 +105,10 @@ func DeleteDashboard(ctx context.Context, uuid string, fm interfaces.FeatureLook
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboard(ctx context.Context, uuid string) (*Dashboard, *model.ApiError) {
|
func GetDashboard(ctx context.Context, orgID, uuid string) (*types.Dashboard, *model.ApiError) {
|
||||||
|
|
||||||
dashboard := Dashboard{}
|
dashboard := types.Dashboard{}
|
||||||
query := `SELECT * FROM dashboards WHERE uuid=?`
|
err := db.NewSelect().Model(&dashboard).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Scan(ctx)
|
||||||
|
|
||||||
err := db.Get(&dashboard, query, uuid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no dashboard found with uuid: %s", uuid)}
|
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no dashboard found with uuid: %s", uuid)}
|
||||||
}
|
}
|
||||||
@ -177,7 +116,7 @@ func GetDashboard(ctx context.Context, uuid string) (*Dashboard, *model.ApiError
|
|||||||
return &dashboard, nil
|
return &dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface{}, fm interfaces.FeatureLookup) (*Dashboard, *model.ApiError) {
|
func UpdateDashboard(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}, fm interfaces.FeatureLookup) (*types.Dashboard, *model.ApiError) {
|
||||||
|
|
||||||
mapData, err := json.Marshal(data)
|
mapData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,17 +124,13 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
|
|||||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard, apiErr := GetDashboard(ctx, uuid)
|
dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var userEmail string
|
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
||||||
if user := common.GetUserFromContext(ctx); user != nil {
|
return nil, model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to edit it"))
|
||||||
userEmail = user.Email
|
|
||||||
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
|
||||||
return nil, model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to edit it"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the total count of panels has reduced by more than 1,
|
// if the total count of panels has reduced by more than 1,
|
||||||
@ -210,11 +145,10 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboard.UpdatedAt = time.Now()
|
dashboard.UpdatedAt = time.Now()
|
||||||
dashboard.UpdateBy = &userEmail
|
dashboard.UpdatedBy = userEmail
|
||||||
dashboard.Data = data
|
dashboard.Data = data
|
||||||
|
|
||||||
_, err = db.Exec("UPDATE dashboards SET updated_at=$1, updated_by=$2, data=$3 WHERE uuid=$4;",
|
_, err = db.NewUpdate().Model(dashboard).Set("updated_at = ?", dashboard.UpdatedAt).Set("updated_by = ?", userEmail).Set("data = ?", mapData).Where("uuid = ?", dashboard.UUID).Exec(ctx)
|
||||||
dashboard.UpdatedAt, userEmail, mapData, dashboard.Uuid)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in inserting dashboard data", zap.Any("data", data), zap.Error(err))
|
zap.L().Error("Error in inserting dashboard data", zap.Any("data", data), zap.Error(err))
|
||||||
@ -223,16 +157,20 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
|
|||||||
return dashboard, nil
|
return dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LockUnlockDashboard(ctx context.Context, uuid string, lock bool) *model.ApiError {
|
func LockUnlockDashboard(ctx context.Context, orgID, uuid string, lock bool) *model.ApiError {
|
||||||
var query string
|
dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
|
||||||
if lock {
|
if apiErr != nil {
|
||||||
query = `UPDATE dashboards SET locked=1 WHERE uuid=?;`
|
return apiErr
|
||||||
} else {
|
|
||||||
query = `UPDATE dashboards SET locked=0 WHERE uuid=?;`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := db.Exec(query, uuid)
|
var lockValue int
|
||||||
|
if lock {
|
||||||
|
lockValue = 1
|
||||||
|
} else {
|
||||||
|
lockValue = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.NewUpdate().Model(dashboard).Set("locked = ?", lockValue).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in updating dashboard", zap.String("uuid", uuid), zap.Error(err))
|
zap.L().Error("Error in updating dashboard", zap.String("uuid", uuid), zap.Error(err))
|
||||||
return &model.ApiError{Typ: model.ErrorExec, Err: err}
|
return &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
@ -241,17 +179,6 @@ func LockUnlockDashboard(ctx context.Context, uuid string, lock bool) *model.Api
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSlug updates the slug
|
|
||||||
func (d *Dashboard) UpdateSlug() {
|
|
||||||
var title string
|
|
||||||
|
|
||||||
if val, ok := d.Data["title"]; ok {
|
|
||||||
title = val.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Slug = SlugifyTitle(title)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsPostDataSane(data *map[string]interface{}) error {
|
func IsPostDataSane(data *map[string]interface{}) error {
|
||||||
val, ok := (*data)["title"]
|
val, ok := (*data)["title"]
|
||||||
if !ok || val == nil {
|
if !ok || val == nil {
|
||||||
@ -261,21 +188,6 @@ func IsPostDataSane(data *map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SlugifyTitle(title string) string {
|
|
||||||
s := slug.Make(strings.ToLower(title))
|
|
||||||
if s == "" {
|
|
||||||
// If the dashboard name is only characters outside of the
|
|
||||||
// sluggable characters, the slug creation will return an
|
|
||||||
// empty string which will mess up URLs. This failsafe picks
|
|
||||||
// that up and creates the slug as a base64 identifier instead.
|
|
||||||
s = base64.RawURLEncoding.EncodeToString([]byte(title))
|
|
||||||
if slug.MaxLength != 0 && len(s) > slug.MaxLength {
|
|
||||||
s = s[:slug.MaxLength]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWidgetIds(data map[string]interface{}) []string {
|
func getWidgetIds(data map[string]interface{}) []string {
|
||||||
widgetIds := []string{}
|
widgetIds := []string{}
|
||||||
if data != nil && data["widgets"] != nil {
|
if data != nil && data["widgets"] != nil {
|
||||||
@ -329,9 +241,8 @@ func getIdDifference(existingIds []string, newIds []string) []string {
|
|||||||
func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
|
func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
|
||||||
dashboardsInfo := model.DashboardsInfo{}
|
dashboardsInfo := model.DashboardsInfo{}
|
||||||
// fetch dashboards from dashboard db
|
// fetch dashboards from dashboard db
|
||||||
query := "SELECT data FROM dashboards"
|
dashboards := []types.Dashboard{}
|
||||||
var dashboardsData []Dashboard
|
err := db.NewSelect().Model(&dashboards).Scan(ctx)
|
||||||
err := db.Select(&dashboardsData, query)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||||
return &dashboardsInfo, err
|
return &dashboardsInfo, err
|
||||||
@ -340,7 +251,7 @@ func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
|
|||||||
var dashboardNames []string
|
var dashboardNames []string
|
||||||
count := 0
|
count := 0
|
||||||
queriesWithTagAttrs := 0
|
queriesWithTagAttrs := 0
|
||||||
for _, dashboard := range dashboardsData {
|
for _, dashboard := range dashboards {
|
||||||
if isDashboardWithPanelAndName(dashboard.Data) {
|
if isDashboardWithPanelAndName(dashboard.Data) {
|
||||||
totalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName + 1
|
totalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName + 1
|
||||||
}
|
}
|
||||||
@ -371,7 +282,7 @@ func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboardsInfo.DashboardNames = dashboardNames
|
dashboardsInfo.DashboardNames = dashboardNames
|
||||||
dashboardsInfo.TotalDashboards = len(dashboardsData)
|
dashboardsInfo.TotalDashboards = len(dashboards)
|
||||||
dashboardsInfo.TotalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName
|
dashboardsInfo.TotalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName
|
||||||
dashboardsInfo.QueriesWithTSV2 = count
|
dashboardsInfo.QueriesWithTSV2 = count
|
||||||
dashboardsInfo.QueriesWithTagAttrs = queriesWithTagAttrs
|
dashboardsInfo.QueriesWithTagAttrs = queriesWithTagAttrs
|
||||||
@ -538,17 +449,13 @@ func countPanelsInDashboard(inputData map[string]interface{}) model.DashboardsIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboardsWithMetricNames(ctx context.Context, metricNames []string) (map[string][]map[string]string, *model.ApiError) {
|
func GetDashboardsWithMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, *model.ApiError) {
|
||||||
// Get all dashboards first
|
dashboards := []types.Dashboard{}
|
||||||
query := `SELECT uuid, data FROM dashboards`
|
err := db.NewSelect().Model(&dashboards).Where("org_id = ?", orgID).Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
type dashboardRow struct {
|
zap.L().Error("Error in getting dashboards", zap.Error(err))
|
||||||
Uuid string `db:"uuid"`
|
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
Data json.RawMessage `db:"data"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dashboards []dashboardRow
|
|
||||||
err := db.Select(&dashboards, query)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in getting dashboards", zap.Error(err))
|
zap.L().Error("Error in getting dashboards", zap.Error(err))
|
||||||
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
|
||||||
@ -556,16 +463,10 @@ func GetDashboardsWithMetricNames(ctx context.Context, metricNames []string) (ma
|
|||||||
|
|
||||||
// Initialize result map for each metric
|
// Initialize result map for each metric
|
||||||
result := make(map[string][]map[string]string)
|
result := make(map[string][]map[string]string)
|
||||||
// for _, metricName := range metricNames {
|
|
||||||
// result[metricName] = []map[string]string{}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Process the JSON data in Go
|
// Process the JSON data in Go
|
||||||
for _, dashboard := range dashboards {
|
for _, dashboard := range dashboards {
|
||||||
var dashData map[string]interface{}
|
var dashData = dashboard.Data
|
||||||
if err := json.Unmarshal(dashboard.Data, &dashData); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dashTitle, _ := dashData["title"].(string)
|
dashTitle, _ := dashData["title"].(string)
|
||||||
widgets, ok := dashData["widgets"].([]interface{})
|
widgets, ok := dashData["widgets"].([]interface{})
|
||||||
@ -617,7 +518,7 @@ func GetDashboardsWithMetricNames(ctx context.Context, metricNames []string) (ma
|
|||||||
for _, metricName := range metricNames {
|
for _, metricName := range metricNames {
|
||||||
if strings.TrimSpace(key) == metricName {
|
if strings.TrimSpace(key) == metricName {
|
||||||
result[metricName] = append(result[metricName], map[string]string{
|
result[metricName] = append(result[metricName], map[string]string{
|
||||||
"dashboard_id": dashboard.Uuid,
|
"dashboard_id": dashboard.UUID,
|
||||||
"widget_title": widgetTitle,
|
"widget_title": widgetTitle,
|
||||||
"widget_id": widgetID,
|
"widget_id": widgetID,
|
||||||
"dashboard_title": dashTitle,
|
"dashboard_title": dashTitle,
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package dashboards
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readCurrentDir(dir string, fm interfaces.FeatureLookup) error {
|
|
||||||
file, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Warn("failed opening directory", zap.Error(err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
list, _ := file.Readdirnames(0) // 0 to read all files and folders
|
|
||||||
for _, filename := range list {
|
|
||||||
if strings.ToLower(filepath.Ext(filename)) != ".json" {
|
|
||||||
zap.L().Debug("Skipping non-json file", zap.String("filename", filename))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
zap.L().Info("Provisioning dashboard: ", zap.String("filename", filename))
|
|
||||||
|
|
||||||
// using filepath.Join for platform specific path creation
|
|
||||||
// which is equivalent to "dir+/+filename" (on unix based systems) but cleaner
|
|
||||||
plan, err := os.ReadFile(filepath.Join(dir, filename))
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("Creating Dashboards: Error in reading json fron file", zap.String("filename", filename), zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var data map[string]interface{}
|
|
||||||
err = json.Unmarshal(plan, &data)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("Creating Dashboards: Error in unmarshalling json from file", zap.String("filename", filename), zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = IsPostDataSane(&data)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Info("Creating Dashboards: Error in file", zap.String("filename", filename), zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id := data["uuid"]
|
|
||||||
if id == nil {
|
|
||||||
_, apiErr := CreateDashboard(context.Background(), data, fm)
|
|
||||||
if apiErr != nil {
|
|
||||||
zap.L().Error("Creating Dashboards: Error in file", zap.String("filename", filename), zap.Error(apiErr.Err))
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
apiErr := upsertDashboard(id.(string), data, filename, fm)
|
|
||||||
if apiErr != nil {
|
|
||||||
zap.L().Error("Creating Dashboards: Error upserting dashboard", zap.String("filename", filename), zap.Error(apiErr.Err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func upsertDashboard(uuid string, data map[string]interface{}, filename string, fm interfaces.FeatureLookup) *model.ApiError {
|
|
||||||
_, apiErr := GetDashboard(context.Background(), uuid)
|
|
||||||
if apiErr == nil {
|
|
||||||
zap.S().Infof("Creating Dashboards: Already exists: %s\t%s", filename, "Dashboard already present in database, Updating dashboard")
|
|
||||||
_, apiErr := UpdateDashboard(context.Background(), uuid, data, fm)
|
|
||||||
return apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
zap.S().Infof("Creating Dashboards: UUID not found: %s\t%s", filename, "Dashboard not present in database, Creating dashboard")
|
|
||||||
_, apiErr = CreateDashboard(context.Background(), data, fm)
|
|
||||||
return apiErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadDashboardFiles(fm interfaces.FeatureLookup) error {
|
|
||||||
dashboardsPath := constants.GetOrDefaultEnv("DASHBOARDS_PATH", "./config/dashboards")
|
|
||||||
return readCurrentDir(dashboardsPath, fm)
|
|
||||||
}
|
|
@ -9,45 +9,32 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/uptrace/bun"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *sqlx.DB
|
var db *bun.DB
|
||||||
|
|
||||||
type SavedView struct {
|
|
||||||
UUID string `json:"uuid" db:"uuid"`
|
|
||||||
Name string `json:"name" db:"name"`
|
|
||||||
Category string `json:"category" db:"category"`
|
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
||||||
CreatedBy string `json:"created_by" db:"created_by"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
||||||
UpdatedBy string `json:"updated_by" db:"updated_by"`
|
|
||||||
SourcePage string `json:"source_page" db:"source_page"`
|
|
||||||
Tags string `json:"tags" db:"tags"`
|
|
||||||
Data string `json:"data" db:"data"`
|
|
||||||
ExtraData string `json:"extra_data" db:"extra_data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitWithDSN sets up setting up the connection pool global variable.
|
// InitWithDSN sets up setting up the connection pool global variable.
|
||||||
func InitWithDSN(inputDB *sqlx.DB) error {
|
func InitWithDSN(inputDB *bun.DB) error {
|
||||||
db = inputDB
|
db = inputDB
|
||||||
telemetry.GetInstance().SetSavedViewsInfoCallback(GetSavedViewsInfo)
|
telemetry.GetInstance().SetSavedViewsInfoCallback(GetSavedViewsInfo)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitWithDB(sqlDB *sqlx.DB) {
|
func InitWithDB(bunDB *bun.DB) {
|
||||||
db = sqlDB
|
db = bunDB
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetViews() ([]*v3.SavedView, error) {
|
func GetViews(ctx context.Context, orgID string) ([]*v3.SavedView, error) {
|
||||||
var views []SavedView
|
var views []types.SavedView
|
||||||
err := db.Select(&views, "SELECT * FROM saved_views")
|
err := db.NewSelect().Model(&views).Where("org_id = ?", orgID).Scan(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -76,13 +63,13 @@ func GetViews() ([]*v3.SavedView, error) {
|
|||||||
return savedViews, nil
|
return savedViews, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetViewsForFilters(sourcePage string, name string, category string) ([]*v3.SavedView, error) {
|
func GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error) {
|
||||||
var views []SavedView
|
var views []types.SavedView
|
||||||
var err error
|
var err error
|
||||||
if len(category) == 0 {
|
if len(category) == 0 {
|
||||||
err = db.Select(&views, "SELECT * FROM saved_views WHERE source_page = ? AND name LIKE ?", sourcePage, "%"+name+"%")
|
err = db.NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND name LIKE ?", orgID, sourcePage, "%"+name+"%").Scan(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = db.Select(&views, "SELECT * FROM saved_views WHERE source_page = ? AND category LIKE ? AND name LIKE ?", sourcePage, "%"+category+"%", "%"+name+"%")
|
err = db.NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND category LIKE ? AND name LIKE ?", orgID, sourcePage, "%"+category+"%", "%"+name+"%").Scan(ctx)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||||
@ -111,7 +98,7 @@ func GetViewsForFilters(sourcePage string, name string, category string) ([]*v3.
|
|||||||
return savedViews, nil
|
return savedViews, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateView(ctx context.Context, view v3.SavedView) (string, error) {
|
func CreateView(ctx context.Context, orgID string, view v3.SavedView) (string, error) {
|
||||||
data, err := json.Marshal(view.CompositeQuery)
|
data, err := json.Marshal(view.CompositeQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
return "", fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||||
@ -133,29 +120,35 @@ func CreateView(ctx context.Context, view v3.SavedView) (string, error) {
|
|||||||
createBy := claims.Email
|
createBy := claims.Email
|
||||||
updatedBy := claims.Email
|
updatedBy := claims.Email
|
||||||
|
|
||||||
_, err = db.Exec(
|
dbView := types.SavedView{
|
||||||
"INSERT INTO saved_views (uuid, name, category, created_at, created_by, updated_at, updated_by, source_page, tags, data, extra_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
TimeAuditable: types.TimeAuditable{
|
||||||
uuid_,
|
CreatedAt: createdAt,
|
||||||
view.Name,
|
UpdatedAt: updatedAt,
|
||||||
view.Category,
|
},
|
||||||
createdAt,
|
UserAuditable: types.UserAuditable{
|
||||||
createBy,
|
CreatedBy: createBy,
|
||||||
updatedAt,
|
UpdatedBy: updatedBy,
|
||||||
updatedBy,
|
},
|
||||||
view.SourcePage,
|
OrgID: orgID,
|
||||||
strings.Join(view.Tags, ","),
|
UUID: uuid_,
|
||||||
data,
|
Name: view.Name,
|
||||||
view.ExtraData,
|
Category: view.Category,
|
||||||
)
|
SourcePage: view.SourcePage,
|
||||||
|
Tags: strings.Join(view.Tags, ","),
|
||||||
|
Data: string(data),
|
||||||
|
ExtraData: view.ExtraData,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.NewInsert().Model(&dbView).Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error in creating saved view: %s", err.Error())
|
return "", fmt.Errorf("error in creating saved view: %s", err.Error())
|
||||||
}
|
}
|
||||||
return uuid_, nil
|
return uuid_, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetView(uuid_ string) (*v3.SavedView, error) {
|
func GetView(ctx context.Context, orgID string, uuid_ string) (*v3.SavedView, error) {
|
||||||
var view SavedView
|
var view types.SavedView
|
||||||
err := db.Get(&view, "SELECT * FROM saved_views WHERE uuid = ?", uuid_)
|
err := db.NewSelect().Model(&view).Where("org_id = ? AND uuid = ?", orgID, uuid_).Scan(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error in getting saved view: %s", err.Error())
|
return nil, fmt.Errorf("error in getting saved view: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -180,7 +173,7 @@ func GetView(uuid_ string) (*v3.SavedView, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateView(ctx context.Context, uuid_ string, view v3.SavedView) error {
|
func UpdateView(ctx context.Context, orgID string, uuid_ string, view v3.SavedView) error {
|
||||||
data, err := json.Marshal(view.CompositeQuery)
|
data, err := json.Marshal(view.CompositeQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
return fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||||
@ -194,16 +187,25 @@ func UpdateView(ctx context.Context, uuid_ string, view v3.SavedView) error {
|
|||||||
updatedAt := time.Now()
|
updatedAt := time.Now()
|
||||||
updatedBy := claims.Email
|
updatedBy := claims.Email
|
||||||
|
|
||||||
_, err = db.Exec("UPDATE saved_views SET updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ? WHERE uuid = ?",
|
_, err = db.NewUpdate().
|
||||||
updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData, uuid_)
|
Model(&types.SavedView{}).
|
||||||
|
Set("updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ?",
|
||||||
|
updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData).
|
||||||
|
Where("uuid = ?", uuid_).
|
||||||
|
Where("org_id = ?", orgID).
|
||||||
|
Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in updating saved view: %s", err.Error())
|
return fmt.Errorf("error in updating saved view: %s", err.Error())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteView(uuid_ string) error {
|
func DeleteView(ctx context.Context, orgID string, uuid_ string) error {
|
||||||
_, err := db.Exec("DELETE FROM saved_views WHERE uuid = ?", uuid_)
|
_, err := db.NewDelete().
|
||||||
|
Model(&types.SavedView{}).
|
||||||
|
Where("uuid = ?", uuid_).
|
||||||
|
Where("org_id = ?", orgID).
|
||||||
|
Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in deleting explorer query: %s", err.Error())
|
return fmt.Errorf("error in deleting explorer query: %s", err.Error())
|
||||||
}
|
}
|
||||||
@ -212,7 +214,17 @@ func DeleteView(uuid_ string) error {
|
|||||||
|
|
||||||
func GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) {
|
func GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) {
|
||||||
savedViewsInfo := model.SavedViewsInfo{}
|
savedViewsInfo := model.SavedViewsInfo{}
|
||||||
savedViews, err := GetViews()
|
// get single org ID from db
|
||||||
|
var orgIDs []string
|
||||||
|
err := db.NewSelect().Model((*types.Organization)(nil)).Column("id").Scan(ctx, &orgIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in getting org IDs: %s", err.Error())
|
||||||
|
}
|
||||||
|
if len(orgIDs) != 1 {
|
||||||
|
zap.S().Warn("GetSavedViewsInfo: Zero or multiple org IDs found in the database", zap.Int("orgIDs", len(orgIDs)))
|
||||||
|
return &savedViewsInfo, nil
|
||||||
|
}
|
||||||
|
savedViews, err := GetViews(ctx, orgIDs[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Debug("Error in fetching saved views info: ", err)
|
zap.S().Debug("Error in fetching saved views info: ", err)
|
||||||
return &savedViewsInfo, err
|
return &savedViewsInfo, err
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/alertmanager"
|
"go.signoz.io/signoz/pkg/alertmanager"
|
||||||
|
errorsV2 "go.signoz.io/signoz/pkg/errors"
|
||||||
|
"go.signoz.io/signoz/pkg/http/render"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
|
"go.signoz.io/signoz/pkg/query-service/app/metricsexplorer"
|
||||||
"go.signoz.io/signoz/pkg/signoz"
|
"go.signoz.io/signoz/pkg/signoz"
|
||||||
|
|
||||||
@ -49,7 +51,6 @@ import (
|
|||||||
tracesV4 "go.signoz.io/signoz/pkg/query-service/app/traces/v4"
|
tracesV4 "go.signoz.io/signoz/pkg/query-service/app/traces/v4"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||||
"go.signoz.io/signoz/pkg/query-service/common"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/contextlinks"
|
"go.signoz.io/signoz/pkg/query-service/contextlinks"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
@ -274,11 +275,6 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
|||||||
}
|
}
|
||||||
aH.queryBuilder = queryBuilder.NewQueryBuilder(builderOpts, aH.featureFlags)
|
aH.queryBuilder = queryBuilder.NewQueryBuilder(builderOpts, aH.featureFlags)
|
||||||
|
|
||||||
dashboards.LoadDashboardFiles(aH.featureFlags)
|
|
||||||
// if errReadingDashboards != nil {
|
|
||||||
// return nil, errReadingDashboards
|
|
||||||
// }
|
|
||||||
|
|
||||||
// check if at least one user is created
|
// check if at least one user is created
|
||||||
hasUsers, err := aH.appDao.GetUsersWithOpts(context.Background(), 1)
|
hasUsers, err := aH.appDao.GetUsersWithOpts(context.Background(), 1)
|
||||||
if err.Error() != "" {
|
if err.Error() != "" {
|
||||||
@ -1059,7 +1055,12 @@ func (aH *APIHandler) listRules(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
allDashboards, err := dashboards.GetDashboards(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
allDashboards, err := dashboards.GetDashboards(r.Context(), claims.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, err, nil)
|
RespondError(w, err, nil)
|
||||||
return
|
return
|
||||||
@ -1113,7 +1114,7 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
|||||||
inter = Intersection(inter, tags2Dash[tag])
|
inter = Intersection(inter, tags2Dash[tag])
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredDashboards := []dashboards.Dashboard{}
|
filteredDashboards := []types.Dashboard{}
|
||||||
for _, val := range inter {
|
for _, val := range inter {
|
||||||
dash := (allDashboards)[val]
|
dash := (allDashboards)[val]
|
||||||
filteredDashboards = append(filteredDashboards, dash)
|
filteredDashboards = append(filteredDashboards, dash)
|
||||||
@ -1125,7 +1126,12 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
uuid := mux.Vars(r)["uuid"]
|
uuid := mux.Vars(r)["uuid"]
|
||||||
err := dashboards.DeleteDashboard(r.Context(), uuid, aH.featureFlags)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := dashboards.DeleteDashboard(r.Context(), claims.OrgID, uuid, aH.featureFlags)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, err, nil)
|
RespondError(w, err, nil)
|
||||||
@ -1212,7 +1218,12 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard, apiError := dashboards.UpdateDashboard(r.Context(), uuid, postData, aH.featureFlags)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard, apiError := dashboards.UpdateDashboard(r.Context(), claims.OrgID, claims.Email, uuid, postData, aH.featureFlags)
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
RespondError(w, apiError, nil)
|
RespondError(w, apiError, nil)
|
||||||
return
|
return
|
||||||
@ -1226,7 +1237,12 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
uuid := mux.Vars(r)["uuid"]
|
uuid := mux.Vars(r)["uuid"]
|
||||||
|
|
||||||
dashboard, apiError := dashboards.GetDashboard(r.Context(), uuid)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dashboard, apiError := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
|
||||||
|
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
if apiError.Type() != model.ErrorNotFound {
|
if apiError.Type() != model.ErrorNotFound {
|
||||||
@ -1275,8 +1291,12 @@ func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
|||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body")
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
dash, apiErr := dashboards.CreateDashboard(r.Context(), postData, aH.featureFlags)
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dash, apiErr := dashboards.CreateDashboard(r.Context(), claims.OrgID, claims.Email, postData, aH.featureFlags)
|
||||||
|
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
@ -3385,10 +3405,14 @@ func (aH *APIHandler) getUserPreference(
|
|||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
) {
|
) {
|
||||||
preferenceId := mux.Vars(r)["preferenceId"]
|
preferenceId := mux.Vars(r)["preferenceId"]
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
preference, apiErr := preferences.GetUserPreference(
|
preference, apiErr := preferences.GetUserPreference(
|
||||||
r.Context(), preferenceId, user.User.OrgID, user.User.ID,
|
r.Context(), preferenceId, claims.OrgID, claims.UserID,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
@ -3402,7 +3426,11 @@ func (aH *APIHandler) updateUserPreference(
|
|||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
) {
|
) {
|
||||||
preferenceId := mux.Vars(r)["preferenceId"]
|
preferenceId := mux.Vars(r)["preferenceId"]
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
req := preferences.UpdatePreference{}
|
req := preferences.UpdatePreference{}
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
@ -3411,7 +3439,7 @@ func (aH *APIHandler) updateUserPreference(
|
|||||||
RespondError(w, model.BadRequest(err), nil)
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.ID)
|
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, claims.UserID)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
return
|
return
|
||||||
@ -3423,9 +3451,13 @@ func (aH *APIHandler) updateUserPreference(
|
|||||||
func (aH *APIHandler) getAllUserPreferences(
|
func (aH *APIHandler) getAllUserPreferences(
|
||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
) {
|
) {
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
preference, apiErr := preferences.GetAllUserPreferences(
|
preference, apiErr := preferences.GetAllUserPreferences(
|
||||||
r.Context(), user.User.OrgID, user.User.ID,
|
r.Context(), claims.OrgID, claims.UserID,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
@ -3439,9 +3471,13 @@ func (aH *APIHandler) getOrgPreference(
|
|||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
) {
|
) {
|
||||||
preferenceId := mux.Vars(r)["preferenceId"]
|
preferenceId := mux.Vars(r)["preferenceId"]
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
preference, apiErr := preferences.GetOrgPreference(
|
preference, apiErr := preferences.GetOrgPreference(
|
||||||
r.Context(), preferenceId, user.User.OrgID,
|
r.Context(), preferenceId, claims.OrgID,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
@ -3456,7 +3492,11 @@ func (aH *APIHandler) updateOrgPreference(
|
|||||||
) {
|
) {
|
||||||
preferenceId := mux.Vars(r)["preferenceId"]
|
preferenceId := mux.Vars(r)["preferenceId"]
|
||||||
req := preferences.UpdatePreference{}
|
req := preferences.UpdatePreference{}
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
|
||||||
@ -3464,7 +3504,7 @@ func (aH *APIHandler) updateOrgPreference(
|
|||||||
RespondError(w, model.BadRequest(err), nil)
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.OrgID)
|
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, claims.OrgID)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
return
|
return
|
||||||
@ -3476,9 +3516,13 @@ func (aH *APIHandler) updateOrgPreference(
|
|||||||
func (aH *APIHandler) getAllOrgPreferences(
|
func (aH *APIHandler) getAllOrgPreferences(
|
||||||
w http.ResponseWriter, r *http.Request,
|
w http.ResponseWriter, r *http.Request,
|
||||||
) {
|
) {
|
||||||
user := common.GetUserFromContext(r.Context())
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
preference, apiErr := preferences.GetAllOrgPreferences(
|
preference, apiErr := preferences.GetAllOrgPreferences(
|
||||||
r.Context(), user.User.OrgID,
|
r.Context(), claims.OrgID,
|
||||||
)
|
)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, apiErr, nil)
|
RespondError(w, apiErr, nil)
|
||||||
@ -4525,7 +4569,12 @@ func (aH *APIHandler) getSavedViews(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := r.URL.Query().Get("name")
|
name := r.URL.Query().Get("name")
|
||||||
category := r.URL.Query().Get("category")
|
category := r.URL.Query().Get("category")
|
||||||
|
|
||||||
queries, err := explorer.GetViewsForFilters(sourcePage, name, category)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queries, err := explorer.GetViewsForFilters(r.Context(), claims.OrgID, sourcePage, name, category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
return
|
return
|
||||||
@ -4545,7 +4594,13 @@ func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) {
|
|||||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uuid, err := explorer.CreateView(r.Context(), view)
|
|
||||||
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uuid, err := explorer.CreateView(r.Context(), claims.OrgID, view)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
return
|
return
|
||||||
@ -4556,7 +4611,12 @@ func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) {
|
||||||
viewID := mux.Vars(r)["viewId"]
|
viewID := mux.Vars(r)["viewId"]
|
||||||
view, err := explorer.GetView(viewID)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
view, err := explorer.GetView(r.Context(), claims.OrgID, viewID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
return
|
return
|
||||||
@ -4579,7 +4639,12 @@ func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = explorer.UpdateView(r.Context(), viewID, view)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = explorer.UpdateView(r.Context(), claims.OrgID, viewID, view)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
return
|
return
|
||||||
@ -4591,7 +4656,12 @@ func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (aH *APIHandler) deleteSavedView(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) deleteSavedView(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
viewID := mux.Vars(r)["viewId"]
|
viewID := mux.Vars(r)["viewId"]
|
||||||
err := explorer.DeleteView(viewID)
|
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := explorer.DeleteView(r.Context(), claims.OrgID, viewID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||||
return
|
return
|
||||||
|
@ -5,10 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/agentConf"
|
"go.signoz.io/signoz/pkg/query-service/agentConf"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/signoz/pkg/sqlstore"
|
"go.signoz.io/signoz/pkg/sqlstore"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@ -130,12 +130,12 @@ func (c *Controller) GetPipelinesForInstalledIntegrations(
|
|||||||
|
|
||||||
func (c *Controller) GetDashboardsForInstalledIntegrations(
|
func (c *Controller) GetDashboardsForInstalledIntegrations(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]dashboards.Dashboard, *model.ApiError) {
|
) ([]types.Dashboard, *model.ApiError) {
|
||||||
return c.mgr.GetDashboardsForInstalledIntegrations(ctx)
|
return c.mgr.GetDashboardsForInstalledIntegrations(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) GetInstalledIntegrationDashboardById(
|
func (c *Controller) GetInstalledIntegrationDashboardById(
|
||||||
ctx context.Context, dashboardUuid string,
|
ctx context.Context, dashboardUuid string,
|
||||||
) (*dashboards.Dashboard, *model.ApiError) {
|
) (*types.Dashboard, *model.ApiError) {
|
||||||
return c.mgr.GetInstalledIntegrationDashboardById(ctx, dashboardUuid)
|
return c.mgr.GetInstalledIntegrationDashboardById(ctx, dashboardUuid)
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/signoz/pkg/query-service/rules"
|
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationAuthor struct {
|
type IntegrationAuthor struct {
|
||||||
@ -32,8 +32,8 @@ type IntegrationSummary struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IntegrationAssets struct {
|
type IntegrationAssets struct {
|
||||||
Logs LogsAssets `json:"logs"`
|
Logs LogsAssets `json:"logs"`
|
||||||
Dashboards []dashboards.Data `json:"dashboards"`
|
Dashboards []types.DashboardData `json:"dashboards"`
|
||||||
|
|
||||||
Alerts []rules.PostableRule `json:"alerts"`
|
Alerts []rules.PostableRule `json:"alerts"`
|
||||||
}
|
}
|
||||||
@ -306,7 +306,7 @@ func (m *Manager) parseDashboardUuid(dashboardUuid string) (
|
|||||||
func (m *Manager) GetInstalledIntegrationDashboardById(
|
func (m *Manager) GetInstalledIntegrationDashboardById(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dashboardUuid string,
|
dashboardUuid string,
|
||||||
) (*dashboards.Dashboard, *model.ApiError) {
|
) (*types.Dashboard, *model.ApiError) {
|
||||||
integrationId, dashboardId, apiErr := m.parseDashboardUuid(dashboardUuid)
|
integrationId, dashboardId, apiErr := m.parseDashboardUuid(dashboardUuid)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
@ -328,14 +328,18 @@ func (m *Manager) GetInstalledIntegrationDashboardById(
|
|||||||
if id, ok := dId.(string); ok && id == dashboardId {
|
if id, ok := dId.(string); ok && id == dashboardId {
|
||||||
isLocked := 1
|
isLocked := 1
|
||||||
author := "integration"
|
author := "integration"
|
||||||
return &dashboards.Dashboard{
|
return &types.Dashboard{
|
||||||
Uuid: m.dashboardUuid(integrationId, string(dashboardId)),
|
UUID: m.dashboardUuid(integrationId, string(dashboardId)),
|
||||||
Locked: &isLocked,
|
Locked: &isLocked,
|
||||||
Data: dd,
|
Data: dd,
|
||||||
CreatedAt: integration.Installation.InstalledAt,
|
TimeAuditable: types.TimeAuditable{
|
||||||
CreateBy: &author,
|
CreatedAt: integration.Installation.InstalledAt,
|
||||||
UpdatedAt: integration.Installation.InstalledAt,
|
UpdatedAt: integration.Installation.InstalledAt,
|
||||||
UpdateBy: &author,
|
},
|
||||||
|
UserAuditable: types.UserAuditable{
|
||||||
|
CreatedBy: author,
|
||||||
|
UpdatedBy: author,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,13 +352,13 @@ func (m *Manager) GetInstalledIntegrationDashboardById(
|
|||||||
|
|
||||||
func (m *Manager) GetDashboardsForInstalledIntegrations(
|
func (m *Manager) GetDashboardsForInstalledIntegrations(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]dashboards.Dashboard, *model.ApiError) {
|
) ([]types.Dashboard, *model.ApiError) {
|
||||||
installedIntegrations, apiErr := m.getInstalledIntegrations(ctx)
|
installedIntegrations, apiErr := m.getInstalledIntegrations(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return nil, apiErr
|
return nil, apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
result := []dashboards.Dashboard{}
|
result := []types.Dashboard{}
|
||||||
|
|
||||||
for _, ii := range installedIntegrations {
|
for _, ii := range installedIntegrations {
|
||||||
for _, dd := range ii.Assets.Dashboards {
|
for _, dd := range ii.Assets.Dashboards {
|
||||||
@ -362,14 +366,18 @@ func (m *Manager) GetDashboardsForInstalledIntegrations(
|
|||||||
if dashboardId, ok := dId.(string); ok {
|
if dashboardId, ok := dId.(string); ok {
|
||||||
isLocked := 1
|
isLocked := 1
|
||||||
author := "integration"
|
author := "integration"
|
||||||
result = append(result, dashboards.Dashboard{
|
result = append(result, types.Dashboard{
|
||||||
Uuid: m.dashboardUuid(ii.IntegrationSummary.Id, dashboardId),
|
UUID: m.dashboardUuid(ii.IntegrationSummary.Id, dashboardId),
|
||||||
Locked: &isLocked,
|
Locked: &isLocked,
|
||||||
Data: dd,
|
Data: dd,
|
||||||
CreatedAt: ii.Installation.InstalledAt,
|
TimeAuditable: types.TimeAuditable{
|
||||||
CreateBy: &author,
|
CreatedAt: ii.Installation.InstalledAt,
|
||||||
UpdatedAt: ii.Installation.InstalledAt,
|
UpdatedAt: ii.Installation.InstalledAt,
|
||||||
UpdateBy: &author,
|
},
|
||||||
|
UserAuditable: types.UserAuditable{
|
||||||
|
CreatedBy: author,
|
||||||
|
UpdatedBy: author,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
"go.signoz.io/signoz/pkg/query-service/rules"
|
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTestIntegrationsManager(t *testing.T) *Manager {
|
func NewTestIntegrationsManager(t *testing.T) *Manager {
|
||||||
@ -92,7 +92,7 @@ func (t *TestAvailableIntegrationsRepo) list(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dashboards: []dashboards.Data{},
|
Dashboards: []types.DashboardData{},
|
||||||
Alerts: []rules.PostableRule{},
|
Alerts: []rules.PostableRule{},
|
||||||
},
|
},
|
||||||
ConnectionTests: &IntegrationConnectionTests{
|
ConnectionTests: &IntegrationConnectionTests{
|
||||||
@ -160,7 +160,7 @@ func (t *TestAvailableIntegrationsRepo) list(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dashboards: []dashboards.Data{},
|
Dashboards: []types.DashboardData{},
|
||||||
Alerts: []rules.PostableRule{},
|
Alerts: []rules.PostableRule{},
|
||||||
},
|
},
|
||||||
ConnectionTests: &IntegrationConnectionTests{
|
ConnectionTests: &IntegrationConnectionTests{
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"go.signoz.io/signoz/pkg/query-service/model/metrics_explorer"
|
"go.signoz.io/signoz/pkg/query-service/model/metrics_explorer"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
"go.signoz.io/signoz/pkg/query-service/rules"
|
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
|
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -155,7 +156,11 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, metricNam
|
|||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
var metricNames []string
|
var metricNames []string
|
||||||
metricNames = append(metricNames, metricName)
|
metricNames = append(metricNames, metricName)
|
||||||
data, err := dashboards.GetDashboardsWithMetricNames(ctx, metricNames)
|
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: errors.New("failed to get claims")}
|
||||||
|
}
|
||||||
|
data, err := dashboards.GetDashboardsWithMetricNames(ctx, claims.OrgID, metricNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -322,7 +327,11 @@ func (receiver *SummaryService) GetRelatedMetrics(ctx context.Context, params *m
|
|||||||
alertsRelatedData := make(map[string][]metrics_explorer.Alert)
|
alertsRelatedData := make(map[string][]metrics_explorer.Alert)
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
names, apiError := dashboards.GetDashboardsWithMetricNames(ctx, metricNames)
|
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: errors.New("failed to get claims")}
|
||||||
|
}
|
||||||
|
names, apiError := dashboards.GetDashboardsWithMetricNames(ctx, claims.OrgID, metricNames)
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
return apiError
|
return apiError
|
||||||
}
|
}
|
||||||
|
@ -101,11 +101,11 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore.BunDB()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := explorer.InitWithDSN(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
if err := explorer.InitWithDSN(serverOptions.SigNoz.SQLStore.BunDB()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,8 @@ func IsViewer(user *types.GettableUser) bool { return user.GroupID == AuthCacheO
|
|||||||
func IsEditor(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.EditorGroupId }
|
func IsEditor(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.EditorGroupId }
|
||||||
func IsAdmin(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.AdminGroupId }
|
func IsAdmin(user *types.GettableUser) bool { return user.GroupID == AuthCacheObj.AdminGroupId }
|
||||||
|
|
||||||
|
func IsAdminV2(claims authtypes.Claims) bool { return claims.GroupID == AuthCacheObj.AdminGroupId }
|
||||||
|
|
||||||
func ValidatePassword(password string) error {
|
func ValidatePassword(password string) error {
|
||||||
if len(password) < minimumPasswordLength {
|
if len(password) < minimumPasswordLength {
|
||||||
return errors.Errorf("Password should be atleast %d characters.", minimumPasswordLength)
|
return errors.Errorf("Password should be atleast %d characters.", minimumPasswordLength)
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
|
||||||
"go.signoz.io/signoz/pkg/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetUserFromContext(ctx context.Context) *types.GettableUser {
|
|
||||||
user, ok := ctx.Value(constants.ContextUserKey).(*types.GettableUser)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||||||
"go.signoz.io/signoz/pkg/http/middleware"
|
"go.signoz.io/signoz/pkg/http/middleware"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app"
|
"go.signoz.io/signoz/pkg/query-service/app"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
|
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/integrations"
|
"go.signoz.io/signoz/pkg/query-service/app/integrations"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
@ -350,7 +349,7 @@ func TestDashboardsForInstalledIntegrationDashboards(t *testing.T) {
|
|||||||
require.GreaterOrEqual(dashboards[0].UpdatedAt.Unix(), tsBeforeInstallation)
|
require.GreaterOrEqual(dashboards[0].UpdatedAt.Unix(), tsBeforeInstallation)
|
||||||
|
|
||||||
// Should be able to get installed integrations dashboard by id
|
// Should be able to get installed integrations dashboard by id
|
||||||
dd := integrationsTB.GetDashboardByIdFromQS(dashboards[0].Uuid)
|
dd := integrationsTB.GetDashboardByIdFromQS(dashboards[0].UUID)
|
||||||
require.GreaterOrEqual(dd.CreatedAt.Unix(), tsBeforeInstallation)
|
require.GreaterOrEqual(dd.CreatedAt.Unix(), tsBeforeInstallation)
|
||||||
require.GreaterOrEqual(dd.UpdatedAt.Unix(), tsBeforeInstallation)
|
require.GreaterOrEqual(dd.UpdatedAt.Unix(), tsBeforeInstallation)
|
||||||
require.Equal(*dd, dashboards[0])
|
require.Equal(*dd, dashboards[0])
|
||||||
@ -464,7 +463,7 @@ func (tb *IntegrationsTestBed) RequestQSToUninstallIntegration(
|
|||||||
tb.RequestQS("/api/v1/integrations/uninstall", request)
|
tb.RequestQS("/api/v1/integrations/uninstall", request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *IntegrationsTestBed) GetDashboardsFromQS() []dashboards.Dashboard {
|
func (tb *IntegrationsTestBed) GetDashboardsFromQS() []types.Dashboard {
|
||||||
result := tb.RequestQS("/api/v1/dashboards", nil)
|
result := tb.RequestQS("/api/v1/dashboards", nil)
|
||||||
|
|
||||||
dataJson, err := json.Marshal(result.Data)
|
dataJson, err := json.Marshal(result.Data)
|
||||||
@ -472,7 +471,7 @@ func (tb *IntegrationsTestBed) GetDashboardsFromQS() []dashboards.Dashboard {
|
|||||||
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
|
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboards := []dashboards.Dashboard{}
|
dashboards := []types.Dashboard{}
|
||||||
err = json.Unmarshal(dataJson, &dashboards)
|
err = json.Unmarshal(dataJson, &dashboards)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tb.t.Fatalf(" could not unmarshal apiResponse.Data json into dashboards")
|
tb.t.Fatalf(" could not unmarshal apiResponse.Data json into dashboards")
|
||||||
@ -481,7 +480,7 @@ func (tb *IntegrationsTestBed) GetDashboardsFromQS() []dashboards.Dashboard {
|
|||||||
return dashboards
|
return dashboards
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *IntegrationsTestBed) GetDashboardByIdFromQS(dashboardUuid string) *dashboards.Dashboard {
|
func (tb *IntegrationsTestBed) GetDashboardByIdFromQS(dashboardUuid string) *types.Dashboard {
|
||||||
result := tb.RequestQS(fmt.Sprintf("/api/v1/dashboards/%s", dashboardUuid), nil)
|
result := tb.RequestQS(fmt.Sprintf("/api/v1/dashboards/%s", dashboardUuid), nil)
|
||||||
|
|
||||||
dataJson, err := json.Marshal(result.Data)
|
dataJson, err := json.Marshal(result.Data)
|
||||||
@ -489,7 +488,7 @@ func (tb *IntegrationsTestBed) GetDashboardByIdFromQS(dashboardUuid string) *das
|
|||||||
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
|
tb.t.Fatalf("could not marshal apiResponse.Data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard := dashboards.Dashboard{}
|
dashboard := types.Dashboard{}
|
||||||
err = json.Unmarshal(dataJson, &dashboard)
|
err = json.Unmarshal(dataJson, &dashboard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tb.t.Fatalf(" could not unmarshal apiResponse.Data json into dashboards")
|
tb.t.Fatalf(" could not unmarshal apiResponse.Data json into dashboards")
|
||||||
|
@ -48,6 +48,7 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
|
|||||||
sqlmigration.NewModifyDatetimeFactory(),
|
sqlmigration.NewModifyDatetimeFactory(),
|
||||||
sqlmigration.NewModifyOrgDomainFactory(),
|
sqlmigration.NewModifyOrgDomainFactory(),
|
||||||
sqlmigration.NewUpdateOrganizationFactory(sqlStore),
|
sqlmigration.NewUpdateOrganizationFactory(sqlStore),
|
||||||
|
sqlmigration.NewUpdateDashboardAndSavedViewsFactory(sqlStore),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -69,7 +70,7 @@ func NewQueryServiceDBForTests(t *testing.T) sqlstore.SQLStore {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not initialize dao: %v", err)
|
t.Fatalf("could not initialize dao: %v", err)
|
||||||
}
|
}
|
||||||
dashboards.InitDB(sqlStore.SQLxDB())
|
dashboards.InitDB(sqlStore.BunDB())
|
||||||
|
|
||||||
return sqlStore
|
return sqlStore
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
|
|||||||
sqlmigration.NewModifyOrgDomainFactory(),
|
sqlmigration.NewModifyOrgDomainFactory(),
|
||||||
sqlmigration.NewUpdateOrganizationFactory(sqlstore),
|
sqlmigration.NewUpdateOrganizationFactory(sqlstore),
|
||||||
sqlmigration.NewAddAlertmanagerFactory(),
|
sqlmigration.NewAddAlertmanagerFactory(),
|
||||||
|
sqlmigration.NewUpdateDashboardAndSavedViewsFactory(sqlstore),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
80
pkg/sqlmigration/015_update_dashboards_savedviews.go
Normal file
80
pkg/sqlmigration/015_update_dashboards_savedviews.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package sqlmigration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
"github.com/uptrace/bun/migrate"
|
||||||
|
"go.signoz.io/signoz/pkg/factory"
|
||||||
|
"go.signoz.io/signoz/pkg/sqlstore"
|
||||||
|
"go.signoz.io/signoz/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateDashboardAndSavedViews struct {
|
||||||
|
store sqlstore.SQLStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateDashboardAndSavedViewsFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("update_group"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||||
|
return newUpdateDashboardAndSavedViews(ctx, ps, c, sqlstore)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateDashboardAndSavedViews(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) {
|
||||||
|
return &updateDashboardAndSavedViews{
|
||||||
|
store: store,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateDashboardAndSavedViews) Register(migrations *migrate.Migrations) error {
|
||||||
|
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateDashboardAndSavedViews) Up(ctx context.Context, db *bun.DB) error {
|
||||||
|
|
||||||
|
// begin transaction
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// get all org ids
|
||||||
|
var orgIDs []string
|
||||||
|
if err := migration.store.BunDB().NewSelect().Model((*types.Organization)(nil)).Column("id").Scan(ctx, &orgIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add org id to dashboards table
|
||||||
|
for _, table := range []string{"dashboards", "saved_views"} {
|
||||||
|
if exists, err := migration.store.Dialect().ColumnExists(ctx, tx, table, "org_id"); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !exists {
|
||||||
|
if _, err := tx.NewAddColumn().Table(table).ColumnExpr("org_id TEXT REFERENCES organizations(id) ON DELETE CASCADE").Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there is one org ID if yes then set it to all dashboards.
|
||||||
|
if len(orgIDs) == 1 {
|
||||||
|
orgID := orgIDs[0]
|
||||||
|
if _, err := tx.NewUpdate().Table(table).Set("org_id = ?", orgID).Where("org_id IS NULL").Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateDashboardAndSavedViews) Down(ctx context.Context, db *bun.DB) error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,22 +1,71 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gosimple/slug"
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
bun.BaseModel `bun:"table:dashboards"`
|
bun.BaseModel `bun:"table:dashboards"`
|
||||||
|
|
||||||
ID int `bun:"id,pk,autoincrement"`
|
TimeAuditable
|
||||||
UUID string `bun:"uuid,type:text,notnull,unique"`
|
UserAuditable
|
||||||
CreatedAt time.Time `bun:"created_at,type:datetime,notnull"`
|
OrgID string `json:"-" bun:"org_id,notnull"`
|
||||||
CreatedBy string `bun:"created_by,type:text,notnull"`
|
ID int `json:"id" bun:"id,pk,autoincrement"`
|
||||||
UpdatedAt time.Time `bun:"updated_at,type:datetime,notnull"`
|
UUID string `json:"uuid" bun:"uuid,type:text,notnull,unique"`
|
||||||
UpdatedBy string `bun:"updated_by,type:text,notnull"`
|
Data DashboardData `json:"data" bun:"data,type:text,notnull"`
|
||||||
Data string `bun:"data,type:text,notnull"`
|
Locked *int `json:"isLocked" bun:"locked,notnull,default:0"`
|
||||||
Locked int `bun:"locked,notnull,default:0"`
|
|
||||||
|
Slug string `json:"-" bun:"-"`
|
||||||
|
Title string `json:"-" bun:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSlug updates the slug
|
||||||
|
func (d *Dashboard) UpdateSlug() {
|
||||||
|
var title string
|
||||||
|
|
||||||
|
if val, ok := d.Data["title"]; ok {
|
||||||
|
title = val.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Slug = SlugifyTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SlugifyTitle(title string) string {
|
||||||
|
s := slug.Make(strings.ToLower(title))
|
||||||
|
if s == "" {
|
||||||
|
// If the dashboard name is only characters outside of the
|
||||||
|
// sluggable characters, the slug creation will return an
|
||||||
|
// empty string which will mess up URLs. This failsafe picks
|
||||||
|
// that up and creates the slug as a base64 identifier instead.
|
||||||
|
s = base64.RawURLEncoding.EncodeToString([]byte(title))
|
||||||
|
if slug.MaxLength != 0 && len(s) > slug.MaxLength {
|
||||||
|
s = s[:slug.MaxLength]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardData map[string]interface{}
|
||||||
|
|
||||||
|
func (c DashboardData) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DashboardData) Scan(src interface{}) error {
|
||||||
|
var data []byte
|
||||||
|
if b, ok := src.([]byte); ok {
|
||||||
|
data = b
|
||||||
|
} else if s, ok := src.(string); ok {
|
||||||
|
data = []byte(s)
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SavedView struct {
|
type SavedView struct {
|
||||||
bun.BaseModel `bun:"table:saved_views"`
|
bun.BaseModel `bun:"table:saved_views"`
|
||||||
|
|
||||||
UUID string `bun:"uuid,pk,type:text"`
|
TimeAuditable
|
||||||
Name string `bun:"name,type:text,notnull"`
|
UserAuditable
|
||||||
Category string `bun:"category,type:text,notnull"`
|
OrgID string `json:"orgId" bun:"org_id,notnull"`
|
||||||
CreatedAt time.Time `bun:"created_at,type:datetime,notnull"`
|
UUID string `json:"uuid" bun:"uuid,pk,type:text"`
|
||||||
CreatedBy string `bun:"created_by,type:text"`
|
Name string `json:"name" bun:"name,type:text,notnull"`
|
||||||
UpdatedAt time.Time `bun:"updated_at,type:datetime,notnull"`
|
Category string `json:"category" bun:"category,type:text,notnull"`
|
||||||
UpdatedBy string `bun:"updated_by,type:text"`
|
SourcePage string `json:"sourcePage" bun:"source_page,type:text,notnull"`
|
||||||
SourcePage string `bun:"source_page,type:text,notnull"`
|
Tags string `json:"tags" bun:"tags,type:text"`
|
||||||
Tags string `bun:"tags,type:text"`
|
Data string `json:"data" bun:"data,type:text,notnull"`
|
||||||
Data string `bun:"data,type:text,notnull"`
|
ExtraData string `json:"extraData" bun:"extra_data,type:text"`
|
||||||
ExtraData string `bun:"extra_data,type:text"`
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user