mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-20 00:49:05 +08:00
chore(savedview|apdex|dashboard): create modules and handlers (#7960)
* 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
This commit is contained in:
parent
6821efeb99
commit
da084b4686
@ -66,7 +66,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
||||
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
|
||||
Reader: opts.DataConnector,
|
||||
PreferSpanMetrics: opts.PreferSpanMetrics,
|
||||
AppDao: opts.AppDao,
|
||||
RuleManager: opts.RulesManager,
|
||||
FeatureFlags: opts.FeatureFlags,
|
||||
IntegrationsController: opts.IntegrationsController,
|
||||
|
@ -6,7 +6,6 @@ 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/gorilla/mux"
|
||||
)
|
||||
@ -41,9 +40,9 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, apiErr := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
|
||||
if apiErr != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get dashboard"))
|
||||
dashboard, err := ah.Signoz.Modules.Dashboard.Get(r.Context(), claims.OrgID, uuid)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -53,9 +52,9 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
// Lock/Unlock the dashboard
|
||||
apiErr = dashboards.LockUnlockDashboard(r.Context(), claims.OrgID, uuid, lock)
|
||||
if apiErr != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to lock/unlock dashboard"))
|
||||
err = ah.Signoz.Modules.Dashboard.LockUnlock(r.Context(), claims.OrgID, uuid, lock)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/query-service/app/api"
|
||||
"github.com/SigNoz/signoz/ee/query-service/app/db"
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao/sqlite"
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
@ -36,8 +36,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"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||
@ -92,19 +90,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
// NewServer creates and initializes Server
|
||||
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
modelDao, err := dao.InitDao(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := baseexplorer.InitWithDSN(serverOptions.SigNoz.SQLStore); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modelDao := sqlite.NewModelDao(serverOptions.SigNoz.SQLStore)
|
||||
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -116,9 +102,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set license manager as feature flag provider in dao
|
||||
modelDao.SetFlagProvider(lm)
|
||||
|
||||
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -197,6 +180,11 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
telemetry.GetInstance().SetReader(reader)
|
||||
telemetry.GetInstance().SetSqlStore(serverOptions.SigNoz.SQLStore)
|
||||
telemetry.GetInstance().SetSaasOperator(constants.SaasSegmentKey)
|
||||
telemetry.GetInstance().SetSavedViewsInfoCallback(telemetry.GetSavedViewsInfo)
|
||||
telemetry.GetInstance().SetAlertsInfoCallback(telemetry.GetAlertsInfo)
|
||||
telemetry.GetInstance().SetGetUsersCallback(telemetry.GetUsers)
|
||||
telemetry.GetInstance().SetUserCountCallback(telemetry.GetUserCount)
|
||||
telemetry.GetInstance().SetDashboardsInfoCallback(telemetry.GetDashboardsInfo)
|
||||
|
||||
fluxInterval, err := time.ParseDuration(serverOptions.FluxInterval)
|
||||
if err != nil {
|
||||
|
@ -1,10 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao/sqlite"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
func InitDao(sqlStore sqlstore.SQLStore) (ModelDao, error) {
|
||||
return sqlite.InitDB(sqlStore)
|
||||
}
|
@ -5,23 +5,13 @@ import (
|
||||
"net/url"
|
||||
|
||||
eeTypes "github.com/SigNoz/signoz/ee/types"
|
||||
basedao "github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type ModelDao interface {
|
||||
basedao.ModelDao
|
||||
|
||||
// SetFlagProvider sets the feature lookup provider
|
||||
SetFlagProvider(flags baseint.FeatureLookup)
|
||||
|
||||
DB() *bun.DB
|
||||
|
||||
// auth methods
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*types.GettableOrgDomain, error)
|
||||
|
||||
|
@ -70,7 +70,7 @@ func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url
|
||||
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.DB().NewSelect().
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("name = ?", name).
|
||||
Limit(1).
|
||||
@ -94,7 +94,7 @@ func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*types.Get
|
||||
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.DB().NewSelect().
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("id = ?", id).
|
||||
Limit(1).
|
||||
@ -119,7 +119,7 @@ func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]types.Getta
|
||||
domains := []types.GettableOrgDomain{}
|
||||
|
||||
stored := []types.StorableOrgDomain{}
|
||||
err := m.DB().NewSelect().
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("org_id = ?", orgId).
|
||||
Scan(ctx)
|
||||
@ -167,7 +167,7 @@ func (m *modelDao) CreateDomain(ctx context.Context, domain *types.GettableOrgDo
|
||||
TimeAuditable: ossTypes.TimeAuditable{CreatedAt: time.Now(), UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = m.DB().NewInsert().
|
||||
_, err = m.sqlStore.BunDB().NewInsert().
|
||||
Model(&storableDomain).
|
||||
Exec(ctx)
|
||||
|
||||
@ -201,7 +201,7 @@ func (m *modelDao) UpdateDomain(ctx context.Context, domain *types.GettableOrgDo
|
||||
TimeAuditable: ossTypes.TimeAuditable{UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = m.DB().NewUpdate().
|
||||
_, err = m.sqlStore.BunDB().NewUpdate().
|
||||
Model(storableDomain).
|
||||
Column("data", "updated_at").
|
||||
WherePK().
|
||||
@ -224,7 +224,7 @@ func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.Bas
|
||||
}
|
||||
|
||||
storableDomain := &types.StorableOrgDomain{ID: id}
|
||||
_, err := m.DB().NewDelete().
|
||||
_, err := m.sqlStore.BunDB().NewDelete().
|
||||
Model(storableDomain).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
@ -251,7 +251,7 @@ func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*types.G
|
||||
parsedDomain := components[1]
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.DB().NewSelect().
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("name = ?", parsedDomain).
|
||||
Limit(1).
|
||||
|
@ -1,50 +1,18 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
basedao "github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
basedsql "github.com/SigNoz/signoz/pkg/query-service/dao/sqlite"
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type modelDao struct {
|
||||
*basedsql.ModelDaoSqlite
|
||||
flags baseint.FeatureLookup
|
||||
userModule user.Module
|
||||
}
|
||||
|
||||
// SetFlagProvider sets the feature lookup provider
|
||||
func (m *modelDao) SetFlagProvider(flags baseint.FeatureLookup) {
|
||||
m.flags = flags
|
||||
}
|
||||
|
||||
// CheckFeature confirms if a feature is available
|
||||
func (m *modelDao) checkFeature(key string) error {
|
||||
if m.flags == nil {
|
||||
return fmt.Errorf("flag provider not set")
|
||||
}
|
||||
|
||||
return m.flags.CheckFeature(key)
|
||||
sqlStore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
// InitDB creates and extends base model DB repository
|
||||
func InitDB(sqlStore sqlstore.SQLStore) (*modelDao, error) {
|
||||
dao, err := basedsql.InitDB(sqlStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// set package variable so dependent base methods (e.g. AuthCache) will work
|
||||
basedao.SetDB(dao)
|
||||
func NewModelDao(sqlStore sqlstore.SQLStore) *modelDao {
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore))
|
||||
m := &modelDao{ModelDaoSqlite: dao, userModule: userModule}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *modelDao) DB() *bun.DB {
|
||||
return m.ModelDaoSqlite.DB()
|
||||
return &modelDao{userModule: userModule, sqlStore: sqlStore}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError) {
|
||||
p.StorablePersonalAccessToken.OrgID = orgID
|
||||
p.StorablePersonalAccessToken.ID = valuer.GenerateUUID()
|
||||
_, err := m.DB().NewInsert().
|
||||
_, err := m.sqlStore.BunDB().NewInsert().
|
||||
Model(&p.StorablePersonalAccessToken).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
@ -50,7 +50,7 @@ func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.Gettable
|
||||
}
|
||||
|
||||
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError {
|
||||
_, err := m.DB().NewUpdate().
|
||||
_, err := m.sqlStore.BunDB().NewUpdate().
|
||||
Model(&p.StorablePersonalAccessToken).
|
||||
Column("role", "name", "updated_at", "updated_by_user_id").
|
||||
Where("id = ?", id.StringValue()).
|
||||
@ -67,7 +67,7 @@ func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.Gettable
|
||||
func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
if err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&pats).
|
||||
Where("revoked = false").
|
||||
Where("org_id = ?", orgID).
|
||||
@ -134,7 +134,7 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.Gettable
|
||||
|
||||
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError {
|
||||
updatedAt := time.Now().Unix()
|
||||
_, err := m.DB().NewUpdate().
|
||||
_, err := m.sqlStore.BunDB().NewUpdate().
|
||||
Model(&types.StorablePersonalAccessToken{}).
|
||||
Set("revoked = ?", true).
|
||||
Set("updated_by_user_id = ?", userID).
|
||||
@ -152,7 +152,7 @@ func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id valuer.UUID,
|
||||
func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
if err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&pats).
|
||||
Where("token = ?", token).
|
||||
Where("revoked = false").
|
||||
@ -177,7 +177,7 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT
|
||||
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError) {
|
||||
pats := []types.StorablePersonalAccessToken{}
|
||||
|
||||
if err := m.DB().NewSelect().
|
||||
if err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&pats).
|
||||
Where("id = ?", id.StringValue()).
|
||||
Where("org_id = ?", orgID).
|
||||
|
20
pkg/modules/apdex/apdex.go
Normal file
20
pkg/modules/apdex/apdex.go
Normal file
@ -0,0 +1,20 @@
|
||||
package apdex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Get(context.Context, string, []string) ([]*types.ApdexSettings, error)
|
||||
|
||||
Set(context.Context, string, *types.ApdexSettings) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Get(http.ResponseWriter, *http.Request)
|
||||
|
||||
Set(http.ResponseWriter, *http.Request)
|
||||
}
|
66
pkg/modules/apdex/implapdex/handler.go
Normal file
66
pkg/modules/apdex/implapdex/handler.go
Normal file
@ -0,0 +1,66 @@
|
||||
package implapdex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module apdex.Module
|
||||
}
|
||||
|
||||
func NewHandler(module apdex.Module) apdex.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Set(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
var apdexSettings types.ApdexSettings
|
||||
if err := json.NewDecoder(req.Body).Decode(&apdexSettings); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := handler.module.Set(ctx, claims.OrgID, &apdexSettings); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, map[string]string{"data": "apdex score updated successfully"})
|
||||
}
|
||||
|
||||
func (handler *handler) Get(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(req.Context())
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
services := req.URL.Query().Get("services")
|
||||
apdexSettings, err := handler.module.Get(ctx, claims.OrgID, strings.Split(strings.TrimSpace(services), ","))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, apdexSettings)
|
||||
}
|
@ -1,28 +1,43 @@
|
||||
package sqlite
|
||||
package implapdex
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
const defaultApdexThreshold = 0.5
|
||||
const (
|
||||
defaultApdexThreshold float64 = 0.5
|
||||
)
|
||||
|
||||
func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, orgID string, services []string) ([]types.ApdexSettings, *model.ApiError) {
|
||||
var apdexSettings []types.ApdexSettings
|
||||
type module struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
err := mds.bundb.NewSelect().
|
||||
func NewModule(sqlstore sqlstore.SQLStore) apdex.Module {
|
||||
return &module{
|
||||
sqlstore: sqlstore,
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID string, services []string) ([]*types.ApdexSettings, error) {
|
||||
var apdexSettings []*types.ApdexSettings
|
||||
|
||||
err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&apdexSettings).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("service_name IN (?)", bun.In(services)).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{
|
||||
Err: err,
|
||||
}
|
||||
return nil, module.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "apdex settings not found for services %v", services)
|
||||
}
|
||||
|
||||
// add default apdex settings for services that don't have any
|
||||
@ -36,7 +51,7 @@ func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, orgID string, s
|
||||
}
|
||||
|
||||
if !found {
|
||||
apdexSettings = append(apdexSettings, types.ApdexSettings{
|
||||
apdexSettings = append(apdexSettings, &types.ApdexSettings{
|
||||
ServiceName: service,
|
||||
Threshold: defaultApdexThreshold,
|
||||
})
|
||||
@ -46,21 +61,21 @@ func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, orgID string, s
|
||||
return apdexSettings, nil
|
||||
}
|
||||
|
||||
func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, orgID string, apdexSettings *types.ApdexSettings) *model.ApiError {
|
||||
// Set the org_id from the parameter since it's required for the foreign key constraint
|
||||
func (module *module) Set(ctx context.Context, orgID string, apdexSettings *types.ApdexSettings) error {
|
||||
apdexSettings.OrgID = orgID
|
||||
apdexSettings.Identifiable.ID = valuer.GenerateUUID()
|
||||
|
||||
_, err := mds.bundb.NewInsert().
|
||||
_, err := module.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(apdexSettings).
|
||||
On("CONFLICT (org_id, service_name) DO UPDATE").
|
||||
Set("threshold = EXCLUDED.threshold").
|
||||
Set("exclude_status_codes = EXCLUDED.exclude_status_codes").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return &model.ApiError{
|
||||
Err: err,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
28
pkg/modules/dashboard/dashboard.go
Normal file
28
pkg/modules/dashboard/dashboard.go
Normal file
@ -0,0 +1,28 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
Create(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, error)
|
||||
|
||||
List(ctx context.Context, orgID string) ([]*types.Dashboard, error)
|
||||
|
||||
Delete(ctx context.Context, orgID, uuid string) error
|
||||
|
||||
Get(ctx context.Context, orgID, uuid string) (*types.Dashboard, error)
|
||||
|
||||
GetByMetricNames(ctx context.Context, orgID string, metricNames []string) (map[string][]map[string]string, error)
|
||||
|
||||
Update(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, error)
|
||||
|
||||
LockUnlock(ctx context.Context, orgID, uuid string, lock bool) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
}
|
41
pkg/modules/dashboard/impldashboard/handler.go
Normal file
41
pkg/modules/dashboard/impldashboard/handler.go
Normal file
@ -0,0 +1,41 @@
|
||||
package impldashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module dashboard.Module
|
||||
}
|
||||
|
||||
func NewHandler(module dashboard.Module) dashboard.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := mux.Vars(req)["uuid"]
|
||||
|
||||
err = handler.module.Delete(ctx, claims.OrgID, uuid)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, nil)
|
||||
}
|
331
pkg/modules/dashboard/impldashboard/module.go
Normal file
331
pkg/modules/dashboard/impldashboard/module.go
Normal file
@ -0,0 +1,331 @@
|
||||
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
|
||||
}
|
165
pkg/modules/savedview/implsavedview/handler.go
Normal file
165
pkg/modules/savedview/implsavedview/handler.go
Normal file
@ -0,0 +1,165 @@
|
||||
package implsavedview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
module savedview.Module
|
||||
}
|
||||
|
||||
func NewHandler(module savedview.Module) savedview.Handler {
|
||||
return &handler{module: module}
|
||||
}
|
||||
|
||||
func (handler *handler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var view v3.SavedView
|
||||
if err := json.NewDecoder(r.Body).Decode(&view); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode request body"))
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := view.Validate(); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to validate request body"))
|
||||
return
|
||||
}
|
||||
|
||||
uuid, err := handler.module.CreateView(ctx, claims.OrgID, view)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, uuid)
|
||||
}
|
||||
|
||||
func (handler *handler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
|
||||
return
|
||||
}
|
||||
|
||||
view, err := handler.module.GetView(ctx, claims.OrgID, viewUUID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, view)
|
||||
}
|
||||
|
||||
func (handler *handler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
|
||||
return
|
||||
}
|
||||
var view v3.SavedView
|
||||
if err := json.NewDecoder(r.Body).Decode(&view); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode request body"))
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := view.Validate(); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to validate request body"))
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.UpdateView(ctx, claims.OrgID, viewUUID, view)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, view)
|
||||
}
|
||||
|
||||
func (handler *handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse view id"))
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.DeleteView(ctx, claims.OrgID, viewUUID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, nil)
|
||||
}
|
||||
|
||||
func (handler *handler) List(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
sourcePage := r.URL.Query().Get("sourcePage")
|
||||
name := r.URL.Query().Get("name")
|
||||
category := r.URL.Query().Get("category")
|
||||
|
||||
queries, err := handler.module.GetViewsForFilters(r.Context(), claims.OrgID, sourcePage, name, category)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, queries)
|
||||
}
|
@ -1,75 +1,35 @@
|
||||
package explorer
|
||||
package implsavedview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var store sqlstore.SQLStore
|
||||
|
||||
// InitWithDSN sets up setting up the connection pool global variable.
|
||||
func InitWithDSN(sqlStore sqlstore.SQLStore) error {
|
||||
store = sqlStore
|
||||
telemetry.GetInstance().SetSavedViewsInfoCallback(GetSavedViewsInfo)
|
||||
|
||||
return nil
|
||||
type module struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func InitWithDB(sqlStore sqlstore.SQLStore) {
|
||||
store = sqlStore
|
||||
func NewModule(sqlstore sqlstore.SQLStore) savedview.Module {
|
||||
return &module{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func GetViews(ctx context.Context, orgID string) ([]*v3.SavedView, error) {
|
||||
var views []types.SavedView
|
||||
err := store.BunDB().NewSelect().Model(&views).Where("org_id = ?", orgID).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||
}
|
||||
|
||||
var savedViews []*v3.SavedView
|
||||
for _, view := range views {
|
||||
var compositeQuery v3.CompositeQuery
|
||||
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
savedViews = append(savedViews, &v3.SavedView{
|
||||
ID: view.ID,
|
||||
Name: view.Name,
|
||||
Category: view.Category,
|
||||
CreatedAt: view.CreatedAt,
|
||||
CreatedBy: view.CreatedBy,
|
||||
UpdatedAt: view.UpdatedAt,
|
||||
UpdatedBy: view.UpdatedBy,
|
||||
Tags: strings.Split(view.Tags, ","),
|
||||
SourcePage: view.SourcePage,
|
||||
CompositeQuery: &compositeQuery,
|
||||
ExtraData: view.ExtraData,
|
||||
})
|
||||
}
|
||||
return savedViews, nil
|
||||
}
|
||||
|
||||
func GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error) {
|
||||
func (module *module) GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error) {
|
||||
var views []types.SavedView
|
||||
var err error
|
||||
if len(category) == 0 {
|
||||
err = store.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND name LIKE ?", orgID, sourcePage, "%"+name+"%").Scan(ctx)
|
||||
err = module.sqlstore.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND name LIKE ?", orgID, sourcePage, "%"+name+"%").Scan(ctx)
|
||||
} else {
|
||||
err = store.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND category LIKE ? AND name LIKE ?", orgID, sourcePage, "%"+category+"%", "%"+name+"%").Scan(ctx)
|
||||
err = module.sqlstore.BunDB().NewSelect().Model(&views).Where("org_id = ? AND source_page = ? AND category LIKE ? AND name LIKE ?", orgID, sourcePage, "%"+category+"%", "%"+name+"%").Scan(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||
@ -98,7 +58,7 @@ func GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, na
|
||||
return savedViews, nil
|
||||
}
|
||||
|
||||
func CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error) {
|
||||
func (module *module) CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error) {
|
||||
data, err := json.Marshal(view.CompositeQuery)
|
||||
if err != nil {
|
||||
return valuer.UUID{}, fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||
@ -137,16 +97,16 @@ func CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UU
|
||||
ExtraData: view.ExtraData,
|
||||
}
|
||||
|
||||
_, err = store.BunDB().NewInsert().Model(&dbView).Exec(ctx)
|
||||
_, err = module.sqlstore.BunDB().NewInsert().Model(&dbView).Exec(ctx)
|
||||
if err != nil {
|
||||
return valuer.UUID{}, fmt.Errorf("error in creating saved view: %s", err.Error())
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error) {
|
||||
func (module *module) GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error) {
|
||||
var view types.SavedView
|
||||
err := store.BunDB().NewSelect().Model(&view).Where("org_id = ? AND id = ?", orgID, uuid.StringValue()).Scan(ctx)
|
||||
err := module.sqlstore.BunDB().NewSelect().Model(&view).Where("org_id = ? AND id = ?", orgID, uuid.StringValue()).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting saved view: %s", err.Error())
|
||||
}
|
||||
@ -171,7 +131,7 @@ func GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error {
|
||||
func (module *module) UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error {
|
||||
data, err := json.Marshal(view.CompositeQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||
@ -185,7 +145,7 @@ func UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.Sav
|
||||
updatedAt := time.Now()
|
||||
updatedBy := claims.Email
|
||||
|
||||
_, err = store.BunDB().NewUpdate().
|
||||
_, err = module.sqlstore.BunDB().NewUpdate().
|
||||
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).
|
||||
@ -198,8 +158,8 @@ func UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.Sav
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error {
|
||||
_, err := store.BunDB().NewDelete().
|
||||
func (module *module) DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error {
|
||||
_, err := module.sqlstore.BunDB().NewDelete().
|
||||
Model(&types.SavedView{}).
|
||||
Where("id = ?", uuid.StringValue()).
|
||||
Where("org_id = ?", orgID).
|
||||
@ -209,43 +169,3 @@ func DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSavedViewsInfo(ctx context.Context) (*model.SavedViewsInfo, error) {
|
||||
savedViewsInfo := model.SavedViewsInfo{}
|
||||
// get single org ID from db
|
||||
var orgIDs []string
|
||||
err := store.BunDB().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 {
|
||||
zap.S().Debug("Error in fetching saved views info: ", err)
|
||||
return &savedViewsInfo, err
|
||||
}
|
||||
savedViewsInfo.TotalSavedViews = len(savedViews)
|
||||
for _, view := range savedViews {
|
||||
if view.SourcePage == "traces" {
|
||||
savedViewsInfo.TracesSavedViews += 1
|
||||
} else if view.SourcePage == "logs" {
|
||||
savedViewsInfo.LogsSavedViews += 1
|
||||
|
||||
for _, query := range view.CompositeQuery.BuilderQueries {
|
||||
if query.Filters != nil {
|
||||
for _, item := range query.Filters.Items {
|
||||
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, string(item.Operator)) {
|
||||
if item.Key.Key != "body" {
|
||||
savedViewsInfo.LogsSavedViewWithContainsOp += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &savedViewsInfo, nil
|
||||
}
|
38
pkg/modules/savedview/savedview.go
Normal file
38
pkg/modules/savedview/savedview.go
Normal file
@ -0,0 +1,38 @@
|
||||
package savedview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, name string, category string) ([]*v3.SavedView, error)
|
||||
|
||||
CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error)
|
||||
|
||||
GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error)
|
||||
|
||||
UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error
|
||||
|
||||
DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
// Creates the saved view
|
||||
Create(http.ResponseWriter, *http.Request)
|
||||
|
||||
// Gets the saved view
|
||||
Get(http.ResponseWriter, *http.Request)
|
||||
|
||||
// Updates the saved view
|
||||
Update(http.ResponseWriter, *http.Request)
|
||||
|
||||
// Deletes the saved view
|
||||
Delete(http.ResponseWriter, *http.Request)
|
||||
|
||||
// Lists the saved views
|
||||
List(http.ResponseWriter, *http.Request)
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
req, err := parseSetApdexScoreRequest(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dao.DB().SetApdexSettings(r.Context(), claims.OrgID, req); err != nil {
|
||||
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, map[string]string{"data": "apdex score updated successfully"})
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||
services := r.URL.Query().Get("services")
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
apdexSet, err := dao.DB().GetApdexSettings(r.Context(), claims.OrgID, strings.Split(strings.TrimSpace(services), ","))
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.WriteJSON(w, r, apdexSet)
|
||||
}
|
@ -521,9 +521,9 @@ 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,
|
||||
[]*types.Dashboard, *model.ApiError,
|
||||
) {
|
||||
allDashboards := []types.Dashboard{}
|
||||
allDashboards := []*types.Dashboard{}
|
||||
|
||||
for _, provider := range []string{"aws"} {
|
||||
providerDashboards, apiErr := c.AvailableDashboardsForCloudProvider(ctx, orgId, provider)
|
||||
@ -541,7 +541,7 @@ func (c *Controller) AvailableDashboards(ctx context.Context, orgId string) (
|
||||
|
||||
func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
ctx context.Context, orgID string, cloudProvider string,
|
||||
) ([]types.Dashboard, *model.ApiError) {
|
||||
) ([]*types.Dashboard, *model.ApiError) {
|
||||
|
||||
accountRecords, apiErr := c.accountsRepo.listConnected(ctx, orgID, cloudProvider)
|
||||
if apiErr != nil {
|
||||
@ -573,14 +573,14 @@ func (c *Controller) AvailableDashboardsForCloudProvider(
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
svcDashboards := []types.Dashboard{}
|
||||
svcDashboards := []*types.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{
|
||||
svcDashboards = append(svcDashboards, &types.Dashboard{
|
||||
UUID: c.dashboardUuid(cloudProvider, svc.Id, d.Id),
|
||||
Locked: &isLocked,
|
||||
Data: *d.Definition,
|
||||
@ -619,7 +619,7 @@ func (c *Controller) GetDashboardById(
|
||||
|
||||
for _, d := range allDashboards {
|
||||
if d.UUID == dashboardUuid {
|
||||
return &d, nil
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,532 +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
|
||||
}
|
@ -39,8 +39,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
"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/explorer"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
|
||||
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/thirdPartyApi"
|
||||
@ -67,7 +65,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
@ -91,7 +88,6 @@ func NewRouter() *mux.Router {
|
||||
// APIHandler implements the query service public API
|
||||
type APIHandler struct {
|
||||
reader interfaces.Reader
|
||||
appDao dao.ModelDao
|
||||
ruleManager *rules.Manager
|
||||
featureFlags interfaces.FeatureLookup
|
||||
querier interfaces.Querier
|
||||
@ -156,9 +152,6 @@ type APIHandlerOpts struct {
|
||||
|
||||
PreferSpanMetrics bool
|
||||
|
||||
// dao layer to perform crud on app objects like dashboard, alerts etc
|
||||
AppDao dao.ModelDao
|
||||
|
||||
// rule manager handles rule crud operations
|
||||
RuleManager *rules.Manager
|
||||
|
||||
@ -223,12 +216,11 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
||||
statefulsetsRepo := inframetrics.NewStatefulSetsRepo(opts.Reader, querierv2)
|
||||
jobsRepo := inframetrics.NewJobsRepo(opts.Reader, querierv2)
|
||||
pvcsRepo := inframetrics.NewPvcsRepo(opts.Reader, querierv2)
|
||||
summaryService := metricsexplorer.NewSummaryService(opts.Reader, opts.RuleManager)
|
||||
summaryService := metricsexplorer.NewSummaryService(opts.Reader, opts.RuleManager, opts.Signoz.Modules.Dashboard)
|
||||
//quickFilterModule := quickfilter.NewAPI(opts.QuickFilterModule)
|
||||
|
||||
aH := &APIHandler{
|
||||
reader: opts.Reader,
|
||||
appDao: opts.AppDao,
|
||||
preferSpanMetrics: opts.PreferSpanMetrics,
|
||||
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
||||
ruleManager: opts.RuleManager,
|
||||
@ -531,14 +523,14 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
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/v1/dashboards/{uuid}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Delete)).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)
|
||||
router.HandleFunc("/api/v1/explorer/views", am.EditAccess(aH.createSavedViews)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.getSavedView)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.updateSavedView)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.deleteSavedView)).Methods(http.MethodDelete)
|
||||
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/explorer/views", am.EditAccess(aH.Signoz.Handlers.SavedView.Create)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.ViewAccess(aH.Signoz.Handlers.SavedView.Get)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.Signoz.Handlers.SavedView.Update)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/explorer/views/{viewId}", am.EditAccess(aH.Signoz.Handlers.SavedView.Delete)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/feedback", am.OpenAccess(aH.submitFeedback)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/event", am.ViewAccess(aH.registerEvent)).Methods(http.MethodPost)
|
||||
@ -553,8 +545,8 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/dependency_graph", am.ViewAccess(aH.dependencyGraph)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/ttl", am.AdminAccess(aH.setTTL)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/ttl", am.ViewAccess(aH.getTTL)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/settings/apdex", am.AdminAccess(aH.setApdexSettings)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/apdex", am.ViewAccess(aH.getApdexSettings)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/settings/apdex", am.AdminAccess(aH.Signoz.Handlers.Apdex.Set)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/settings/apdex", am.ViewAccess(aH.Signoz.Handlers.Apdex.Get)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v2/traces/fields", am.ViewAccess(aH.traceFields)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v2/traces/fields", am.EditAccess(aH.updateTraceField)).Methods(http.MethodPost)
|
||||
@ -1093,9 +1085,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, errv2 := aH.Signoz.Modules.Dashboard.List(r.Context(), claims.OrgID)
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1147,7 +1140,7 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
inter = Intersection(inter, tags2Dash[tag])
|
||||
}
|
||||
|
||||
filteredDashboards := []types.Dashboard{}
|
||||
filteredDashboards := []*types.Dashboard{}
|
||||
for _, val := range inter {
|
||||
dash := (allDashboards)[val]
|
||||
filteredDashboards = append(filteredDashboards, dash)
|
||||
@ -1155,23 +1148,6 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
aH.Respond(w, filteredDashboards)
|
||||
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, nil)
|
||||
|
||||
}
|
||||
|
||||
func prepareQuery(r *http.Request) (string, error) {
|
||||
@ -1235,6 +1211,12 @@ func (aH *APIHandler) queryDashboardVarsV2(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
|
||||
uuid := mux.Vars(r)["uuid"]
|
||||
|
||||
var postData map[string]interface{}
|
||||
@ -1243,20 +1225,16 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body")
|
||||
return
|
||||
}
|
||||
err = dashboards.IsPostDataSane(&postData)
|
||||
|
||||
err = aH.IsDashboardPostDataSane(&postData)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body")
|
||||
return
|
||||
}
|
||||
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
dashboard, apiError := dashboards.UpdateDashboard(r.Context(), claims.OrgID, claims.Email, uuid, postData)
|
||||
dashboard, apiError := aH.Signoz.Modules.Dashboard.Update(r.Context(), claims.OrgID, claims.Email, uuid, postData)
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
render.Error(w, apiError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1264,6 +1242,15 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func (aH *APIHandler) IsDashboardPostDataSane(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 (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
uuid := mux.Vars(r)["uuid"]
|
||||
@ -1273,11 +1260,12 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
dashboard, apiError := dashboards.GetDashboard(r.Context(), claims.OrgID, uuid)
|
||||
dashboard, errv2 := aH.Signoz.Modules.Dashboard.Get(r.Context(), claims.OrgID, uuid)
|
||||
|
||||
if apiError != nil {
|
||||
if apiError.Type() != model.ErrorNotFound {
|
||||
RespondError(w, apiError, nil)
|
||||
var apiError *model.ApiError
|
||||
if errv2 != nil {
|
||||
if !errorsV2.Ast(errv2, errorsV2.TypeNotFound) {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1308,7 +1296,6 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var postData map[string]interface{}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&postData)
|
||||
@ -1317,7 +1304,7 @@ func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = dashboards.IsPostDataSane(&postData)
|
||||
err = aH.IsDashboardPostDataSane(&postData)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body")
|
||||
return
|
||||
@ -1327,10 +1314,10 @@ func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
dash, apiErr := dashboards.CreateDashboard(r.Context(), claims.OrgID, claims.Email, postData)
|
||||
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
dash, errv2 := aH.Signoz.Modules.Dashboard.Create(r.Context(), claims.OrgID, claims.Email, postData)
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
|
||||
@ -4233,129 +4220,6 @@ func (aH *APIHandler) CreateLogsPipeline(w http.ResponseWriter, r *http.Request)
|
||||
aH.Respond(w, res)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getSavedViews(w http.ResponseWriter, r *http.Request) {
|
||||
// get sourcePage, name, and category from the query params
|
||||
sourcePage := r.URL.Query().Get("sourcePage")
|
||||
name := r.URL.Query().Get("name")
|
||||
category := r.URL.Query().Get("category")
|
||||
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
|
||||
queries, err := explorer.GetViewsForFilters(r.Context(), claims.OrgID, sourcePage, name, category)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, queries)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) {
|
||||
var view v3.SavedView
|
||||
err := json.NewDecoder(r.Body).Decode(&view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := view.Validate(); err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
uuid, err := explorer.CreateView(r.Context(), claims.OrgID, view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, uuid)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
view, err := explorer.GetView(r.Context(), claims.OrgID, viewUUID)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, view)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
|
||||
return
|
||||
}
|
||||
var view v3.SavedView
|
||||
err = json.NewDecoder(r.Body).Decode(&view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
// validate the query
|
||||
if err := view.Validate(); err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
err = explorer.UpdateView(r.Context(), claims.OrgID, viewUUID, view)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, view)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) deleteSavedView(w http.ResponseWriter, r *http.Request) {
|
||||
viewID := mux.Vars(r)["viewId"]
|
||||
viewUUID, err := valuer.NewUUID(viewID)
|
||||
if err != nil {
|
||||
render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error()))
|
||||
return
|
||||
}
|
||||
claims, errv2 := authtypes.ClaimsFromContext(r.Context())
|
||||
if errv2 != nil {
|
||||
render.Error(w, errv2)
|
||||
return
|
||||
}
|
||||
err = explorer.DeleteView(r.Context(), claims.OrgID, viewUUID)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
aH.Respond(w, nil)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) autocompleteAggregateAttributes(w http.ResponseWriter, r *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
|
@ -129,7 +129,7 @@ func (c *Controller) GetPipelinesForInstalledIntegrations(
|
||||
|
||||
func (c *Controller) GetDashboardsForInstalledIntegrations(
|
||||
ctx context.Context, orgId string,
|
||||
) ([]types.Dashboard, *model.ApiError) {
|
||||
) ([]*types.Dashboard, *model.ApiError) {
|
||||
return c.mgr.GetDashboardsForInstalledIntegrations(ctx, orgId)
|
||||
}
|
||||
|
||||
|
@ -355,13 +355,13 @@ func (m *Manager) GetInstalledIntegrationDashboardById(
|
||||
func (m *Manager) GetDashboardsForInstalledIntegrations(
|
||||
ctx context.Context,
|
||||
orgId string,
|
||||
) ([]types.Dashboard, *model.ApiError) {
|
||||
) ([]*types.Dashboard, *model.ApiError) {
|
||||
installedIntegrations, apiErr := m.getInstalledIntegrations(ctx, orgId)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
result := []types.Dashboard{}
|
||||
result := []*types.Dashboard{}
|
||||
|
||||
for _, ii := range installedIntegrations {
|
||||
for _, dd := range ii.Assets.Dashboards {
|
||||
@ -369,7 +369,7 @@ func (m *Manager) GetDashboardsForInstalledIntegrations(
|
||||
if dashboardId, ok := dId.(string); ok {
|
||||
isLocked := 1
|
||||
author := "integration"
|
||||
result = append(result, types.Dashboard{
|
||||
result = append(result, &types.Dashboard{
|
||||
UUID: m.dashboardUuid(ii.IntegrationSummary.Id, dashboardId),
|
||||
Locked: &isLocked,
|
||||
Data: dd,
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/dashboards"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model/metrics_explorer"
|
||||
@ -24,10 +24,11 @@ import (
|
||||
type SummaryService struct {
|
||||
reader interfaces.Reader
|
||||
rulesManager *rules.Manager
|
||||
dashboard dashboard.Module
|
||||
}
|
||||
|
||||
func NewSummaryService(reader interfaces.Reader, alertManager *rules.Manager) *SummaryService {
|
||||
return &SummaryService{reader: reader, rulesManager: alertManager}
|
||||
func NewSummaryService(reader interfaces.Reader, alertManager *rules.Manager, dashboard dashboard.Module) *SummaryService {
|
||||
return &SummaryService{reader: reader, rulesManager: alertManager, dashboard: dashboard}
|
||||
}
|
||||
|
||||
func (receiver *SummaryService) FilterKeys(ctx context.Context, params *metrics_explorer.FilterKeyRequest) (*metrics_explorer.FilterKeyResponse, *model.ApiError) {
|
||||
@ -164,7 +165,7 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, orgID val
|
||||
if errv2 != nil {
|
||||
return &model.ApiError{Typ: model.ErrorInternal, Err: errv2}
|
||||
}
|
||||
data, err := dashboards.GetDashboardsWithMetricNames(ctx, claims.OrgID, metricNames)
|
||||
data, err := receiver.dashboard.GetByMetricNames(ctx, claims.OrgID, metricNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -337,9 +338,9 @@ func (receiver *SummaryService) GetRelatedMetrics(ctx context.Context, params *m
|
||||
if errv2 != nil {
|
||||
return &model.ApiError{Typ: model.ErrorInternal, Err: errv2}
|
||||
}
|
||||
names, apiError := dashboards.GetDashboardsWithMetricNames(ctx, claims.OrgID, metricNames)
|
||||
if apiError != nil {
|
||||
return apiError
|
||||
names, err := receiver.dashboard.GetByMetricNames(ctx, claims.OrgID, metricNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if names != nil {
|
||||
jsonData, err := json.Marshal(names)
|
||||
|
@ -32,7 +32,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
querytemplate "github.com/SigNoz/signoz/pkg/query-service/utils/queryTemplate"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
chVariables "github.com/SigNoz/signoz/pkg/variables/clickhouse"
|
||||
)
|
||||
|
||||
@ -472,14 +471,6 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
|
||||
return &model.GetTTLParams{Type: typeTTL}, nil
|
||||
}
|
||||
|
||||
func parseSetApdexScoreRequest(r *http.Request) (*types.ApdexSettings, error) {
|
||||
var req types.ApdexSettings
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
func parseAggregateAttributeRequest(r *http.Request) (*v3.AggregateAttributeRequest, error) {
|
||||
var req v3.AggregateAttributeRequest
|
||||
|
||||
|
@ -20,7 +20,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"
|
||||
@ -34,9 +33,7 @@ import (
|
||||
"github.com/soheilhy/cmux"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/explorer"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
@ -84,19 +81,6 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
// NewServer creates and initializes Server
|
||||
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
var err error
|
||||
if err := dao.InitDao(serverOptions.SigNoz.SQLStore); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// initiate feature manager
|
||||
fm := featureManager.StartManager()
|
||||
|
||||
@ -150,12 +134,17 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
telemetry.GetInstance().SetReader(reader)
|
||||
telemetry.GetInstance().SetSqlStore(serverOptions.SigNoz.SQLStore)
|
||||
telemetry.GetInstance().SetSavedViewsInfoCallback(telemetry.GetSavedViewsInfo)
|
||||
telemetry.GetInstance().SetAlertsInfoCallback(telemetry.GetAlertsInfo)
|
||||
telemetry.GetInstance().SetGetUsersCallback(telemetry.GetUsers)
|
||||
telemetry.GetInstance().SetUserCountCallback(telemetry.GetUserCount)
|
||||
telemetry.GetInstance().SetDashboardsInfoCallback(telemetry.GetDashboardsInfo)
|
||||
|
||||
quickfiltermodule := quickfilterscore.NewQuickFilters(quickfilterscore.NewStore(serverOptions.SigNoz.SQLStore))
|
||||
quickFilter := quickfilter.NewAPI(quickfiltermodule)
|
||||
apiHandler, err := NewAPIHandler(APIHandlerOpts{
|
||||
Reader: reader,
|
||||
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
|
||||
AppDao: dao.DB(),
|
||||
RuleManager: rm,
|
||||
FeatureFlags: fm,
|
||||
IntegrationsController: integrationsController,
|
||||
|
@ -1,31 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao/sqlite"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
var db ModelDao
|
||||
|
||||
func InitDao(sqlStore sqlstore.SQLStore) error {
|
||||
var err error
|
||||
db, err = sqlite.InitDB(sqlStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDB is used by ee for setting modelDAO
|
||||
func SetDB(m ModelDao) {
|
||||
db = m
|
||||
}
|
||||
|
||||
func DB() ModelDao {
|
||||
if db == nil {
|
||||
// Should never reach here
|
||||
panic("GetDB called before initialization")
|
||||
}
|
||||
return db
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type ModelDao interface {
|
||||
Queries
|
||||
Mutations
|
||||
}
|
||||
|
||||
type Queries interface {
|
||||
GetApdexSettings(ctx context.Context, orgID string, services []string) ([]types.ApdexSettings, *model.ApiError)
|
||||
}
|
||||
|
||||
type Mutations interface {
|
||||
UpdateUserRole(ctx context.Context, userId string, role types.Role) *model.ApiError
|
||||
SetApdexSettings(ctx context.Context, orgID string, set *types.ApdexSettings) *model.ApiError
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type ModelDaoSqlite struct {
|
||||
bundb *bun.DB
|
||||
}
|
||||
|
||||
// InitDB sets up setting up the connection pool global variable.
|
||||
func InitDB(sqlStore sqlstore.SQLStore) (*ModelDaoSqlite, error) {
|
||||
mds := &ModelDaoSqlite{bundb: sqlStore.BunDB()}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := mds.initializeOrgPreferences(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
telemetry.GetInstance().SetGetUsersCallback(telemetry.GetUsers)
|
||||
telemetry.GetInstance().SetUserCountCallback(telemetry.GetUserCount)
|
||||
|
||||
return mds, nil
|
||||
}
|
||||
|
||||
// DB returns database connection
|
||||
func (mds *ModelDaoSqlite) DB() *bun.DB {
|
||||
return mds.bundb
|
||||
}
|
||||
|
||||
// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have
|
||||
// multiple orgs in the system. In case of multiple orgs, there will be separate instance
|
||||
// of in-memory telemetry for each of the org, having their own settings. As of now, we only
|
||||
// have one org so this method relies on the settings of this org to initialize the telemetry etc.
|
||||
// TODO(Ahsan): Make it multi-tenant when we move to a system with multiple orgs.
|
||||
func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error {
|
||||
|
||||
// set anonymous setting as default in case of any failures to fetch UserPreference in below section
|
||||
telemetry.GetInstance().SetTelemetryAnonymous(constants.DEFAULT_TELEMETRY_ANONYMOUS)
|
||||
|
||||
orgs, apiError := mds.GetOrgs(ctx)
|
||||
if apiError != nil {
|
||||
return apiError.Err
|
||||
}
|
||||
|
||||
if len(orgs) > 1 {
|
||||
return errors.Errorf("Found %d organizations, expected one or none.", len(orgs))
|
||||
}
|
||||
|
||||
var org types.Organization
|
||||
if len(orgs) == 1 {
|
||||
org = orgs[0]
|
||||
}
|
||||
|
||||
// set telemetry fields from userPreferences
|
||||
telemetry.GetInstance().SetDistinctId(org.ID.StringValue())
|
||||
|
||||
return nil
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]types.Organization, *model.ApiError) {
|
||||
var orgs []types.Organization
|
||||
err := mds.bundb.NewSelect().
|
||||
Model(&orgs).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||
}
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
func (mds *ModelDaoSqlite) UpdateUserRole(ctx context.Context, userId string, role types.Role) *model.ApiError {
|
||||
|
||||
_, err := mds.bundb.NewUpdate().
|
||||
Model(&types.User{}).
|
||||
Set("role = ?", role).
|
||||
Where("id = ?", userId).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -22,7 +22,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
@ -197,7 +196,6 @@ func NewManager(o *ManagerOptions) (*Manager, error) {
|
||||
ruleStore := sqlrulestore.NewRuleStore(o.DBConn, o.SQLStore)
|
||||
maintenanceStore := sqlrulestore.NewMaintenanceStore(o.SQLStore)
|
||||
|
||||
telemetry.GetInstance().SetAlertsInfoCallback(ruleStore.GetAlertsInfo)
|
||||
m := &Manager{
|
||||
tasks: map[string]Task{},
|
||||
rules: map[string]Rule{},
|
||||
|
225
pkg/query-service/telemetry/dashboard.go
Normal file
225
pkg/query-service/telemetry/dashboard.go
Normal 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/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// GetDashboardsInfo returns analytics data for dashboards
|
||||
func GetDashboardsInfo(ctx context.Context, sqlstore sqlstore.SQLStore) (*model.DashboardsInfo, error) {
|
||||
dashboardsInfo := model.DashboardsInfo{}
|
||||
// fetch dashboards from dashboard db
|
||||
dashboards := []types.Dashboard{}
|
||||
err := sqlstore.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,
|
||||
}
|
||||
}
|
140
pkg/query-service/telemetry/rule.go
Normal file
140
pkg/query-service/telemetry/rule.go
Normal file
@ -0,0 +1,140 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func getChannels(ctx context.Context, sqlstore sqlstore.SQLStore) ([]*alertmanagertypes.Channel, error) {
|
||||
channels := []*alertmanagertypes.Channel{}
|
||||
if err := sqlstore.BunDB().NewSelect().Model(&channels).Scan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func GetAlertsInfo(ctx context.Context, sqlstore sqlstore.SQLStore) (*model.AlertsInfo, error) {
|
||||
alertsInfo := model.AlertsInfo{}
|
||||
|
||||
var alertsData []string
|
||||
var alertNames []string
|
||||
err := sqlstore.BunDB().NewSelect().Model((*ruletypes.Rule)(nil)).Column("data").Scan(ctx, &alertsData)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return &alertsInfo, err
|
||||
}
|
||||
for _, alert := range alertsData {
|
||||
var rule ruletypes.GettableRule
|
||||
if strings.Contains(alert, "time_series_v2") {
|
||||
alertsInfo.AlertsWithTSV2 = alertsInfo.AlertsWithTSV2 + 1
|
||||
}
|
||||
err = json.Unmarshal([]byte(alert), &rule)
|
||||
if err != nil {
|
||||
zap.L().Error("invalid rule data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
alertNames = append(alertNames, rule.AlertName)
|
||||
if rule.AlertType == ruletypes.AlertTypeLogs {
|
||||
alertsInfo.LogsBasedAlerts = alertsInfo.LogsBasedAlerts + 1
|
||||
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
if strings.Contains(alert, "signoz_logs.distributed_logs") ||
|
||||
strings.Contains(alert, "signoz_logs.logs") {
|
||||
alertsInfo.AlertsWithLogsChQuery = alertsInfo.AlertsWithLogsChQuery + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||
if query.Filters != nil {
|
||||
for _, item := range query.Filters.Items {
|
||||
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, string(item.Operator)) {
|
||||
if item.Key.Key != "body" {
|
||||
alertsInfo.AlertsWithLogsContainsOp += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if rule.AlertType == ruletypes.AlertTypeMetric {
|
||||
alertsInfo.MetricBasedAlerts = alertsInfo.MetricBasedAlerts + 1
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||
alertsInfo.MetricsBuilderQueries = alertsInfo.MetricsBuilderQueries + 1
|
||||
} else if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
alertsInfo.MetricsClickHouseQueries = alertsInfo.MetricsClickHouseQueries + 1
|
||||
} else if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypePromQL {
|
||||
alertsInfo.MetricsPrometheusQueries = alertsInfo.MetricsPrometheusQueries + 1
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.PromQueries {
|
||||
if strings.Contains(query.Query, "signoz_") {
|
||||
alertsInfo.SpanMetricsPrometheusQueries = alertsInfo.SpanMetricsPrometheusQueries + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if rule.RuleType == ruletypes.RuleTypeAnomaly {
|
||||
alertsInfo.AnomalyBasedAlerts = alertsInfo.AnomalyBasedAlerts + 1
|
||||
}
|
||||
} else if rule.AlertType == ruletypes.AlertTypeTraces {
|
||||
alertsInfo.TracesBasedAlerts = alertsInfo.TracesBasedAlerts + 1
|
||||
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
if strings.Contains(alert, "signoz_traces.distributed_signoz_index_v2") ||
|
||||
strings.Contains(alert, "signoz_traces.distributed_signoz_spans") ||
|
||||
strings.Contains(alert, "signoz_traces.distributed_signoz_error_index_v2") {
|
||||
alertsInfo.AlertsWithTraceChQuery = alertsInfo.AlertsWithTraceChQuery + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1
|
||||
if !rule.PostableRule.Disabled {
|
||||
alertsInfo.TotalActiveAlerts = alertsInfo.TotalActiveAlerts + 1
|
||||
}
|
||||
}
|
||||
alertsInfo.AlertNames = alertNames
|
||||
|
||||
channels, err := getChannels(ctx, sqlstore)
|
||||
if err != nil {
|
||||
return &alertsInfo, err
|
||||
}
|
||||
if channels != nil {
|
||||
alertsInfo.TotalChannels = len(channels)
|
||||
for _, channel := range channels {
|
||||
if channel.Type == "slack" {
|
||||
alertsInfo.SlackChannels = alertsInfo.SlackChannels + 1
|
||||
}
|
||||
if channel.Type == "webhook" {
|
||||
alertsInfo.WebHookChannels = alertsInfo.WebHookChannels + 1
|
||||
}
|
||||
if channel.Type == "email" {
|
||||
alertsInfo.EmailChannels = alertsInfo.EmailChannels + 1
|
||||
}
|
||||
if channel.Type == "pagerduty" {
|
||||
alertsInfo.PagerDutyChannels = alertsInfo.PagerDutyChannels + 1
|
||||
}
|
||||
if channel.Type == "opsgenie" {
|
||||
alertsInfo.OpsGenieChannels = alertsInfo.OpsGenieChannels + 1
|
||||
}
|
||||
if channel.Type == "msteams" {
|
||||
alertsInfo.MSTeamsChannels = alertsInfo.MSTeamsChannels + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &alertsInfo, nil
|
||||
}
|
87
pkg/query-service/telemetry/saved_view.go
Normal file
87
pkg/query-service/telemetry/saved_view.go
Normal file
@ -0,0 +1,87 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func GetViews(ctx context.Context, sqlstore sqlstore.SQLStore, orgID string) ([]*v3.SavedView, error) {
|
||||
var views []types.SavedView
|
||||
err := sqlstore.BunDB().NewSelect().Model(&views).Where("org_id = ?", orgID).Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting saved views: %s", err.Error())
|
||||
}
|
||||
|
||||
var savedViews []*v3.SavedView
|
||||
for _, view := range views {
|
||||
var compositeQuery v3.CompositeQuery
|
||||
err = json.Unmarshal([]byte(view.Data), &compositeQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
savedViews = append(savedViews, &v3.SavedView{
|
||||
ID: view.ID,
|
||||
Name: view.Name,
|
||||
Category: view.Category,
|
||||
CreatedAt: view.CreatedAt,
|
||||
CreatedBy: view.CreatedBy,
|
||||
UpdatedAt: view.UpdatedAt,
|
||||
UpdatedBy: view.UpdatedBy,
|
||||
Tags: strings.Split(view.Tags, ","),
|
||||
SourcePage: view.SourcePage,
|
||||
CompositeQuery: &compositeQuery,
|
||||
ExtraData: view.ExtraData,
|
||||
})
|
||||
}
|
||||
return savedViews, nil
|
||||
}
|
||||
|
||||
func GetSavedViewsInfo(ctx context.Context, sqlstore sqlstore.SQLStore) (*model.SavedViewsInfo, error) {
|
||||
savedViewsInfo := model.SavedViewsInfo{}
|
||||
// get single org ID from db
|
||||
var orgIDs []string
|
||||
err := sqlstore.BunDB().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, sqlstore, orgIDs[0])
|
||||
if err != nil {
|
||||
zap.S().Debug("Error in fetching saved views info: ", err)
|
||||
return &savedViewsInfo, err
|
||||
}
|
||||
|
||||
savedViewsInfo.TotalSavedViews = len(savedViews)
|
||||
for _, view := range savedViews {
|
||||
if view.SourcePage == "traces" {
|
||||
savedViewsInfo.TracesSavedViews += 1
|
||||
} else if view.SourcePage == "logs" {
|
||||
savedViewsInfo.LogsSavedViews += 1
|
||||
|
||||
for _, query := range view.CompositeQuery.BuilderQueries {
|
||||
if query.Filters != nil {
|
||||
for _, item := range query.Filters.Items {
|
||||
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, string(item.Operator)) {
|
||||
if item.Key.Key != "body" {
|
||||
savedViewsInfo.LogsSavedViewWithContainsOp += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &savedViewsInfo, nil
|
||||
}
|
@ -195,7 +195,6 @@ type Telemetry struct {
|
||||
userEmail string
|
||||
isEnabled bool
|
||||
isAnonymous bool
|
||||
distinctId string
|
||||
reader interfaces.Reader
|
||||
sqlStore sqlstore.SQLStore
|
||||
companyDomain string
|
||||
@ -206,14 +205,14 @@ type Telemetry struct {
|
||||
patTokenUser bool
|
||||
mutex sync.RWMutex
|
||||
|
||||
alertsInfoCallback func(ctx context.Context) (*model.AlertsInfo, error)
|
||||
alertsInfoCallback func(ctx context.Context, store sqlstore.SQLStore) (*model.AlertsInfo, error)
|
||||
userCountCallback func(ctx context.Context, store sqlstore.SQLStore) (int, error)
|
||||
getUsersCallback func(ctx context.Context, store sqlstore.SQLStore) ([]TelemetryUser, error)
|
||||
dashboardsInfoCallback func(ctx context.Context) (*model.DashboardsInfo, error)
|
||||
savedViewsInfoCallback func(ctx context.Context) (*model.SavedViewsInfo, error)
|
||||
dashboardsInfoCallback func(ctx context.Context, store sqlstore.SQLStore) (*model.DashboardsInfo, error)
|
||||
savedViewsInfoCallback func(ctx context.Context, store sqlstore.SQLStore) (*model.SavedViewsInfo, error)
|
||||
}
|
||||
|
||||
func (a *Telemetry) SetAlertsInfoCallback(callback func(ctx context.Context) (*model.AlertsInfo, error)) {
|
||||
func (a *Telemetry) SetAlertsInfoCallback(callback func(ctx context.Context, store sqlstore.SQLStore) (*model.AlertsInfo, error)) {
|
||||
a.alertsInfoCallback = callback
|
||||
}
|
||||
|
||||
@ -225,11 +224,11 @@ func (a *Telemetry) SetGetUsersCallback(callback func(ctx context.Context, store
|
||||
a.getUsersCallback = callback
|
||||
}
|
||||
|
||||
func (a *Telemetry) SetSavedViewsInfoCallback(callback func(ctx context.Context) (*model.SavedViewsInfo, error)) {
|
||||
func (a *Telemetry) SetSavedViewsInfoCallback(callback func(ctx context.Context, store sqlstore.SQLStore) (*model.SavedViewsInfo, error)) {
|
||||
a.savedViewsInfoCallback = callback
|
||||
}
|
||||
|
||||
func (a *Telemetry) SetDashboardsInfoCallback(callback func(ctx context.Context) (*model.DashboardsInfo, error)) {
|
||||
func (a *Telemetry) SetDashboardsInfoCallback(callback func(ctx context.Context, store sqlstore.SQLStore) (*model.DashboardsInfo, error)) {
|
||||
a.dashboardsInfoCallback = callback
|
||||
}
|
||||
|
||||
@ -352,14 +351,14 @@ func createTelemetry() {
|
||||
}
|
||||
}
|
||||
|
||||
alertsInfo, err := telemetry.alertsInfoCallback(ctx)
|
||||
alertsInfo, err := telemetry.alertsInfoCallback(ctx, telemetry.sqlStore)
|
||||
if err != nil {
|
||||
telemetry.SendEvent(TELEMETRY_EVENT_DASHBOARDS_ALERTS, map[string]interface{}{"error": err.Error()}, "", true, false)
|
||||
}
|
||||
if err == nil {
|
||||
dashboardsInfo, err := telemetry.dashboardsInfoCallback(ctx)
|
||||
dashboardsInfo, err := telemetry.dashboardsInfoCallback(ctx, telemetry.sqlStore)
|
||||
if err == nil {
|
||||
savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx)
|
||||
savedViewsInfo, err := telemetry.savedViewsInfoCallback(ctx, telemetry.sqlStore)
|
||||
if err == nil {
|
||||
dashboardsAlertsData := map[string]interface{}{
|
||||
"totalDashboards": dashboardsInfo.TotalDashboards,
|
||||
@ -739,7 +738,7 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}, userEma
|
||||
|
||||
userId := a.ipAddress
|
||||
if a.isTelemetryAnonymous() || userId == IP_NOT_FOUND_PLACEHOLDER {
|
||||
userId = a.GetDistinctId()
|
||||
userId = "anonymous"
|
||||
}
|
||||
|
||||
// check if event is part of SAAS_EVENTS_LIST
|
||||
@ -774,13 +773,6 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}, userEma
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Telemetry) GetDistinctId() string {
|
||||
return a.distinctId
|
||||
}
|
||||
func (a *Telemetry) SetDistinctId(distinctId string) {
|
||||
a.distinctId = distinctId
|
||||
}
|
||||
|
||||
func (a *Telemetry) isTelemetryAnonymous() bool {
|
||||
return a.isAnonymous
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
@ -311,7 +310,6 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
FeatureFlags: fm,
|
||||
JWT: jwt,
|
||||
Signoz: &signoz.SigNoz{
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
|
||||
opampModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/queryBuilderToExpr"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
@ -484,7 +483,6 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
|
||||
quickFilterModule := quickfilter.NewAPI(quickfilterscore.NewQuickFilters(quickfilterscore.NewStore(sqlStore)))
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
AppDao: dao.DB(),
|
||||
LogsParsingPipelineController: controller,
|
||||
JWT: jwt,
|
||||
Signoz: &signoz.SigNoz{
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@ -375,7 +374,6 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
CloudIntegrationsController: controller,
|
||||
FeatureFlags: fm,
|
||||
JWT: jwt,
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"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/integrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
@ -582,7 +581,6 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
IntegrationsController: controller,
|
||||
FeatureFlags: fm,
|
||||
JWT: jwt,
|
||||
|
@ -7,8 +7,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/dashboards"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/dao"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigrator"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@ -83,12 +81,5 @@ func NewTestSqliteDB(t *testing.T) (sqlStore sqlstore.SQLStore, testDBFilePath s
|
||||
|
||||
func NewQueryServiceDBForTests(t *testing.T) sqlstore.SQLStore {
|
||||
sqlStore, _ := NewTestSqliteDB(t)
|
||||
|
||||
err := dao.InitDao(sqlStore)
|
||||
if err != nil {
|
||||
t.Fatalf("could not initialize dao: %v", err)
|
||||
}
|
||||
_ = dashboards.InitDB(sqlStore)
|
||||
|
||||
return sqlStore
|
||||
}
|
||||
|
@ -2,12 +2,7 @@ package sqlrulestore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
@ -151,134 +146,3 @@ func (r *rule) ListOrgs(ctx context.Context) ([]valuer.UUID, error) {
|
||||
|
||||
return orgIDs, nil
|
||||
}
|
||||
|
||||
func (r *rule) getChannels() (*[]model.ChannelItem, *model.ApiError) {
|
||||
channels := []model.ChannelItem{}
|
||||
|
||||
query := "SELECT id, created_at, updated_at, name, type, data FROM notification_channel"
|
||||
|
||||
err := r.Select(&channels, query)
|
||||
|
||||
zap.L().Info(query)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||
}
|
||||
|
||||
return &channels, nil
|
||||
}
|
||||
|
||||
func (r *rule) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) {
|
||||
alertsInfo := model.AlertsInfo{}
|
||||
// fetch alerts from rules db
|
||||
query := "SELECT data FROM rule"
|
||||
var alertsData []string
|
||||
var alertNames []string
|
||||
err := r.Select(&alertsData, query)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in processing sql query", zap.Error(err))
|
||||
return &alertsInfo, err
|
||||
}
|
||||
for _, alert := range alertsData {
|
||||
var rule ruletypes.GettableRule
|
||||
if strings.Contains(alert, "time_series_v2") {
|
||||
alertsInfo.AlertsWithTSV2 = alertsInfo.AlertsWithTSV2 + 1
|
||||
}
|
||||
err = json.Unmarshal([]byte(alert), &rule)
|
||||
if err != nil {
|
||||
zap.L().Error("invalid rule data", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
alertNames = append(alertNames, rule.AlertName)
|
||||
if rule.AlertType == ruletypes.AlertTypeLogs {
|
||||
alertsInfo.LogsBasedAlerts = alertsInfo.LogsBasedAlerts + 1
|
||||
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
if strings.Contains(alert, "signoz_logs.distributed_logs") ||
|
||||
strings.Contains(alert, "signoz_logs.logs") {
|
||||
alertsInfo.AlertsWithLogsChQuery = alertsInfo.AlertsWithLogsChQuery + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||
if query.Filters != nil {
|
||||
for _, item := range query.Filters.Items {
|
||||
if slices.Contains([]string{"contains", "ncontains", "like", "nlike"}, string(item.Operator)) {
|
||||
if item.Key.Key != "body" {
|
||||
alertsInfo.AlertsWithLogsContainsOp += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if rule.AlertType == ruletypes.AlertTypeMetric {
|
||||
alertsInfo.MetricBasedAlerts = alertsInfo.MetricBasedAlerts + 1
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||
alertsInfo.MetricsBuilderQueries = alertsInfo.MetricsBuilderQueries + 1
|
||||
} else if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
alertsInfo.MetricsClickHouseQueries = alertsInfo.MetricsClickHouseQueries + 1
|
||||
} else if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypePromQL {
|
||||
alertsInfo.MetricsPrometheusQueries = alertsInfo.MetricsPrometheusQueries + 1
|
||||
for _, query := range rule.RuleCondition.CompositeQuery.PromQueries {
|
||||
if strings.Contains(query.Query, "signoz_") {
|
||||
alertsInfo.SpanMetricsPrometheusQueries = alertsInfo.SpanMetricsPrometheusQueries + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if rule.RuleType == ruletypes.RuleTypeAnomaly {
|
||||
alertsInfo.AnomalyBasedAlerts = alertsInfo.AnomalyBasedAlerts + 1
|
||||
}
|
||||
} else if rule.AlertType == ruletypes.AlertTypeTraces {
|
||||
alertsInfo.TracesBasedAlerts = alertsInfo.TracesBasedAlerts + 1
|
||||
|
||||
if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil {
|
||||
if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
|
||||
if strings.Contains(alert, "signoz_traces.distributed_signoz_index_v2") ||
|
||||
strings.Contains(alert, "signoz_traces.distributed_signoz_spans") ||
|
||||
strings.Contains(alert, "signoz_traces.distributed_signoz_error_index_v2") {
|
||||
alertsInfo.AlertsWithTraceChQuery = alertsInfo.AlertsWithTraceChQuery + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1
|
||||
if !rule.PostableRule.Disabled {
|
||||
alertsInfo.TotalActiveAlerts = alertsInfo.TotalActiveAlerts + 1
|
||||
}
|
||||
}
|
||||
alertsInfo.AlertNames = alertNames
|
||||
|
||||
channels, _ := r.getChannels()
|
||||
if channels != nil {
|
||||
alertsInfo.TotalChannels = len(*channels)
|
||||
for _, channel := range *channels {
|
||||
if channel.Type == "slack" {
|
||||
alertsInfo.SlackChannels = alertsInfo.SlackChannels + 1
|
||||
}
|
||||
if channel.Type == "webhook" {
|
||||
alertsInfo.WebHookChannels = alertsInfo.WebHookChannels + 1
|
||||
}
|
||||
if channel.Type == "email" {
|
||||
alertsInfo.EmailChannels = alertsInfo.EmailChannels + 1
|
||||
}
|
||||
if channel.Type == "pagerduty" {
|
||||
alertsInfo.PagerDutyChannels = alertsInfo.PagerDutyChannels + 1
|
||||
}
|
||||
if channel.Type == "opsgenie" {
|
||||
alertsInfo.OpsGenieChannels = alertsInfo.OpsGenieChannels + 1
|
||||
}
|
||||
if channel.Type == "msteams" {
|
||||
alertsInfo.MSTeamsChannels = alertsInfo.MSTeamsChannels + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &alertsInfo, nil
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
package signoz
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference/implpreference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
)
|
||||
|
||||
@ -12,6 +18,9 @@ type Handlers struct {
|
||||
Organization organization.Handler
|
||||
Preference preference.Handler
|
||||
User user.Handler
|
||||
SavedView savedview.Handler
|
||||
Apdex apdex.Handler
|
||||
Dashboard dashboard.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(modules Modules, user user.Handler) Handlers {
|
||||
@ -19,5 +28,8 @@ func NewHandlers(modules Modules, user user.Handler) Handlers {
|
||||
Organization: implorganization.NewHandler(modules.Organization),
|
||||
Preference: implpreference.NewHandler(modules.Preference),
|
||||
User: user,
|
||||
SavedView: implsavedview.NewHandler(modules.SavedView),
|
||||
Apdex: implapdex.NewHandler(modules.Apdex),
|
||||
Dashboard: impldashboard.NewHandler(modules.Dashboard),
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
package signoz
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/preference/implpreference"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
||||
@ -14,6 +20,9 @@ type Modules struct {
|
||||
Organization organization.Module
|
||||
Preference preference.Module
|
||||
User user.Module
|
||||
SavedView savedview.Module
|
||||
Apdex apdex.Module
|
||||
Dashboard dashboard.Module
|
||||
}
|
||||
|
||||
func NewModules(sqlstore sqlstore.SQLStore, user user.Module) Modules {
|
||||
@ -21,5 +30,8 @@ func NewModules(sqlstore sqlstore.SQLStore, user user.Module) Modules {
|
||||
Organization: implorganization.NewModule(implorganization.NewStore(sqlstore)),
|
||||
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewDefaultPreferenceMap()),
|
||||
User: user,
|
||||
SavedView: implsavedview.NewModule(sqlstore),
|
||||
Apdex: implapdex.NewModule(sqlstore),
|
||||
Dashboard: impldashboard.NewModule(sqlstore),
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package ruletypes
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
@ -32,6 +31,5 @@ type RuleStore interface {
|
||||
GetStoredRules(context.Context, string) ([]*Rule, error)
|
||||
GetStoredRule(context.Context, valuer.UUID) (*Rule, error)
|
||||
GetRuleUUID(context.Context, int) (*RuleHistory, error)
|
||||
GetAlertsInfo(context.Context) (*model.AlertsInfo, error)
|
||||
ListOrgs(context.Context) ([]valuer.UUID, error)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user