mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-31 16:02:00 +08:00

* chore(savedview): refactor into module and handler * chore(rule): move telemetry inside telemetry * chore(apdex): refactor apdex and delete dao * chore(dashboard): create a dashboard module * chore(ee): get rid of the init nonesense * chore(dashboard): fix err and apierror confusion * chore: address comments
332 lines
7.5 KiB
Go
332 lines
7.5 KiB
Go
package impldashboard
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type module struct {
|
|
sqlstore sqlstore.SQLStore
|
|
}
|
|
|
|
func NewModule(sqlstore sqlstore.SQLStore) dashboard.Module {
|
|
return &module{
|
|
sqlstore: sqlstore,
|
|
}
|
|
}
|
|
|
|
// CreateDashboard creates a new dashboard
|
|
func (module *module) Create(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, error) {
|
|
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 := module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewInsert().
|
|
Model(dash).
|
|
Returning("id").
|
|
Scan(ctx, &dash.ID)
|
|
if err != nil {
|
|
return nil, module.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "dashboard with uuid %s already exists", dash.UUID)
|
|
}
|
|
|
|
return dash, nil
|
|
}
|
|
|
|
func (module *module) List(ctx context.Context, orgID string) ([]*types.Dashboard, error) {
|
|
dashboards := []*types.Dashboard{}
|
|
|
|
err := module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(&dashboards).
|
|
Where("org_id = ?", orgID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return dashboards, nil
|
|
}
|
|
|
|
func (module *module) Delete(ctx context.Context, orgID, uuid string) error {
|
|
dashboard, err := module.Get(ctx, orgID, uuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be able to delete it")
|
|
}
|
|
|
|
result, err := module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewDelete().
|
|
Model(&types.Dashboard{}).
|
|
Where("org_id = ?", orgID).
|
|
Where("uuid = ?", uuid).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
affectedRows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if affectedRows == 0 {
|
|
return errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "no dashboard found with uuid: %s", uuid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (module *module) Get(ctx context.Context, orgID, uuid string) (*types.Dashboard, error) {
|
|
dashboard := types.Dashboard{}
|
|
err := module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(&dashboard).
|
|
Where("org_id = ?", orgID).
|
|
Where("uuid = ?", uuid).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, module.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "dashboard with uuid %s not found", uuid)
|
|
}
|
|
|
|
return &dashboard, nil
|
|
}
|
|
|
|
func (module *module) Update(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, error) {
|
|
mapData, err := json.Marshal(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dashboard, err := module.Get(ctx, orgID, uuid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if dashboard.Locked != nil && *dashboard.Locked == 1 {
|
|
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "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, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "deleting more than one panel is not supported")
|
|
}
|
|
|
|
dashboard.UpdatedAt = time.Now()
|
|
dashboard.UpdatedBy = userEmail
|
|
dashboard.Data = data
|
|
|
|
_, err = module.sqlstore.
|
|
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 {
|
|
return nil, err
|
|
}
|
|
|
|
return dashboard, nil
|
|
}
|
|
|
|
func (module *module) LockUnlock(ctx context.Context, orgID, uuid string, lock bool) error {
|
|
dashboard, err := module.Get(ctx, orgID, uuid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var lockValue int
|
|
if lock {
|
|
lockValue = 1
|
|
} else {
|
|
lockValue = 0
|
|
}
|
|
|
|
_, err = module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewUpdate().
|
|
Model(dashboard).
|
|
Set("locked = ?", lockValue).
|
|
Where("org_id = ?", orgID).
|
|
Where("uuid = ?", uuid).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (module *module) GetByMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, error) {
|
|
dashboards := []types.Dashboard{}
|
|
err := module.
|
|
sqlstore.
|
|
BunDB().
|
|
NewSelect().
|
|
Model(&dashboards).
|
|
Where("org_id = ?", orgID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, 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
|
|
}
|
|
|
|
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
|
|
}
|