Compare commits

...

1 Commits

Author SHA1 Message Date
grandwizard28
28d5b7210f fix(dashboards): part 1 of refactoring 2025-05-02 07:40:07 +05:30
15 changed files with 687 additions and 667 deletions

View File

@@ -6,8 +6,8 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/query-service/app/dashboards"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -41,10 +41,15 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
return
}
dashboard, err := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
// 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
// }
dashboard, err := ah.Signoz.Modules.Dashboard.Get(r.Context(), valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(uuid))
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get dashboard"))
return
render.Error(w, err)
}
if err := claims.IsAdmin(); err != nil && (dashboard.CreatedBy != claims.Email) {
@@ -52,12 +57,17 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
return
}
// Lock/Unlock the dashboard
err = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
if err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to lock/unlock dashboard"))
if err := ah.Signoz.Modules.Dashboard.Update(r.Context(), nil); err != nil {
render.Error(w, err)
return
}
// Lock/Unlock the dashboard
// err = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
// if err != nil {
// render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to lock/unlock dashboard"))
// return
// }
ah.Respond(w, "Dashboard updated successfully")
}

View File

@@ -35,7 +35,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/dashboards"
baseexplorer "github.com/SigNoz/signoz/pkg/query-service/app/explorer"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
@@ -102,10 +101,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err
}
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err

View File

@@ -0,0 +1,22 @@
package dashboard
import (
"context"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
Create(context.Context, valuer.UUID, map[string]any, string) error
Get(context.Context, valuer.UUID, valuer.UUID) (*dashboardtypes.Dashboard, error)
GetByMetricNames(context.Context, valuer.UUID, []string) ([]*dashboardtypes.Dashboard, error)
List(context.Context, valuer.UUID) ([]*dashboardtypes.Dashboard, error)
Update(context.Context, *dashboardtypes.Dashboard) error
Delete(context.Context, valuer.UUID, valuer.UUID) error
}

View File

@@ -0,0 +1,236 @@
package impldashboard
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type module struct {
store dashboardtypes.Store
}
func NewModule(store dashboardtypes.Store) dashboard.Module {
return &module{store: store}
}
func (module *module) Create(ctx context.Context, orgID valuer.UUID, data map[string]any, email string) error {
storableDashboard, err := dashboardtypes.NewStorableDashboard(data, email, orgID)
if err != nil {
return err
}
return module.store.Create(ctx, storableDashboard)
}
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
storableDashboard, err := module.store.Get(ctx, id)
if err != nil {
return nil, err
}
return dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard), nil
}
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) ([]*dashboardtypes.Dashboard, error) {
// dashboards := []types.Dashboard{}
// err := store.BunDB().NewSelect().Model(&dashboards).Where("org_id = ?", orgID).Scan(ctx)
// if err != nil {
// zap.L().Error("Error in getting dashboards", zap.Error(err))
// return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
// }
// if err != nil {
// zap.L().Error("Error in getting dashboards", zap.Error(err))
// return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
// }
// // Initialize result map for each metric
// result := make(map[string][]map[string]string)
// // Process the JSON data in Go
// for _, dashboard := range dashboards {
// var dashData = dashboard.Data
// dashTitle, _ := dashData["title"].(string)
// widgets, ok := dashData["widgets"].([]interface{})
// if !ok {
// continue
// }
// for _, w := range widgets {
// widget, ok := w.(map[string]interface{})
// if !ok {
// continue
// }
// widgetTitle, _ := widget["title"].(string)
// widgetID, _ := widget["id"].(string)
// query, ok := widget["query"].(map[string]interface{})
// if !ok {
// continue
// }
// builder, ok := query["builder"].(map[string]interface{})
// if !ok {
// continue
// }
// queryData, ok := builder["queryData"].([]interface{})
// if !ok {
// continue
// }
// for _, qd := range queryData {
// data, ok := qd.(map[string]interface{})
// if !ok {
// continue
// }
// if dataSource, ok := data["dataSource"].(string); !ok || dataSource != "metrics" {
// continue
// }
// aggregateAttr, ok := data["aggregateAttribute"].(map[string]interface{})
// if !ok {
// continue
// }
// if key, ok := aggregateAttr["key"].(string); ok {
// // Check if this metric is in our list of interest
// for _, metricName := range metricNames {
// if strings.TrimSpace(key) == metricName {
// result[metricName] = append(result[metricName], map[string]string{
// "dashboard_id": dashboard.UUID,
// "widget_name": widgetTitle,
// "widget_id": widgetID,
// "dashboard_name": dashTitle,
// })
// }
// }
// }
// }
// }
// }
// return result, nil
return nil, nil
}
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
storableDashboards, err := module.store.List(ctx, orgID)
if err != nil {
return nil, err
}
dashboards := make([]*dashboardtypes.Dashboard, len(storableDashboards))
for idx, storableDashboard := range storableDashboards {
dashboards[idx] = dashboardtypes.NewDashboardFromStorableDashboard(storableDashboard)
}
return dashboards, nil
}
func (module *module) Update(ctx context.Context, updatedOrganization *dashboardtypes.Dashboard) error {
return nil
// mapData, err := json.Marshal(data)
// if err != nil {
// zap.L().Error("Error in marshalling data field in dashboard: ", zap.Any("data", data), zap.Error(err))
// return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
// }
// dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
// if apiErr != nil {
// return nil, apiErr
// }
// 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,
// // return error
// existingIds := getWidgetIds(dashboard.Data)
// newIds := getWidgetIds(data)
// differenceIds := getIdDifference(existingIds, newIds)
// if len(differenceIds) > 1 {
// return nil, model.BadRequest(fmt.Errorf("deleting more than one panel is not supported"))
// }
// dashboard.UpdatedAt = time.Now()
// dashboard.UpdatedBy = userEmail
// dashboard.Data = data
// _, err = store.BunDB().NewUpdate().Model(dashboard).Set("updated_at = ?", dashboard.UpdatedAt).Set("updated_by = ?", userEmail).Set("data = ?", mapData).Where("uuid = ?", dashboard.UUID).Exec(ctx)
// if err != nil {
// zap.L().Error("Error in inserting dashboard data", zap.Any("data", data), zap.Error(err))
// return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
// }
// return dashboard, nil
}
func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
dashboard, err := module.Get(ctx, orgID, id)
if err != nil {
return err
}
if dashboard.Locked {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked")
}
return module.store.Delete(ctx, id)
}
// func getIdDifference(existingIds []string, newIds []string) []string {
// // Convert newIds array to a map for faster lookups
// newIdsMap := make(map[string]bool)
// for _, id := range newIds {
// newIdsMap[id] = true
// }
// // Initialize a map to keep track of elements in the difference array
// differenceMap := make(map[string]bool)
// // Initialize the difference array
// difference := []string{}
// // Iterate through existingIds
// for _, id := range existingIds {
// // If the id is not found in newIds, and it's not already in the difference array
// if _, found := newIdsMap[id]; !found && !differenceMap[id] {
// difference = append(difference, id)
// differenceMap[id] = true // Mark the id as seen in the difference array
// }
// }
// return difference
// }
// func LockUnlockDashboard(ctx context.Context, orgID, uuid string, lock bool) *model.ApiError {
// dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
// if apiErr != nil {
// return apiErr
// }
// var lockValue int
// if lock {
// lockValue = 1
// } else {
// lockValue = 0
// }
// _, err := store.BunDB().NewUpdate().Model(dashboard).Set("locked = ?", lockValue).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Exec(ctx)
// if err != nil {
// zap.L().Error("Error in updating dashboard", zap.String("uuid", uuid), zap.Error(err))
// return &model.ApiError{Typ: model.ErrorExec, Err: err}
// }
// return nil
// }

