Compare commits

..

2 Commits

Author SHA1 Message Date
SagarRajput-7
1ad8043936 feat: remove console.log 2025-04-03 01:09:50 +05:30
SagarRajput-7
44df81776d feat: added span filtering to queryBuildersearchv2 2025-04-03 01:07:07 +05:30
155 changed files with 2978 additions and 5451 deletions

1
.gitignore vendored
View File

@@ -83,4 +83,3 @@ queries.active
# .devenv tmp files
.devenv/**/tmp/**
.qodo

View File

@@ -1,31 +0,0 @@
package licensingserver
import (
"time"
"github.com/SigNoz/signoz/pkg/licensing"
)
type Config struct {
PollingConfig PollingConfig `mapstructure:"polling"`
}
type PollingConfig struct {
Interval time.Duration `mapstructure:"interval"`
}
func NewConfig() Config {
return Config{
PollingConfig: PollingConfig{
Interval: 24 * time.Hour,
},
}
}
func NewConfigFromLicensingConfig(config licensing.Config) Config {
return Config{
PollingConfig: PollingConfig{
Interval: config.PollingConfig.Interval,
},
}
}

View File

@@ -1,72 +0,0 @@
package licensingserver
import (
"context"
"log/slog"
"sync"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
)
type Server struct {
logger *slog.Logger
cfg Config
orgID valuer.UUID
zeus zeus.Zeus
store licensetypes.Store
license licensetypes.License
mtx sync.RWMutex
}
func NewServer(logger *slog.Logger, config Config, orgID valuer.UUID, zeus zeus.Zeus, store licensetypes.Store) *Server {
return &Server{
logger: logger,
cfg: config,
orgID: orgID,
zeus: zeus,
store: store,
license: licensetypes.NewNoop(),
}
}
func (server *Server) Fetch(ctx context.Context) error {
license, err := server.store.GetLatest(ctx, server.orgID)
if err != nil {
if errors.Ast(err, errors.TypeNotFound) {
return nil
}
return err
}
fetchedLicense, err := server.zeus.GetLicense(ctx, license.Key())
if err != nil {
return err
}
return server.SetLicense(ctx, fetchedLicense)
}
func (server *Server) SetLicense(ctx context.Context, license licensetypes.License) error {
server.mtx.Lock()
defer server.mtx.Unlock()
server.license = license
return nil
}
func (server *Server) GetLicense(ctx context.Context) licensetypes.License {
server.mtx.RLock()
defer server.mtx.RUnlock()
return server.license
}

View File

@@ -1,35 +0,0 @@
package sqllicensingstore
import (
"context"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type store struct {
sqlstore sqlstore.SQLStore
}
func NewStore(sqlstore sqlstore.SQLStore) licensetypes.Store {
return &store{
sqlstore: sqlstore,
}
}
func (store *store) Set(ctx context.Context, license licensetypes.License) error {
return nil
}
func (store *store) Get(ctx context.Context, orgID valuer.UUID) ([]licensetypes.License, error) {
return nil, nil
}
func (store *store) GetLatest(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
return nil, nil
}
func (store *store) ListOrgs(ctx context.Context) ([]valuer.UUID, error) {
return nil, nil
}

View File

@@ -1,109 +0,0 @@
package pollinglicensing
import (
"context"
"time"
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
)
type provider struct {
config licensing.Config
settings factory.ScopedProviderSettings
zeus zeus.Zeus
service *Service
store licensetypes.Store
stopC chan struct{}
}
func NewFactory(zeus zeus.Zeus, sqlstore sqlstore.SQLStore) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
return factory.NewProviderFactory(factory.MustNewName("sql"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
return New(ctx, providerSettings, config, zeus, sqlstore)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config, zeus zeus.Zeus, sqlstore sqlstore.SQLStore) (licensing.Licensing, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/licensing/pollinglicensing")
store := sqllicensingstore.NewStore(sqlstore)
return &provider{
config: config,
settings: settings,
zeus: zeus,
service: NewService(ctx, settings, config, store, zeus),
stopC: make(chan struct{}),
}, nil
}
func (provider *provider) Start(ctx context.Context) error {
if err := provider.service.SyncServers(ctx); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to sync licensing servers", "error", err)
return err
}
ticker := time.NewTicker(provider.config.PollingConfig.Interval)
defer ticker.Stop()
for {
select {
case <-provider.stopC:
return nil
case <-ticker.C:
if err := provider.service.SyncServers(ctx); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to sync licensing servers", "error", err)
}
}
}
}
func (provider *provider) GetLatestLicense(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
server, err := provider.service.getServer(orgID)
if err != nil {
return nil, err
}
return server.GetLicense(ctx), nil
}
func (provider *provider) GetLicenses(ctx context.Context, orgID valuer.UUID, params licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error) {
if params.Active != nil {
if *params.Active {
license, err := provider.GetLatestLicense(ctx, orgID)
if err != nil {
return nil, err
}
return licensetypes.GettableLicenses{license}, nil
}
}
licenses, err := provider.store.Get(ctx, orgID)
if err != nil {
return nil, err
}
return licenses, nil
}
func (provider *provider) SetLicense(ctx context.Context, orgID valuer.UUID, key string) error {
license, err := provider.zeus.GetLicense(ctx, key)
if err != nil {
return err
}
if err := provider.store.Set(ctx, license); err != nil {
return err
}
return provider.service.SyncOrgServer(ctx, orgID)
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
return nil
}

View File

@@ -1,103 +0,0 @@
package pollinglicensing
import (
"context"
"sync"
"github.com/SigNoz/signoz/ee/licensing/licensingserver"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
)
type Service struct {
// config is the config for the licensing service
config licensing.Config
// store is the store for the licensing service
store licensetypes.Store
// zeus
zeus zeus.Zeus
// settings is the settings for the licensing service
settings factory.ScopedProviderSettings
// Map of organization id to alertmanager server
servers map[valuer.UUID]*licensingserver.Server
// Mutex to protect the servers map
serversMtx sync.RWMutex
}
func NewService(ctx context.Context, settings factory.ScopedProviderSettings, config licensing.Config, store licensetypes.Store, zeus zeus.Zeus) *Service {
service := &Service{
config: config,
store: store,
zeus: zeus,
settings: settings,
servers: make(map[valuer.UUID]*licensingserver.Server),
serversMtx: sync.RWMutex{},
}
return service
}
func (service *Service) SyncServers(ctx context.Context) error {
orgIDs, err := service.store.ListOrgs(ctx)
if err != nil {
return err
}
service.serversMtx.Lock()
for _, orgID := range orgIDs {
// If the server is not present, create it and sync the config
if _, ok := service.servers[orgID]; !ok {
server := licensingserver.NewServer(service.settings.Logger(), licensingserver.NewConfigFromLicensingConfig(service.config), orgID, service.zeus, service.store)
service.servers[orgID] = server
}
err = service.servers[orgID].Fetch(ctx)
if err != nil {
service.settings.Logger().Error("failed to fetch license for licensing server", "orgID", orgID, "error", err)
continue
}
}
service.serversMtx.Unlock()
return nil
}
func (service *Service) SyncOrgServer(ctx context.Context, orgID valuer.UUID) error {
service.serversMtx.Lock()
defer service.serversMtx.Unlock()
_, ok := service.servers[orgID]
if !ok {
server := licensingserver.NewServer(service.settings.Logger(), licensingserver.NewConfigFromLicensingConfig(service.config), orgID, service.zeus, service.store)
service.servers[orgID] = server
}
err := service.servers[orgID].Fetch(ctx)
if err != nil {
service.settings.Logger().Error("failed to fetch license for licensing server", "orgID", orgID, "error", err)
return err
}
return nil
}
func (service *Service) getServer(orgID valuer.UUID) (*licensingserver.Server, error) {
service.serversMtx.RLock()
defer service.serversMtx.RUnlock()
server, ok := service.servers[orgID]
if !ok {
return nil, errors.Newf(errors.TypeNotFound, licensing.ErrCodeLicensingServerNotFound, "server not found for %s", orgID.StringValue())
}
return server, nil
}

View File

@@ -28,10 +28,11 @@ func NewDailyProvider(opts ...GenericProviderOption[*DailyProvider]) *DailyProvi
}
dp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: dp.reader,
Cache: dp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: dp.fluxInterval,
Reader: dp.reader,
Cache: dp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: dp.fluxInterval,
FeatureLookup: dp.ff,
})
return dp

View File

@@ -28,10 +28,11 @@ func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyPr
}
hp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: hp.reader,
Cache: hp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: hp.fluxInterval,
Reader: hp.reader,
Cache: hp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: hp.fluxInterval,
FeatureLookup: hp.ff,
})
return hp

View File

@@ -38,6 +38,12 @@ func WithKeyGenerator[T BaseProvider](keyGenerator cache.KeyGenerator) GenericPr
}
}
func WithFeatureLookup[T BaseProvider](ff interfaces.FeatureLookup) GenericProviderOption[T] {
return func(p T) {
p.GetBaseSeasonalProvider().ff = ff
}
}
func WithReader[T BaseProvider](reader interfaces.Reader) GenericProviderOption[T] {
return func(p T) {
p.GetBaseSeasonalProvider().reader = reader
@@ -50,6 +56,7 @@ type BaseSeasonalProvider struct {
fluxInterval time.Duration
cache cache.Cache
keyGenerator cache.KeyGenerator
ff interfaces.FeatureLookup
}
func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomalyQueryParams {

View File

@@ -27,10 +27,11 @@ func NewWeeklyProvider(opts ...GenericProviderOption[*WeeklyProvider]) *WeeklyPr
}
wp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: wp.reader,
Cache: wp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: wp.fluxInterval,
Reader: wp.reader,
Cache: wp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: wp.fluxInterval,
FeatureLookup: wp.ff,
})
return wp

View File

@@ -7,11 +7,10 @@ import (
"github.com/SigNoz/signoz/ee/query-service/dao"
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
"github.com/SigNoz/signoz/ee/query-service/interfaces"
"github.com/SigNoz/signoz/ee/query-service/license"
"github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/modules/preference"
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
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/integrations"
@@ -22,13 +21,12 @@ import (
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/gorilla/mux"
)
type APIHandlerOptions struct {
DataConnector baseint.Reader
DataConnector interfaces.DataConnector
SkipConfig *basemodel.SkipConfig
PreferSpanMetrics bool
AppDao dao.ModelDao
@@ -56,7 +54,6 @@ type APIHandler struct {
// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
preference := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(signoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
@@ -74,7 +71,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
UseTraceNewSchema: opts.UseTraceNewSchema,
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
Signoz: signoz,
Preference: preference,
})
if err != nil {
@@ -161,6 +157,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.getInvite)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
// PAT APIs
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)

View File

@@ -10,12 +10,9 @@ import (
"github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/ee/types"
eeTypes "github.com/SigNoz/signoz/ee/types"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/query-service/auth"
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
"go.uber.org/zap"
)
@@ -96,12 +93,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
}
req.UpdatedByUserID = user.ID
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now()
zap.L().Info("Got Update PAT request", zap.Any("pat", req))
var apierr basemodel.BaseApiError
@@ -134,12 +126,7 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
id := mux.Vars(r)["id"]
user, err := auth.GetUserFromReqContext(r.Context())
if err != nil {
RespondError(w, &model.ApiError{
@@ -149,7 +136,7 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
return
}
zap.L().Info("Revoke PAT with id", zap.String("id", id.StringValue()))
zap.L().Info("Revoke PAT with id", zap.String("id", id))
if apierr := ah.AppDao().RevokePAT(ctx, user.OrgID, id, user.ID); apierr != nil {
RespondError(w, apierr, nil)
return

View File

@@ -88,24 +88,28 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
anomaly.WithCache[*anomaly.WeeklyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.WeeklyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](aH.opts.FeatureFlags),
)
case anomaly.SeasonalityDaily:
provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
)
case anomaly.SeasonalityHourly:
provider = anomaly.NewHourlyProvider(
anomaly.WithCache[*anomaly.HourlyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
)
default:
provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
)
}
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})

View File

@@ -0,0 +1,33 @@
package api
import (
"net/http"
"github.com/SigNoz/signoz/ee/query-service/app/db"
"github.com/SigNoz/signoz/ee/query-service/model"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
if !ah.CheckFeature(basemodel.SmartTraceDetail) {
zap.L().Info("SmartTraceDetail feature is not enabled in this plan")
ah.APIHandler.SearchTraces(w, r)
return
}
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
return
}
result, err := ah.opts.DataConnector.SearchTraces(r.Context(), searchTracesParams, db.SmartTraceAlgorithm)
if ah.HandleError(w, err, http.StatusBadRequest) {
return
}
ah.WriteJSON(w, r, result)
}

View File

@@ -5,33 +5,36 @@ import (
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/jmoiron/sqlx"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/prometheus"
basechr "github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
"github.com/SigNoz/signoz/pkg/telemetrystore"
)
type ClickhouseReader struct {
conn clickhouse.Conn
appdb sqlstore.SQLStore
appdb *sqlx.DB
*basechr.ClickHouseReader
}
func NewDataConnector(
sqlDB sqlstore.SQLStore,
localDB *sqlx.DB,
telemetryStore telemetrystore.TelemetryStore,
prometheus prometheus.Prometheus,
lm interfaces.FeatureLookup,
cluster string,
useLogsNewSchema bool,
useTraceNewSchema bool,
fluxIntervalForTraceDetail time.Duration,
cache cache.Cache,
) *ClickhouseReader {
chReader := basechr.NewReader(sqlDB, telemetryStore, prometheus, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache)
chReader := basechr.NewReader(localDB, telemetryStore, prometheus, lm, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache)
return &ClickhouseReader{
conn: telemetryStore.ClickhouseDB(),
appdb: sqlDB,
appdb: localDB,
ClickHouseReader: chReader,
}
}

View File

@@ -44,6 +44,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"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/app/preferences"
"github.com/SigNoz/signoz/pkg/query-service/cache"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
@@ -115,6 +116,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
if err := preferences.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
return nil, err
}
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err
}
@@ -139,9 +144,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
reader := db.NewDataConnector(
serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.SQLStore.SQLxDB(),
serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus,
lm,
serverOptions.Cluster,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
@@ -172,6 +178,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
reader,
c,
serverOptions.DisableRules,
lm,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
serverOptions.SigNoz.Alertmanager,
@@ -222,7 +229,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// start the usagemanager
usageManager, err := usage.New(lm.GetRepo(), serverOptions.SigNoz.TelemetryStore, serverOptions.SigNoz.Zeus)
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.Config.TelemetryStore.Clickhouse.DSN)
if err != nil {
return nil, err
}
@@ -531,6 +538,7 @@ func makeRulesManager(
ch baseint.Reader,
cache cache.Cache,
disableRules bool,
fm baseint.FeatureLookup,
useLogsNewSchema bool,
useTraceNewSchema bool,
alertmanager alertmanager.Alertmanager,
@@ -547,6 +555,7 @@ func makeRulesManager(
Context: context.Background(),
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),

View File

@@ -10,7 +10,6 @@ import (
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
ossTypes "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/google/uuid"
"github.com/uptrace/bun"
)
@@ -37,10 +36,10 @@ type ModelDao interface {
GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError)
CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError)
UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError
UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id string) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*types.GettablePAT, basemodel.BaseApiError)
GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError)
GetPATByID(ctx context.Context, orgID string, id string) (*types.GettablePAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError)
ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError
RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError
}

View File

@@ -9,14 +9,12 @@ import (
"github.com/SigNoz/signoz/ee/types"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
ossTypes "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"go.uber.org/zap"
)
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().
Model(&p.StorablePersonalAccessToken).
Exec(ctx)
@@ -48,11 +46,11 @@ func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.Gettable
return p, nil
}
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError {
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id string) basemodel.BaseApiError {
_, err := m.DB().NewUpdate().
Model(&p.StorablePersonalAccessToken).
Column("role", "name", "updated_at", "updated_by_user_id").
Where("id = ?", id.StringValue()).
Where("id = ?", id).
Where("org_id = ?", orgID).
Where("revoked = false").
Exec(ctx)
@@ -129,14 +127,14 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.Gettable
return patsWithUsers, nil
}
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError {
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError {
updatedAt := time.Now().Unix()
_, err := m.DB().NewUpdate().
Model(&types.StorablePersonalAccessToken{}).
Set("revoked = ?", true).
Set("updated_by_user_id = ?", userID).
Set("updated_at = ?", updatedAt).
Where("id = ?", id.StringValue()).
Where("id = ?", id).
Where("org_id = ?", orgID).
Exec(ctx)
if err != nil {
@@ -171,12 +169,12 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT
return &patWithUser, nil
}
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError) {
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*types.GettablePAT, basemodel.BaseApiError) {
pats := []types.StorablePersonalAccessToken{}
if err := m.DB().NewSelect().
Model(&pats).
Where("id = ?", id.StringValue()).
Where("id = ?", id).
Where("org_id = ?", orgID).
Where("revoked = false").
Scan(ctx); err != nil {

View File

@@ -0,0 +1,11 @@
package interfaces
import (
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
)
// Connector defines methods for interaction
// with o11y data. for example - clickhouse
type DataConnector interface {
baseint.Reader
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/zeus"
validate "github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
"github.com/SigNoz/signoz/ee/query-service/model"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
@@ -29,7 +29,6 @@ var validationFrequency = 24 * 60 * time.Minute
type Manager struct {
repo *Repo
zeus zeus.Zeus
mutex sync.Mutex
validatorRunning bool
// end the license validation, this is important to gracefully
@@ -46,7 +45,7 @@ type Manager struct {
activeFeatures basemodel.FeatureSet
}
func StartManager(db *sqlx.DB, store sqlstore.SQLStore, zeus zeus.Zeus, features ...basemodel.Feature) (*Manager, error) {
func StartManager(db *sqlx.DB, store sqlstore.SQLStore, features ...basemodel.Feature) (*Manager, error) {
if LM != nil {
return LM, nil
}
@@ -54,7 +53,6 @@ func StartManager(db *sqlx.DB, store sqlstore.SQLStore, zeus zeus.Zeus, features
repo := NewLicenseRepo(db, store)
m := &Manager{
repo: &repo,
zeus: zeus,
}
if err := m.start(features...); err != nil {
return m, err
@@ -175,12 +173,14 @@ func (lm *Manager) ValidatorV3(ctx context.Context) {
}
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
license, err := lm.zeus.GetLicense(ctx, lm.activeLicenseV3.Key)
if err != nil {
return model.BadRequest(errors.Wrap(err, "failed to get license"))
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
if apiError != nil {
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
return apiError
}
err = lm.repo.UpdateLicenseV3(ctx, license)
err := lm.repo.UpdateLicenseV3(ctx, license)
if err != nil {
return model.BadRequest(errors.Wrap(err, "failed to update the new license"))
}
@@ -247,9 +247,10 @@ func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseRe
}
}()
license, errv2 := lm.zeus.GetLicense(ctx, lm.activeLicenseV3.Key)
if errv2 != nil {
return nil, model.BadRequest(errors.Wrap(errv2, "failed to get license"))
license, apiError := validate.ValidateLicenseV3(licenseKey)
if apiError != nil {
zap.L().Error("failed to get the license", zap.Error(apiError.Err))
return nil, apiError
}
// insert the new license to the sqlite db

View File

@@ -0,0 +1,246 @@
package model
import (
"encoding/json"
"fmt"
"reflect"
"time"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/pkg/errors"
)
type License struct {
Key string `json:"key" db:"key"`
ActivationId string `json:"activationId" db:"activationId"`
CreatedAt time.Time `db:"created_at"`
// PlanDetails contains the encrypted plan info
PlanDetails string `json:"planDetails" db:"planDetails"`
// stores parsed license details
LicensePlan
FeatureSet basemodel.FeatureSet
// populated in case license has any errors
ValidationMessage string `db:"validationMessage"`
// used only for sending details to front-end
IsCurrent bool `json:"isCurrent"`
}
func (l *License) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Key string `json:"key" db:"key"`
ActivationId string `json:"activationId" db:"activationId"`
ValidationMessage string `db:"validationMessage"`
IsCurrent bool `json:"isCurrent"`
PlanKey string `json:"planKey"`
ValidFrom time.Time `json:"ValidFrom"`
ValidUntil time.Time `json:"ValidUntil"`
Status string `json:"status"`
}{
Key: l.Key,
ActivationId: l.ActivationId,
IsCurrent: l.IsCurrent,
PlanKey: l.PlanKey,
ValidFrom: time.Unix(l.ValidFrom, 0),
ValidUntil: time.Unix(l.ValidUntil, 0),
Status: l.Status,
ValidationMessage: l.ValidationMessage,
})
}
type LicensePlan struct {
PlanKey string `json:"planKey"`
ValidFrom int64 `json:"validFrom"`
ValidUntil int64 `json:"validUntil"`
Status string `json:"status"`
}
type Licenses struct {
TrialStart int64 `json:"trialStart"`
TrialEnd int64 `json:"trialEnd"`
OnTrial bool `json:"onTrial"`
WorkSpaceBlock bool `json:"workSpaceBlock"`
TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"`
GracePeriodEnd int64 `json:"gracePeriodEnd"`
Licenses []License `json:"licenses"`
}
type SubscriptionServerResp struct {
Status string `json:"status"`
Data Licenses `json:"data"`
}
type Plan struct {
Name string `json:"name"`
}
type LicenseDB struct {
ID string `json:"id"`
Key string `json:"key"`
Data string `json:"data"`
}
type LicenseV3 struct {
ID string
Key string
Data map[string]interface{}
PlanName string
Features basemodel.FeatureSet
Status string
IsCurrent bool
ValidFrom int64
ValidUntil int64
}
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
var zeroValue T
if val, ok := data[key]; ok {
if value, ok := val.(T); ok {
return value, nil
}
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
}
return zeroValue, fmt.Errorf("%s key is missing", key)
}
func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
var features basemodel.FeatureSet
// extract id from data
licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
if err != nil {
return nil, err
}
delete(data, "id")
// extract key from data
licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
if err != nil {
return nil, err
}
delete(data, "key")
// extract status from data
status, err := extractKeyFromMapStringInterface[string](data, "status")
if err != nil {
return nil, err
}
planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
if err != nil {
return nil, err
}
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
if err != nil {
return nil, err
}
// if license status is invalid then default it to basic
if status == LicenseStatusInvalid {
planName = PlanNameBasic
}
featuresFromZeus := basemodel.FeatureSet{}
if _features, ok := data["features"]; ok {
featuresData, err := json.Marshal(_features)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal features data")
}
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal features data")
}
}
switch planName {
case PlanNameTeams:
features = append(features, ProPlan...)
case PlanNameEnterprise:
features = append(features, EnterprisePlan...)
case PlanNameBasic:
features = append(features, BasicPlan...)
default:
features = append(features, BasicPlan...)
}
if len(featuresFromZeus) > 0 {
for _, feature := range featuresFromZeus {
exists := false
for i, existingFeature := range features {
if existingFeature.Name == feature.Name {
features[i] = feature // Replace existing feature
exists = true
break
}
}
if !exists {
features = append(features, feature) // Append if it doesn't exist
}
}
}
data["features"] = features
_validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
if err != nil {
_validFrom = 0
}
validFrom := int64(_validFrom)
_validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
if err != nil {
_validUntil = 0
}
validUntil := int64(_validUntil)
return &LicenseV3{
ID: licenseID,
Key: licenseKey,
Data: data,
PlanName: planName,
Features: features,
ValidFrom: validFrom,
ValidUntil: validUntil,
Status: status,
}, nil
}
func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
licenseDataWithIdAndKey := data
licenseDataWithIdAndKey["id"] = id
licenseDataWithIdAndKey["key"] = key
return NewLicenseV3(licenseDataWithIdAndKey)
}
func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
if !ok {
planKeyFromPlanName = Basic
}
return &License{
Key: l.Key,
ActivationId: "",
PlanDetails: "",
FeatureSet: l.Features,
ValidationMessage: "",
IsCurrent: l.IsCurrent,
LicensePlan: LicensePlan{
PlanKey: planKeyFromPlanName,
ValidFrom: l.ValidFrom,
ValidUntil: l.ValidUntil,
Status: l.Status},
}
}
type CheckoutRequest struct {
SuccessURL string `json:"url"`
}
type PortalRequest struct {
SuccessURL string `json:"url"`
}

View File

@@ -0,0 +1,170 @@
package model
import (
"encoding/json"
"testing"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewLicenseV3(t *testing.T) {
testCases := []struct {
name string
data []byte
pass bool
expected *LicenseV3
error error
}{
{
name: "Error for missing license id",
data: []byte(`{}`),
pass: false,
error: errors.New("id key is missing"),
},
{
name: "Error for license id not being a valid string",
data: []byte(`{"id": 10}`),
pass: false,
error: errors.New("id key is not a valid string"),
},
{
name: "Error for missing license key",
data: []byte(`{"id":"does-not-matter"}`),
pass: false,
error: errors.New("key key is missing"),
},
{
name: "Error for invalid string license key",
data: []byte(`{"id":"does-not-matter","key":10}`),
pass: false,
error: errors.New("key key is not a valid string"),
},
{
name: "Error for missing license status",
data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
pass: false,
error: errors.New("status key is missing"),
},
{
name: "Error for invalid string license status",
data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
pass: false,
error: errors.New("status key is not a valid string"),
},
{
name: "Error for missing license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
pass: false,
error: errors.New("plan key is missing"),
},
{
name: "Error for invalid json license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
pass: false,
error: errors.New("plan key is not a valid map[string]interface {}"),
},
{
name: "Error for invalid license plan",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
pass: false,
error: errors.New("name key is missing"),
},
{
name: "Parse the entire license properly",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"category": "FREE",
"status": "ACTIVE",
"valid_from": float64(1730899309),
"valid_until": float64(-1),
},
PlanName: PlanNameTeams,
ValidFrom: 1730899309,
ValidUntil: -1,
Status: "ACTIVE",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
{
name: "Fallback to basic plan if license status is invalid",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"category": "FREE",
"status": "INVALID",
"valid_from": float64(1730899309),
"valid_until": float64(-1),
},
PlanName: PlanNameBasic,
ValidFrom: 1730899309,
ValidUntil: -1,
Status: "INVALID",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
{
name: "fallback states for validFrom and validUntil",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
pass: true,
expected: &LicenseV3{
ID: "does-not-matter",
Key: "does-not-matter-key",
Data: map[string]interface{}{
"plan": map[string]interface{}{
"name": "TEAMS",
},
"valid_from": 1234.456,
"valid_until": 5678.567,
"category": "FREE",
"status": "ACTIVE",
},
PlanName: PlanNameTeams,
ValidFrom: 1234,
ValidUntil: 5678,
Status: "ACTIVE",
IsCurrent: false,
Features: model.FeatureSet{},
},
},
}
for _, tc := range testCases {
var licensePayload map[string]interface{}
err := json.Unmarshal(tc.data, &licensePayload)
require.NoError(t, err)
license, err := NewLicenseV3(licensePayload)
if license != nil {
license.Features = make(model.FeatureSet, 0)
delete(license.Data, "features")
}
if tc.pass {
require.NoError(t, err)
require.NotNil(t, license)
assert.Equal(t, tc.expected, license)
} else {
require.Error(t, err)
assert.EqualError(t, err, tc.error.Error())
require.Nil(t, license)
}
}
}

View File

@@ -1,6 +1,7 @@
package model
import (
"github.com/SigNoz/signoz/pkg/query-service/constants"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
)
@@ -23,6 +24,7 @@ var (
LicenseStatusInvalid = "INVALID"
)
const DisableUpsell = "DISABLE_UPSELL"
const Onboarding = "ONBOARDING"
const ChatSupport = "CHAT_SUPPORT"
const Gateway = "GATEWAY"
@@ -36,6 +38,90 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: DisableUpsell,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
@@ -64,6 +150,142 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.TraceFunnels,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
basemodel.Feature{
Name: SSO,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: PremiumSupport,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AnomalyDetection,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.TraceFunnels,
Active: false,
@@ -81,6 +303,83 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
@@ -123,6 +422,13 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.TraceFunnels,
Active: false,

View File

@@ -53,6 +53,7 @@ type AnomalyRule struct {
func NewAnomalyRule(
id string,
p *baserules.PostableRule,
featureFlags interfaces.FeatureLookup,
reader interfaces.Reader,
cache cache.Cache,
opts ...baserules.RuleOption,
@@ -88,9 +89,10 @@ func NewAnomalyRule(
zap.L().Info("using seasonality", zap.String("seasonality", t.seasonality.String()))
querierOptsV2 := querierV2.QuerierOptions{
Reader: reader,
Cache: cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
Reader: reader,
Cache: cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FeatureLookup: featureFlags,
}
t.querierV2 = querierV2.NewQuerier(querierOptsV2)
@@ -100,18 +102,21 @@ func NewAnomalyRule(
anomaly.WithCache[*anomaly.HourlyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.HourlyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](featureFlags),
)
} else if t.seasonality == anomaly.SeasonalityDaily {
t.provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](featureFlags),
)
} else if t.seasonality == anomaly.SeasonalityWeekly {
t.provider = anomaly.NewWeeklyProvider(
anomaly.WithCache[*anomaly.WeeklyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.WeeklyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](featureFlags),
)
}
return &t, nil

View File

@@ -23,6 +23,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
tr, err := baserules.NewThresholdRule(
ruleId,
opts.Rule,
opts.FF,
opts.Reader,
opts.UseLogsNewSchema,
opts.UseTraceNewSchema,
@@ -65,6 +66,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
ar, err := NewAnomalyRule(
ruleId,
opts.Rule,
opts.FF,
opts.Reader,
opts.Cache,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
@@ -121,6 +123,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
rule, err = baserules.NewThresholdRule(
alertname,
parsedRule,
opts.FF,
opts.Reader,
opts.UseLogsNewSchema,
opts.UseTraceNewSchema,
@@ -157,6 +160,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
rule, err = NewAnomalyRule(
alertname,
parsedRule,
opts.FF,
opts.Reader,
opts.Cache,
baserules.WithSendAlways(),

View File

@@ -4,21 +4,22 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"sync/atomic"
"time"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/go-co-op/gocron"
"github.com/google/uuid"
"go.uber.org/zap"
"github.com/SigNoz/signoz/ee/query-service/dao"
licenseserver "github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
"github.com/SigNoz/signoz/ee/query-service/license"
"github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/utils/encryption"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/zeus"
)
const (
@@ -33,20 +34,35 @@ var (
)
type Manager struct {
telemetryStore telemetrystore.TelemetryStore
licenseRepo *license.Repo
scheduler *gocron.Scheduler
zeus zeus.Zeus
clickhouseConn clickhouse.Conn
licenseRepo *license.Repo
scheduler *gocron.Scheduler
modelDao dao.ModelDao
tenantID string
}
func New(licenseRepo *license.Repo, telemetryStore telemetrystore.TelemetryStore, zeus zeus.Zeus) (*Manager, error) {
m := &Manager{
licenseRepo: licenseRepo,
telemetryStore: telemetryStore,
zeus: zeus,
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
func New(modelDao dao.ModelDao, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn, chUrl string) (*Manager, error) {
hostNameRegex := regexp.MustCompile(`tcp://(?P<hostname>.*):`)
hostNameRegexMatches := hostNameRegex.FindStringSubmatch(chUrl)
tenantID := ""
if len(hostNameRegexMatches) == 2 {
tenantID = hostNameRegexMatches[1]
tenantID = strings.TrimSuffix(tenantID, "-clickhouse")
}
m := &Manager{
// repository: repo,
clickhouseConn: clickhouseConn,
licenseRepo: licenseRepo,
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
modelDao: modelDao,
tenantID: tenantID,
}
return m, nil
}
@@ -104,7 +120,7 @@ func (lm *Manager) UploadUsage() {
for _, db := range dbs {
dbusages := []model.UsageDB{}
err := lm.telemetryStore.ClickhouseDB().Select(ctx, &dbusages, fmt.Sprintf(query, db, db), time.Now().Add(-(24 * time.Hour)))
err := lm.clickhouseConn.Select(ctx, &dbusages, fmt.Sprintf(query, db, db), time.Now().Add(-(24 * time.Hour)))
if err != nil && !strings.Contains(err.Error(), "doesn't exist") {
zap.L().Error("failed to get usage from clickhouse: %v", zap.Error(err))
return
@@ -120,6 +136,17 @@ func (lm *Manager) UploadUsage() {
return
}
zap.L().Info("uploading usage data")
orgName := ""
orgNames, orgError := lm.modelDao.GetOrgs(ctx)
if orgError != nil {
zap.L().Error("failed to get org data: %v", zap.Error(orgError))
}
if len(orgNames) == 1 {
orgName = orgNames[0].Name
}
usagesPayload := []model.Usage{}
for _, usage := range usages {
usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data))
@@ -139,8 +166,8 @@ func (lm *Manager) UploadUsage() {
usageData.ExporterID = usage.ExporterID
usageData.Type = usage.Type
usageData.Tenant = "default"
usageData.OrgName = "default"
usageData.TenantId = "default"
usageData.OrgName = orgName
usageData.TenantId = lm.tenantID
usagesPayload = append(usagesPayload, usageData)
}
@@ -149,7 +176,6 @@ func (lm *Manager) UploadUsage() {
LicenseKey: key,
Usage: usagesPayload,
}
lm.UploadUsageWithExponentalBackOff(ctx, payload)
}

View File

@@ -4,28 +4,10 @@ import (
"context"
"fmt"
"reflect"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/uptrace/bun"
)
var (
Identity = "id"
Integer = "bigint"
Text = "text"
)
var (
Org = "org"
User = "user"
)
var (
OrgReference = `("org_id") REFERENCES "organizations" ("id")`
UserReference = `("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
)
type dialect struct {
}
@@ -193,10 +175,7 @@ func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table inte
return true, nil
}
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, references []string, cb func(context.Context) error) error {
if len(references) == 0 {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error {
exists, err := dialect.TableExists(ctx, bun, newModel)
if err != nil {
return err
@@ -205,25 +184,12 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
return nil
}
var fkReferences []string
for _, reference := range references {
if reference == Org && !slices.Contains(fkReferences, OrgReference) {
fkReferences = append(fkReferences, OrgReference)
} else if reference == User && !slices.Contains(fkReferences, UserReference) {
fkReferences = append(fkReferences, UserReference)
}
}
createTable := bun.
_, err = bun.
NewCreateTable().
IfNotExists().
Model(newModel)
Model(newModel).
Exec(ctx)
for _, fk := range fkReferences {
createTable = createTable.ForeignKey(fk)
}
_, err = createTable.Exec(ctx)
if err != nil {
return err
}
@@ -252,115 +218,3 @@ func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.I
}
return nil
}
func (dialect *dialect) UpdatePrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
if reference == "" {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
columnType, err := dialect.GetColumnType(ctx, bun, oldTableName, Identity)
if err != nil {
return err
}
if columnType == Text {
return nil
}
fkReference := ""
if reference == Org {
fkReference = OrgReference
} else if reference == User {
fkReference = UserReference
}
_, err = bun.
NewCreateTable().
IfNotExists().
Model(newModel).
ForeignKey(fkReference).
Exec(ctx)
if err != nil {
return err
}
err = cb(ctx)
if err != nil {
return err
}
_, err = bun.
NewDropTable().
IfExists().
Model(oldModel).
Exec(ctx)
if err != nil {
return err
}
_, err = bun.
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
if err != nil {
return err
}
return nil
}
func (dialect *dialect) AddPrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
if reference == "" {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
identityExists, err := dialect.ColumnExists(ctx, bun, oldTableName, Identity)
if err != nil {
return err
}
if identityExists {
return nil
}
fkReference := ""
if reference == Org {
fkReference = OrgReference
} else if reference == User {
fkReference = UserReference
}
_, err = bun.
NewCreateTable().
IfNotExists().
Model(newModel).
ForeignKey(fkReference).
Exec(ctx)
if err != nil {
return err
}
err = cb(ctx)
if err != nil {
return err
}
_, err = bun.
NewDropTable().
IfExists().
Model(oldModel).
Exec(ctx)
if err != nil {
return err
}
_, err = bun.
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
if err != nil {
return err
}
return nil
}

View File

@@ -1,33 +0,0 @@
package featuretypes
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
var (
SingleSignOn = featuretypes.MustNewName("SingleSignOn")
)
func NewEnterpriseRegistry() (featuretypes.Registry, error) {
enterpriseRegistry, err := featuretypes.NewRegistry(
&featuretypes.Feature{
Name: SingleSignOn,
Kind: featuretypes.KindBoolean,
Description: "Enable single sign on.",
Stage: featuretypes.StageStable,
Default: true,
},
)
if err != nil {
return nil, err
}
return enterpriseRegistry.MergeOrOverride(featuretypes.MustNewCommunityRegistry()), nil
}
func MustNewEnterpriseRegistry() featuretypes.Registry {
enterpriseRegistry, err := NewEnterpriseRegistry()
if err != nil {
panic(err)
}
return enterpriseRegistry
}

View File

@@ -1 +0,0 @@
package licensetypes

View File

@@ -1,245 +0,0 @@
package licensetypes
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
type License struct {
ID string
Key string
Contents map[string]any
OrgFeatures []*featuretypes.StorableOrgFeature
}
// type License struct {
// Key string `json:"key" db:"key"`
// ActivationId string `json:"activationId" db:"activationId"`
// CreatedAt time.Time `db:"created_at"`
// // PlanDetails contains the encrypted plan info
// PlanDetails string `json:"planDetails" db:"planDetails"`
// // stores parsed license details
// LicensePlan
// FeatureSet basemodel.FeatureSet
// // populated in case license has any errors
// ValidationMessage string `db:"validationMessage"`
// // used only for sending details to front-end
// IsCurrent bool `json:"isCurrent"`
// }
// func (l *License) MarshalJSON() ([]byte, error) {
// return json.Marshal(&struct {
// Key string `json:"key" db:"key"`
// ActivationId string `json:"activationId" db:"activationId"`
// ValidationMessage string `db:"validationMessage"`
// IsCurrent bool `json:"isCurrent"`
// PlanKey string `json:"planKey"`
// ValidFrom time.Time `json:"ValidFrom"`
// ValidUntil time.Time `json:"ValidUntil"`
// Status string `json:"status"`
// }{
// Key: l.Key,
// ActivationId: l.ActivationId,
// IsCurrent: l.IsCurrent,
// PlanKey: l.PlanKey,
// ValidFrom: time.Unix(l.ValidFrom, 0),
// ValidUntil: time.Unix(l.ValidUntil, 0),
// Status: l.Status,
// ValidationMessage: l.ValidationMessage,
// })
// }
// type LicensePlan struct {
// PlanKey string `json:"planKey"`
// ValidFrom int64 `json:"validFrom"`
// ValidUntil int64 `json:"validUntil"`
// Status string `json:"status"`
// }
// type Licenses struct {
// TrialStart int64 `json:"trialStart"`
// TrialEnd int64 `json:"trialEnd"`
// OnTrial bool `json:"onTrial"`
// WorkSpaceBlock bool `json:"workSpaceBlock"`
// TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"`
// GracePeriodEnd int64 `json:"gracePeriodEnd"`
// Licenses []License `json:"licenses"`
// }
// type SubscriptionServerResp struct {
// Status string `json:"status"`
// Data Licenses `json:"data"`
// }
// type Plan struct {
// Name string `json:"name"`
// }
// type LicenseDB struct {
// ID string `json:"id"`
// Key string `json:"key"`
// Data string `json:"data"`
// }
// type LicenseV3 struct {
// ID string
// Key string
// Data map[string]interface{}
// PlanName string
// Features basemodel.FeatureSet
// Status string
// IsCurrent bool
// ValidFrom int64
// ValidUntil int64
// }
// func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
// var zeroValue T
// if val, ok := data[key]; ok {
// if value, ok := val.(T); ok {
// return value, nil
// }
// return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
// }
// return zeroValue, fmt.Errorf("%s key is missing", key)
// }
// func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
// var features basemodel.FeatureSet
// // extract id from data
// licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
// if err != nil {
// return nil, err
// }
// delete(data, "id")
// // extract key from data
// licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
// if err != nil {
// return nil, err
// }
// delete(data, "key")
// // extract status from data
// status, err := extractKeyFromMapStringInterface[string](data, "status")
// if err != nil {
// return nil, err
// }
// planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
// if err != nil {
// return nil, err
// }
// planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
// if err != nil {
// return nil, err
// }
// // if license status is invalid then default it to basic
// if status == LicenseStatusInvalid {
// planName = PlanNameBasic
// }
// featuresFromZeus := basemodel.FeatureSet{}
// if _features, ok := data["features"]; ok {
// featuresData, err := json.Marshal(_features)
// if err != nil {
// return nil, errors.Wrap(err, "failed to marshal features data")
// }
// if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
// return nil, errors.Wrap(err, "failed to unmarshal features data")
// }
// }
// switch planName {
// case PlanNameTeams:
// features = append(features, ProPlan...)
// case PlanNameEnterprise:
// features = append(features, EnterprisePlan...)
// case PlanNameBasic:
// features = append(features, BasicPlan...)
// default:
// features = append(features, BasicPlan...)
// }
// if len(featuresFromZeus) > 0 {
// for _, feature := range featuresFromZeus {
// exists := false
// for i, existingFeature := range features {
// if existingFeature.Name == feature.Name {
// features[i] = feature // Replace existing feature
// exists = true
// break
// }
// }
// if !exists {
// features = append(features, feature) // Append if it doesn't exist
// }
// }
// }
// data["features"] = features
// _validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
// if err != nil {
// _validFrom = 0
// }
// validFrom := int64(_validFrom)
// _validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
// if err != nil {
// _validUntil = 0
// }
// validUntil := int64(_validUntil)
// return &LicenseV3{
// ID: licenseID,
// Key: licenseKey,
// Data: data,
// PlanName: planName,
// Features: features,
// ValidFrom: validFrom,
// ValidUntil: validUntil,
// Status: status,
// }, nil
// }
// func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
// licenseDataWithIdAndKey := data
// licenseDataWithIdAndKey["id"] = id
// licenseDataWithIdAndKey["key"] = key
// return NewLicenseV3(licenseDataWithIdAndKey)
// }
// func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
// planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
// if !ok {
// planKeyFromPlanName = Basic
// }
// return &License{
// Key: l.Key,
// ActivationId: "",
// PlanDetails: "",
// FeatureSet: l.Features,
// ValidationMessage: "",
// IsCurrent: l.IsCurrent,
// LicensePlan: LicensePlan{
// PlanKey: planKeyFromPlanName,
// ValidFrom: l.ValidFrom,
// ValidUntil: l.ValidUntil,
// Status: l.Status},
// }
// }
// type CheckoutRequest struct {
// SuccessURL string `json:"url"`
// }
// type PortalRequest struct {
// SuccessURL string `json:"url"`
// }

View File

@@ -1,160 +0,0 @@
package licensetypes
// func TestNewLicenseV3(t *testing.T) {
// testCases := []struct {
// name string
// data []byte
// pass bool
// expected *LicenseV3
// error error
// }{
// {
// name: "Error for missing license id",
// data: []byte(`{}`),
// pass: false,
// error: errors.New("id key is missing"),
// },
// {
// name: "Error for license id not being a valid string",
// data: []byte(`{"id": 10}`),
// pass: false,
// error: errors.New("id key is not a valid string"),
// },
// {
// name: "Error for missing license key",
// data: []byte(`{"id":"does-not-matter"}`),
// pass: false,
// error: errors.New("key key is missing"),
// },
// {
// name: "Error for invalid string license key",
// data: []byte(`{"id":"does-not-matter","key":10}`),
// pass: false,
// error: errors.New("key key is not a valid string"),
// },
// {
// name: "Error for missing license status",
// data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
// pass: false,
// error: errors.New("status key is missing"),
// },
// {
// name: "Error for invalid string license status",
// data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
// pass: false,
// error: errors.New("status key is not a valid string"),
// },
// {
// name: "Error for missing license plan",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
// pass: false,
// error: errors.New("plan key is missing"),
// },
// {
// name: "Error for invalid json license plan",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
// pass: false,
// error: errors.New("plan key is not a valid map[string]interface {}"),
// },
// {
// name: "Error for invalid license plan",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
// pass: false,
// error: errors.New("name key is missing"),
// },
// {
// name: "Parse the entire license properly",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
// pass: true,
// expected: &LicenseV3{
// ID: "does-not-matter",
// Key: "does-not-matter-key",
// Data: map[string]interface{}{
// "plan": map[string]interface{}{
// "name": "TEAMS",
// },
// "category": "FREE",
// "status": "ACTIVE",
// "valid_from": float64(1730899309),
// "valid_until": float64(-1),
// },
// PlanName: PlanNameTeams,
// ValidFrom: 1730899309,
// ValidUntil: -1,
// Status: "ACTIVE",
// IsCurrent: false,
// Features: model.FeatureSet{},
// },
// },
// {
// name: "Fallback to basic plan if license status is invalid",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
// pass: true,
// expected: &LicenseV3{
// ID: "does-not-matter",
// Key: "does-not-matter-key",
// Data: map[string]interface{}{
// "plan": map[string]interface{}{
// "name": "TEAMS",
// },
// "category": "FREE",
// "status": "INVALID",
// "valid_from": float64(1730899309),
// "valid_until": float64(-1),
// },
// PlanName: PlanNameBasic,
// ValidFrom: 1730899309,
// ValidUntil: -1,
// Status: "INVALID",
// IsCurrent: false,
// Features: model.FeatureSet{},
// },
// },
// {
// name: "fallback states for validFrom and validUntil",
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
// pass: true,
// expected: &LicenseV3{
// ID: "does-not-matter",
// Key: "does-not-matter-key",
// Data: map[string]interface{}{
// "plan": map[string]interface{}{
// "name": "TEAMS",
// },
// "valid_from": 1234.456,
// "valid_until": 5678.567,
// "category": "FREE",
// "status": "ACTIVE",
// },
// PlanName: PlanNameTeams,
// ValidFrom: 1234,
// ValidUntil: 5678,
// Status: "ACTIVE",
// IsCurrent: false,
// Features: model.FeatureSet{},
// },
// },
// }
// for _, tc := range testCases {
// var licensePayload map[string]interface{}
// err := json.Unmarshal(tc.data, &licensePayload)
// require.NoError(t, err)
// license, err := NewLicenseV3(licensePayload)
// if license != nil {
// license.Features = make(model.FeatureSet, 0)
// delete(license.Data, "features")
// }
// if tc.pass {
// require.NoError(t, err)
// require.NotNil(t, license)
// assert.Equal(t, tc.expected, license)
// } else {
// require.Error(t, err)
// assert.EqualError(t, err, tc.error.Error())
// require.Nil(t, license)
// }
// }
// }

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
@@ -29,10 +28,11 @@ func NewGettablePAT(name, role, userID string, expiresAt int64) GettablePAT {
}
type StorablePersonalAccessToken struct {
bun.BaseModel `bun:"table:personal_access_token"`
types.Identifiable
bun.BaseModel `bun:"table:personal_access_tokens"`
types.TimeAuditable
OrgID string `json:"orgId" bun:"org_id,type:text,notnull"`
ID int `json:"id" bun:"id,pk,autoincrement"`
Role string `json:"role" bun:"role,type:text,notnull,default:'ADMIN'"`
UserID string `json:"userId" bun:"user_id,type:text,notnull"`
Token string `json:"token" bun:"token,type:text,notnull,unique"`
@@ -69,8 +69,5 @@ func NewStorablePersonalAccessToken(name, role, userID string, expiresAt int64)
CreatedAt: now,
UpdatedAt: now,
},
Identifiable: types.Identifiable{
ID: valuer.GenerateUUID(),
},
}
}

View File

@@ -1,31 +0,0 @@
package zeus
import (
"fmt"
neturl "net/url"
"sync"
"github.com/SigNoz/signoz/pkg/zeus"
)
// This will be set via ldflags at build time.
var (
url string = "<unset>"
once sync.Once
GlobalConfig zeus.Config
)
// init initializes and validates the Zeus configuration
func init() {
once.Do(func() {
parsedURL, err := neturl.Parse(url)
if err != nil {
panic(fmt.Errorf("invalid zeus URL: %w", err))
}
GlobalConfig = zeus.Config{URL: parsedURL}
if err := GlobalConfig.Validate(); err != nil {
panic(fmt.Errorf("invalid zeus config: %w", err))
}
})
}

View File

@@ -1,61 +0,0 @@
package implzeus
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/http/client"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/types/metertypes"
"github.com/SigNoz/signoz/pkg/zeus"
)
type Provider struct {
settings factory.ScopedProviderSettings
config zeus.Config
client *client.Client
}
func NewProviderFactory() factory.ProviderFactory[zeus.Zeus, zeus.Config] {
return factory.NewProviderFactory(factory.MustNewName("impl"), func(ctx context.Context, providerSettings factory.ProviderSettings, config zeus.Config) (zeus.Zeus, error) {
return New(ctx, providerSettings, config)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config zeus.Config) (zeus.Zeus, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/zeus/implzeus")
httpClient := client.New(
settings.Logger(),
providerSettings.TracerProvider,
providerSettings.MeterProvider,
client.WithRequestResponseLog(true),
client.WithRetryCount(3),
)
return &Provider{
settings: settings,
config: config,
client: httpClient,
}, nil
}
func (provider *Provider) GetLicense(ctx context.Context, key string) (*licensetypes.License, error) {
return nil, nil
}
func (provider *Provider) GetCheckoutURL(ctx context.Context, key string) (string, error) {
return "", nil
}
func (provider *Provider) GetPortalURL(ctx context.Context, key string) (string, error) {
return "", nil
}
func (provider *Provider) GetDeployment(ctx context.Context, key string) ([]byte, error) {
return nil, nil
}
func (provider *Provider) PutMeters(ctx context.Context, key string, meters metertypes.Meters) error {
return nil
}

View File

@@ -18,13 +18,6 @@
"field_send_resolved": "Send resolved alerts",
"field_channel_type": "Type",
"field_webhook_url": "Webhook URL",
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to": "Enter email addresses separated by commas.",
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"field_slack_recipient": "Recipient",
"field_slack_title": "Title",
"field_slack_description": "Description",

View File

@@ -18,12 +18,6 @@
"field_send_resolved": "Send resolved alerts",
"field_channel_type": "Type",
"field_webhook_url": "Webhook URL",
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to": "Enter email addresses separated by commas.",
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"field_slack_recipient": "Recipient",
"field_slack_title": "Title",
"field_slack_description": "Description",

View File

@@ -1,12 +1,29 @@
// keep this consistent with backend constants.go
export enum FeatureKeys {
SSO = 'SSO',
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
BASIC_PLAN = 'BASIC_PLAN',
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
DurationSort = 'DurationSort',
TimestampSort = 'TimestampSort',
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
DISABLE_UPSELL = 'DISABLE_UPSELL',
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
OSS = 'OSS',
ONBOARDING = 'ONBOARDING',
CHAT_SUPPORT = 'CHAT_SUPPORT',
GATEWAY = 'GATEWAY',
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
AWS_INTEGRATION = 'AWS_INTEGRATION',
ONBOARDING_V3 = 'ONBOARDING_V3',
THIRD_PARTY_API = 'THIRD_PARTY_API',
TRACE_FUNNELS = 'TRACE_FUNNELS',

View File

@@ -31,10 +31,6 @@ jest.mock('hooks/useNotifications', () => ({
})),
}));
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Create Alert Channel', () => {
afterEach(() => {
jest.clearAllMocks();

View File

@@ -18,10 +18,6 @@ import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Create Alert Channel (Normal User)', () => {
afterEach(() => {
jest.clearAllMocks();

View File

@@ -20,10 +20,6 @@ jest.mock('hooks/useNotifications', () => ({
})),
}));
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Should check if the edit alert channel is properly displayed ', () => {
beforeEach(() => {
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);

View File

@@ -1,5 +1,4 @@
import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,20 +9,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
return (
<>
<Form.Item
name="webhook_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_ms_teams_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Form.Item name="webhook_url" label={t('field_webhook_url')}>
<Input
onChange={(event): void => {
setSelectedConfig((value) => ({

View File

@@ -1,5 +1,4 @@
import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { useTranslation } from 'react-i18next';
import { OpsgenieChannel } from '../../CreateAlertChannels/config';
@@ -20,21 +19,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
return (
<>
<Form.Item
name="api_key"
label={t('field_opsgenie_api_key')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_opsgenie_api_key')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
required
>
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
<Input
onChange={handleInputChange('api_key')}
data-testid="opsgenie-api-key-textbox"

View File

@@ -1,5 +1,4 @@
import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
@@ -11,20 +10,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
const { t } = useTranslation('channels');
return (
<>
<Form.Item
name="routing_key"
label={t('field_pager_routing_key')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_pager_routing_key')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Form.Item name="routing_key" label={t('field_pager_routing_key')} required>
<Input
onChange={(event): void => {
setSelectedConfig((value) => ({

View File

@@ -1,5 +1,4 @@
import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
@@ -12,20 +11,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
return (
<>
<Form.Item
name="api_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_slack_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Form.Item name="api_url" label={t('field_webhook_url')}>
<Input
onChange={(event): void => {
setSelectedConfig((value) => ({

View File

@@ -1,5 +1,4 @@
import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,20 +9,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
return (
<>
<Form.Item
name="api_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_webhook_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Form.Item name="api_url" label={t('field_webhook_url')}>
<Input
onChange={(event): void => {
setSelectedConfig((value) => ({

View File

@@ -1,5 +1,6 @@
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
import { Store } from 'antd/lib/form/interface';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import {
ChannelType,
@@ -10,8 +11,11 @@ import {
WebhookChannel,
} from 'container/CreateAlertChannels/config';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
import { isFeatureKeys } from 'utils/app';
import EmailSettings from './Settings/Email';
import MsTeamsSettings from './Settings/MsTeams';
@@ -35,6 +39,17 @@ function FormAlertChannels({
editing = false,
}: FormAlertChannelsProps): JSX.Element {
const { t } = useTranslation('channels');
const { featureFlags } = useAppContext();
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
const featureKey = isFeatureKeys(feature)
? feature
: FeatureKeys.ALERT_CHANNEL_SLACK;
const hasFeature = featureFlags?.find(
(flag: FeatureFlagProps) => flag.name === featureKey,
);
const renderSettings = (): ReactElement | null => {
switch (type) {
@@ -131,7 +146,7 @@ function FormAlertChannels({
<Form.Item>
<Button
disabled={savingState}
disabled={savingState || !hasFeature}
loading={savingState}
type="primary"
onClick={(): void => onSaveHandler(type)}
@@ -139,7 +154,7 @@ function FormAlertChannels({
{t('button_save_channel')}
</Button>
<Button
disabled={testingState}
disabled={testingState || !hasFeature}
loading={testingState}
onClick={(): void => onTestHandler(type)}
>

View File

@@ -467,6 +467,10 @@ function FormAlertRules({
panelType,
]);
const isAlertAvailable =
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_ALERTS)
?.active || false;
const saveRule = useCallback(async () => {
if (!isFormValid()) {
return;
@@ -685,6 +689,7 @@ function FormAlertRules({
const isAlertNameMissing = !formInstance.getFieldValue('alert');
const isAlertAvailableToSave =
isAlertAvailable &&
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
alertType !== AlertTypes.METRICS_BASED_ALERT;

View File

@@ -5,6 +5,7 @@ import { WarningOutlined } from '@ant-design/icons';
import { Button, Flex, Modal, Space, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import {
initialQueriesMap,
@@ -26,6 +27,7 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
import { Check, X } from 'lucide-react';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import {
getNextWidgets,
@@ -77,6 +79,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const { t } = useTranslation(['dashboard']);
const { featureFlags } = useAppContext();
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const {
@@ -562,7 +566,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isQueryBuilderActive =
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_PANELS)
?.active || false;
const isNewTraceLogsAvailable =
isQueryBuilderActive &&
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
currentQuery.builder.queryData.find(
(query) => query.dataSource !== DataSource.METRICS,

View File

@@ -13,7 +13,11 @@ function OrganizationSettings(): JSX.Element {
const isNotSSO =
!featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
const isAuthDomain = !isNotSSO;
const isNoUpSell =
!featureFlags?.find((flag) => flag.name === FeatureKeys.DISABLE_UPSELL)
?.active || false;
const isAuthDomain = !isNoUpSell || (isNoUpSell && !isNotSSO);
if (!org) {
return <div />;

View File

@@ -453,7 +453,7 @@ export const Query = memo(function Query({
</Col>
)}
<Col flex="1" className="qb-search-container">
{query.dataSource === DataSource.LOGS ? (
{[DataSource.LOGS, DataSource.TRACES].includes(query.dataSource) ? (
<QueryBuilderSearchV2
query={query}
onChange={handleChangeTagFilters}

View File

@@ -2,6 +2,7 @@
import './QueryBuilderSearchV2.styles.scss';
import { Typography } from 'antd';
import cx from 'classnames';
import {
ArrowDown,
ArrowUp,
@@ -25,6 +26,7 @@ interface ICustomDropdownProps {
exampleQueries: TagFilter[];
onChange: (value: TagFilter) => void;
currentFilterItem?: ITag;
isLogsDataSource: boolean;
}
export default function QueryBuilderSearchDropdown(
@@ -38,11 +40,14 @@ export default function QueryBuilderSearchDropdown(
exampleQueries,
options,
onChange,
isLogsDataSource,
} = props;
const userOs = getUserOperatingSystem();
return (
<>
<div className="content">
<div
className={cx('content', { 'non-logs-data-source': !isLogsDataSource })}
>
{!currentFilterItem?.key ? (
<div className="suggested-filters">Suggested Filters</div>
) : !currentFilterItem?.op ? (

View File

@@ -11,6 +11,11 @@
.rc-virtual-list-holder {
height: 115px;
}
&.non-logs-data-source {
.rc-virtual-list-holder {
height: 256px;
}
}
}
}

View File

@@ -10,6 +10,7 @@ import {
QUERY_BUILDER_SEARCH_VALUES,
} from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import ROUTES from 'constants/routes';
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { WhereClauseConfig } from 'hooks/queryBuilder/useAutoComplete';
@@ -40,6 +41,7 @@ import {
useRef,
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import {
BaseAutocompleteData,
DataTypes,
@@ -54,6 +56,7 @@ import { v4 as uuid } from 'uuid';
import { selectStyle } from '../QueryBuilderSearch/config';
import { PLACEHOLDER } from '../QueryBuilderSearch/constant';
import SpanScopeSelector from '../QueryBuilderSearch/SpanScopeSelector';
import { TypographyText } from '../QueryBuilderSearch/style';
import {
checkCommaInValue,
@@ -123,6 +126,8 @@ function QueryBuilderSearchV2(
hardcodedAttributeKeys,
} = props;
const { pathname } = useLocation();
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const { handleRunQuery, currentQuery } = useQueryBuilder();
@@ -689,12 +694,29 @@ function QueryBuilderSearchV2(
})),
);
} else {
setDropdownOptions(
data?.payload?.attributeKeys?.map((key) => ({
setDropdownOptions([
// Add user typed option if it doesn't exist in the payload
...(!isEmpty(tagKey) &&
!data?.payload?.attributeKeys?.some((val) => isEqual(val.key, tagKey))
? [
{
label: tagKey,
value: {
key: tagKey,
dataType: DataTypes.EMPTY,
type: '',
isColumn: false,
isJSON: false,
},
},
]
: []),
// Map existing attribute keys from payload
...(data?.payload?.attributeKeys?.map((key) => ({
label: key.key,
value: key,
})) || [],
);
})) || []),
]);
}
}
if (currentState === DropdownState.OPERATOR) {
@@ -907,6 +929,11 @@ function QueryBuilderSearchV2(
);
};
const isTracesExplorerPage = useMemo(
() => pathname === ROUTES.TRACES_EXPLORER,
[pathname],
);
return (
<div className="query-builder-search-v2">
<Select
@@ -964,6 +991,7 @@ function QueryBuilderSearchV2(
exampleQueries={suggestionsData?.payload?.example_queries || []}
tags={tags}
currentFilterItem={currentFilterItem}
isLogsDataSource={isLogsDataSource}
/>
)}
>
@@ -990,6 +1018,7 @@ function QueryBuilderSearchV2(
);
})}
</Select>
{isTracesExplorerPage && <SpanScopeSelector queryName={query.queryName} />}
</div>
);
}

View File

@@ -170,11 +170,7 @@ export const useOptions = (
(option, index, self) =>
index ===
self.findIndex(
(o) =>
// to remove duplicate & empty options from list
o.label === option.label &&
o.value === option.value &&
o.dataType?.toLowerCase() === option.dataType?.toLowerCase(), // handle case sensitivity
(o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list
) && option.value !== '',
) || []
).map((option) => {

View File

@@ -1,9 +1,9 @@
.traces-funnels-header {
.header {
display: flex;
flex-direction: column;
gap: 4px;
.traces-funnels-header-title {
&__title {
color: var(--bg-vanilla-100);
font-size: 18px;
font-style: normal;
@@ -13,7 +13,7 @@
margin: 0;
}
.traces-funnels-header-subtitle {
&__subtitle {
color: var(--bg-vanilla-400);
font-size: 14px;
line-height: 20px;
@@ -21,13 +21,13 @@
}
.lightMode {
.traces-funnels-header {
.traces-funnels-header-title {
.header {
&__title {
color: var(--bg-ink-500);
}
.traces-funnels-header-subtitle {
&__subtitle {
color: var(--bg-ink-400);
}
}
}
}

View File

@@ -1,10 +1,8 @@
function Header(): JSX.Element {
return (
<div className="traces-funnels-header">
<div className="traces-funnels-header-title">Funnels</div>
<div className="traces-funnels-header-subtitle">
Create and manage tracing funnels.
</div>
<div className="header">
<div className="header__title">Funnels</div>
<div className="header__subtitle">Create and manage tracing funnels.</div>
</div>
);
}

View File

@@ -21,7 +21,6 @@ import { useQuery } from 'react-query';
import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeaturesFlags';
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import {
LicensePlatform,
LicenseState,
LicenseV3ResModel,
TrialInfo,
@@ -146,8 +145,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
).unix(),
onTrial: isOnTrial,
workSpaceBlock:
activeLicenseV3Data.payload.state === LicenseState.EVALUATION_EXPIRED &&
activeLicenseV3Data.payload.platform === LicensePlatform.CLOUD,
activeLicenseV3Data.payload.state === LicenseState.EVALUATION_EXPIRED,
trialConvertedToSubscription:
activeLicenseV3Data.payload.state !== LicenseState.ISSUED &&
activeLicenseV3Data.payload.state !== LicenseState.EVALUATING &&

View File

@@ -186,6 +186,83 @@ export function getAppContextMock(
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.OSS,
active: false,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.DISABLE_UPSELL,
active: false,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.SMART_TRACE_DETAIL,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.CUSTOM_METRICS_FUNCTION,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.QUERY_BUILDER_PANELS,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.QUERY_BUILDER_ALERTS,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ALERT_CHANNEL_SLACK,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ALERT_CHANNEL_WEBHOOK,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ALERT_CHANNEL_PAGERDUTY,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ALERT_CHANNEL_OPSGENIE,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ALERT_CHANNEL_MSTEAMS,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.USE_SPAN_METRICS,
active: false,
@@ -214,6 +291,20 @@ export function getAppContextMock(
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.DurationSort,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.TimestampSort,
active: true,
usage: 0,
usage_limit: -1,
route: '',
},
{
name: FeatureKeys.ONBOARDING,
active: true,

7
go.mod
View File

@@ -22,7 +22,7 @@ require (
github.com/go-redis/redismock/v8 v8.11.5
github.com/go-viper/mapstructure/v2 v2.1.0
github.com/gojek/heimdall/v7 v7.0.3
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.1
@@ -77,6 +77,7 @@ require (
gopkg.in/segmentio/analytics-go.v3 v3.1.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
honnef.co/go/tools v0.0.1-2020.1.4
k8s.io/apimachinery v0.31.3
)
@@ -88,6 +89,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/ClickHouse/ch-go v0.61.5 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
@@ -108,7 +110,7 @@ require (
github.com/ebitengine/purego v0.8.0 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/elastic/lunes v0.1.0 // indirect
github.com/expr-lang/expr v1.17.0 // indirect
github.com/expr-lang/expr v1.16.9 // indirect
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
@@ -181,7 +183,6 @@ require (
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/open-feature/go-sdk v1.14.1 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.111.0 // indirect
github.com/paulmach/orb v0.11.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect

12
go.sum
View File

@@ -83,6 +83,7 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
@@ -231,8 +232,8 @@ github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQ
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/expr-lang/expr v1.17.0 h1:+vpszOyzKLQXC9VF+wA8cVA0tlA984/Wabc/1hF9Whg=
github.com/expr-lang/expr v1.17.0/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -333,8 +334,8 @@ github.com/gojek/heimdall/v7 v7.0.3 h1:+5sAhl8S0m+qRRL8IVeHCJudFh/XkG3wyO++nvOg+
github.com/gojek/heimdall/v7 v7.0.3/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5pmsTyiMic=
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM=
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -717,8 +718,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/open-feature/go-sdk v1.14.1 h1:jcxjCIG5Up3XkgYwWN5Y/WWfc6XobOhqrIwjyDBsoQo=
github.com/open-feature/go-sdk v1.14.1/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg=
github.com/open-telemetry/opamp-go v0.5.0 h1:2YFbb6G4qBkq3yTRdVb5Nfz9hKHW/ldUyex352e1J7g=
github.com/open-telemetry/opamp-go v0.5.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M=
github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.111.0 h1:n1p2DedLvPEN1XEx26s1PR1PCuXTgCY4Eo+kDTq7q0s=
@@ -1654,6 +1653,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8=
k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE=

View File

@@ -6,7 +6,6 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
@@ -34,16 +33,16 @@ type Alertmanager interface {
ListAllChannels(context.Context) ([]*alertmanagertypes.Channel, error)
// GetChannelByID gets a channel for the organization.
GetChannelByID(context.Context, string, valuer.UUID) (*alertmanagertypes.Channel, error)
GetChannelByID(context.Context, string, int) (*alertmanagertypes.Channel, error)
// UpdateChannel updates a channel for the organization.
UpdateChannelByReceiverAndID(context.Context, string, alertmanagertypes.Receiver, valuer.UUID) error
UpdateChannelByReceiverAndID(context.Context, string, alertmanagertypes.Receiver, int) error
// CreateChannel creates a channel for the organization.
CreateChannel(context.Context, string, alertmanagertypes.Receiver) error
// DeleteChannelByID deletes a channel for the organization.
DeleteChannelByID(context.Context, string, valuer.UUID) error
DeleteChannelByID(context.Context, string, int) error
// SetConfig sets the config for the organization.
SetConfig(context.Context, *alertmanagertypes.Config) error

View File

@@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/tidwall/gjson"
"github.com/uptrace/bun"
)
@@ -100,7 +99,7 @@ func (store *config) CreateChannel(ctx context.Context, channel *alertmanagertyp
}, opts...)
}
func (store *config) GetChannelByID(ctx context.Context, orgID string, id valuer.UUID) (*alertmanagertypes.Channel, error) {
func (store *config) GetChannelByID(ctx context.Context, orgID string, id int) (*alertmanagertypes.Channel, error) {
channel := new(alertmanagertypes.Channel)
err := store.
@@ -109,11 +108,11 @@ func (store *config) GetChannelByID(ctx context.Context, orgID string, id valuer
NewSelect().
Model(channel).
Where("org_id = ?", orgID).
Where("id = ?", id.StringValue()).
Where("id = ?", id).
Scan(ctx)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.Newf(errors.TypeNotFound, alertmanagertypes.ErrCodeAlertmanagerChannelNotFound, "cannot find channel with id %s", id.StringValue())
return nil, errors.Newf(errors.TypeNotFound, alertmanagertypes.ErrCodeAlertmanagerChannelNotFound, "cannot find channel with id %d", id)
}
return nil, err
}
@@ -137,7 +136,7 @@ func (store *config) UpdateChannel(ctx context.Context, orgID string, channel *a
}, opts...)
}
func (store *config) DeleteChannelByID(ctx context.Context, orgID string, id valuer.UUID, opts ...alertmanagertypes.StoreOption) error {
func (store *config) DeleteChannelByID(ctx context.Context, orgID string, id int, opts ...alertmanagertypes.StoreOption) error {
return store.wrap(ctx, func(ctx context.Context) error {
channel := new(alertmanagertypes.Channel)
@@ -147,7 +146,7 @@ func (store *config) DeleteChannelByID(ctx context.Context, orgID string, id val
NewDelete().
Model(channel).
Where("org_id = ?", orgID).
Where("id = ?", id.StringValue()).
Where("id = ?", id).
Exec(ctx); err != nil {
return err
}

View File

@@ -4,13 +4,13 @@ import (
"context"
"io"
"net/http"
"strconv"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -140,9 +140,9 @@ func (api *API) GetChannelByID(rw http.ResponseWriter, req *http.Request) {
return
}
id, err := valuer.NewUUID(idString)
id, err := strconv.Atoi(idString)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid integer"))
return
}
@@ -177,9 +177,9 @@ func (api *API) UpdateChannelByID(rw http.ResponseWriter, req *http.Request) {
return
}
id, err := valuer.NewUUID(idString)
id, err := strconv.Atoi(idString)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid integer"))
return
}
@@ -227,9 +227,9 @@ func (api *API) DeleteChannelByID(rw http.ResponseWriter, req *http.Request) {
return
}
id, err := valuer.NewUUID(idString)
id, err := strconv.Atoi(idString)
if err != nil {
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid integer"))
return
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/tidwall/gjson"
)
@@ -270,11 +269,11 @@ func (provider *provider) ListAllChannels(ctx context.Context) ([]*alertmanagert
return channels, nil
}
func (provider *provider) GetChannelByID(ctx context.Context, orgID string, channelID valuer.UUID) (*alertmanagertypes.Channel, error) {
func (provider *provider) GetChannelByID(ctx context.Context, orgID string, channelID int) (*alertmanagertypes.Channel, error) {
return provider.configStore.GetChannelByID(ctx, orgID, channelID)
}
func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver, id valuer.UUID) error {
func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver, id int) error {
channel, err := provider.configStore.GetChannelByID(ctx, orgID, id)
if err != nil {
return err
@@ -379,7 +378,7 @@ func (provider *provider) CreateChannel(ctx context.Context, orgID string, recei
}))
}
func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, channelID valuer.UUID) error {
func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, channelID int) error {
channel, err := provider.configStore.GetChannelByID(ctx, orgID, channelID)
if err != nil {
return err

View File

@@ -10,7 +10,6 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type provider struct {
@@ -100,11 +99,11 @@ func (provider *provider) ListAllChannels(ctx context.Context) ([]*alertmanagert
return nil, errors.Newf(errors.TypeUnsupported, errors.CodeUnsupported, "not supported by provider signoz")
}
func (provider *provider) GetChannelByID(ctx context.Context, orgID string, channelID valuer.UUID) (*alertmanagertypes.Channel, error) {
func (provider *provider) GetChannelByID(ctx context.Context, orgID string, channelID int) (*alertmanagertypes.Channel, error) {
return provider.configStore.GetChannelByID(ctx, orgID, channelID)
}
func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver, id valuer.UUID) error {
func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver, id int) error {
channel, err := provider.configStore.GetChannelByID(ctx, orgID, id)
if err != nil {
return err
@@ -128,7 +127,7 @@ func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgI
}))
}
func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, channelID valuer.UUID) error {
func (provider *provider) DeleteChannelByID(ctx context.Context, orgID string, channelID int) error {
channel, err := provider.configStore.GetChannelByID(ctx, orgID, channelID)
if err != nil {
return err

View File

@@ -1 +0,0 @@
package flagger

View File

@@ -1,35 +0,0 @@
package flagger
import "github.com/SigNoz/signoz/pkg/factory"
var _ factory.Config = Config{}
type Config struct {
Provider string `json:"provider"`
Memory Memory `json:"memory"`
}
type Memory struct {
Boolean Boolean `json:"boolean"`
}
type Boolean struct {
Enabled []string `json:"enabled"`
Disabled []string `json:"disabled"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("flagger"), newConfig)
}
func newConfig() factory.Config {
return &Config{
Provider: "memory",
Memory: Memory{},
}
}
func (c Config) Validate() error {
return nil
}

View File

@@ -1,27 +0,0 @@
package flagger
import (
"github.com/open-feature/go-sdk/openfeature"
)
type Provider = openfeature.FeatureProvider
type FlaggerHook = openfeature.Hook
type Flagger = openfeature.IClient
type flagger struct {
*openfeature.Client
}
func New(provider Provider, hooks ...FlaggerHook) (Flagger, error) {
client := openfeature.NewClient("signoz")
if err := openfeature.SetNamedProviderAndWait("signoz", provider); err != nil {
return nil, err
}
client.AddHooks(hooks...)
return &flagger{Client: client}, nil
}

View File

@@ -1,241 +0,0 @@
package memoryprovider
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/open-feature/go-sdk/openfeature"
)
type provider struct {
config flagger.Config
settings factory.ScopedProviderSettings
featureValues map[featuretypes.Name]featuretypes.FeatureValue
registry featuretypes.Registry
}
func NewFactory(registry featuretypes.Registry) factory.ProviderFactory[flagger.Provider, flagger.Config] {
return factory.NewProviderFactory(factory.MustNewName("memory"), func(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config) (flagger.Provider, error) {
return New(ctx, providerSettings, config, registry)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config, registry featuretypes.Registry) (flagger.Provider, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/flagger/memoryprovider")
featureValues := make(map[featuretypes.Name]featuretypes.FeatureValue)
for _, flag := range config.Memory.Boolean.Enabled {
name, err := featuretypes.NewName(flag)
if err != nil {
settings.Logger().Error("invalid flag name encountered", "flag", flag, "error", err)
continue
}
featureValues[name] = featuretypes.FeatureValue{
Name: name,
Variant: featuretypes.KindBooleanVariantEnabled,
}
}
for _, flag := range config.Memory.Boolean.Disabled {
name, err := featuretypes.NewName(flag)
if err != nil {
settings.Logger().Error("invalid flag name encountered", "flag", flag, "error", err)
continue
}
if _, ok := featureValues[name]; ok {
settings.Logger().Error("flag already exists and has been enabled", "flag", flag)
continue
}
featureValues[name] = featuretypes.FeatureValue{
Name: name,
Variant: featuretypes.KindBooleanVariantDisabled,
}
}
return &provider{
config: config,
settings: settings,
featureValues: featureValues,
registry: registry,
}, nil
}
func (provider *provider) Metadata() openfeature.Metadata {
return openfeature.Metadata{
Name: "memory",
}
}
func (provider *provider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
feature, detail, err := provider.registry.Get(flag)
if err != nil {
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
if featureValue, ok := provider.featureValues[feature.Name]; ok {
value, detail, err := featuretypes.GetVariantValue[bool](feature, featureValue.Variant)
if err != nil {
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
return openfeature.BoolResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
return openfeature.BoolResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: feature.DefaultVariant,
},
}
}
func (provider *provider) StringEvaluation(ctx context.Context, flag string, defaultValue string, evalCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail {
feature, detail, err := provider.registry.Get(flag)
if err != nil {
return openfeature.StringResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
if featureValue, ok := provider.featureValues[feature.Name]; ok {
value, detail, err := featuretypes.GetVariantValue[string](feature, featureValue.Variant)
if err != nil {
return openfeature.StringResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
return openfeature.StringResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
return openfeature.StringResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: feature.DefaultVariant,
},
}
}
func (provider *provider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail {
feature, detail, err := provider.registry.Get(flag)
if err != nil {
return openfeature.FloatResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
if featureValue, ok := provider.featureValues[feature.Name]; ok {
value, detail, err := featuretypes.GetVariantValue[float64](feature, featureValue.Variant)
if err != nil {
return openfeature.FloatResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
return openfeature.FloatResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
return openfeature.FloatResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: feature.DefaultVariant,
},
}
}
func (provider *provider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, evalCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail {
feature, detail, err := provider.registry.Get(flag)
if err != nil {
return openfeature.IntResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
if featureValue, ok := provider.featureValues[feature.Name]; ok {
value, detail, err := featuretypes.GetVariantValue[int64](feature, featureValue.Variant)
if err != nil {
return openfeature.IntResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
return openfeature.IntResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
return openfeature.IntResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: feature.DefaultVariant,
},
}
}
func (provider *provider) ObjectEvaluation(ctx context.Context, flag string, defaultValue interface{}, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail {
feature, detail, err := provider.registry.Get(flag)
if err != nil {
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
if featureValue, ok := provider.featureValues[feature.Name]; ok {
value, detail, err := featuretypes.GetVariantValue[interface{}](feature, featureValue.Variant)
if err != nil {
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: detail,
}
}
return openfeature.InterfaceResolutionDetail{
Value: value,
ProviderResolutionDetail: detail,
}
}
return openfeature.InterfaceResolutionDetail{
Value: defaultValue,
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
Reason: openfeature.StaticReason,
Variant: feature.DefaultVariant,
},
}
}
func (provider *provider) Hooks() []openfeature.Hook {
return []openfeature.Hook{}
}

View File

@@ -1,34 +0,0 @@
package flagger
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
var (
FeatureUseTracesNewSchema = featuretypes.MustNewName("UseTracesNewSchema")
FeatureUseLogsNewSchema = featuretypes.MustNewName("UseLogsNewSchema")
)
func MustNewRegistry() featuretypes.Registry {
registry, err := featuretypes.NewRegistry(
&featuretypes.Feature{
Name: FeatureUseTracesNewSchema,
Kind: featuretypes.KindBoolean,
Description: "Use new traces schema.",
Stage: featuretypes.StageStable,
DefaultVariant: featuretypes.KindBooleanVariantDisabled,
Variants: featuretypes.NewBooleanFeatureVariants(),
},
&featuretypes.Feature{
Name: FeatureUseLogsNewSchema,
Kind: featuretypes.KindBoolean,
Description: "Use new logs schema.",
Stage: featuretypes.StageStable,
DefaultVariant: featuretypes.KindBooleanVariantDisabled,
Variants: featuretypes.NewBooleanFeatureVariants(),
},
)
if err != nil {
panic(err)
}
return registry
}

View File

@@ -17,7 +17,7 @@ type Client struct {
netc *http.Client
}
func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider metric.MeterProvider, opts ...Option) *Client {
func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider metric.MeterProvider, opts ...Option) (*Client, error) {
clientOpts := options{
retryCount: 3,
requestResponseLog: false,
@@ -46,7 +46,7 @@ func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider
return &Client{
netc: netc,
c: c,
}
}, nil
}
func (c *Client) Do(request *http.Request) (*http.Response, error) {

View File

@@ -1,16 +0,0 @@
package licensing
// type API interface {
// GetLicenses(context.Context, valuer.UUID, licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error)
// }
// func Get(){
// //
// return 501
// }
// func Put() {
// //400 bad request
// //501
// }

View File

@@ -1,17 +0,0 @@
package licensing
import "time"
type Config struct {
Provider string `mapstructure:"provider"`
PollingConfig PollingConfig `mapstructure:"polling"`
}
type PollingConfig struct {
Interval time.Duration `mapstructure:"interval"`
}
func (c Config) Validate() error {
return nil
}

View File

@@ -1,27 +0,0 @@
package licensing
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
var (
ErrCodeLicensingServerNotFound = errors.MustNewCode("licensing_server_not_found")
)
type Licensing interface {
factory.Service
// GetLicenses gets the licenses for the organization.
GetLicenses(context.Context, valuer.UUID, licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error)
// GetLatestLicense gets the latest license for the organization.
GetLatestLicense(context.Context, valuer.UUID) (licensetypes.License, error)
// SetLicense sets the license for the organization.
SetLicense(context.Context, valuer.UUID, string) error
}

View File

@@ -1,50 +0,0 @@
package nooplicensing
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type provider struct {
stopC chan struct{}
license licensetypes.License
}
func NewFactory() factory.ProviderFactory[licensing.Licensing, licensing.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
return New(ctx, providerSettings, config)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
return &provider{
stopC: make(chan struct{}),
license: licensetypes.NewNoop(),
}, nil
}
func (provider *provider) Start(ctx context.Context) error {
<-provider.stopC
return nil
}
func (provider *provider) GetLicenses(ctx context.Context, orgID valuer.UUID, params licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error) {
return licensetypes.GettableLicenses{provider.license}, nil
}
func (provider *provider) GetLatestLicense(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
return provider.license, nil
}
func (provider *provider) SetLicense(ctx context.Context, orgID valuer.UUID, key string) error {
return nil
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
return nil
}

View File

@@ -1,149 +0,0 @@
package preference
import (
"encoding/json"
"net/http"
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/gorilla/mux"
)
type API interface {
GetOrgPreference(http.ResponseWriter, *http.Request)
UpdateOrgPreference(http.ResponseWriter, *http.Request)
GetAllOrgPreferences(http.ResponseWriter, *http.Request)
GetUserPreference(http.ResponseWriter, *http.Request)
UpdateUserPreference(http.ResponseWriter, *http.Request)
GetAllUserPreferences(http.ResponseWriter, *http.Request)
}
type preferenceAPI struct {
usecase Usecase
}
func NewAPI(usecase Usecase) API {
return &preferenceAPI{usecase: usecase}
}
func (p *preferenceAPI) GetOrgPreference(rw http.ResponseWriter, r *http.Request) {
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, err := p.usecase.GetOrgPreference(
r.Context(), preferenceId, claims.OrgID,
)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, preference)
}
func (p *preferenceAPI) UpdateOrgPreference(rw http.ResponseWriter, r *http.Request) {
preferenceId := mux.Vars(r)["preferenceId"]
req := preferencetypes.UpdatablePreference{}
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
render.Error(rw, err)
return
}
err = p.usecase.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, claims.OrgID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (p *preferenceAPI) GetAllOrgPreferences(rw http.ResponseWriter, r *http.Request) {
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preferences, err := p.usecase.GetAllOrgPreferences(
r.Context(), claims.OrgID,
)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, preferences)
}
func (p *preferenceAPI) GetUserPreference(rw http.ResponseWriter, r *http.Request) {
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, err := p.usecase.GetUserPreference(
r.Context(), preferenceId, claims.OrgID, claims.UserID,
)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, preference)
}
func (p *preferenceAPI) UpdateUserPreference(rw http.ResponseWriter, r *http.Request) {
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
req := preferencetypes.UpdatablePreference{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
render.Error(rw, err)
return
}
err = p.usecase.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, claims.UserID)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusNoContent, nil)
}
func (p *preferenceAPI) GetAllUserPreferences(rw http.ResponseWriter, r *http.Request) {
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(rw, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preferences, err := p.usecase.GetAllUserPreferences(
r.Context(), claims.OrgID, claims.UserID,
)
if err != nil {
render.Error(rw, err)
return
}
render.Success(rw, http.StatusOK, preferences)
}

View File

@@ -1,278 +0,0 @@
package core
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type usecase struct {
store preferencetypes.PreferenceStore
defaultMap map[string]preferencetypes.Preference
}
func NewPreference(store preferencetypes.PreferenceStore, defaultMap map[string]preferencetypes.Preference) preference.Usecase {
return &usecase{store: store, defaultMap: defaultMap}
}
func (usecase *usecase) GetOrgPreference(ctx context.Context, preferenceID string, orgID string) (*preferencetypes.GettablePreference, error) {
preference, seen := usecase.defaultMap[preferenceID]
if !seen {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("no such preferenceID exists: %s", preferenceID))
}
isPreferenceEnabled := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope)
if !isPreferenceEnabled {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("preference is not enabled at org scope: %s", preferenceID))
}
orgPreference, err := usecase.store.GetOrgPreference(ctx, orgID, preferenceID)
if err != nil {
if err == sql.ErrNoRows {
return &preferencetypes.GettablePreference{
PreferenceID: preferenceID,
PreferenceValue: preference.DefaultValue,
}, nil
}
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, fmt.Sprintf("error in fetching the org preference: %s", preferenceID))
}
return &preferencetypes.GettablePreference{
PreferenceID: preferenceID,
PreferenceValue: preference.SanitizeValue(orgPreference.PreferenceValue),
}, nil
}
func (usecase *usecase) UpdateOrgPreference(ctx context.Context, preferenceID string, preferenceValue interface{}, orgID string) error {
preference, seen := usecase.defaultMap[preferenceID]
if !seen {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("no such preferenceID exists: %s", preferenceID))
}
isPreferenceEnabled := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope)
if !isPreferenceEnabled {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("preference is not enabled at org scope: %s", preferenceID))
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return err
}
storablePreferenceValue, encodeErr := json.Marshal(preferenceValue)
if encodeErr != nil {
return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value")
}
orgPreference, dberr := usecase.store.GetOrgPreference(ctx, orgID, preferenceID)
if dberr != nil && dberr != sql.ErrNoRows {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value")
}
if dberr != nil {
orgPreference.ID = valuer.GenerateUUID()
orgPreference.PreferenceID = preferenceID
orgPreference.PreferenceValue = string(storablePreferenceValue)
orgPreference.OrgID = orgID
} else {
orgPreference.PreferenceValue = string(storablePreferenceValue)
}
dberr = usecase.store.UpsertOrgPreference(ctx, orgPreference)
if dberr != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value")
}
return nil
}
func (usecase *usecase) GetAllOrgPreferences(ctx context.Context, orgID string) ([]*preferencetypes.PreferenceWithValue, error) {
allOrgPreferences := []*preferencetypes.PreferenceWithValue{}
orgPreferences, err := usecase.store.GetAllOrgPreferences(ctx, orgID)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all org preference values")
}
preferenceValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferences {
preferenceValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue
}
for _, preference := range usecase.defaultMap {
isEnabledForOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope)
if isEnabledForOrgScope {
preferenceWithValue := &preferencetypes.PreferenceWithValue{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
value, seen := preferenceValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
} else {
preferenceWithValue.Value = preference.DefaultValue
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allOrgPreferences = append(allOrgPreferences, preferenceWithValue)
}
}
return allOrgPreferences, nil
}
func (usecase *usecase) GetUserPreference(ctx context.Context, preferenceID string, orgID string, userID string) (*preferencetypes.GettablePreference, error) {
preference, seen := usecase.defaultMap[preferenceID]
if !seen {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("no such preferenceID exists: %s", preferenceID))
}
preferenceValue := preferencetypes.GettablePreference{
PreferenceID: preferenceID,
PreferenceValue: preference.DefaultValue,
}
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("preference is not enabled at user scope: %s", preferenceID))
}
isPreferenceEnabledAtOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope)
if isPreferenceEnabledAtOrgScope {
orgPreference, err := usecase.store.GetOrgPreference(ctx, orgID, preferenceID)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, fmt.Sprintf("error in fetching the org preference: %s", preferenceID))
}
if err == nil {
preferenceValue.PreferenceValue = orgPreference.PreferenceValue
}
}
userPreference, err := usecase.store.GetUserPreference(ctx, userID, preferenceID)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, fmt.Sprintf("error in fetching the user preference: %s", preferenceID))
}
if err == nil {
preferenceValue.PreferenceValue = userPreference.PreferenceValue
}
return &preferencetypes.GettablePreference{
PreferenceID: preferenceValue.PreferenceID,
PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue),
}, nil
}
func (usecase *usecase) UpdateUserPreference(ctx context.Context, preferenceID string, preferenceValue interface{}, userID string) error {
preference, seen := usecase.defaultMap[preferenceID]
if !seen {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("no such preferenceID exists: %s", preferenceID))
}
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, fmt.Sprintf("preference is not enabled at user scope: %s", preferenceID))
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return err
}
storablePreferenceValue, encodeErr := json.Marshal(preferenceValue)
if encodeErr != nil {
return errors.Wrapf(encodeErr, errors.TypeInvalidInput, errors.CodeInvalidInput, "error in encoding the preference value")
}
userPreference, dberr := usecase.store.GetUserPreference(ctx, userID, preferenceID)
if dberr != nil && dberr != sql.ErrNoRows {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in getting the preference value")
}
if dberr != nil {
userPreference.ID = valuer.GenerateUUID()
userPreference.PreferenceID = preferenceID
userPreference.PreferenceValue = string(storablePreferenceValue)
userPreference.UserID = userID
} else {
userPreference.PreferenceValue = string(storablePreferenceValue)
}
dberr = usecase.store.UpsertUserPreference(ctx, userPreference)
if dberr != nil {
return errors.Wrapf(dberr, errors.TypeInternal, errors.CodeInternal, "error in setting the preference value")
}
return nil
}
func (usecase *usecase) GetAllUserPreferences(ctx context.Context, orgID string, userID string) ([]*preferencetypes.PreferenceWithValue, error) {
allUserPreferences := []*preferencetypes.PreferenceWithValue{}
orgPreferences, err := usecase.store.GetAllOrgPreferences(ctx, orgID)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all org preference values")
}
preferenceOrgValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferences {
preferenceOrgValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue
}
userPreferences, err := usecase.store.GetAllUserPreferences(ctx, userID)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error in setting all user preference values")
}
preferenceUserValueMap := map[string]interface{}{}
for _, preferenceValue := range userPreferences {
preferenceUserValueMap[preferenceValue.PreferenceID] = preferenceValue.PreferenceValue
}
for _, preference := range usecase.defaultMap {
isEnabledForUserScope := preference.IsEnabledForScope(preferencetypes.UserAllowedScope)
if isEnabledForUserScope {
preferenceWithValue := &preferencetypes.PreferenceWithValue{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
preferenceWithValue.Value = preference.DefaultValue
isEnabledForOrgScope := preference.IsEnabledForScope(preferencetypes.OrgAllowedScope)
if isEnabledForOrgScope {
value, seen := preferenceOrgValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
}
value, seen := preferenceUserValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allUserPreferences = append(allUserPreferences, preferenceWithValue)
}
}
return allUserPreferences, nil
}

View File

@@ -1,116 +0,0 @@
package core
import (
"context"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
)
type store struct {
store sqlstore.SQLStore
}
func NewStore(db sqlstore.SQLStore) preferencetypes.PreferenceStore {
return &store{store: db}
}
func (store *store) GetOrgPreference(ctx context.Context, orgID string, preferenceID string) (*preferencetypes.StorableOrgPreference, error) {
orgPreference := new(preferencetypes.StorableOrgPreference)
err := store.
store.
BunDB().
NewSelect().
Model(orgPreference).
Where("preference_id = ?", preferenceID).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return orgPreference, err
}
return orgPreference, nil
}
func (store *store) GetAllOrgPreferences(ctx context.Context, orgID string) ([]*preferencetypes.StorableOrgPreference, error) {
orgPreferences := make([]*preferencetypes.StorableOrgPreference, 0)
err := store.
store.
BunDB().
NewSelect().
Model(&orgPreferences).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return orgPreferences, err
}
return orgPreferences, nil
}
func (store *store) UpsertOrgPreference(ctx context.Context, orgPreference *preferencetypes.StorableOrgPreference) error {
_, err := store.
store.
BunDB().
NewInsert().
Model(orgPreference).
On("CONFLICT (id) DO UPDATE").
Exec(ctx)
if err != nil {
return err
}
return nil
}
func (store *store) GetUserPreference(ctx context.Context, userID string, preferenceID string) (*preferencetypes.StorableUserPreference, error) {
userPreference := new(preferencetypes.StorableUserPreference)
err := store.
store.
BunDB().
NewSelect().
Model(userPreference).
Where("preference_id = ?", preferenceID).
Where("user_id = ?", userID).
Scan(ctx)
if err != nil {
return userPreference, err
}
return userPreference, nil
}
func (store *store) GetAllUserPreferences(ctx context.Context, userID string) ([]*preferencetypes.StorableUserPreference, error) {
userPreferences := make([]*preferencetypes.StorableUserPreference, 0)
err := store.
store.
BunDB().
NewSelect().
Model(&userPreferences).
Where("user_id = ?", userID).
Scan(ctx)
if err != nil {
return userPreferences, err
}
return userPreferences, nil
}
func (store *store) UpsertUserPreference(ctx context.Context, userPreference *preferencetypes.StorableUserPreference) error {
_, err := store.
store.
BunDB().
NewInsert().
Model(userPreference).
On("CONFLICT (id) DO UPDATE").
Exec(ctx)
if err != nil {
return err
}
return nil
}

View File

@@ -1,17 +0,0 @@
package preference
import (
"context"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
)
type Usecase interface {
GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*preferencetypes.GettablePreference, error)
UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) error
GetAllOrgPreferences(ctx context.Context, orgId string) ([]*preferencetypes.PreferenceWithValue, error)
GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*preferencetypes.GettablePreference, error)
UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) error
GetAllUserPreferences(ctx context.Context, orgId string, userId string) ([]*preferencetypes.PreferenceWithValue, error)
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
@@ -40,7 +41,7 @@ func InitDB(sqlStore sqlstore.SQLStore) error {
}
// CreateDashboard creates a new dashboard
func CreateDashboard(ctx context.Context, orgID string, email string, data map[string]interface{}) (*types.Dashboard, *model.ApiError) {
func CreateDashboard(ctx context.Context, orgID string, email string, data map[string]interface{}, fm interfaces.FeatureLookup) (*types.Dashboard, *model.ApiError) {
dash := &types.Dashboard{
Data: data,
}
@@ -76,7 +77,7 @@ func GetDashboards(ctx context.Context, orgID string) ([]types.Dashboard, *model
return dashboards, nil
}
func DeleteDashboard(ctx context.Context, orgID, uuid string) *model.ApiError {
func DeleteDashboard(ctx context.Context, orgID, uuid string, fm interfaces.FeatureLookup) *model.ApiError {
dashboard, dErr := GetDashboard(ctx, orgID, uuid)
if dErr != nil {
@@ -115,7 +116,7 @@ func GetDashboard(ctx context.Context, orgID, uuid string) (*types.Dashboard, *m
return &dashboard, nil
}
func UpdateDashboard(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}) (*types.Dashboard, *model.ApiError) {
func UpdateDashboard(ctx context.Context, orgID, userEmail, uuid string, data map[string]interface{}, fm interfaces.FeatureLookup) (*types.Dashboard, *model.ApiError) {
mapData, err := json.Marshal(data)
if err != nil {

View File

@@ -21,7 +21,6 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager"
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/preference"
"github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -45,6 +44,7 @@ import (
logsv4 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v4"
"github.com/SigNoz/signoz/pkg/query-service/app/metrics"
metricsv3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
"github.com/SigNoz/signoz/pkg/query-service/app/preferences"
"github.com/SigNoz/signoz/pkg/query-service/app/querier"
querierV2 "github.com/SigNoz/signoz/pkg/query-service/app/querier/v2"
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
@@ -142,8 +142,6 @@ type APIHandler struct {
AlertmanagerAPI *alertmanager.API
Signoz *signoz.SigNoz
Preference preference.API
}
type APIHandlerOpts struct {
@@ -189,8 +187,6 @@ type APIHandlerOpts struct {
AlertmanagerAPI *alertmanager.API
Signoz *signoz.SigNoz
Preference preference.API
}
// NewAPIHandler returns an APIHandler
@@ -200,6 +196,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
Cache: opts.Cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: opts.FluxInterval,
FeatureLookup: opts.FeatureFlags,
UseLogsNewSchema: opts.UseLogsNewSchema,
UseTraceNewSchema: opts.UseTraceNewSchema,
}
@@ -209,6 +206,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
Cache: opts.Cache,
KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: opts.FluxInterval,
FeatureLookup: opts.FeatureFlags,
UseLogsNewSchema: opts.UseLogsNewSchema,
UseTraceNewSchema: opts.UseTraceNewSchema,
}
@@ -259,7 +257,6 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
SummaryService: summaryService,
AlertmanagerAPI: opts.AlertmanagerAPI,
Signoz: opts.Signoz,
Preference: opts.Preference,
}
logsQueryBuilder := logsv3.PrepareLogsQuery
@@ -277,7 +274,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
BuildTraceQuery: tracesQueryBuilder,
BuildLogQuery: logsQueryBuilder,
}
aH.queryBuilder = queryBuilder.NewQueryBuilder(builderOpts)
aH.queryBuilder = queryBuilder.NewQueryBuilder(builderOpts, aH.featureFlags)
// check if at least one user is created
hasUsers, err := aH.appDao.GetUsersWithOpts(context.Background(), 1)
@@ -549,6 +546,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
router.HandleFunc("/api/v1/services/list", am.ViewAccess(aH.getServicesList)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/service/top_operations", am.ViewAccess(aH.getTopOperations)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/service/top_level_operations", am.ViewAccess(aH.getServicesTopLevelOps)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(aH.SearchTraces)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/usage", am.ViewAccess(aH.getUsage)).Methods(http.MethodGet)
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)
@@ -1145,7 +1143,7 @@ func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
err := dashboards.DeleteDashboard(r.Context(), claims.OrgID, uuid)
err := dashboards.DeleteDashboard(r.Context(), claims.OrgID, uuid, aH.featureFlags)
if err != nil {
RespondError(w, err, nil)
@@ -1237,7 +1235,7 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
dashboard, apiError := dashboards.UpdateDashboard(r.Context(), claims.OrgID, claims.Email, uuid, postData)
dashboard, apiError := dashboards.UpdateDashboard(r.Context(), claims.OrgID, claims.Email, uuid, postData, aH.featureFlags)
if apiError != nil {
RespondError(w, apiError, nil)
return
@@ -1310,7 +1308,7 @@ func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
dash, apiErr := dashboards.CreateDashboard(r.Context(), claims.OrgID, claims.Email, postData)
dash, apiErr := dashboards.CreateDashboard(r.Context(), claims.OrgID, claims.Email, postData, aH.featureFlags)
if apiErr != nil {
RespondError(w, apiErr, nil)
@@ -1724,6 +1722,23 @@ func (aH *APIHandler) getServicesList(w http.ResponseWriter, r *http.Request) {
}
func (aH *APIHandler) SearchTraces(w http.ResponseWriter, r *http.Request) {
params, err := ParseSearchTracesParams(r)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
return
}
result, err := aH.reader.SearchTraces(r.Context(), params, nil)
if aH.HandleError(w, err, http.StatusBadRequest) {
return
}
aH.WriteJSON(w, r, result)
}
func (aH *APIHandler) GetWaterfallSpansForTraceWithMetadata(w http.ResponseWriter, r *http.Request) {
traceID := mux.Vars(r)["traceId"]
if traceID == "" {
@@ -1850,15 +1865,8 @@ func (aH *APIHandler) setTTL(w http.ResponseWriter, r *http.Request) {
return
}
ctx := r.Context()
claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
RespondError(w, &model.ApiError{Err: errors.New("failed to get org id from context"), Typ: model.ErrorInternal}, nil)
return
}
// Context is not used here as TTL is long duration DB operation
result, apiErr := aH.reader.SetTTL(context.Background(), claims.OrgID, ttlParams)
result, apiErr := aH.reader.SetTTL(context.Background(), ttlParams)
if apiErr != nil {
if apiErr.Typ == model.ErrorConflict {
aH.HandleError(w, apiErr.Err, http.StatusConflict)
@@ -1878,14 +1886,7 @@ func (aH *APIHandler) getTTL(w http.ResponseWriter, r *http.Request) {
return
}
ctx := r.Context()
claims, ok := authtypes.ClaimsFromContext(ctx)
if !ok {
RespondError(w, &model.ApiError{Err: errors.New("failed to get org id from context"), Typ: model.ErrorInternal}, nil)
return
}
result, apiErr := aH.reader.GetTTL(r.Context(), claims.OrgID, ttlParams)
result, apiErr := aH.reader.GetTTL(r.Context(), ttlParams)
if apiErr != nil && aH.HandleError(w, apiErr.Err, http.StatusInternalServerError) {
return
}
@@ -3414,37 +3415,132 @@ func (aH *APIHandler) getProducerConsumerEval(
func (aH *APIHandler) getUserPreference(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.GetUserPreference(w, r)
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, apiErr := preferences.GetUserPreference(
r.Context(), preferenceId, claims.OrgID, claims.UserID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
func (aH *APIHandler) updateUserPreference(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.UpdateUserPreference(w, r)
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
req := preferences.UpdatePreference{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
return
}
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, claims.UserID)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
func (aH *APIHandler) getAllUserPreferences(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.GetAllUserPreferences(w, r)
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, apiErr := preferences.GetAllUserPreferences(
r.Context(), claims.OrgID, claims.UserID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
func (aH *APIHandler) getOrgPreference(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.GetOrgPreference(w, r)
preferenceId := mux.Vars(r)["preferenceId"]
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, apiErr := preferences.GetOrgPreference(
r.Context(), preferenceId, claims.OrgID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
func (aH *APIHandler) updateOrgPreference(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.UpdateOrgPreference(w, r)
preferenceId := mux.Vars(r)["preferenceId"]
req := preferences.UpdatePreference{}
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
return
}
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, claims.OrgID)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
func (aH *APIHandler) getAllOrgPreferences(
w http.ResponseWriter, r *http.Request,
) {
aH.Preference.GetAllOrgPreferences(w, r)
claims, ok := authtypes.ClaimsFromContext(r.Context())
if !ok {
render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated"))
return
}
preference, apiErr := preferences.GetAllOrgPreferences(
r.Context(), claims.OrgID,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
aH.Respond(w, preference)
}
// RegisterIntegrationRoutes Registers all Integrations

View File

@@ -246,7 +246,7 @@ func buildLogsTimeSeriesFilterQuery(fs *v3.FilterSet, groupBy []v3.AttributeKey,
return queryString, nil
}
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string) (string, error) {
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string, preferRPM bool) (string, error) {
filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy, mq.AggregateAttribute)
if err != nil {
@@ -315,6 +315,9 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
switch mq.AggregateOperator {
case v3.AggregateOperatorRate:
rate := float64(step)
if preferRPM {
rate = rate / 60.0
}
op := fmt.Sprintf("count(%s)/%f", aggregationKey, rate)
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
@@ -325,6 +328,9 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
v3.AggregateOperatorRateAvg,
v3.AggregateOperatorRateMin:
rate := float64(step)
if preferRPM {
rate = rate / 60.0
}
op := fmt.Sprintf("%s(%s)/%f", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate)
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
@@ -507,7 +513,7 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
return query, nil
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
// give me just the groupby names
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}
@@ -515,14 +521,14 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
return query, nil
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}
return query, nil
}
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}

View File

@@ -352,6 +352,7 @@ var testBuildLogsQueryData = []struct {
AggregateOperator v3.AggregateOperator
ExpectedQuery string
Type int
PreferRPM bool
}{
{
Name: "Test aggregate count on select field",
@@ -697,8 +698,9 @@ var testBuildLogsQueryData = []struct {
OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}},
},
TableName: "logs",
PreferRPM: true,
ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`" +
", sum(`attribute_float64_bytes`)/60.000000 as value from signoz_logs.distributed_logs " +
", sum(`attribute_float64_bytes`)/1.000000 as value from signoz_logs.distributed_logs " +
"where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) " +
"AND has(attributes_string_key, 'method') " +
"AND `attribute_float64_bytes_exists`=true " +
@@ -720,6 +722,7 @@ var testBuildLogsQueryData = []struct {
OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}},
},
TableName: "logs",
PreferRPM: false,
ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`" +
", count(attributes_float64_value[indexOf(attributes_float64_key, 'bytes')])/60.000000 as value " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) " +
@@ -744,9 +747,10 @@ var testBuildLogsQueryData = []struct {
OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}},
},
TableName: "logs",
PreferRPM: true,
ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 60 SECOND) AS ts, " +
"attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`, " +
"sum(attributes_float64_value[indexOf(attributes_float64_key, 'bytes')])/60.000000 as value " +
"sum(attributes_float64_value[indexOf(attributes_float64_key, 'bytes')])/1.000000 as value " +
"from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) " +
"AND has(attributes_string_key, 'method') " +
"AND has(attributes_float64_key, 'bytes') " +
@@ -1057,7 +1061,7 @@ var testBuildLogsQueryData = []struct {
func TestBuildLogsQuery(t *testing.T) {
for _, tt := range testBuildLogsQueryData {
Convey("TestBuildLogsQuery", t, func() {
query, err := buildLogsQuery(tt.PanelType, tt.Start, tt.End, tt.BuilderQuery.StepInterval, tt.BuilderQuery, "")
query, err := buildLogsQuery(tt.PanelType, tt.Start, tt.End, tt.BuilderQuery.StepInterval, tt.BuilderQuery, "", tt.PreferRPM)
So(err, ShouldBeNil)
So(query, ShouldEqual, tt.ExpectedQuery)
@@ -1234,7 +1238,7 @@ var testPrepLogsQueryData = []struct {
},
TableName: "logs",
ExpectedQuery: "SELECT `method` from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND has(attributes_string_key, 'method') AND has(attributes_string_key, 'name') group by `method` order by value DESC) LIMIT 10",
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: true},
},
{
Name: "Test TS with limit- first - with order by value",
@@ -1257,7 +1261,7 @@ var testPrepLogsQueryData = []struct {
},
TableName: "logs",
ExpectedQuery: "SELECT `method` from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND has(attributes_string_key, 'method') AND has(attributes_string_key, 'name') group by `method` order by value ASC) LIMIT 10",
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: true},
},
{
Name: "Test TS with limit- first - with order by attribute",
@@ -1280,7 +1284,7 @@ var testPrepLogsQueryData = []struct {
},
TableName: "logs",
ExpectedQuery: "SELECT `method` from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as `method`, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND has(attributes_string_key, 'method') AND has(attributes_string_key, 'name') group by `method` order by `method` ASC) LIMIT 10",
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
Options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: true},
},
{
Name: "Test TS with limit- second",

View File

@@ -285,6 +285,7 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []
func generateAggregateClause(aggOp v3.AggregateOperator,
aggKey string,
step int64,
preferRPM bool,
timeFilter string,
whereClause string,
groupBy string,
@@ -298,6 +299,9 @@ func generateAggregateClause(aggOp v3.AggregateOperator,
switch aggOp {
case v3.AggregateOperatorRate:
rate := float64(step)
if preferRPM {
rate = rate / 60.0
}
op := fmt.Sprintf("count(%s)/%f", aggKey, rate)
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
@@ -308,6 +312,9 @@ func generateAggregateClause(aggOp v3.AggregateOperator,
v3.AggregateOperatorRateAvg,
v3.AggregateOperatorRateMin:
rate := float64(step)
if preferRPM {
rate = rate / 60.0
}
op := fmt.Sprintf("%s(%s)/%f", logsV3.AggregateOperatorToSQLFunc[aggOp], aggKey, rate)
query := fmt.Sprintf(queryTmpl, op, whereClause, groupBy, having, orderBy)
@@ -342,7 +349,7 @@ func generateAggregateClause(aggOp v3.AggregateOperator,
}
}
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string) (string, error) {
func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.BuilderQuery, graphLimitQtype string, preferRPM bool) (string, error) {
// timerange will be sent in epoch millisecond
logsStart := utils.GetEpochNanoSecs(start)
logsEnd := utils.GetEpochNanoSecs(end)
@@ -418,7 +425,7 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", logsV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "#LIMIT_PLACEHOLDER)"
}
aggClause, err := generateAggregateClause(mq.AggregateOperator, aggregationKey, step, timeFilter, filterSubQuery, groupBy, having, orderBy)
aggClause, err := generateAggregateClause(mq.AggregateOperator, aggregationKey, step, preferRPM, timeFilter, filterSubQuery, groupBy, having, orderBy)
if err != nil {
return "", err
}
@@ -498,7 +505,7 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
return query, nil
} else if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
// give me just the group_by names (no values)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}
@@ -506,14 +513,14 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan
return query, nil
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}
return query, nil
}
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype)
query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype, options.PreferRPM)
if err != nil {
return "", err
}

View File

@@ -574,6 +574,7 @@ func Test_generateAggregateClause(t *testing.T) {
op v3.AggregateOperator
aggKey string
step int64
preferRPM bool
timeFilter string
whereClause string
groupBy string
@@ -592,6 +593,7 @@ func Test_generateAggregateClause(t *testing.T) {
op: v3.AggregateOperatorRate,
aggKey: "test",
step: 60,
preferRPM: false,
timeFilter: "(timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458)",
whereClause: " AND attributes_string['service.name'] = 'test'",
groupBy: " group by `user_name`",
@@ -608,6 +610,7 @@ func Test_generateAggregateClause(t *testing.T) {
op: v3.AggregateOperatorRate,
aggKey: "test",
step: 60,
preferRPM: false,
timeFilter: "(timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458)",
whereClause: " AND attributes_string['service.name'] = 'test'",
groupBy: " group by `user_name`",
@@ -621,7 +624,7 @@ func Test_generateAggregateClause(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := generateAggregateClause(tt.args.op, tt.args.aggKey, tt.args.step, tt.args.timeFilter, tt.args.whereClause, tt.args.groupBy, tt.args.having, tt.args.orderBy)
got, err := generateAggregateClause(tt.args.op, tt.args.aggKey, tt.args.step, tt.args.preferRPM, tt.args.timeFilter, tt.args.whereClause, tt.args.groupBy, tt.args.having, tt.args.orderBy)
if (err != nil) != tt.wantErr {
t.Errorf("generateAggreagteClause() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -641,6 +644,7 @@ func Test_buildLogsQuery(t *testing.T) {
step int64
mq *v3.BuilderQuery
graphLimitQtype string
preferRPM bool
}
tests := []struct {
name string
@@ -785,7 +789,7 @@ func Test_buildLogsQuery(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype)
got, err := buildLogsQuery(tt.args.panelType, tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.graphLimitQtype, tt.args.preferRPM)
if (err != nil) != tt.wantErr {
t.Errorf("buildLogsQuery() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -873,7 +877,7 @@ func TestPrepareLogsQuery(t *testing.T) {
Limit: 10,
GroupBy: []v3.AttributeKey{{Key: "user", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
},
options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
options: v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: true},
},
want: "SELECT `user` from (SELECT attributes_string['user'] as `user`, toFloat64(count(distinct(attributes_string['name']))) as value from signoz_logs.distributed_logs_v2 " +
"where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['method'] = 'GET' " +

View File

@@ -14,7 +14,9 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/utils"
)
type Options struct{}
type Options struct {
PreferRPM bool
}
var aggregateOperatorToPercentile = map[v3.AggregateOperator]float64{
v3.AggregateOperatorP05: 0.05,
@@ -385,10 +387,29 @@ func PrepareMetricQuery(start, end int64, queryType v3.QueryType, panelType v3.P
query, err = buildMetricQuery(start, end, mq.StepInterval, mq)
}
}
if err != nil {
return "", err
}
if options.PreferRPM && (mq.AggregateOperator == v3.AggregateOperatorRate ||
mq.AggregateOperator == v3.AggregateOperatorSumRate ||
mq.AggregateOperator == v3.AggregateOperatorAvgRate ||
mq.AggregateOperator == v3.AggregateOperatorMaxRate ||
mq.AggregateOperator == v3.AggregateOperatorMinRate ||
mq.AggregateOperator == v3.AggregateOperatorRateSum ||
mq.AggregateOperator == v3.AggregateOperatorRateAvg ||
mq.AggregateOperator == v3.AggregateOperatorRateMax ||
mq.AggregateOperator == v3.AggregateOperatorRateMin) {
var selectLabels string
if mq.AggregateOperator == v3.AggregateOperatorRate {
selectLabels = "fullLabels,"
} else {
selectLabels = groupSelectAttributeKeyTags(mq.GroupBy...)
}
query = `SELECT ` + selectLabels + ` ts, ceil(value * 60) as value FROM (` + query + `)`
}
if having(mq.Having) != "" {
query = fmt.Sprintf("SELECT * FROM (%s) HAVING %s", query, having(mq.Having))
}

View File

@@ -27,7 +27,7 @@ func TestBuildQuery(t *testing.T) {
PanelType: v3.PanelTypeGraph,
},
}
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, "WHERE metric_name IN ['name']")
})
@@ -55,7 +55,7 @@ func TestBuildQueryWithFilters(t *testing.T) {
},
},
}
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, "WHERE metric_name IN ['name'] AND temporality = 'Cumulative' AND __normalized = true AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000 AND JSONExtractString(labels, 'a') != 'b'")
@@ -94,7 +94,7 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) {
},
}
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, "WHERE metric_name IN ['name'] AND temporality = 'Cumulative' AND __normalized = true AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000 AND JSONExtractString(labels, 'in') IN ['a','b','c']")
@@ -148,7 +148,60 @@ func TestBuildQueryXRate(t *testing.T) {
PanelType: v3.PanelTypeGraph,
},
}
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Equal(t, query, c.expectedQuery)
}
})
}
func TestBuildQueryRPM(t *testing.T) {
t.Run("TestBuildQueryXRate", func(t *testing.T) {
tmpl := `SELECT ts, ceil(value * 60) as value FROM (SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v4_1day WHERE metric_name IN ['name'] AND temporality = '' AND __normalized = true AND unix_milli >= 1650931200000 AND unix_milli < 1651078380000) as filtered_time_series USING fingerprint WHERE metric_name IN ['name'] AND unix_milli >= 1650991920000 AND unix_milli < 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts)`
cases := []struct {
aggregateOperator v3.AggregateOperator
expectedQuery string
}{
{
aggregateOperator: v3.AggregateOperatorAvgRate,
expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorAvgRate]),
},
{
aggregateOperator: v3.AggregateOperatorMaxRate,
expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorMaxRate]),
},
{
aggregateOperator: v3.AggregateOperatorMinRate,
expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorMinRate]),
},
{
aggregateOperator: v3.AggregateOperatorSumRate,
expectedQuery: fmt.Sprintf(tmpl, aggregateOperatorToSQLFunc[v3.AggregateOperatorSumRate]),
},
}
for _, c := range cases {
q := &v3.QueryRangeParamsV3{
Start: 1650991982000,
End: 1651078382000,
CompositeQuery: &v3.CompositeQuery{
BuilderQueries: map[string]*v3.BuilderQuery{
"A": {
QueryName: "A",
StepInterval: 60,
AggregateAttribute: v3.AttributeKey{Key: "name"},
AggregateOperator: c.aggregateOperator,
Expression: "A",
},
},
QueryType: v3.QueryTypeBuilder,
PanelType: v3.PanelTypeGraph,
},
}
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: true})
require.NoError(t, err)
require.Equal(t, query, c.expectedQuery)
}
@@ -320,7 +373,7 @@ func TestBuildQueryAdjustedTimes(t *testing.T) {
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
q := testCase.params
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, testCase.expected)
@@ -480,7 +533,7 @@ func TestBuildQueryWithDotInMetricAndAttributes(t *testing.T) {
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
q := testCase.params
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{})
query, err := PrepareMetricQuery(q.Start, q.End, q.CompositeQuery.QueryType, q.CompositeQuery.PanelType, q.CompositeQuery.BuilderQueries["A"], Options{PreferRPM: false})
require.NoError(t, err)
require.Contains(t, query, testCase.expected)

View File

@@ -0,0 +1,84 @@
package preferences
var preferenceMap = map[string]Preference{
"ORG_ONBOARDING": {
Key: "ORG_ONBOARDING",
Name: "Organisation Onboarding",
Description: "Organisation Onboarding",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"org"},
},
"WELCOME_CHECKLIST_DO_LATER": {
Key: "WELCOME_CHECKLIST_DO_LATER",
Name: "Welcome Checklist Do Later",
Description: "Welcome Checklist Do Later",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SEND_LOGS_SKIPPED": {
Key: "WELCOME_CHECKLIST_SEND_LOGS_SKIPPED",
Name: "Welcome Checklist Send Logs Skipped",
Description: "Welcome Checklist Send Logs Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SEND_TRACES_SKIPPED": {
Key: "WELCOME_CHECKLIST_SEND_TRACES_SKIPPED",
Name: "Welcome Checklist Send Traces Skipped",
Description: "Welcome Checklist Send Traces Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED": {
Key: "WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED",
Name: "Welcome Checklist Send Infra Metrics Skipped",
Description: "Welcome Checklist Send Infra Metrics Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED": {
Key: "WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED",
Name: "Welcome Checklist Setup Dashboards Skipped",
Description: "Welcome Checklist Setup Dashboards Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED": {
Key: "WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED",
Name: "Welcome Checklist Setup Alerts Skipped",
Description: "Welcome Checklist Setup Alerts Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
"WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED": {
Key: "WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED",
Name: "Welcome Checklist Setup Saved View Skipped",
Description: "Welcome Checklist Setup Saved View Skipped",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user"},
},
}

View File

@@ -0,0 +1,500 @@
package preferences
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/jmoiron/sqlx"
)
type Range struct {
Min int64 `json:"min"`
Max int64 `json:"max"`
}
type Preference struct {
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
ValueType string `json:"valueType"`
DefaultValue interface{} `json:"defaultValue"`
AllowedValues []interface{} `json:"allowedValues"`
IsDiscreteValues bool `json:"isDiscreteValues"`
Range Range `json:"range"`
AllowedScopes []string `json:"allowedScopes"`
}
func (p *Preference) ErrorValueTypeMismatch() *model.ApiError {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not of expected type: %s", p.ValueType)}
}
const (
PreferenceValueTypeInteger string = "integer"
PreferenceValueTypeFloat string = "float"
PreferenceValueTypeString string = "string"
PreferenceValueTypeBoolean string = "boolean"
)
const (
OrgAllowedScope string = "org"
UserAllowedScope string = "user"
)
func (p *Preference) checkIfInAllowedValues(preferenceValue interface{}) (bool, *model.ApiError) {
switch p.ValueType {
case PreferenceValueTypeInteger:
_, ok := preferenceValue.(int64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeFloat:
_, ok := preferenceValue.(float64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeString:
_, ok := preferenceValue.(string)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeBoolean:
_, ok := preferenceValue.(bool)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
}
isInAllowedValues := false
for _, value := range p.AllowedValues {
switch p.ValueType {
case PreferenceValueTypeInteger:
allowedValue, ok := value.(int64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeFloat:
allowedValue, ok := value.(float64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeString:
allowedValue, ok := value.(string)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeBoolean:
allowedValue, ok := value.(bool)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
}
}
return isInAllowedValues, nil
}
func (p *Preference) IsValidValue(preferenceValue interface{}) *model.ApiError {
typeSafeValue := preferenceValue
switch p.ValueType {
case PreferenceValueTypeInteger:
val, ok := preferenceValue.(int64)
if !ok {
floatVal, ok := preferenceValue.(float64)
if !ok || floatVal != float64(int64(floatVal)) {
return p.ErrorValueTypeMismatch()
}
val = int64(floatVal)
typeSafeValue = val
}
if !p.IsDiscreteValues {
if val < p.Range.Min || val > p.Range.Max {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the range specified, min: %v , max:%v", p.Range.Min, p.Range.Max)}
}
}
case PreferenceValueTypeString:
_, ok := preferenceValue.(string)
if !ok {
return p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeFloat:
_, ok := preferenceValue.(float64)
if !ok {
return p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeBoolean:
_, ok := preferenceValue.(bool)
if !ok {
return p.ErrorValueTypeMismatch()
}
}
// check the validity of the value being part of allowed values or the range specified if any
if p.IsDiscreteValues {
if p.AllowedValues != nil {
isInAllowedValues, valueMisMatchErr := p.checkIfInAllowedValues(typeSafeValue)
if valueMisMatchErr != nil {
return valueMisMatchErr
}
if !isInAllowedValues {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the list of allowedValues: %v", p.AllowedValues)}
}
}
}
return nil
}
func (p *Preference) IsEnabledForScope(scope string) bool {
isPreferenceEnabledForGivenScope := false
if p.AllowedScopes != nil {
for _, allowedScope := range p.AllowedScopes {
if allowedScope == strings.ToLower(scope) {
isPreferenceEnabledForGivenScope = true
}
}
}
return isPreferenceEnabledForGivenScope
}
func (p *Preference) SanitizeValue(preferenceValue interface{}) interface{} {
switch p.ValueType {
case PreferenceValueTypeBoolean:
if preferenceValue == "1" || preferenceValue == true {
return true
} else {
return false
}
default:
return preferenceValue
}
}
type AllPreferences struct {
Preference
Value interface{} `json:"value"`
}
type PreferenceKV struct {
PreferenceId string `json:"preference_id" db:"preference_id"`
PreferenceValue interface{} `json:"preference_value" db:"preference_value"`
}
type UpdatePreference struct {
PreferenceValue interface{} `json:"preference_value"`
}
var db *sqlx.DB
func InitDB(inputDB *sqlx.DB) error {
db = inputDB
return nil
}
// org preference functions
func GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists or not
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled for org scope or not
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
if !isPreferenceEnabled {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
}
// fetch the value from the database
var orgPreference PreferenceKV
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
err := db.Get(&orgPreference, query, preferenceId, orgId)
// if the value doesn't exist in db then return the default value
if err != nil {
if err == sql.ErrNoRows {
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.DefaultValue,
}, nil
}
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in fetching the org preference: %s", err.Error())}
}
// else return the value fetched from the org_preference table
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.SanitizeValue(orgPreference.PreferenceValue),
}, nil
}
func UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists or not
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled at org scope or not
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
if !isPreferenceEnabled {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return nil, err
}
// update the values in the org_preference table and return the key and the value
query := `INSERT INTO org_preference(preference_id,preference_value,org_id) VALUES($1,$2,$3)
ON CONFLICT(preference_id,org_id) DO
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND org_id=$3;`
_, dberr := db.Exec(query, preferenceId, preferenceValue, orgId)
if dberr != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberr.Error())}
}
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preferenceValue,
}, nil
}
func GetAllOrgPreferences(ctx context.Context, orgId string) (*[]AllPreferences, *model.ApiError) {
// filter out all the org enabled preferences from the preference variable
allOrgPreferences := []AllPreferences{}
// fetch all the org preference values stored in org_preference table
orgPreferenceValues := []PreferenceKV{}
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
err := db.Select(&orgPreferenceValues, query, orgId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferenceValues {
preferenceValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// update in the above filtered list wherver value present in the map
for _, preference := range preferenceMap {
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
if isEnabledForOrgScope {
preferenceWithValue := AllPreferences{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
value, seen := preferenceValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
} else {
preferenceWithValue.Value = preference.DefaultValue
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allOrgPreferences = append(allOrgPreferences, preferenceWithValue)
}
}
return &allOrgPreferences, nil
}
// user preference functions
func GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
preferenceValue := PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.DefaultValue,
}
// check if the preference is enabled at user scope
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
}
isPreferenceEnabledAtOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
// get the value from the org scope if enabled at org scope
if isPreferenceEnabledAtOrgScope {
orgPreference := PreferenceKV{}
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
err := db.Get(&orgPreference, query, preferenceId, orgId)
// if there is error in getting values and its not an empty rows error return from here
if err != nil && err != sql.ErrNoRows {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting org preference values: %s", err.Error())}
}
// if there is no error update the preference value with value from org preference
if err == nil {
preferenceValue.PreferenceValue = orgPreference.PreferenceValue
}
}
// get the value from the user_preference table, if exists return this value else the one calculated in the above step
userPreference := PreferenceKV{}
query := `SELECT preference_id, preference_value FROM user_preference WHERE preference_id=$1 AND user_id=$2;`
err := db.Get(&userPreference, query, preferenceId, userId)
if err != nil && err != sql.ErrNoRows {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting user preference values: %s", err.Error())}
}
if err == nil {
preferenceValue.PreferenceValue = userPreference.PreferenceValue
}
return &PreferenceKV{
PreferenceId: preferenceValue.PreferenceId,
PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue),
}, nil
}
func UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) (*PreferenceKV, *model.ApiError) {
// check if the preference id is valid
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled at user scope
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return nil, err
}
// update the user preference values
query := `INSERT INTO user_preference(preference_id,preference_value,user_id) VALUES($1,$2,$3)
ON CONFLICT(preference_id,user_id) DO
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND user_id=$3;`
_, dberrr := db.Exec(query, preferenceId, preferenceValue, userId)
if dberrr != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberrr.Error())}
}
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preferenceValue,
}, nil
}
func GetAllUserPreferences(ctx context.Context, orgId string, userId string) (*[]AllPreferences, *model.ApiError) {
allUserPreferences := []AllPreferences{}
// fetch all the org preference values stored in org_preference table
orgPreferenceValues := []PreferenceKV{}
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
err := db.Select(&orgPreferenceValues, query, orgId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceOrgValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferenceValues {
preferenceOrgValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// fetch all the user preference values stored in user_preference table
userPreferenceValues := []PreferenceKV{}
query = `SELECT preference_id,preference_value FROM user_preference WHERE user_id=$1;`
err = db.Select(&userPreferenceValues, query, userId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all user preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceUserValueMap := map[string]interface{}{}
for _, preferenceValue := range userPreferenceValues {
preferenceUserValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// update in the above filtered list wherver value present in the map
for _, preference := range preferenceMap {
isEnabledForUserScope := preference.IsEnabledForScope(UserAllowedScope)
if isEnabledForUserScope {
preferenceWithValue := AllPreferences{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
preferenceWithValue.Value = preference.DefaultValue
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
if isEnabledForOrgScope {
value, seen := preferenceOrgValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
}
value, seen := preferenceUserValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allUserPreferences = append(allUserPreferences, preferenceWithValue)
}
}
return &allUserPreferences, nil
}

View File

@@ -25,6 +25,7 @@ func prepareLogsQuery(_ context.Context,
end int64,
builderQuery *v3.BuilderQuery,
params *v3.QueryRangeParamsV3,
preferRPM bool,
) (string, error) {
query := ""
@@ -45,7 +46,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -56,7 +57,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -71,7 +72,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{},
v3.QBOptions{PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -90,6 +91,12 @@ func (q *querier) runBuilderQuery(
defer wg.Done()
queryName := builderQuery.QueryName
var preferRPM bool
if q.featureLookUp != nil {
preferRPM = q.featureLookUp.CheckFeature(constants.PreferRPM) == nil
}
start := params.Start
end := params.End
if builderQuery.ShiftBy != 0 {
@@ -102,7 +109,7 @@ func (q *querier) runBuilderQuery(
var err error
if _, ok := cacheKeys[queryName]; !ok || params.NoCache {
zap.L().Info("skipping cache for logs query", zap.String("queryName", queryName), zap.Int64("start", start), zap.Int64("end", end), zap.Int64("step", builderQuery.StepInterval), zap.Bool("noCache", params.NoCache), zap.String("cacheKey", cacheKeys[queryName]))
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, start, end, builderQuery, params)
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, start, end, builderQuery, params, preferRPM)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return
@@ -117,7 +124,7 @@ func (q *querier) runBuilderQuery(
missedSeries := make([]querycache.CachedSeriesData, 0)
filteredMissedSeries := make([]querycache.CachedSeriesData, 0)
for _, miss := range misses {
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, miss.Start, miss.End, builderQuery, params)
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, miss.Start, miss.End, builderQuery, params, preferRPM)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return
@@ -184,7 +191,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
@@ -195,7 +202,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
@@ -208,7 +215,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{},
v3.QBOptions{PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
@@ -237,7 +244,7 @@ func (q *querier) runBuilderQuery(
// If the query is not cached, we execute the query and return the result without caching it.
if _, ok := cacheKeys[queryName]; !ok || params.NoCache {
zap.L().Info("skipping cache for metrics query", zap.String("queryName", queryName), zap.Int64("start", start), zap.Int64("end", end), zap.Int64("step", builderQuery.StepInterval), zap.Bool("noCache", params.NoCache), zap.String("cacheKey", cacheKeys[queryName]))
query, err := metricsV3.PrepareMetricQuery(start, end, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, metricsV3.Options{})
query, err := metricsV3.PrepareMetricQuery(start, end, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, metricsV3.Options{PreferRPM: preferRPM})
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return

View File

@@ -42,7 +42,9 @@ type querier struct {
fluxInterval time.Duration
builder *queryBuilder.QueryBuilder
builder *queryBuilder.QueryBuilder
featureLookUp interfaces.FeatureLookup
// used for testing
// TODO(srikanthccv): remove this once we have a proper mock
testingMode bool
@@ -57,10 +59,11 @@ type querier struct {
}
type QuerierOptions struct {
Reader interfaces.Reader
Cache cache.Cache
KeyGenerator cache.KeyGenerator
FluxInterval time.Duration
Reader interfaces.Reader
Cache cache.Cache
KeyGenerator cache.KeyGenerator
FluxInterval time.Duration
FeatureLookup interfaces.FeatureLookup
// used for testing
TestingMode bool
@@ -93,7 +96,8 @@ func NewQuerier(opts QuerierOptions) interfaces.Querier {
BuildTraceQuery: tracesQueryBuilder,
BuildLogQuery: logsQueryBuilder,
BuildMetricQuery: metricsV3.PrepareMetricQuery,
}),
}, opts.FeatureLookup),
featureLookUp: opts.FeatureLookup,
testingMode: opts.TestingMode,
returnedSeries: opts.ReturnedSeries,

View File

@@ -17,6 +17,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
tracesV3 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v3"
"github.com/SigNoz/signoz/pkg/query-service/cache/inmemory"
"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/querycache"
"github.com/SigNoz/signoz/pkg/query-service/utils"
@@ -1369,6 +1370,7 @@ func Test_querier_runWindowBasedListQuery(t *testing.T) {
nil,
telemetryStore,
prometheustest.New(instrumentationtest.New().Logger(), prometheus.Config{}),
featureManager.StartManager(),
"",
true,
true,
@@ -1382,6 +1384,7 @@ func Test_querier_runWindowBasedListQuery(t *testing.T) {
queryBuilder.QueryBuilderOptions{
BuildTraceQuery: tracesV3.PrepareTracesQuery,
},
featureManager.StartManager(),
),
}
// Update query parameters

View File

@@ -25,6 +25,7 @@ func prepareLogsQuery(_ context.Context,
end int64,
builderQuery *v3.BuilderQuery,
params *v3.QueryRangeParamsV3,
preferRPM bool,
) (string, error) {
logsQueryBuilder := logsV3.PrepareLogsQuery
if useLogsNewSchema {
@@ -44,7 +45,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -55,7 +56,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -70,7 +71,7 @@ func prepareLogsQuery(_ context.Context,
params.CompositeQuery.QueryType,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{},
v3.QBOptions{PreferRPM: preferRPM},
)
if err != nil {
return query, err
@@ -88,6 +89,13 @@ func (q *querier) runBuilderQuery(
) {
defer wg.Done()
queryName := builderQuery.QueryName
var preferRPM bool
if q.featureLookUp != nil {
preferRPM = q.featureLookUp.CheckFeature(constants.PreferRPM) == nil
}
// making a local clone since we should not update the global params if there is sift by
start := params.Start
end := params.End
@@ -102,7 +110,7 @@ func (q *querier) runBuilderQuery(
var err error
if _, ok := cacheKeys[queryName]; !ok || params.NoCache {
zap.L().Info("skipping cache for logs query", zap.String("queryName", queryName), zap.Int64("start", params.Start), zap.Int64("end", params.End), zap.Int64("step", params.Step), zap.Bool("noCache", params.NoCache), zap.String("cacheKey", cacheKeys[queryName]))
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, start, end, builderQuery, params)
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, start, end, builderQuery, params, preferRPM)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return
@@ -116,7 +124,7 @@ func (q *querier) runBuilderQuery(
missedSeries := make([]querycache.CachedSeriesData, 0)
filteredMissedSeries := make([]querycache.CachedSeriesData, 0)
for _, miss := range misses {
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, miss.Start, miss.End, builderQuery, params)
query, err = prepareLogsQuery(ctx, q.UseLogsNewSchema, miss.Start, miss.End, builderQuery, params, preferRPM)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return
@@ -184,7 +192,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
@@ -195,7 +203,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit},
v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: limitQuery, Series: nil}
@@ -208,7 +216,7 @@ func (q *querier) runBuilderQuery(
end,
params.CompositeQuery.PanelType,
builderQuery,
v3.QBOptions{},
v3.QBOptions{PreferRPM: preferRPM},
)
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
@@ -237,7 +245,7 @@ func (q *querier) runBuilderQuery(
// If the query is not cached, we execute the query and return the result without caching it.
if _, ok := cacheKeys[queryName]; !ok || params.NoCache {
zap.L().Info("skipping cache for metrics query", zap.String("queryName", queryName), zap.Int64("start", params.Start), zap.Int64("end", params.End), zap.Int64("step", params.Step), zap.Bool("noCache", params.NoCache), zap.String("cacheKey", cacheKeys[queryName]))
query, err := metricsV4.PrepareMetricQuery(start, end, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, metricsV3.Options{})
query, err := metricsV4.PrepareMetricQuery(start, end, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, metricsV3.Options{PreferRPM: preferRPM})
if err != nil {
ch <- channelResult{Err: err, Name: queryName, Query: query, Series: nil}
return

View File

@@ -42,7 +42,8 @@ type querier struct {
fluxInterval time.Duration
builder *queryBuilder.QueryBuilder
builder *queryBuilder.QueryBuilder
featureLookUp interfaces.FeatureLookup
// used for testing
// TODO(srikanthccv): remove this once we have a proper mock
@@ -57,10 +58,11 @@ type querier struct {
}
type QuerierOptions struct {
Reader interfaces.Reader
Cache cache.Cache
KeyGenerator cache.KeyGenerator
FluxInterval time.Duration
Reader interfaces.Reader
Cache cache.Cache
KeyGenerator cache.KeyGenerator
FluxInterval time.Duration
FeatureLookup interfaces.FeatureLookup
// used for testing
TestingMode bool
@@ -94,7 +96,8 @@ func NewQuerier(opts QuerierOptions) interfaces.Querier {
BuildTraceQuery: tracesQueryBuilder,
BuildLogQuery: logsQueryBuilder,
BuildMetricQuery: metricsV4.PrepareMetricQuery,
}),
}, opts.FeatureLookup),
featureLookUp: opts.FeatureLookup,
testingMode: opts.TestingMode,
returnedSeries: opts.ReturnedSeries,

View File

@@ -17,6 +17,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
tracesV3 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v3"
"github.com/SigNoz/signoz/pkg/query-service/cache/inmemory"
"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/querycache"
"github.com/SigNoz/signoz/pkg/query-service/utils"
@@ -1423,6 +1424,7 @@ func Test_querier_runWindowBasedListQuery(t *testing.T) {
nil,
telemetryStore,
prometheustest.New(instrumentationtest.New().Logger(), prometheus.Config{}),
featureManager.StartManager(),
"",
true,
true,
@@ -1436,6 +1438,7 @@ func Test_querier_runWindowBasedListQuery(t *testing.T) {
queryBuilder.QueryBuilderOptions{
BuildTraceQuery: tracesV3.PrepareTracesQuery,
},
featureManager.StartManager(),
),
}
// Update query parameters

View File

@@ -8,6 +8,7 @@ import (
metricsV3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
"github.com/SigNoz/signoz/pkg/query-service/cache"
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"go.uber.org/zap"
)
@@ -45,7 +46,8 @@ type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelTy
type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, options metricsV3.Options) (string, error)
type QueryBuilder struct {
options QueryBuilderOptions
options QueryBuilderOptions
featureFlags interfaces.FeatureLookup
}
type QueryBuilderOptions struct {
@@ -54,9 +56,10 @@ type QueryBuilderOptions struct {
BuildMetricQuery prepareMetricQueryFunc
}
func NewQueryBuilder(options QueryBuilderOptions) *QueryBuilder {
func NewQueryBuilder(options QueryBuilderOptions, featureFlags interfaces.FeatureLookup) *QueryBuilder {
return &QueryBuilder{
options: options,
options: options,
featureFlags: featureFlags,
}
}
@@ -176,6 +179,8 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
compositeQuery := params.CompositeQuery
if compositeQuery != nil {
err := qb.featureFlags.CheckFeature(constants.PreferRPM)
PreferRPMFeatureEnabled := err == nil
// Build queries for each builder query
for queryName, query := range compositeQuery.BuilderQueries {
// making a local clone since we should not update the global params if there is sift by
@@ -191,12 +196,12 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
// for ts query with group by and limit form two queries
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
limitQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType, query,
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit})
v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: PreferRPMFeatureEnabled})
if err != nil {
return nil, err
}
placeholderQuery, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit})
query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: PreferRPMFeatureEnabled})
if err != nil {
return nil, err
}
@@ -204,7 +209,7 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
queries[queryName] = query
} else {
queryString, err := qb.options.BuildTraceQuery(start, end, compositeQuery.PanelType,
query, v3.QBOptions{GraphLimitQtype: ""})
query, v3.QBOptions{PreferRPM: PreferRPMFeatureEnabled, GraphLimitQtype: ""})
if err != nil {
return nil, err
}
@@ -213,25 +218,25 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3) (map[strin
case v3.DataSourceLogs:
// for ts query with limit replace it as it is already formed
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit})
limitQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.FirstQueryGraphLimit, PreferRPM: PreferRPMFeatureEnabled})
if err != nil {
return nil, err
}
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit})
placeholderQuery, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: constants.SecondQueryGraphLimit, PreferRPM: PreferRPMFeatureEnabled})
if err != nil {
return nil, err
}
query := fmt.Sprintf(placeholderQuery, limitQuery)
queries[queryName] = query
} else {
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{GraphLimitQtype: ""})
queryString, err := qb.options.BuildLogQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, v3.QBOptions{PreferRPM: PreferRPMFeatureEnabled, GraphLimitQtype: ""})
if err != nil {
return nil, err
}
queries[queryName] = queryString
}
case v3.DataSourceMetrics:
queryString, err := qb.options.BuildMetricQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, metricsV3.Options{})
queryString, err := qb.options.BuildMetricQuery(start, end, compositeQuery.QueryType, compositeQuery.PanelType, query, metricsV3.Options{PreferRPM: PreferRPMFeatureEnabled})
if err != nil {
return nil, err
}

View File

@@ -8,6 +8,7 @@ import (
logsV4 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v4"
metricsv3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/stretchr/testify/require"
)
@@ -50,7 +51,8 @@ func TestBuildQueryWithMultipleQueriesAndFormula(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildMetricQuery: metricsv3.PrepareMetricQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
queries, err := qb.PrepareQueries(q)
@@ -91,7 +93,8 @@ func TestBuildQueryWithIncorrectQueryRef(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildMetricQuery: metricsv3.PrepareMetricQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
_, err := qb.PrepareQueries(q)
@@ -165,7 +168,8 @@ func TestBuildQueryWithThreeOrMoreQueriesRefAndFormula(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildMetricQuery: metricsv3.PrepareMetricQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
queries, err := qb.PrepareQueries(q)
@@ -334,7 +338,8 @@ func TestBuildQueryWithThreeOrMoreQueriesRefAndFormula(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildMetricQuery: metricsv3.PrepareMetricQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
queries, err := qb.PrepareQueries(q)
require.Contains(t, queries["F1"], "SELECT A.`os.type` as `os.type`, A.`ts` as `ts`, A.value + B.value as value FROM (SELECT `os.type`, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, avg(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT JSONExtractString(labels, 'os.type') as `os.type`, fingerprint FROM signoz_metrics.time_series_v4_1day WHERE metric_name IN ['system.memory.usage'] AND temporality = '' AND __normalized = true AND unix_milli >= 1734998400000 AND unix_milli < 1735637880000 AND JSONExtractString(labels, 'os.type') = 'linux') as filtered_time_series USING fingerprint WHERE metric_name IN ['system.memory.usage'] AND unix_milli >= 1735036080000 AND unix_milli < 1735637880000 GROUP BY `os.type`, ts ORDER BY `os.type` ASC, ts) as A INNER JOIN (SELECT * FROM (SELECT `os.type`, toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL 60 SECOND) as ts, sum(value) as value FROM signoz_metrics.distributed_samples_v4 INNER JOIN (SELECT DISTINCT JSONExtractString(labels, 'os.type') as `os.type`, fingerprint FROM signoz_metrics.time_series_v4_1day WHERE metric_name IN ['system.network.io'] AND temporality = '' AND __normalized = true AND unix_milli >= 1734998400000 AND unix_milli < 1735637880000) as filtered_time_series USING fingerprint WHERE metric_name IN ['system.network.io'] AND unix_milli >= 1735036020000 AND unix_milli < 1735637880000 GROUP BY `os.type`, ts ORDER BY `os.type` ASC, ts) HAVING value > 4) as B ON A.`os.type` = B.`os.type` AND A.`ts` = B.`ts`")
@@ -493,7 +498,8 @@ func TestDeltaQueryBuilder(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildMetricQuery: metricsv3.PrepareMetricQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
queries, err := qb.PrepareQueries(c.query)
require.NoError(t, err)
@@ -697,7 +703,8 @@ func TestLogsQueryWithFormula(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildLogQuery: logsV3.PrepareLogsQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
for _, test := range testLogsWithFormula {
t.Run(test.Name, func(t *testing.T) {
@@ -907,7 +914,8 @@ func TestLogsQueryWithFormulaV2(t *testing.T) {
qbOptions := QueryBuilderOptions{
BuildLogQuery: logsV4.PrepareLogsQuery,
}
qb := NewQueryBuilder(qbOptions)
fm := featureManager.StartManager()
qb := NewQueryBuilder(qbOptions, fm)
for _, test := range testLogsWithFormulaV2 {
t.Run(test.Name, func(t *testing.T) {

View File

@@ -14,8 +14,6 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/preference"
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
@@ -25,12 +23,12 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"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/app/preferences"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/web"
"github.com/rs/cors"
"github.com/soheilhy/cmux"
@@ -100,6 +98,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
if err := preferences.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
return nil, err
}
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err
}
@@ -117,9 +119,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
reader := clickhouseReader.NewReader(
serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.SQLStore.SQLxDB(),
serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus,
fm,
serverOptions.Cluster,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
@@ -151,6 +154,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
reader,
c,
serverOptions.DisableRules,
fm,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
serverOptions.SigNoz.SQLStore,
@@ -184,7 +188,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
telemetry.GetInstance().SetReader(reader)
preferenceModule := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(serverOptions.SigNoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
apiHandler, err := NewAPIHandler(APIHandlerOpts{
Reader: reader,
SkipConfig: skipConfig,
@@ -202,7 +205,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
JWT: serverOptions.Jwt,
AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager),
Signoz: serverOptions.SigNoz,
Preference: preferenceModule,
})
if err != nil {
return nil, err
@@ -482,6 +484,7 @@ func makeRulesManager(
ch interfaces.Reader,
cache cache.Cache,
disableRules bool,
fm interfaces.FeatureLookup,
useLogsNewSchema bool,
useTraceNewSchema bool,
sqlstore sqlstore.SQLStore,
@@ -497,6 +500,7 @@ func makeRulesManager(
Context: context.Background(),
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
Cache: cache,
EvalDelay: constants.GetEvalDelay(),

View File

@@ -310,6 +310,10 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
v3.AggregateOperatorRate:
rate := float64(step)
if options.PreferRPM {
rate = rate / 60.0
}
op := fmt.Sprintf("%s(%s)/%f", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate)
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
return query, nil

Some files were not shown because too many files have changed in this diff Show More