View File

@@ -11,6 +11,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"golang.org/x/exp/maps"
)
@@ -510,10 +511,8 @@ func (c *Controller) UpdateServiceConfig(
// All dashboards that are available based on cloud integrations configuration
// across all cloud providers
func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
[]types.Dashboard, *model.ApiError,
) {
allDashboards := []types.Dashboard{}
func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) ([]*dashboardtypes.Dashboard, *model.ApiError) {
allDashboards := []*dashboardtypes.Dashboard{}
for _, provider := range []string{"aws"} {
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
@@ -529,10 +528,7 @@ func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
return allDashboards, nil
}
func (c *Controller) AvailableDashboardsForCloudProvider(
ctx context.Context, orgID string, cloudProvider string,
) ([]types.Dashboard, *model.ApiError) {
func (c *Controller) AvailableDashboardsForCloudProvider(ctx context.Context, orgID string, cloudProvider string) ([]*dashboardtypes.Dashboard, *model.ApiError) {
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID, cloudProvider)
if apiErr != nil {
return nil, model.WrapApiError(apiErr, "couldn't list connected cloud accounts")
@@ -563,16 +559,15 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
return nil, apiErr
}
svcDashboards := []types.Dashboard{}
svcDashboards := []*dashboardtypes.Dashboard{}
for _, svc := range allServices {
serviceDashboardsCreatedAt := servicesWithAvailableMetrics[svc.Id]
if serviceDashboardsCreatedAt != nil {
for _, d := range svc.Assets.Dashboards {
isLocked := 1
author := fmt.Sprintf("%s-integration", cloudProvider)
svcDashboards = append(svcDashboards, types.Dashboard{
UUID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
Locked: &isLocked,
svcDashboards = append(svcDashboards, &dashboardtypes.Dashboard{
ID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
Locked: true,
Data: *d.Definition,
TimeAuditable: types.TimeAuditable{
CreatedAt: *serviceDashboardsCreatedAt,
@@ -590,11 +585,7 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
return svcDashboards, nil
}
func (c *Controller) GetDashboardById(
ctx context.Context,
orgId string,
dashboardUuid string,
) (*types.Dashboard, *model.ApiError) {
func (c *Controller) GetDashboardById(ctx context.Context, orgId string, dashboardUuid string) (*dashboardtypes.Dashboard, *model.ApiError) {
cloudProvider, _, _, apiErr := c.parseDashboardUuid(dashboardUuid)
if apiErr != nil {
return nil, apiErr
@@ -608,8 +599,8 @@ func (c *Controller) GetDashboardById(
}
for _, d := range allDashboards {
if d.UUID == dashboardUuid {
return &d, nil
if d.ID == dashboardUuid {
return d, nil
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
)
type CloudServiceSummary struct {
@@ -42,7 +43,7 @@ type CloudServiceDashboard struct {
Title string `json:"title"`
Description string `json:"description"`
Image string `json:"image"`
Definition *types.DashboardData `json:"definition,omitempty"`
Definition *dashboardtypes.Data `json:"definition,omitempty"`
}
type SupportedSignals struct {

View File

@@ -1,533 +0,0 @@
package dashboards
import (
"context"
"encoding/json"
"fmt"
"regexp"
"slices"
"strings"
"time"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/google/uuid"
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
"go.uber.org/zap"
)
// This time the global variable is unexported.
var store sqlstore.SQLStore
// User for mapping job,instance from grafana
var (
instanceEQRE = regexp.MustCompile("instance(?s)=(?s)\\\"{{.instance}}\\\"")
nodeEQRE = regexp.MustCompile("instance(?s)=(?s)\\\"{{.node}}\\\"")
jobEQRE = regexp.MustCompile("job(?s)=(?s)\\\"{{.job}}\\\"")
instanceRERE = regexp.MustCompile("instance(?s)=~(?s)\\\"{{.instance}}\\\"")
nodeRERE = regexp.MustCompile("instance(?s)=~(?s)\\\"{{.node}}\\\"")
jobRERE = regexp.MustCompile("job(?s)=~(?s)\\\"{{.job}}\\\"")
)
// InitDB sets up setting up the connection pool global variable.
func InitDB(sqlStore sqlstore.SQLStore) error {
store = sqlStore
telemetry.GetInstance().SetDashboardsInfoCallback(GetDashboardsInfo)
return nil
}
// CreateDashboard creates a new dashboard
func CreateDashboard(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, *model.ApiError) {
dash := &types.Dashboard{
Data: data,
}
dash.OrgID = orgID
dash.CreatedAt = time.Now()
dash.CreatedBy = email
dash.UpdatedAt = time.Now()
dash.UpdatedBy = email
dash.UpdateSlug()
dash.UUID = uuid.New().String()
if data["uuid"] != nil {
dash.UUID = data["uuid"].(string)
}
err := store.BunDB().NewInsert().Model(dash).Returning("id").Scan(ctx, &dash.ID)
if err != nil {
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 dash, nil
}
func GetDashboards(ctx context.Context, orgID string) ([]types.Dashboard, *model.ApiError) {
dashboards := []types.Dashboard{}
err := store.BunDB().NewSelect().Model(&dashboards).Where("org_id = ?", orgID).Scan(ctx)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
}
return dashboards, nil
}
func DeleteDashboard(ctx context.Context, orgID, uuid string) *model.ApiError {
dashboard, dErr := GetDashboard(ctx, orgID, uuid)
if dErr != nil {
zap.L().Error("Error in getting dashboard: ", zap.String("uuid", uuid), zap.Any("error", dErr))
return dErr
}
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"))
}
result, err := store.BunDB().NewDelete().Model(&types.Dashboard{}).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Exec(ctx)
if err != nil {
return &model.ApiError{Typ: model.ErrorExec, Err: err}
}
affectedRows, err := result.RowsAffected()
if err != nil {
return &model.ApiError{Typ: model.ErrorExec, Err: err}
}
if affectedRows == 0 {
return &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no dashboard found with uuid: %s", uuid)}
}
return nil
}
func GetDashboard(ctx context.Context, orgID, uuid string) (*types.Dashboard, *model.ApiError) {
dashboard := types.Dashboard{}
err := store.BunDB().NewSelect().Model(&dashboard).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Scan(ctx)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no dashboard found with uuid: %s", uuid)}
}
return &dashboard, nil
}
func UpdateDashboard(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, *model.ApiError) {
mapData, err := json.Marshal(data)
if err != nil {
zap.L().Error("Error in marshalling data field in dashboard: ", zap.Any("data", data), zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
if apiErr != nil {
return nil, apiErr
}
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,
// return error
existingIds := getWidgetIds(dashboard.Data)
newIds := getWidgetIds(data)
differenceIds := getIdDifference(existingIds, newIds)
if len(differenceIds) > 1 {
return nil, model.BadRequest(fmt.Errorf("deleting more than one panel is not supported"))
}
dashboard.UpdatedAt = time.Now()
dashboard.UpdatedBy = userEmail
dashboard.Data = data
_, err = store.BunDB().NewUpdate().Model(dashboard).Set("updated_at = ?", dashboard.UpdatedAt).Set("updated_by = ?", userEmail).Set("data = ?", mapData).Where("uuid = ?", dashboard.UUID).Exec(ctx)
if err != nil {
zap.L().Error("Error in inserting dashboard data", zap.Any("data", data), zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
}
return dashboard, nil
}
func LockUnlockDashboard(ctx context.Context, orgID, uuid string, lock bool) *model.ApiError {
dashboard, apiErr := GetDashboard(ctx, orgID, uuid)
if apiErr != nil {
return apiErr
}
var lockValue int
if lock {
lockValue = 1
} else {
lockValue = 0
}
_, err := store.BunDB().NewUpdate().Model(dashboard).Set("locked = ?", lockValue).Where("org_id = ?", orgID).Where("uuid = ?", uuid).Exec(ctx)
if err != nil {
zap.L().Error("Error in updating dashboard", zap.String("uuid", uuid), zap.Error(err))
return &model.ApiError{Typ: model.ErrorExec, Err: err}
}
return nil
}
func IsPostDataSane(data *map[string]interface{}) error {
val, ok := (*data)["title"]
if !ok || val == nil {
return fmt.Errorf("title not found in post data")
}
return nil
}
func getWidgetIds(data map[string]interface{}) []string {
widgetIds := []string{}
if data != nil && data["widgets"] != nil {
widgets, ok := data["widgets"]
if ok {
data, ok := widgets.([]interface{})
if ok {
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil && sData["id"] != nil {
id, ok := sData["id"].(string)
if ok {
widgetIds = append(widgetIds, id)
}
}
}
}
}
}
return widgetIds
}
func getIdDifference(existingIds []string, newIds []string) []string {
// Convert newIds array to a map for faster lookups
newIdsMap := make(map[string]bool)
for _, id := range newIds {
newIdsMap[id] = true
}
// Initialize a map to keep track of elements in the difference array
differenceMap := make(map[string]bool)
// Initialize the difference array
difference := []string{}
// Iterate through existingIds
for _, id := range existingIds {
// If the id is not found in newIds, and it's not already in the difference array
if _, found := newIdsMap[id]; !found && !differenceMap[id] {
difference = append(difference, id)
differenceMap[id] = true // Mark the id as seen in the difference array
}
}
return difference
}
// GetDashboardsInfo returns analytics data for dashboards
func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
dashboardsInfo := model.DashboardsInfo{}
// fetch dashboards from dashboard db
dashboards := []types.Dashboard{}
err := store.BunDB().NewSelect().Model(&dashboards).Scan(ctx)
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return &dashboardsInfo, err
}
totalDashboardsWithPanelAndName := 0
var dashboardNames []string
count := 0
queriesWithTagAttrs := 0
for _, dashboard := range dashboards {
if isDashboardWithPanelAndName(dashboard.Data) {
totalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName + 1
}
dashboardName := extractDashboardName(dashboard.Data)
if dashboardName != "" {
dashboardNames = append(dashboardNames, dashboardName)
}
dashboardInfo := countPanelsInDashboard(dashboard.Data)
dashboardsInfo.LogsBasedPanels += dashboardInfo.LogsBasedPanels
dashboardsInfo.TracesBasedPanels += dashboardInfo.TracesBasedPanels
dashboardsInfo.MetricBasedPanels += dashboardInfo.MetricBasedPanels
dashboardsInfo.LogsPanelsWithAttrContainsOp += dashboardInfo.LogsPanelsWithAttrContainsOp
dashboardsInfo.DashboardsWithLogsChQuery += dashboardInfo.DashboardsWithLogsChQuery
dashboardsInfo.DashboardsWithTraceChQuery += dashboardInfo.DashboardsWithTraceChQuery
if isDashboardWithTSV2(dashboard.Data) {
count = count + 1
}
if isDashboardWithTagAttrs(dashboard.Data) {
queriesWithTagAttrs += 1
}
if dashboardInfo.DashboardsWithTraceChQuery > 0 {
dashboardsInfo.DashboardNamesWithTraceChQuery = append(dashboardsInfo.DashboardNamesWithTraceChQuery, dashboardName)
}
// check if dashboard is a has a log operator with contains
}
dashboardsInfo.DashboardNames = dashboardNames
dashboardsInfo.TotalDashboards = len(dashboards)
dashboardsInfo.TotalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName
dashboardsInfo.QueriesWithTSV2 = count
dashboardsInfo.QueriesWithTagAttrs = queriesWithTagAttrs
return &dashboardsInfo, nil
}
func isDashboardWithTSV2(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
return strings.Contains(string(jsonData), "time_series_v2")
}
func isDashboardWithTagAttrs(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
return strings.Contains(string(jsonData), "span_attributes") ||
strings.Contains(string(jsonData), "tag_attributes")
}
func isDashboardWithLogsClickhouseQuery(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
result := strings.Contains(string(jsonData), "signoz_logs.distributed_logs") ||
strings.Contains(string(jsonData), "signoz_logs.logs")
return result
}
func isDashboardWithTracesClickhouseQuery(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
// also check if the query is actually active
str := string(jsonData)
result := strings.Contains(str, "signoz_traces.distributed_signoz_index_v2") ||
strings.Contains(str, "signoz_traces.distributed_signoz_spans") ||
strings.Contains(str, "signoz_traces.distributed_signoz_error_index_v2")
return result
}
func isDashboardWithPanelAndName(data map[string]interface{}) bool {
isDashboardName := false
isDashboardWithPanelAndName := false
if data != nil && data["title"] != nil && data["widgets"] != nil {
title, ok := data["title"].(string)
if ok && title != "Sample Title" {
isDashboardName = true
}
widgets, ok := data["widgets"]
if ok && isDashboardName {
data, ok := widgets.([]interface{})
if ok && len(data) > 0 {
isDashboardWithPanelAndName = true
}
}
}
return isDashboardWithPanelAndName
}
func extractDashboardName(data map[string]interface{}) string {
if data != nil && data["title"] != nil {
title, ok := data["title"].(string)
if ok {
return title
}
}
return ""
}
func checkLogPanelAttrContains(data map[string]interface{}) int {
var logsPanelsWithAttrContains int
filters, ok := data["filters"].(map[string]interface{})
if ok && filters["items"] != nil {
items, ok := filters["items"].([]interface{})
if ok {
for _, item := range items {
itemMap, ok := item.(map[string]interface{})
if ok {
opStr, ok := itemMap["op"].(string)
if ok {
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, opStr) {
// check if it's not body
key, ok := itemMap["key"].(map[string]string)
if ok && key["key"] != "body" {
logsPanelsWithAttrContains++
}
}
}
}
}
}
}
return logsPanelsWithAttrContains
}
func countPanelsInDashboard(inputData map[string]interface{}) model.DashboardsInfo {
var logsPanelCount, tracesPanelCount, metricsPanelCount, logsPanelsWithAttrContains int
traceChQueryCount := 0
logChQueryCount := 0
// totalPanels := 0
if inputData != nil && inputData["widgets"] != nil {
widgets, ok := inputData["widgets"]
if ok {
data, ok := widgets.([]interface{})
if ok {
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil {
// totalPanels++
query, ok := sData["query"].(map[string]interface{})
if ok && query["queryType"] == "builder" && query["builder"] != nil {
builderData, ok := query["builder"].(map[string]interface{})
if ok && builderData["queryData"] != nil {
builderQueryData, ok := builderData["queryData"].([]interface{})
if ok {
for _, queryData := range builderQueryData {
data, ok := queryData.(map[string]interface{})
if ok {
if data["dataSource"] == "traces" {
tracesPanelCount++
} else if data["dataSource"] == "metrics" {
metricsPanelCount++
} else if data["dataSource"] == "logs" {
logsPanelCount++
logsPanelsWithAttrContains += checkLogPanelAttrContains(data)
}
}
}
}
}
} else if ok && query["queryType"] == "clickhouse_sql" && query["clickhouse_sql"] != nil {
if isDashboardWithLogsClickhouseQuery(inputData) {
logChQueryCount = 1
}
if isDashboardWithTracesClickhouseQuery(inputData) {
traceChQueryCount = 1
}
}
}
}
}
}
}
return model.DashboardsInfo{
LogsBasedPanels: logsPanelCount,
TracesBasedPanels: tracesPanelCount,
MetricBasedPanels: metricsPanelCount,
DashboardsWithLogsChQuery: logChQueryCount,
DashboardsWithTraceChQuery: traceChQueryCount,
LogsPanelsWithAttrContainsOp: logsPanelsWithAttrContains,
}
}
func GetDashboardsWithMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, *model.ApiError) {
dashboards := []types.Dashboard{}
err := store.BunDB().NewSelect().Model(&dashboards).Where("org_id = ?", orgID).Scan(ctx)
if err != nil {
zap.L().Error("Error in getting dashboards", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
}
if err != nil {
zap.L().Error("Error in getting dashboards", zap.Error(err))
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
}
// Initialize result map for each metric
result := make(map[string][]map[string]string)
// Process the JSON data in Go
for _, dashboard := range dashboards {
var dashData = dashboard.Data
dashTitle, _ := dashData["title"].(string)
widgets, ok := dashData["widgets"].([]interface{})
if !ok {
continue
}
for _, w := range widgets {
widget, ok := w.(map[string]interface{})
if !ok {
continue
}
widgetTitle, _ := widget["title"].(string)
widgetID, _ := widget["id"].(string)
query, ok := widget["query"].(map[string]interface{})
if !ok {
continue
}
builder, ok := query["builder"].(map[string]interface{})
if !ok {
continue
}
queryData, ok := builder["queryData"].([]interface{})
if !ok {
continue
}
for _, qd := range queryData {
data, ok := qd.(map[string]interface{})
if !ok {
continue
}
if dataSource, ok := data["dataSource"].(string); !ok || dataSource != "metrics" {
continue
}
aggregateAttr, ok := data["aggregateAttribute"].(map[string]interface{})
if !ok {
continue
}
if key, ok := aggregateAttr["key"].(string); ok {
// Check if this metric is in our list of interest
for _, metricName := range metricNames {
if strings.TrimSpace(key) == metricName {
result[metricName] = append(result[metricName], map[string]string{
"dashboard_id": dashboard.UUID,
"widget_name": widgetTitle,
"widget_id": widgetID,
"dashboard_name": dashTitle,
})
}
}
}
}
}
}
return result, nil
}

View File

@@ -58,6 +58,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
@@ -511,11 +512,11 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.editDowntimeSchedule)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/downtime_schedules/{id}", am.EditAccess(aH.deleteDowntimeSchedule)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/dashboards", am.ViewAccess(aH.getDashboards)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards", am.EditAccess(aH.createDashboards)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/dashboards/{uuid}", am.ViewAccess(aH.getDashboard)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards/{uuid}", am.EditAccess(aH.updateDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}", am.EditAccess(aH.deleteDashboard)).Methods(http.MethodDelete)
router.HandleFunc("/api/v2/orgs/me/dashboards", am.ViewAccess(aH.getDashboards)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/orgs/me/dashboards", am.EditAccess(aH.createDashboards)).Methods(http.MethodPost)
router.HandleFunc("/api/v2/orgs/me/dashboards/{id}", am.ViewAccess(aH.getDashboard)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/orgs/me/dashboards/{id}", am.EditAccess(aH.updateDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v2/orgs/me/dashboards/{uuid}", am.EditAccess(aH.deleteDashboard)).Methods(http.MethodDelete)
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.getSavedViews)).Methods(http.MethodGet)
@@ -1074,9 +1075,10 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
render.Error(w, errv2)
return
}
allDashboards, err := dashboards.GetDashboards(r.Context(), claims.OrgID)
if err != nil {
RespondError(w, err, nil)
allDashboards, err := aH.Signoz.Modules.Dashboard.List(r.Context(), valuer.MustNewUUID(claims.OrgID))
if errv2 != nil {
render.Error(w, errv2)
return
}
@@ -1128,7 +1130,7 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
inter = Intersection(inter, tags2Dash[tag])
}
filteredDashboards := []types.Dashboard{}
filteredDashboards := []*dashboardtypes.Dashboard{}
for _, val := range inter {
dash := (allDashboards)[val]
filteredDashboards = append(filteredDashboards, dash)
@@ -1138,21 +1140,18 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
}
func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) {
uuid := mux.Vars(r)["uuid"]
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
if errv2 != nil {
render.Error(w, errv2)
return
}
err := dashboards.DeleteDashboard(r.Context(), claims.OrgID, uuid)
if err != nil {
RespondError(w, err, nil)
uuid := mux.Vars(r)["uuid"]
if err := aH.Signoz.Modules.Dashboard.Delete(r.Context(), valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(uuid)); err != nil {
render.Error(w, err)
return
}
aH.Respond(w, nil)
}
func prepareQuery(r *http.Request) (string, error) {

View File

@@ -18,7 +18,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/dashboards"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
@@ -87,10 +86,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err
}
if err := explorer.InitWithDSN(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err
}

View File

@@ -0,0 +1,225 @@
package telemetry
import (
"context"
"encoding/json"
"slices"
"strings"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"go.uber.org/zap"
)
// GetDashboardsInfo returns analytics data for dashboards
func GetDashboardsInfo(ctx context.Context) (*model.DashboardsInfo, error) {
dashboardsInfo := model.DashboardsInfo{}
// fetch dashboards from dashboard db
dashboards := []dashboardtypes.Dashboard{}
err := store.BunDB().NewSelect().Model(&dashboards).Scan(ctx)
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return &dashboardsInfo, err
}
totalDashboardsWithPanelAndName := 0
var dashboardNames []string
count := 0
queriesWithTagAttrs := 0
for _, dashboard := range dashboards {
if isDashboardWithPanelAndName(dashboard.Data) {
totalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName + 1
}
dashboardName := extractDashboardName(dashboard.Data)
if dashboardName != "" {
dashboardNames = append(dashboardNames, dashboardName)
}
dashboardInfo := countPanelsInDashboard(dashboard.Data)
dashboardsInfo.LogsBasedPanels += dashboardInfo.LogsBasedPanels
dashboardsInfo.TracesBasedPanels += dashboardInfo.TracesBasedPanels
dashboardsInfo.MetricBasedPanels += dashboardInfo.MetricBasedPanels
dashboardsInfo.LogsPanelsWithAttrContainsOp += dashboardInfo.LogsPanelsWithAttrContainsOp
dashboardsInfo.DashboardsWithLogsChQuery += dashboardInfo.DashboardsWithLogsChQuery
dashboardsInfo.DashboardsWithTraceChQuery += dashboardInfo.DashboardsWithTraceChQuery
if isDashboardWithTSV2(dashboard.Data) {
count = count + 1
}
if isDashboardWithTagAttrs(dashboard.Data) {
queriesWithTagAttrs += 1
}
if dashboardInfo.DashboardsWithTraceChQuery > 0 {
dashboardsInfo.DashboardNamesWithTraceChQuery = append(dashboardsInfo.DashboardNamesWithTraceChQuery, dashboardName)
}
// check if dashboard is a has a log operator with contains
}
dashboardsInfo.DashboardNames = dashboardNames
dashboardsInfo.TotalDashboards = len(dashboards)
dashboardsInfo.TotalDashboardsWithPanelAndName = totalDashboardsWithPanelAndName
dashboardsInfo.QueriesWithTSV2 = count
dashboardsInfo.QueriesWithTagAttrs = queriesWithTagAttrs
return &dashboardsInfo, nil
}
func isDashboardWithTSV2(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
return strings.Contains(string(jsonData), "time_series_v2")
}
func isDashboardWithTagAttrs(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
return strings.Contains(string(jsonData), "span_attributes") ||
strings.Contains(string(jsonData), "tag_attributes")
}
func isDashboardWithLogsClickhouseQuery(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
result := strings.Contains(string(jsonData), "signoz_logs.distributed_logs") ||
strings.Contains(string(jsonData), "signoz_logs.logs")
return result
}
func isDashboardWithTracesClickhouseQuery(data map[string]interface{}) bool {
jsonData, err := json.Marshal(data)
if err != nil {
return false
}
// also check if the query is actually active
str := string(jsonData)
result := strings.Contains(str, "signoz_traces.distributed_signoz_index_v2") ||
strings.Contains(str, "signoz_traces.distributed_signoz_spans") ||
strings.Contains(str, "signoz_traces.distributed_signoz_error_index_v2")
return result
}
func isDashboardWithPanelAndName(data map[string]interface{}) bool {
isDashboardName := false
isDashboardWithPanelAndName := false
if data != nil && data["title"] != nil && data["widgets"] != nil {
title, ok := data["title"].(string)
if ok && title != "Sample Title" {
isDashboardName = true
}
widgets, ok := data["widgets"]
if ok && isDashboardName {
data, ok := widgets.([]interface{})
if ok && len(data) > 0 {
isDashboardWithPanelAndName = true
}
}
}
return isDashboardWithPanelAndName
}
func extractDashboardName(data map[string]interface{}) string {
if data != nil && data["title"] != nil {
title, ok := data["title"].(string)
if ok {
return title
}
}
return ""
}
func checkLogPanelAttrContains(data map[string]interface{}) int {
var logsPanelsWithAttrContains int
filters, ok := data["filters"].(map[string]interface{})
if ok && filters["items"] != nil {
items, ok := filters["items"].([]interface{})
if ok {
for _, item := range items {
itemMap, ok := item.(map[string]interface{})
if ok {
opStr, ok := itemMap["op"].(string)
if ok {
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, opStr) {
// check if it's not body
key, ok := itemMap["key"].(map[string]string)
if ok && key["key"] != "body" {
logsPanelsWithAttrContains++
}
}
}
}
}
}
}
return logsPanelsWithAttrContains
}
func countPanelsInDashboard(inputData map[string]interface{}) model.DashboardsInfo {
var logsPanelCount, tracesPanelCount, metricsPanelCount, logsPanelsWithAttrContains int
traceChQueryCount := 0
logChQueryCount := 0
// totalPanels := 0
if inputData != nil && inputData["widgets"] != nil {
widgets, ok := inputData["widgets"]
if ok {
data, ok := widgets.([]interface{})
if ok {
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil {
// totalPanels++
query, ok := sData["query"].(map[string]interface{})
if ok && query["queryType"] == "builder" && query["builder"] != nil {
builderData, ok := query["builder"].(map[string]interface{})
if ok && builderData["queryData"] != nil {
builderQueryData, ok := builderData["queryData"].([]interface{})
if ok {
for _, queryData := range builderQueryData {
data, ok := queryData.(map[string]interface{})
if ok {
if data["dataSource"] == "traces" {
tracesPanelCount++
} else if data["dataSource"] == "metrics" {
metricsPanelCount++
} else if data["dataSource"] == "logs" {
logsPanelCount++
logsPanelsWithAttrContains += checkLogPanelAttrContains(data)
}
}
}
}
}
} else if ok && query["queryType"] == "clickhouse_sql" && query["clickhouse_sql"] != nil {
if isDashboardWithLogsClickhouseQuery(inputData) {
logChQueryCount = 1
}
if isDashboardWithTracesClickhouseQuery(inputData) {
traceChQueryCount = 1
}
}
}
}
}
}
}
return model.DashboardsInfo{
LogsBasedPanels: logsPanelCount,
TracesBasedPanels: tracesPanelCount,
MetricBasedPanels: metricsPanelCount,
DashboardsWithLogsChQuery: logChQueryCount,
DashboardsWithTraceChQuery: traceChQueryCount,
LogsPanelsWithAttrContainsOp: logsPanelsWithAttrContains,
}
}

View File

@@ -1,6 +1,7 @@
package signoz
import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/preference"
@@ -12,11 +13,13 @@ import (
type Modules struct {
Organization organization.Module
Preference preference.Module
Dashboard dashboard.Module
}
func NewModules(sqlstore sqlstore.SQLStore) Modules {
return Modules{
Organization: implorganization.NewModule(implorganization.NewStore(sqlstore)),
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewDefaultPreferenceMap()),
Dashboard: nil,
}
}

View File

@@ -1,80 +0,0 @@
package types
import (
"database/sql/driver"
"encoding/base64"
"encoding/json"
"strings"
"github.com/gosimple/slug"
"github.com/uptrace/bun"
)
type Dashboard struct {
bun.BaseModel `bun:"table:dashboards"`
TimeAuditable
UserAuditable
OrgID string `json:"-" bun:"org_id,notnull"`
ID int `json:"id" bun:"id,pk,autoincrement"`
UUID string `json:"uuid" bun:"uuid,type:text,notnull,unique"`
Data DashboardData `json:"data" bun:"data,type:text,notnull"`
Locked *int `json:"isLocked" 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 TTLSetting struct {
bun.BaseModel `bun:"table:ttl_setting"`
Identifiable
TimeAuditable
TransactionID string `bun:"transaction_id,type:text,notnull"`
TableName string `bun:"table_name,type:text,notnull"`
TTL int `bun:"ttl,notnull,default:0"`
ColdStorageTTL int `bun:"cold_storage_ttl,notnull,default:0"`
Status string `bun:"status,type:text,notnull"`
OrgID string `json:"-" bun:"org_id,notnull"`
}

View File

@@ -0,0 +1,71 @@
package dashboardtypes
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type Dashboard struct {
ID string `json:"id"`
Data Data `json:"data"`
Locked bool `json:"isLocked"`
OrgID valuer.UUID `json:"orgId"`
types.TimeAuditable
types.UserAuditable
}
type StorableDashboard struct {
bun.BaseModel `bun:"table:dashboards"`
types.Identifiable `bun:"id,pk,type:text"`
Data Data `bun:"data,type:text,notnull"`
IsLocked bool `bun:"is_locked,notnull"`
OrgID valuer.UUID `bun:"org_id,notnull"`
types.TimeAuditable
types.UserAuditable
}
func NewStorableDashboard(raw map[string]any, email string, orgID valuer.UUID) (*StorableDashboard, error) {
data, err := NewData(raw)
if err != nil {
return nil, err
}
return &StorableDashboard{
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
Data: data,
IsLocked: false,
OrgID: orgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
UserAuditable: types.UserAuditable{
CreatedBy: email,
UpdatedBy: email,
},
}, nil
}
func NewDashboardFromStorableDashboard(storableDashboard *StorableDashboard) *Dashboard {
return &Dashboard{
ID: storableDashboard.ID.String(),
Data: storableDashboard.Data,
Locked: storableDashboard.IsLocked,
OrgID: storableDashboard.OrgID,
TimeAuditable: storableDashboard.TimeAuditable,
UserAuditable: storableDashboard.UserAuditable,
}
}
type Store interface {
Create(context.Context, *StorableDashboard) error
Get(context.Context, valuer.UUID) (*StorableDashboard, error)
List(context.Context, valuer.UUID) ([]*StorableDashboard, error)
Update(context.Context, *StorableDashboard) error
Delete(context.Context, valuer.UUID) error
}

View File

@@ -0,0 +1,68 @@
package dashboardtypes
import (
"database/sql/driver"
"encoding/json"
"fmt"
"reflect"
"github.com/SigNoz/signoz/pkg/errors"
)
type Data map[string]any
func NewData(input map[string]any) (Data, error) {
title, ok := input["title"]
if ok && title == "" || !ok {
return Data{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "title is required")
}
return Data(input), nil
}
func (data Data) Value() (driver.Value, error) {
return json.Marshal(data)
}
func (data *Data) Scan(val interface{}) error {
var b []byte
switch val := val.(type) {
case []byte:
b = val
case string:
b = []byte(val)
default:
return fmt.Errorf("data: (non-string \"%s\")", reflect.TypeOf(val).String())
}
return json.Unmarshal(b, data)
}
func (data Data) WidgetIDs() []string {
if data["widgets"] == nil {
return []string{}
}
widgets, ok := data["widgets"]
if !ok {
return []string{}
}
widgetsData, ok := widgets.([]any)
if !ok {
return []string{}
}
var widgetIDs []string
for _, widget := range widgetsData {
widgetData, ok := widget.(map[string]any)
if ok && widgetData["query"] != nil && widgetData["id"] != nil {
if id, ok := widgetData["id"].(string); ok {
widgetIDs = append(widgetIDs, id)
}
}
}
return widgetIDs
}

17
pkg/types/ttl.go Normal file
View File

@@ -0,0 +1,17 @@
package types
import (
"github.com/uptrace/bun"
)
type TTLSetting struct {
bun.BaseModel `bun:"table:ttl_setting"`
Identifiable
TimeAuditable
TransactionID string `bun:"transaction_id,type:text,notnull"`
TableName string `bun:"table_name,type:text,notnull"`
TTL int `bun:"ttl,notnull,default:0"`
ColdStorageTTL int `bun:"cold_storage_ttl,notnull,default:0"`
Status string `bun:"status,type:text,notnull"`
OrgID string `json:"-" bun:"org_id,notnull"`
}