Compare commits

..

2 Commits

Author SHA1 Message Date
aniket
06601a94d6 fix(summary): removed offset from second query 2025-03-05 14:50:03 +05:30
aniket
fe408140c2 fix(summary): removed offset from second query 2025-03-05 14:19:28 +05:30
188 changed files with 2893 additions and 7494 deletions

View File

@@ -70,74 +70,26 @@ sqlstore:
##################### APIServer #####################
apiserver:
timeout:
# Default request timeout.
default: 60s
# Maximum request timeout.
max: 600s
# List of routes to exclude from request timeout.
excluded_routes:
- /api/v1/logs/tail
- /api/v3/logs/livetail
logging:
# List of routes to exclude from request responselogging.
excluded_routes:
- /api/v1/health
##################### TelemetryStore #####################
telemetrystore:
# Specifies the telemetrystore provider to use.
# specifies the telemetrystore provider to use.
provider: clickhouse
clickhouse:
# The DSN to use for ClickHouse.
dsn: http://localhost:9000
# Maximum number of idle connections in the connection pool.
max_idle_conns: 50
# Maximum number of open connections to the database.
max_open_conns: 100
# Maximum time to wait for a connection to be established.
dial_timeout: 5s
clickhouse:
# The DSN to use for ClickHouse.
dsn: http://localhost:9000
##################### Alertmanager #####################
alertmanager:
# Specifies the alertmanager provider to use.
provider: legacy
legacy:
# The API URL (with prefix) of the legacy Alertmanager instance.
api_url: http://localhost:9093/api
signoz:
# The poll interval for periodically syncing the alertmanager with the config in the store.
poll_interval: 1m
# The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
external_url: http://localhost:9093
# The global configuration for the alertmanager. All the exahustive fields can be found in the upstream: https://github.com/prometheus/alertmanager/blob/efa05feffd644ba4accb526e98a8c6545d26a783/config/config.go#L833
global:
# ResolveTimeout is the time after which an alert is declared resolved if it has not been updated.
resolve_timeout: 5m
route:
# GroupByStr is the list of labels to group alerts by.
group_by:
- alertname
# GroupInterval is the interval at which alerts are grouped.
group_interval: 1m
# GroupWait is the time to wait before sending alerts to receivers.
group_wait: 1m
# RepeatInterval is the interval at which alerts are repeated.
repeat_interval: 1h
alerts:
# Interval between garbage collection of alerts.
gc_interval: 30m
silences:
# Maximum number of silences, including expired silences. If negative or zero, no limit is set.
max: 0
# Maximum size of the silences in bytes. If negative or zero, no limit is set.
max_size_bytes: 0
# Interval between garbage collection and snapshotting of the silences. The snapshot will be stored in the state store.
maintenance_interval: 15m
# Retention of the silences.
retention: 120h
nflog:
# Interval between garbage collection and snapshotting of the notification logs. The snapshot will be stored in the state store.
maintenance_interval: 15m
# Retention of the notification logs.
retention: 120h
dial_timeout: 5s

View File

@@ -11,7 +11,6 @@ import (
"go.signoz.io/signoz/ee/query-service/interfaces"
"go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
"go.signoz.io/signoz/pkg/alertmanager"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
"go.signoz.io/signoz/pkg/query-service/app/integrations"
@@ -21,7 +20,6 @@ import (
basemodel "go.signoz.io/signoz/pkg/query-service/model"
rules "go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types/authtypes"
)
@@ -53,7 +51,7 @@ type APIHandler struct {
}
// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
@@ -69,8 +67,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
FluxInterval: opts.FluxInterval,
UseLogsNewSchema: opts.UseLogsNewSchema,
UseTraceNewSchema: opts.UseTraceNewSchema,
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
Signoz: signoz,
})
if err != nil {

View File

@@ -134,7 +134,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
_, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager)
_, registerError := baseauth.Register(ctx, req)
if !registerError.IsNil() {
RespondError(w, apierr, nil)
return

View File

@@ -18,7 +18,6 @@ import (
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap"
)
@@ -46,7 +45,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgID, cloudProvider)
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
@@ -125,7 +124,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
))
}
for _, p := range allPats {
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
if p.UserID == integrationUser.Id && p.Name == integrationPATName {
return p.Token, nil
}
}
@@ -137,7 +136,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
newPAT := model.PAT{
Token: generatePATToken(),
UserID: integrationUser.ID,
UserID: integrationUser.Id,
Name: integrationPATName,
Role: baseconstants.ViewerGroup,
ExpiresAt: 0,
@@ -155,7 +154,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*types.User, *basemodel.ApiError) {
) (*basemodel.User, *basemodel.ApiError) {
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
@@ -172,21 +171,19 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
zap.String("cloudProvider", cloudProvider),
)
newUser := &types.User{
ID: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
OrgID: orgId,
newUser := &basemodel.User{
Id: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
CreatedAt: time.Now().Unix(),
OrgId: orgId,
}
viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user")
}
newUser.GroupID = viewerGroup.ID
newUser.GroupId = viewerGroup.ID
passwordHash, err := auth.PasswordHash(uuid.NewString())
if err != nil {

View File

@@ -54,7 +54,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}
// All the PATs are associated with the user creating the PAT.
pat.UserID = user.ID
pat.UserID = user.Id
pat.CreatedAt = time.Now().Unix()
pat.UpdatedAt = time.Now().Unix()
pat.LastUsed = 0
@@ -112,7 +112,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
return
}
req.UpdatedByUserID = user.ID
req.UpdatedByUserID = user.Id
id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now().Unix()
zap.L().Info("Got Update PAT request", zap.Any("pat", req))
@@ -135,7 +135,7 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
}, nil)
return
}
zap.L().Info("Get PATs for user", zap.String("user_id", user.ID))
zap.L().Info("Get PATs for user", zap.String("user_id", user.Id))
pats, apierr := ah.AppDao().ListPATs(ctx)
if apierr != nil {
RespondError(w, apierr, nil)
@@ -157,7 +157,7 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
}
zap.L().Info("Revoke PAT with id", zap.String("id", id))
if apierr := ah.AppDao().RevokePAT(ctx, id, user.ID); apierr != nil {
if apierr := ah.AppDao().RevokePAT(ctx, id, user.Id); apierr != nil {
RespondError(w, apierr, nil)
return
}

View File

@@ -23,11 +23,8 @@ import (
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
"go.signoz.io/signoz/ee/query-service/rules"
"go.signoz.io/signoz/pkg/alertmanager"
"go.signoz.io/signoz/pkg/http/middleware"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.signoz.io/signoz/pkg/web"
@@ -47,6 +44,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/cache"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
@@ -177,8 +175,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
<-readerReady
rm, err := makeRulesManager(
serverOptions.PromConfigPath,
rm, err := makeRulesManager(serverOptions.PromConfigPath,
baseconst.GetAlertManagerApiPrefix(),
serverOptions.RuleRepoURL,
serverOptions.SigNoz.SQLStore.SQLxDB(),
reader,
@@ -187,8 +185,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
lm,
serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema,
serverOptions.SigNoz.Alertmanager,
serverOptions.SigNoz.SQLStore,
)
if err != nil {
@@ -271,7 +267,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
JWT: serverOptions.Jwt,
}
apiHandler, err := api.NewAPIHandler(apiOpts, serverOptions.SigNoz)
apiHandler, err := api.NewAPIHandler(apiOpts)
if err != nil {
return nil, err
}
@@ -344,14 +340,14 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
r := baseapp.NewRouter()
// add auth middleware
getUserFromRequest := func(ctx context.Context) (*types.GettableUser, error) {
getUserFromRequest := func(ctx context.Context) (*basemodel.UserPayload, error) {
user, err := auth.GetUserFromRequestContext(ctx, apiHandler)
if err != nil {
return nil, err
}
if user.User.OrgID == "" {
if user.User.OrgId == "" {
return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
@@ -533,6 +529,7 @@ func (s *Server) Stop() error {
func makeRulesManager(
promConfigPath,
alertManagerURL string,
ruleRepoURL string,
db *sqlx.DB,
ch baseint.Reader,
@@ -540,34 +537,39 @@ func makeRulesManager(
disableRules bool,
fm baseint.FeatureLookup,
useLogsNewSchema bool,
useTraceNewSchema bool,
alertmanager alertmanager.Alertmanager,
sqlstore sqlstore.SQLStore,
) (*baserules.Manager, error) {
useTraceNewSchema bool) (*baserules.Manager, error) {
// create engine
pqle, err := pqle.FromConfigPath(promConfigPath)
if err != nil {
return nil, fmt.Errorf("failed to create pql engine : %v", err)
}
// notifier opts
notifierOpts := basealm.NotifierOptions{
QueueCapacity: 10000,
Timeout: 1 * time.Second,
AlertManagerURLs: []string{alertManagerURL},
}
// create manager opts
managerOpts := &baserules.ManagerOptions{
PqlEngine: pqle,
RepoURL: ruleRepoURL,
DBConn: db,
Context: context.Background(),
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),
NotifierOpts: notifierOpts,
PqlEngine: pqle,
RepoURL: ruleRepoURL,
DBConn: db,
Context: context.Background(),
Logger: zap.L(),
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),
PrepareTaskFunc: rules.PrepareTaskFunc,
UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema,
PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager,
SQLStore: sqlstore,
}
// create Manager

View File

@@ -7,14 +7,14 @@ import (
"go.signoz.io/signoz/ee/query-service/app/api"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap"
)
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*types.GettableUser, error) {
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) {
patToken, ok := authtypes.UUIDFromContext(ctx)
if ok && patToken != "" {
zap.L().Debug("Received a non-zero length PAT token")
@@ -40,9 +40,9 @@ func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler)
}
telemetry.GetInstance().SetPatTokenUser()
dao.UpdatePATLastUsed(ctx, patToken, time.Now().Unix())
user.User.GroupID = group.ID
user.User.ID = pat.Id
return &types.GettableUser{
user.User.GroupId = group.ID
user.User.Id = pat.Id
return &basemodel.UserPayload{
User: user.User,
Role: pat.Role,
}, nil

View File

@@ -10,7 +10,6 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
)
@@ -40,7 +39,7 @@ type ModelDao interface {
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError
}

View File

@@ -14,12 +14,11 @@ import (
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/utils"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap"
)
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*types.User, basemodel.BaseApiError) {
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
// get auth domain from email domain
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
@@ -43,17 +42,15 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
return nil, apiErr
}
user := &types.User{
ID: uuid.NewString(),
Name: "",
Email: email,
Password: hash,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
},
user := &basemodel.User{
Id: uuid.NewString(),
Name: "",
Email: email,
Password: hash,
CreatedAt: time.Now().Unix(),
ProfilePictureURL: "", // Currently unused
GroupID: group.ID,
OrgID: domain.OrgId,
GroupId: group.ID,
OrgId: domain.OrgId,
}
user, apiErr = m.CreateUser(ctx, user, false)
@@ -76,7 +73,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
return "", model.BadRequestStr("invalid user email received from the auth provider")
}
user := &types.User{}
user := &basemodel.User{}
if userPayload == nil {
newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
@@ -98,7 +95,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
redirectUri,
tokenStore.AccessJwt,
user.ID,
user.Id,
tokenStore.RefreshJwt), nil
}

View File

@@ -8,7 +8,6 @@ import (
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types"
"go.uber.org/zap"
)
@@ -43,10 +42,10 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
}
} else {
p.CreatedByUser = model.User{
Id: createdByUser.ID,
Id: createdByUser.Id,
Name: createdByUser.Name,
Email: createdByUser.Email,
CreatedAt: createdByUser.CreatedAt.Unix(),
CreatedAt: createdByUser.CreatedAt,
ProfilePictureURL: createdByUser.ProfilePictureURL,
NotFound: false,
}
@@ -96,10 +95,10 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApi
}
} else {
pats[i].CreatedByUser = model.User{
Id: createdByUser.ID,
Id: createdByUser.Id,
Name: createdByUser.Name,
Email: createdByUser.Email,
CreatedAt: createdByUser.CreatedAt.Unix(),
CreatedAt: createdByUser.CreatedAt,
ProfilePictureURL: createdByUser.ProfilePictureURL,
NotFound: false,
}
@@ -112,10 +111,10 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApi
}
} else {
pats[i].UpdatedByUser = model.User{
Id: updatedByUser.ID,
Id: updatedByUser.Id,
Name: updatedByUser.Name,
Email: updatedByUser.Email,
CreatedAt: updatedByUser.CreatedAt.Unix(),
CreatedAt: updatedByUser.CreatedAt,
ProfilePictureURL: updatedByUser.ProfilePictureURL,
NotFound: false,
}
@@ -171,8 +170,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basem
}
// deprecated
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError) {
users := []types.GettableUser{}
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
users := []basemodel.UserPayload{}
query := `SELECT
u.id,

View File

@@ -7,6 +7,7 @@ import (
"os"
"os/signal"
"strconv"
"syscall"
"time"
"go.opentelemetry.io/otel/sdk/resource"
@@ -149,14 +150,7 @@ func main() {
zap.L().Fatal("Failed to create config", zap.Error(err))
}
signoz, err := signoz.New(
context.Background(),
config,
signoz.NewCacheProviderFactories(),
signoz.NewWebProviderFactories(),
signoz.NewSQLStoreProviderFactories(),
signoz.NewTelemetryStoreProviderFactories(),
)
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
}
@@ -204,19 +198,16 @@ func main() {
zap.L().Fatal("Failed to initialize auth cache", zap.Error(err))
}
signoz.Start(context.Background())
signalsChannel := make(chan os.Signal, 1)
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)
if err := signoz.Wait(context.Background()); err != nil {
zap.L().Fatal("Failed to start signoz", zap.Error(err))
}
err = server.Stop()
if err != nil {
zap.L().Fatal("Failed to stop server", zap.Error(err))
}
err = signoz.Stop(context.Background())
if err != nil {
zap.L().Fatal("Failed to stop signoz", zap.Error(err))
for {
select {
case status := <-server.HealthCheckStatus():
zap.L().Info("Received HealthCheck status: ", zap.Int("status", int(status)))
case <-signalsChannel:
zap.L().Fatal("Received OS Interrupt Signal ... ")
server.Stop()
}
}
}

View File

@@ -11,7 +11,7 @@ import (
saml2 "github.com/russellhaering/gosaml2"
"go.signoz.io/signoz/ee/query-service/sso"
"go.signoz.io/signoz/ee/query-service/sso/saml"
"go.signoz.io/signoz/pkg/types"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -33,7 +33,7 @@ type OrgDomain struct {
SamlConfig *SamlConfig `json:"samlConfig"`
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
Org *types.Organization
Org *basemodel.Organization
}
func (od *OrgDomain) String() string {

View File

@@ -28,7 +28,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.UseLogsNewSchema,
opts.UseTraceNewSchema,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
@@ -49,7 +48,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.Logger,
opts.Reader,
opts.ManagerOpts.PqlEngine,
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
@@ -70,7 +68,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.Reader,
opts.Cache,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
return task, err
@@ -129,7 +126,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
opts.UseTraceNewSchema,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
@@ -148,7 +144,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
opts.ManagerOpts.PqlEngine,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
@@ -165,7 +160,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
opts.Cache,
baserules.WithSendAlways(),
baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore),
)
if err != nil {
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", rule.Name()), zap.Error(err))

View File

@@ -47,7 +47,6 @@
"@tanstack/react-virtual": "3.11.2",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/hierarchy": "3.12.0",
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@xstate/react": "^3.0.0",
@@ -70,9 +69,8 @@
"cross-env": "^7.0.3",
"css-loader": "5.0.0",
"css-minimizer-webpack-plugin": "5.0.1",
"d3-hierarchy": "3.1.2",
"dayjs": "^1.10.7",
"dompurify": "3.2.4",
"dompurify": "3.1.3",
"dotenv": "8.2.0",
"event-source-polyfill": "1.0.31",
"eventemitter3": "5.0.1",

View File

@@ -4,7 +4,6 @@ import getOrgUser from 'api/user/getOrgUser';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
@@ -14,6 +13,7 @@ import { matchPath, useLocation } from 'react-router-dom';
import { LicenseState, LicenseStatus } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { USER_ROLES } from 'types/roles';
import { isCloudUser } from 'utils/app';
import { routePermission } from 'utils/permission';
import routes, {
@@ -55,7 +55,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
);
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
const currentRoute = mapRoutes.get('current');
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);

View File

@@ -1,20 +1,20 @@
import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useThemeConfig } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { identity, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import AlertRuleProvider from 'providers/Alert';
import { useAppContext } from 'providers/App/App';
@@ -24,7 +24,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { extractDomain } from 'utils/app';
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
import PrivateRoute from './Private';
import defaultRoutes, {
@@ -50,12 +50,11 @@ function App(): JSX.Element {
} = useAppContext();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { trackPageView } = useAnalytics();
const { hostname, pathname } = window.location;
const {
isCloudUser: isCloudUserVal,
isEECloudUser: isEECloudUserVal,
} = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const enableAnalytics = useCallback(
(user: IUser): void => {
@@ -66,21 +65,18 @@ function App(): JSX.Element {
const { name, email, role } = user;
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const identifyPayload = {
email,
name,
company_name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
role,
source: 'signoz-ui',
};
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const groupTraits = {
name: orgName,
tenant_id: hostNameParts[0],
@@ -90,13 +86,8 @@ function App(): JSX.Element {
source: 'signoz-ui',
};
if (email) {
logEvent('Email Identified', identifyPayload, 'identify');
}
if (domain) {
logEvent('Domain Identified', groupTraits, 'group');
}
window.analytics.identify(email, sanitizedIdentifyPayload);
window.analytics.group(domain, groupTraits);
posthog?.identify(email, {
email,
@@ -159,7 +150,7 @@ function App(): JSX.Element {
let updatedRoutes = defaultRoutes;
// if the user is a cloud user
if (isCloudUserVal || isEECloudUserVal) {
if (isCloudUserVal || isEECloudUser()) {
// if the user is on basic plan then remove billing
if (isOnBasicPlan) {
updatedRoutes = updatedRoutes.filter(
@@ -184,7 +175,6 @@ function App(): JSX.Element {
isCloudUserVal,
isFetchingLicenses,
isFetchingUser,
isEECloudUserVal,
]);
useEffect(() => {
@@ -197,7 +187,9 @@ function App(): JSX.Element {
hide_default_launcher: false,
});
}
}, [pathname]);
trackPageView(pathname);
}, [pathname, trackPageView]);
useEffect(() => {
// feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete

View File

@@ -13,11 +13,13 @@ const getTriggered = async (
const response = await axios.get(`/alerts?${queryParams}`);
const amData = JSON.parse(response.data.data);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
payload: amData.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);

View File

@@ -10,7 +10,7 @@ const create = async (
try {
const response = await axios.post('/channels', {
name: props.name,
msteamsv2_configs: [
msteams_configs: [
{
send_resolved: props.send_resolved,
webhook_url: props.webhook_url,

View File

@@ -9,21 +9,19 @@ const create = async (
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
const password = props.password ? props.password.trim() : '';
if (username !== '' && password !== '') {
if (props.username !== '' && props.password !== '') {
httpConfig = {
basic_auth: {
username,
password,
username: props.username,
password: props.password,
},
};
} else if (username === '' && password !== '') {
} else if (props.username === '' && props.password !== '') {
httpConfig = {
authorization: {
type: 'Bearer',
credentials: password,
type: 'bearer',
credentials: props.password,
},
};
}

View File

@@ -10,7 +10,7 @@ const editMsTeams = async (
try {
const response = await axios.put(`/channels/${props.id}`, {
name: props.name,
msteamsv2_configs: [
msteams_configs: [
{
send_resolved: props.send_resolved,
webhook_url: props.webhook_url,

View File

@@ -9,21 +9,18 @@ const editWebhook = async (
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
const password = props.password ? props.password.trim() : '';
if (username !== '' && password !== '') {
if (props.username !== '' && props.password !== '') {
httpConfig = {
basic_auth: {
username,
password,
username: props.username,
password: props.password,
},
};
} else if (username === '' && password !== '') {
} else if (props.username === '' && props.password !== '') {
httpConfig = {
authorization: {
type: 'Bearer',
credentials: password,
type: 'bearer',
credentials: props.password,
},
};
}

View File

@@ -10,7 +10,7 @@ const testMsTeams = async (
try {
const response = await axios.post('/testChannel', {
name: props.name,
msteamsv2_configs: [
msteams_configs: [
{
send_resolved: true,
webhook_url: props.webhook_url,

View File

@@ -9,21 +9,19 @@ const testWebhook = async (
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
let httpConfig = {};
const username = props.username ? props.username.trim() : '';
const password = props.password ? props.password.trim() : '';
if (username !== '' && password !== '') {
if (props.username !== '' && props.password !== '') {
httpConfig = {
basic_auth: {
username,
password,
username: props.username,
password: props.password,
},
};
} else if (username === '' && password !== '') {
} else if (props.username === '' && props.password !== '') {
httpConfig = {
authorization: {
type: 'Bearer',
credentials: password,
type: 'bearer',
credentials: props.password,
},
};
}

View File

@@ -7,15 +7,11 @@ import { EventSuccessPayloadProps } from 'types/api/events/types';
const logEvent = async (
eventName: string,
attributes: Record<string, unknown>,
eventType?: 'track' | 'group' | 'identify',
rateLimited?: boolean,
): Promise<SuccessResponse<EventSuccessPayloadProps> | ErrorResponse> => {
try {
const response = await axios.post('/event', {
eventName,
attributes,
eventType: eventType || 'track',
rateLimited: rateLimited || false, // TODO: Update this once we have a proper way to handle rate limiting
});
return {

View File

@@ -1,67 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import {
OrderByPayload,
TreemapViewType,
} from 'container/MetricsExplorer/Summary/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
export interface MetricsListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: OrderByPayload;
}
export enum MetricType {
SUM = 'Sum',
GAUGE = 'Gauge',
HISTOGRAM = 'Histogram',
SUMMARY = 'Summary',
EXPONENTIAL_HISTOGRAM = 'ExponentialHistogram',
}
export interface MetricsListItemData {
metric_name: string;
description: string;
type: MetricType;
unit: string;
[TreemapViewType.CARDINALITY]: number;
[TreemapViewType.DATAPOINTS]: number;
lastReceived: string;
}
export interface MetricsListResponse {
status: string;
data: {
metrics: MetricsListItemData[];
total?: number;
};
}
export const getMetricsList = async (
props: MetricsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsListResponse> | ErrorResponse> => {
try {
const response = await axios.post('/metrics', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -1,34 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
export interface MetricsListFilterKeysResponse {
status: string;
data: {
metricColumns: string[];
attributeKeys: BaseAutocompleteData[];
};
}
export const getMetricsListFilterKeys = async (
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsListFilterKeysResponse> | ErrorResponse> => {
try {
const response = await axios.get('/metrics/filters/keys', {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -1,44 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
export interface MetricsListFilterValuesPayload {
filterAttributeKeyDataType: string;
filterKey: string;
searchText: string;
limit: number;
}
export interface MetricsListFilterValuesResponse {
status: string;
data: {
FilterValues: BaseAutocompleteData[];
};
}
export const getMetricsListFilterValues = async (
props: MetricsListFilterValuesPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<
SuccessResponse<MetricsListFilterValuesResponse> | ErrorResponse
> => {
try {
const response = await axios.post('/metrics/filters/values', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -1,54 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { TreemapViewType } from 'container/MetricsExplorer/Summary/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
export interface MetricsTreeMapPayload {
filters: TagFilter;
limit?: number;
treemap?: TreemapViewType;
}
export interface MetricsTreeMapResponse {
status: string;
data: {
[TreemapViewType.CARDINALITY]: CardinalityData[];
[TreemapViewType.DATAPOINTS]: DatapointsData[];
};
}
export interface CardinalityData {
percentage: number;
total_value: number;
metric_name: string;
}
export interface DatapointsData {
percentage: number;
metric_name: string;
}
export const getMetricsTreeMap = async (
props: MetricsTreeMapPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsTreeMapResponse> | ErrorResponse> => {
try {
const response = await axios.post('/metrics/treemap', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,26 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/setFlags';
const setFlags = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.patch(`/user/${props.userId}/flags`, {
...props.flags,
});
return {
statusCode: 200,
error: null,
message: response.data?.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default setFlags;

View File

@@ -1,6 +1,7 @@
import './RangePickerModal.styles.scss';
import { DatePicker } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs, { Dayjs } from 'dayjs';
@@ -69,6 +70,9 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
disabledDate={disabledDate}
allowClear
showTime
format={(date: Dayjs): string =>
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
}
onOk={onModalOkHandler}
// eslint-disable-next-line react/jsx-props-no-spreading
{...(selectedTime === 'custom' && {

View File

@@ -6,7 +6,6 @@ import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import { defaultTo } from 'lodash-es';
import { CreditCard, HelpCircle, X } from 'lucide-react';
@@ -17,6 +16,7 @@ import { useLocation } from 'react-router-dom';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { License } from 'types/api/licenses/def';
import { isCloudUser } from 'utils/app';
export interface LaunchChatSupportProps {
eventName: string;
@@ -38,7 +38,7 @@ function LaunchChatSupport({
onHoverText = '',
intercomMessageDisabled = false,
}: LaunchChatSupportProps): JSX.Element | null {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const { notifications } = useNotifications();
const {
licenses,
@@ -77,6 +77,7 @@ function LaunchChatSupport({
) {
let isChatSupportEnabled = false;
let isPremiumSupportEnabled = false;
const isCloudUserVal = isCloudUser();
if (featureFlags && featureFlags.length > 0) {
isChatSupportEnabled =
featureFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)
@@ -98,7 +99,6 @@ function LaunchChatSupport({
}, [
featureFlags,
featureFlagsFetchError,
isCloudUserVal,
isFetchingFeatureFlags,
isLoggedIn,
licenses,

View File

@@ -16,7 +16,6 @@ import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnbo
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { History } from 'history';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Bolt, Check, OctagonAlert, X } from 'lucide-react';
import {
KAFKA_SETUP_DOC_LINK,
@@ -24,6 +23,7 @@ import {
} from 'pages/MessagingQueues/MessagingQueuesUtils';
import { ReactNode, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { isCloudUser } from 'utils/app';
import { v4 as uuid } from 'uuid';
interface AttributeCheckListProps {
@@ -181,7 +181,7 @@ function AttributeCheckList({
const handleFilterChange = (value: AttributesFilters): void => {
setFilter(value);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const history = useHistory();
useEffect(() => {

View File

@@ -0,0 +1,4 @@
export default interface ReleaseNoteProps {
path?: string;
release?: string;
}

View File

@@ -0,0 +1,61 @@
import { Button, Space } from 'antd';
import setFlags from 'api/user/setFlags';
import MessageTip from 'components/MessageTip';
import { useAppContext } from 'providers/App/App';
import { useCallback } from 'react';
import { UserFlags } from 'types/api/user/setFlags';
import ReleaseNoteProps from '../ReleaseNoteProps';
export default function ReleaseNote0120({
release,
}: ReleaseNoteProps): JSX.Element | null {
const { user, setUserFlags } = useAppContext();
const handleDontShow = useCallback(async (): Promise<void> => {
const flags: UserFlags = { ReleaseNote0120Hide: 'Y' };
try {
setUserFlags(flags);
if (!user) {
// no user is set, so escape the routine
return;
}
const response = await setFlags({ userId: user.id, flags });
if (response.statusCode !== 200) {
console.log('failed to complete do not show status', response.error);
}
} catch (e) {
// here we do not nothing as the cost of error is minor,
// the user can switch the do no show option again in the further.
console.log('unexpected error: failed to complete do not show status', e);
}
}, [setUserFlags, user]);
return (
<MessageTip
show
message={
<div>
You are using {release} of SigNoz. We have introduced distributed setup in
v0.12.0 release. If you use or plan to use clickhouse queries in dashboard
or alerts, you might want to read about querying the new distributed tables{' '}
<a
href="https://signoz.io/docs/operate/migration/upgrade-0.12/#querying-distributed-tables"
target="_blank"
rel="noreferrer"
>
here
</a>
</div>
}
action={
<Space>
<Button onClick={handleDontShow}>Do not show again</Button>
</Space>
}
/>
);
}

View File

@@ -0,0 +1,68 @@
import ReleaseNoteProps from 'components/ReleaseNote/ReleaseNoteProps';
import ReleaseNote0120 from 'components/ReleaseNote/Releases/ReleaseNote0120';
import ROUTES from 'constants/routes';
import { useAppContext } from 'providers/App/App';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { UserFlags } from 'types/api/user/setFlags';
import AppReducer from 'types/reducer/app';
interface ComponentMapType {
match: (
path: string | undefined,
version: string,
userFlags: UserFlags | null,
) => boolean;
component: ({ path, release }: ReleaseNoteProps) => JSX.Element | null;
}
const allComponentMap: ComponentMapType[] = [
{
match: (
path: string | undefined,
version: string,
userFlags: UserFlags | null,
): boolean => {
if (!path) {
return false;
}
const allowedPaths: string[] = [
ROUTES.LIST_ALL_ALERT,
ROUTES.APPLICATION,
ROUTES.ALL_DASHBOARD,
];
return (
userFlags?.ReleaseNote0120Hide !== 'Y' &&
allowedPaths.includes(path) &&
version.startsWith('v0.12')
);
},
component: ReleaseNote0120,
},
];
// ReleaseNote prints release specific warnings and notes that
// user needs to be aware of before using the upgraded version.
function ReleaseNote({ path }: ReleaseNoteProps): JSX.Element | null {
const { user } = useAppContext();
const { currentVersion } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const c = allComponentMap.find((item) =>
item.match(path, currentVersion, user.flags),
);
if (!c) {
return null;
}
return <c.component path={path} release={currentVersion} />;
}
ReleaseNote.defaultProps = {
path: '',
};
export default ReleaseNote;

View File

@@ -43,10 +43,4 @@ export const REACT_QUERY_KEY = {
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS',
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
// Metrics Explorer Query Keys
GET_METRICS_LIST: 'GET_METRICS_LIST',
GET_METRICS_TREE_MAP: 'GET_METRICS_TREE_MAP',
GET_METRICS_LIST_FILTER_KEYS: 'GET_METRICS_LIST_FILTER_KEYS',
GET_METRICS_LIST_FILTER_VALUES: 'GET_METRICS_LIST_FILTER_VALUES',
};

View File

@@ -23,7 +23,6 @@ import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isNull } from 'lodash-es';
@@ -55,6 +54,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { LicenseEvent } from 'types/api/licensesV3/getActive';
import { USER_ROLES } from 'types/roles';
import { isCloudUser } from 'utils/app';
import { eventEmitter } from 'utils/getEventEmitter';
import {
getFormattedDate,
@@ -122,8 +122,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
{
queryFn: getUserVersion,
@@ -356,6 +354,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
) {
let isChatSupportEnabled = false;
let isPremiumSupportEnabled = false;
const isCloudUserVal = isCloudUser();
if (featureFlags && featureFlags.length > 0) {
isChatSupportEnabled =
featureFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)
@@ -377,7 +376,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
}, [
featureFlags,
featureFlagsFetchError,
isCloudUserVal,
isFetchingFeatureFlags,
isLoggedIn,
licenses,
@@ -392,16 +390,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
LOCALSTORAGE.DONT_SHOW_SLOW_API_WARNING,
);
logEvent(
`Slow API Warning`,
{
durationMs: data.duration,
url: data.url,
thresholdMs: data.threshold,
},
'track',
true, // rate limited - controlled by Backend
);
logEvent(`Slow API Warning`, {
duration: `${data.duration}ms`,
url: data.url,
threshold: data.threshold,
});
const isDontShowSlowApiWarning = dontShowSlowApiWarning === 'true';

View File

@@ -24,7 +24,6 @@ import Spinner from 'components/Spinner';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useAxiosError from 'hooks/useAxiosError';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import { isEmpty, pick } from 'lodash-es';
import { useAppContext } from 'providers/App/App';
@@ -34,6 +33,7 @@ import { useMutation, useQuery } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { License } from 'types/api/licenses/def';
import { isCloudUser } from 'utils/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
@@ -145,7 +145,7 @@ export default function BillingContainer(): JSX.Element {
const handleError = useAxiosError();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const processUsageData = useCallback(
(data: any): void => {

View File

@@ -173,7 +173,6 @@ function EditAlertChannels({
const prepareEmailRequest = useCallback(
() => ({
name: selectedConfig?.name || '',
send_resolved: selectedConfig?.send_resolved || false,
to: selectedConfig.to || '',
html: selectedConfig.html || '',
headers: selectedConfig.headers || {},
@@ -209,7 +208,6 @@ function EditAlertChannels({
const preparePagerRequest = useCallback(
() => ({
name: selectedConfig.name || '',
send_resolved: selectedConfig?.send_resolved || false,
routing_key: selectedConfig.routing_key,
client: selectedConfig.client,
client_url: selectedConfig.client_url,
@@ -263,7 +261,6 @@ function EditAlertChannels({
const prepareOpsgenieRequest = useCallback(
() => ({
name: selectedConfig.name || '',
send_resolved: selectedConfig?.send_resolved || false,
api_key: selectedConfig.api_key || '',
message: selectedConfig.message || '',
description: selectedConfig.description || '',

View File

@@ -5,7 +5,6 @@ import setRetentionApi from 'api/settings/setRetention';
import TextToolTip from 'components/TextToolTip';
import GeneralSettingsCloud from 'container/GeneralSettingsCloud';
import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import find from 'lodash-es/find';
import { useAppContext } from 'providers/App/App';
@@ -24,6 +23,7 @@ import {
PayloadPropsMetrics as GetRetentionPeriodMetricsPayload,
PayloadPropsTraces as GetRetentionPeriodTracesPayload,
} from 'types/api/settings/getRetention';
import { isCloudUser } from 'utils/app';
import Retention from './Retention';
import StatusMessage from './StatusMessage';
@@ -394,7 +394,7 @@ function GeneralSettings({
onModalToggleHandler(type);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const renderConfig = [
{

View File

@@ -1,5 +1,4 @@
import { Col, Row, Select } from 'antd';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { find } from 'lodash-es';
import {
ChangeEvent,
@@ -9,6 +8,7 @@ import {
useRef,
useState,
} from 'react';
import { isCloudUser } from 'utils/app';
import {
Input,
@@ -39,9 +39,6 @@ function Retention({
initialValue,
);
const interacted = useRef(false);
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
useEffect(() => {
if (!interacted.current) setSelectedValue(initialValue);
}, [initialValue]);
@@ -94,6 +91,8 @@ function Retention({
return null;
}
const isCloudUserVal = isCloudUser();
return (
<RetentionContainer>
<Row justify="space-between">

View File

@@ -1,18 +1,21 @@
import { Space } from 'antd';
import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications';
import { isUndefined } from 'lodash-es';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState';
import ListAlert from './ListAlert';
function ListAlertRules(): JSX.Element {
const { t } = useTranslation('common');
const location = useLocation();
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
queryFn: getAll,
cacheTime: 0,
@@ -67,6 +70,7 @@ function ListAlertRules(): JSX.Element {
return (
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ListAlert
{...{
allAlertRules: data.payload,

View File

@@ -34,7 +34,6 @@ import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/ut
import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { get, isEmpty, isUndefined } from 'lodash-es';
@@ -83,6 +82,7 @@ import {
WidgetRow,
Widgets,
} from 'types/api/dashboard/getAll';
import { isCloudUser } from 'utils/app';
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
import ImportJSON from './ImportJSON';
@@ -111,8 +111,6 @@ function DashboardsList(): JSX.Element {
setListSortOrder: setSortOrder,
} = useDashboard();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const [searchString, setSearchString] = useState<string>(
sortOrder.search || '',
);
@@ -696,7 +694,7 @@ function DashboardsList(): JSX.Element {
Create and manage dashboards for your workspace.
</Typography.Text>
</Flex>
{isCloudUserVal && (
{isCloudUser() && (
<div className="integrations-container">
<div className="integrations-content">
<RequestDashboardBtn />
@@ -737,7 +735,7 @@ function DashboardsList(): JSX.Element {
<Button
type="text"
className="learn-more"
onClick={(): void => handleContactSupport(isCloudUserVal)}
onClick={(): void => handleContactSupport(isCloudUser())}
>
Contact Support
</Button>

View File

@@ -3,15 +3,13 @@
import './LogsError.styles.scss';
import { Typography } from 'antd';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { ArrowRight } from 'lucide-react';
import { isCloudUser } from 'utils/app';
export default function LogsError(): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const handleContactSupport = (): void => {
if (isCloudUserVal) {
if (isCloudUser()) {
history.push('/support');
} else {
window.open('https://signoz.io/slack', '_blank');

View File

@@ -30,6 +30,7 @@ import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
import dayjs from 'dayjs';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
import { LogTimeRange } from 'hooks/logs/types';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
@@ -103,7 +104,7 @@ function LogsExplorerViews({
// this is to respect the panel type present in the URL rather than defaulting it to list always.
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
const { activeLogId } = useCopyLogLink();
const { activeLogId, onTimeRangeChange } = useCopyLogLink();
const { queryData: pageSize } = useUrlQueryData(
QueryParams.pageSize,
@@ -561,6 +562,7 @@ function LogsExplorerViews({
}, [handleSetConfig, panelTypes]);
useEffect(() => {
const currentParams = data?.params as Omit<LogTimeRange, 'pageSize'>;
const currentData = data?.payload?.data?.newResult?.data?.result || [];
if (currentData.length > 0 && currentData[0].list) {
const currentLogs: ILog[] = currentData[0].list.map((item) => ({
@@ -570,6 +572,11 @@ function LogsExplorerViews({
const newLogs = [...logs, ...currentLogs];
setLogs(newLogs);
onTimeRangeChange({
start: currentParams?.start,
end: currentParams?.end,
pageSize: newLogs.length,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -605,6 +612,7 @@ function LogsExplorerViews({
pageSize,
minTime,
activeLogId,
onTimeRangeChange,
panelType,
selectedView,
]);

View File

@@ -1,40 +0,0 @@
import { Select } from 'antd';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { HardHat } from 'lucide-react';
import { TREEMAP_VIEW_OPTIONS } from './constants';
import { MetricsSearchProps } from './types';
function MetricsSearch({
query,
onChange,
heatmapView,
setHeatmapView,
}: MetricsSearchProps): JSX.Element {
return (
<div className="metrics-search-container">
<div className="metrics-search-options">
<Select
style={{ width: 140 }}
options={TREEMAP_VIEW_OPTIONS}
value={heatmapView}
onChange={setHeatmapView}
/>
<DateTimeSelectionV2
showAutoRefresh={false}
showRefreshText={false}
hideShareModal
/>
</div>
<QueryBuilderSearch
query={query}
onChange={onChange}
suffixIcon={<HardHat size={16} />}
isMetricsExplorer
/>
</div>
);
}
export default MetricsSearch;

View File

@@ -1,86 +0,0 @@
import { LoadingOutlined } from '@ant-design/icons';
import {
Spin,
Table,
TablePaginationConfig,
TableProps,
Typography,
} from 'antd';
import { SorterResult } from 'antd/es/table/interface';
import { useCallback } from 'react';
import { MetricsListItemRowData, MetricsTableProps } from './types';
import { metricsTableColumns } from './utils';
function MetricsTable({
isLoading,
data,
pageSize,
currentPage,
onPaginationChange,
setOrderBy,
totalCount,
}: MetricsTableProps): JSX.Element {
const handleTableChange: TableProps<MetricsListItemRowData>['onChange'] = useCallback(
(
_pagination: TablePaginationConfig,
_filters: Record<string, (string | number | boolean)[] | null>,
sorter:
| SorterResult<MetricsListItemRowData>
| SorterResult<MetricsListItemRowData>[],
): void => {
if ('field' in sorter && sorter.order) {
setOrderBy({
columnName: sorter.field as string,
order: sorter.order === 'ascend' ? 'asc' : 'desc',
});
} else {
setOrderBy({
columnName: 'type',
order: 'asc',
});
}
},
[setOrderBy],
);
return (
<div className="metrics-table-container">
<Table
loading={{
spinning: isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
dataSource={data}
columns={metricsTableColumns}
locale={{
emptyText: (
<div className="no-metrics-message-container">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-metrics-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div>
),
}}
tableLayout="fixed"
onChange={handleTableChange}
pagination={{
current: currentPage,
pageSize,
showSizeChanger: true,
hideOnSinglePage: false,
onChange: onPaginationChange,
total: totalCount,
}}
/>
</div>
);
}
export default MetricsTable;

View File

@@ -1,130 +0,0 @@
import { Group } from '@visx/group';
import { Treemap } from '@visx/hierarchy';
import { Empty, Skeleton, Tooltip } from 'antd';
import { stratify, treemapBinary } from 'd3-hierarchy';
import { useMemo } from 'react';
import { useWindowSize } from 'react-use';
import {
TREEMAP_HEIGHT,
TREEMAP_MARGINS,
TREEMAP_SQUARE_PADDING,
} from './constants';
import { TreemapProps, TreemapTile, TreemapViewType } from './types';
import {
getTreemapTileStyle,
getTreemapTileTextStyle,
transformTreemapData,
} from './utils';
function MetricsTreemap({
viewType,
data,
isLoading,
}: TreemapProps): JSX.Element {
const { width: windowWidth } = useWindowSize();
const treemapWidth = useMemo(
() =>
Math.max(
windowWidth - TREEMAP_MARGINS.LEFT - TREEMAP_MARGINS.RIGHT - 70,
300,
),
[windowWidth],
);
const treemapData = useMemo(() => {
const extracedTreemapData =
(viewType === TreemapViewType.CARDINALITY
? data?.data?.[TreemapViewType.CARDINALITY]
: data?.data?.[TreemapViewType.DATAPOINTS]) || [];
return transformTreemapData(extracedTreemapData, viewType);
}, [data, viewType]);
const transformedTreemapData = stratify<TreemapTile>()
.id((d) => d.id)
.parentId((d) => d.parent)(treemapData)
.sum((d) => d.size ?? 0);
const xMax = treemapWidth - TREEMAP_MARGINS.LEFT - TREEMAP_MARGINS.RIGHT;
const yMax = TREEMAP_HEIGHT - TREEMAP_MARGINS.TOP - TREEMAP_MARGINS.BOTTOM;
if (isLoading) {
return (
<Skeleton style={{ width: treemapWidth, height: TREEMAP_HEIGHT }} active />
);
}
if (
!data ||
!data.data ||
data?.status === 'error' ||
(data?.status === 'success' && !data?.data?.[viewType])
) {
return (
<Empty
description="No metrics found"
style={{ width: treemapWidth, height: TREEMAP_HEIGHT, paddingTop: 30 }}
/>
);
}
return (
<div className="metrics-treemap">
<svg width={treemapWidth} height={TREEMAP_HEIGHT}>
<rect
width={treemapWidth}
height={TREEMAP_HEIGHT}
rx={14}
fill="transparent"
/>
<Treemap<TreemapTile>
top={TREEMAP_MARGINS.TOP}
root={transformedTreemapData}
size={[xMax, yMax]}
tile={treemapBinary}
round
>
{(treemap): JSX.Element => (
<Group>
{treemap
.descendants()
.reverse()
.map((node, i) => {
const nodeWidth = node.x1 - node.x0 - TREEMAP_SQUARE_PADDING;
const nodeHeight = node.y1 - node.y0 - TREEMAP_SQUARE_PADDING;
return (
<Group
// eslint-disable-next-line react/no-array-index-key
key={node.data.id || `node-${i}`}
top={node.y0 + TREEMAP_MARGINS.TOP}
left={node.x0 + TREEMAP_MARGINS.LEFT}
>
{node.depth > 0 && (
<Tooltip
title={`${node.data.id}: ${node.data.displayValue}%`}
placement="top"
>
<foreignObject
width={nodeWidth}
height={nodeHeight}
style={getTreemapTileStyle(node.data)}
>
<div style={getTreemapTileTextStyle()}>
{`${node.data.displayValue}%`}
</div>
</foreignObject>
</Tooltip>
)}
</Group>
);
})}
</Group>
)}
</Treemap>
</svg>
</div>
);
}
export default MetricsTreemap;

View File

@@ -1,223 +0,0 @@
.metrics-explorer-summary-tab {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
.metrics-search-container {
display: flex;
flex-direction: column;
gap: 16px;
.metrics-search-options {
display: flex;
justify-content: space-between;
}
}
.metrics-table-container {
margin-left: -16px;
margin-right: -16px;
.ant-table {
max-height: 500px;
overflow-y: auto;
.ant-table-thead > tr > th {
padding: 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
background: var(--bg-ink-500);
border-bottom: none;
color: var(--Vanilla-400, #c0c1c3);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
&::before {
background-color: transparent;
}
}
.ant-table-thead > tr > th:has(.metric-name-column-header) {
background: var(--bg-ink-400);
}
.ant-table-cell {
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--bg-vanilla-100);
background: var(--bg-ink-500);
}
.ant-table-cell:has(.metric-name-column-value) {
background: var(--bg-ink-400);
}
.metric-name-column-value {
color: var(--bg-vanilla-100);
font-family: 'Geist Mono';
font-style: normal;
font-weight: 600;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.status-cell {
.active-tag {
color: var(--bg-forest-500);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
}
.progress-container {
.ant-progress-bg {
height: 8px !important;
border-radius: 4px;
}
}
.ant-table-tbody > tr:hover > td {
background: rgba(255, 255, 255, 0.04);
}
.ant-table-cell:first-child {
text-align: justify;
}
.ant-table-cell:nth-child(2) {
padding-left: 16px;
padding-right: 16px;
}
.ant-table-cell:nth-child(n + 3) {
padding-right: 24px;
}
.column-header-right {
text-align: right;
}
.column-header-left {
text-align: left;
}
.ant-table-tbody > tr > td {
border-bottom: none;
}
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
background-color: transparent;
}
.ant-empty-normal {
visibility: hidden;
}
}
.ant-pagination {
position: fixed;
bottom: 0;
width: calc(100% - 64px);
background: var(--bg-ink-500);
padding: 16px;
margin: 0;
// this is to offset intercom icon till we improve the design
right: 20px;
.ant-pagination-item {
border-radius: 4px;
&-active {
background: var(--bg-robin-500);
border-color: var(--bg-robin-500);
a {
color: var(--bg-ink-500) !important;
}
}
}
}
}
.no-metrics-message-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
min-height: 400px;
gap: 16px;
padding-top: 32px;
}
.metric-type-renderer {
border-radius: 50px;
height: 24px;
width: fit-content;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4px;
text-transform: uppercase;
font-size: 12px;
padding: 5px 10px;
align-self: flex-end;
}
}
.lightMode {
.metrics-table-container {
.ant-table {
.ant-table-thead > tr > th {
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
}
.ant-table-thead > tr > th:has(.metric-name-column-header) {
background: var(--bg-vanilla-100);
}
.ant-table-cell {
background: var(--bg-vanilla-100);
color: var(--bg-ink-500);
}
.ant-table-cell:has(.metric-name-column-value) {
background: var(--bg-vanilla-100);
}
.metric-name-column-value {
color: var(--bg-ink-300);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
.ant-pagination {
background: var(--bg-vanilla-100);
.ant-pagination-item {
&-active {
background: var(--bg-robin-500);
border-color: var(--bg-robin-500);
a {
color: var(--bg-vanilla-100) !important;
}
}
}
}
}
}

View File

@@ -1,162 +1,10 @@
import './Summary.styles.scss';
import * as Sentry from '@sentry/react';
import { usePageSize } from 'container/InfraMonitoringK8s/utils';
import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList';
import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime';
import MetricsSearch from './MetricsSearch';
import MetricsTable from './MetricsTable';
import MetricsTreemap from './MetricsTreemap';
import { OrderByPayload, TreemapViewType } from './types';
import {
convertNanoToMilliseconds,
formatDataForMetricsTable,
getMetricsListQuery,
} from './utils';
function Summary(): JSX.Element {
const { pageSize, setPageSize } = usePageSize('metricsExplorer');
const [currentPage, setCurrentPage] = useState(1);
const [orderBy, setOrderBy] = useState<OrderByPayload>({
columnName: 'type',
order: 'asc',
});
const [heatmapView, setHeatmapView] = useState<TreemapViewType>(
TreemapViewType.CARDINALITY,
);
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { currentQuery } = useQueryBuilder();
const queryFilters = useMemo(
() =>
currentQuery?.builder?.queryData[0]?.filters || {
items: [],
op: 'and',
},
[currentQuery],
);
const { handleChangeQueryData } = useQueryOperations({
index: 0,
query: currentQuery.builder.queryData[0],
entityVersion: '',
});
const metricsListQuery = useMemo(() => {
const baseQuery = getMetricsListQuery();
return {
...baseQuery,
limit: pageSize,
offset: (currentPage - 1) * pageSize,
filters: queryFilters,
start: convertNanoToMilliseconds(minTime),
end: convertNanoToMilliseconds(maxTime),
orderBy,
};
}, [queryFilters, minTime, maxTime, orderBy, pageSize, currentPage]);
const metricsTreemapQuery = useMemo(
() => ({
limit: 100,
filters: queryFilters,
treemap: heatmapView,
start: convertNanoToMilliseconds(minTime),
end: convertNanoToMilliseconds(maxTime),
}),
[queryFilters, heatmapView, minTime, maxTime],
);
const {
data: metricsData,
isLoading: isMetricsLoading,
isFetching: isMetricsFetching,
} = useGetMetricsList(metricsListQuery, {
enabled: !!metricsListQuery,
});
const {
data: treeMapData,
isLoading: isTreeMapLoading,
isFetching: isTreeMapFetching,
} = useGetMetricsTreeMap(metricsTreemapQuery, {
enabled: !!metricsTreemapQuery,
});
const handleFilterChange = useCallback(
(value: TagFilter) => {
handleChangeQueryData('filters', value);
setCurrentPage(1);
},
[handleChangeQueryData],
);
const updatedCurrentQuery = useMemo(
() => ({
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
aggregateOperator: 'noop',
aggregateAttribute: {
...currentQuery.builder.queryData[0].aggregateAttribute,
},
},
],
},
}),
[currentQuery],
);
const searchQuery = updatedCurrentQuery?.builder?.queryData[0] || null;
const onPaginationChange = (page: number, pageSize: number): void => {
setCurrentPage(page);
setPageSize(pageSize);
};
const formattedMetricsData = useMemo(
() => formatDataForMetricsTable(metricsData?.payload?.data?.metrics || []),
[metricsData],
);
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="metrics-explorer-summary-tab">
<MetricsSearch
query={searchQuery}
onChange={handleFilterChange}
heatmapView={heatmapView}
setHeatmapView={setHeatmapView}
/>
<MetricsTreemap
data={treeMapData?.payload}
isLoading={isTreeMapLoading || isTreeMapFetching}
viewType={heatmapView}
/>
<MetricsTable
isLoading={isMetricsLoading || isMetricsFetching}
data={formattedMetricsData}
pageSize={pageSize}
currentPage={currentPage}
onPaginationChange={onPaginationChange}
setOrderBy={setOrderBy}
totalCount={metricsData?.payload?.data.total || 0}
/>
</div>
Summary
</Sentry.ErrorBoundary>
);
}

View File

@@ -1,26 +0,0 @@
import { MetricType } from 'api/metricsExplorer/getMetricsList';
import { TreemapViewType } from './types';
export const METRICS_TABLE_PAGE_SIZE = 10;
export const TREEMAP_VIEW_OPTIONS: {
value: TreemapViewType;
label: string;
}[] = [
{ value: TreemapViewType.CARDINALITY, label: 'Cardinality' },
{ value: TreemapViewType.DATAPOINTS, label: 'Datapoints' },
];
export const TREEMAP_HEIGHT = 300;
export const TREEMAP_SQUARE_PADDING = 5;
export const TREEMAP_MARGINS = { TOP: 10, LEFT: 10, RIGHT: 10, BOTTOM: 10 };
export const METRIC_TYPE_LABEL_MAP = {
[MetricType.SUM]: 'Sum',
[MetricType.GAUGE]: 'Gauge',
[MetricType.HISTOGRAM]: 'Histogram',
[MetricType.SUMMARY]: 'Summary',
[MetricType.EXPONENTIAL_HISTOGRAM]: 'Exp. Histogram',
};

View File

@@ -1,56 +0,0 @@
import { MetricsTreeMapResponse } from 'api/metricsExplorer/getMetricsTreeMap';
import React, { Dispatch, SetStateAction } from 'react';
import {
IBuilderQuery,
TagFilter,
} from 'types/api/queryBuilder/queryBuilderData';
export interface MetricsTableProps {
isLoading: boolean;
data: MetricsListItemRowData[];
pageSize: number;
currentPage: number;
onPaginationChange: (page: number, pageSize: number) => void;
setOrderBy: Dispatch<SetStateAction<OrderByPayload>>;
totalCount: number;
}
export interface MetricsSearchProps {
query: IBuilderQuery;
onChange: (value: TagFilter) => void;
heatmapView: TreemapViewType;
setHeatmapView: (value: TreemapViewType) => void;
}
export interface TreemapProps {
data: MetricsTreeMapResponse | null | undefined;
isLoading: boolean;
viewType: TreemapViewType;
}
export interface OrderByPayload {
columnName: string;
order: 'asc' | 'desc';
}
export interface MetricsListItemRowData {
key: string;
metric_name: React.ReactNode;
description: React.ReactNode;
metric_type: React.ReactNode;
unit: React.ReactNode;
samples: React.ReactNode;
timeseries: React.ReactNode;
}
export enum TreemapViewType {
CARDINALITY = 'timeseries',
DATAPOINTS = 'samples',
}
export interface TreemapTile {
id: string;
size: number;
displayValue: number | string | null;
parent: string | null;
}

View File

@@ -1,241 +0,0 @@
import { Color } from '@signozhq/design-tokens';
import { Tooltip, Typography } from 'antd';
import { ColumnType } from 'antd/es/table';
import {
MetricsListItemData,
MetricsListPayload,
MetricType,
} from 'api/metricsExplorer/getMetricsList';
import {
CardinalityData,
DatapointsData,
} from 'api/metricsExplorer/getMetricsTreeMap';
import {
BarChart,
BarChart2,
BarChartHorizontal,
Diff,
Gauge,
} from 'lucide-react';
import { useMemo } from 'react';
import { METRIC_TYPE_LABEL_MAP } from './constants';
import { MetricsListItemRowData, TreemapTile, TreemapViewType } from './types';
export const metricsTableColumns: ColumnType<MetricsListItemRowData>[] = [
{
title: <div className="metric-name-column-header">METRIC</div>,
dataIndex: 'metric_name',
width: 400,
sorter: true,
className: 'metric-name-column-header',
render: (value: string): React.ReactNode => (
<div className="metric-name-column-value">{value}</div>
),
},
{
title: 'DESCRIPTION',
dataIndex: 'description',
width: 400,
},
{
title: 'TYPE',
dataIndex: 'metric_type',
sorter: true,
width: 150,
},
{
title: 'UNIT',
dataIndex: 'unit',
width: 150,
},
{
title: 'DATAPOINTS',
dataIndex: TreemapViewType.DATAPOINTS,
width: 150,
sorter: true,
},
{
title: 'CARDINALITY',
dataIndex: TreemapViewType.CARDINALITY,
width: 150,
sorter: true,
},
];
export const getMetricsListQuery = (): MetricsListPayload => ({
filters: {
items: [],
op: 'and',
},
orderBy: { columnName: 'metric_name', order: 'asc' },
});
function MetricTypeRenderer({ type }: { type: MetricType }): JSX.Element {
const [icon, color] = useMemo(() => {
switch (type) {
case MetricType.SUM:
return [
<Diff key={type} size={12} color={Color.BG_ROBIN_500} />,
Color.BG_ROBIN_500,
];
case MetricType.GAUGE:
return [
<Gauge key={type} size={12} color={Color.BG_SAKURA_500} />,
Color.BG_SAKURA_500,
];
case MetricType.HISTOGRAM:
return [
<BarChart2 key={type} size={12} color={Color.BG_SIENNA_500} />,
Color.BG_SIENNA_500,
];
case MetricType.SUMMARY:
return [
<BarChartHorizontal key={type} size={12} color={Color.BG_FOREST_500} />,
Color.BG_FOREST_500,
];
case MetricType.EXPONENTIAL_HISTOGRAM:
return [
<BarChart key={type} size={12} color={Color.BG_AQUA_500} />,
Color.BG_AQUA_500,
];
default:
return [null, ''];
}
}, [type]);
return (
<div
className="metric-type-renderer"
style={{
backgroundColor: `${color}33`,
border: `1px solid ${color}`,
color,
}}
>
{icon}
<Typography.Text style={{ color, fontSize: 12 }}>
{METRIC_TYPE_LABEL_MAP[type]}
</Typography.Text>
</div>
);
}
function ValidateRowValueWrapper({
value,
children,
}: {
value: string | number | null;
children: React.ReactNode;
}): JSX.Element {
if (!value) {
return <div>-</div>;
}
return <div>{children}</div>;
}
export const formatDataForMetricsTable = (
data: MetricsListItemData[],
): MetricsListItemRowData[] =>
data.map((metric) => ({
key: metric.metric_name,
metric_name: (
<ValidateRowValueWrapper value={metric.metric_name}>
<Tooltip title={metric.metric_name}>{metric.metric_name}</Tooltip>
</ValidateRowValueWrapper>
),
description: (
<ValidateRowValueWrapper value={metric.description}>
<Tooltip title={metric.description}>{metric.description}</Tooltip>
</ValidateRowValueWrapper>
),
metric_type: <MetricTypeRenderer type={metric.type} />,
unit: (
<ValidateRowValueWrapper value={metric.unit}>
{metric.unit}
</ValidateRowValueWrapper>
),
[TreemapViewType.DATAPOINTS]: (
<ValidateRowValueWrapper value={metric[TreemapViewType.DATAPOINTS]}>
{metric[TreemapViewType.DATAPOINTS]}
</ValidateRowValueWrapper>
),
[TreemapViewType.CARDINALITY]: (
<ValidateRowValueWrapper value={metric[TreemapViewType.CARDINALITY]}>
{metric[TreemapViewType.CARDINALITY]}
</ValidateRowValueWrapper>
),
}));
export const transformTreemapData = (
data: CardinalityData[] | DatapointsData[],
viewType: TreemapViewType,
): TreemapTile[] => {
const totalSize = (data as (CardinalityData | DatapointsData)[]).reduce(
(acc: number, item: CardinalityData | DatapointsData) =>
acc + item.percentage,
0,
);
const children = data.map((item) => ({
id: item.metric_name,
size: totalSize > 0 ? Number((item.percentage / totalSize).toFixed(2)) : 0,
displayValue: Number(item.percentage).toFixed(2),
parent: viewType,
}));
return [
{
id: viewType,
size: 0,
parent: null,
displayValue: null,
},
...children,
];
};
const getTreemapTileBackgroundColor = (node: TreemapTile): string => {
const size = node.size * 10;
if (size > 0.8) {
return Color.BG_AMBER_600;
}
if (size > 0.6) {
return Color.BG_AMBER_500;
}
if (size > 0.4) {
return Color.BG_AMBER_400;
}
if (size > 0.2) {
return Color.BG_AMBER_300;
}
if (size > 0.1) {
return Color.BG_AMBER_200;
}
return Color.BG_AMBER_100;
};
export const getTreemapTileStyle = (
node: TreemapTile,
): React.CSSProperties => ({
overflow: 'visible',
cursor: 'pointer',
backgroundColor: getTreemapTileBackgroundColor(node),
borderRadius: 4,
});
export const getTreemapTileTextStyle = (): React.CSSProperties => ({
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
fontWeight: 'bold',
color: Color.TEXT_SLATE_400,
textAlign: 'center',
padding: '4px',
});
export const convertNanoToMilliseconds = (time: number): number =>
Math.floor(time / 1000000);

View File

@@ -3,10 +3,10 @@ import './NoLogs.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { ArrowUpRight } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder';
import { isCloudUser } from 'utils/app';
import DOCLINKS from 'utils/docLinks';
export default function NoLogs({
@@ -14,15 +14,14 @@ export default function NoLogs({
}: {
dataSource: DataSource;
}): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const cloudUser = isCloudUser();
const handleLinkClick = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
): void => {
e.preventDefault();
e.stopPropagation();
if (isCloudUserVal) {
if (cloudUser) {
if (dataSource === DataSource.TRACES) {
logEvent('Traces Explorer: Navigate to onboarding', {});
} else if (dataSource === DataSource.LOGS) {

View File

@@ -19,6 +19,10 @@ jest.mock('hooks/useNotifications', () => ({
})),
}));
window.analytics = {
track: jest.fn(),
};
describe('Onboarding invite team member flow', () => {
it('initial render and get started page', async () => {
const { findByText } = render(

View File

@@ -281,7 +281,7 @@ function Members(): JSX.Element {
const { joinedOn } = record;
return (
<Typography>
{dayjs(joinedOn).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)}
{dayjs.unix(Number(joinedOn)).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)}
</Typography>
);
},

View File

@@ -38,7 +38,7 @@ const useSampleLogs = ({
filters: filter || initialFilters,
aggregateOperator: LogsAggregatorOperator.NOOP,
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
pageSize: count || DEFAULT_SAMPLE_LOGS_COUNT,
limit: count || DEFAULT_SAMPLE_LOGS_COUNT,
};
return q;
}, [count, filter]);

View File

@@ -75,7 +75,6 @@ function QueryBuilderSearch({
placeholder,
suffixIcon,
isInfraMonitoring,
isMetricsExplorer,
disableNavigationShortcuts,
entity,
}: QueryBuilderSearchProps): JSX.Element {
@@ -114,7 +113,6 @@ function QueryBuilderSearch({
isLogsExplorerPage,
isInfraMonitoring,
entity,
isMetricsExplorer,
);
const [isOpen, setIsOpen] = useState<boolean>(false);
@@ -131,7 +129,6 @@ function QueryBuilderSearch({
isLogsExplorerPage,
isInfraMonitoring,
entity,
isMetricsExplorer,
);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
@@ -140,12 +137,12 @@ function QueryBuilderSearch({
const toggleEditMode = useCallback(
(value: boolean) => {
// Editing mode is required only in infra monitoring or metrics explorer
if (isInfraMonitoring || isMetricsExplorer) {
// Editing mode is required only in infra monitoring mode
if (isInfraMonitoring) {
setIsEditingTag(value);
}
},
[isInfraMonitoring, isMetricsExplorer],
[isInfraMonitoring],
);
const onTagRender = ({
@@ -171,7 +168,7 @@ function QueryBuilderSearch({
updateTag(value);
// Editing starts
toggleEditMode(true);
if (isInfraMonitoring || isMetricsExplorer) {
if (isInfraMonitoring) {
setSearchValue(value);
} else {
handleSearch(value);
@@ -243,11 +240,8 @@ function QueryBuilderSearch({
);
const isMetricsDataSource = useMemo(
() =>
query.dataSource === DataSource.METRICS &&
!isInfraMonitoring &&
!isMetricsExplorer,
[query.dataSource, isInfraMonitoring, isMetricsExplorer],
() => query.dataSource === DataSource.METRICS && !isInfraMonitoring,
[query.dataSource, isInfraMonitoring],
);
const fetchValueDataType = (value: unknown, operator: string): DataTypes => {
@@ -297,8 +291,8 @@ function QueryBuilderSearch({
};
});
// If in infra monitoring or metrics explorer, only run the onChange query when editing is finsished.
if (isInfraMonitoring || isMetricsExplorer) {
// If in infra monitoring, only run the onChange query when editing is finsished.
if (isInfraMonitoring) {
if (!isEditingTag) {
onChange(initialTagFilters);
}
@@ -504,7 +498,6 @@ interface QueryBuilderSearchProps {
isInfraMonitoring?: boolean;
disableNavigationShortcuts?: boolean;
entity?: K8sCategory | null;
isMetricsExplorer?: boolean;
}
QueryBuilderSearch.defaultProps = {
@@ -515,7 +508,6 @@ QueryBuilderSearch.defaultProps = {
isInfraMonitoring: false,
disableNavigationShortcuts: false,
entity: null,
isMetricsExplorer: false,
};
export interface CustomTagProps {

View File

@@ -5,7 +5,6 @@ import { ENTITY_VERSION_V4 } from 'constants/app';
import { MAX_RPS_LIMIT } from 'constants/global';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import { useGetQueriesRange } from 'hooks/queryBuilder/useGetQueriesRange';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useNotifications } from 'hooks/useNotifications';
import { useAppContext } from 'providers/App/App';
import { useEffect, useMemo, useState } from 'react';
@@ -15,6 +14,7 @@ import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { ServicesList } from 'types/api/metrics/getService';
import { GlobalReducer } from 'types/reducer/globalTime';
import { isCloudUser } from 'utils/app';
import { getTotalRPS } from 'utils/services';
import { getColumns } from '../Columns/ServiceColumn';
@@ -34,7 +34,7 @@ function ServiceMetricTable({
const { t: getText } = useTranslation(['services']);
const { licenses, isFetchingLicenses } = useAppContext();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const queries = useGetQueriesRange(queryRangeRequestData, ENTITY_VERSION_V4, {
queryKey: [

View File

@@ -3,11 +3,11 @@ import { Flex, Typography } from 'antd';
import { ResizeTable } from 'components/ResizeTable';
import { MAX_RPS_LIMIT } from 'constants/global';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useAppContext } from 'providers/App/App';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { isCloudUser } from 'utils/app';
import { getTotalRPS } from 'utils/services';
import { getColumns } from '../Columns/ServiceColumn';
@@ -22,7 +22,7 @@ function ServiceTraceTable({
const { t: getText } = useTranslation(['services']);
const { licenses, isFetchingLicenses } = useAppContext();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const tableColumns = useMemo(() => getColumns(search, false), [search]);
useEffect(() => {

View File

@@ -11,7 +11,6 @@ import ROUTES from 'constants/routes';
import { GlobalShortcuts } from 'constants/shortcuts/globalShortcuts';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { LICENSE_PLAN_KEY, LICENSE_PLAN_STATUS } from 'hooks/useLicense';
import history from 'lib/history';
import {
@@ -29,7 +28,7 @@ import { AppState } from 'store/reducers';
import { License } from 'types/api/licenses/def';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { checkVersionState } from 'utils/app';
import { checkVersionState, isCloudUser, isEECloudUser } from 'utils/app';
import { routeConfig } from './config';
import { getQueryString } from './helper';
@@ -87,10 +86,7 @@ function SideNav(): JSX.Element {
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const {
isCloudUser: isCloudUserVal,
isEECloudUser: isEECloudUserVal,
} = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const { t } = useTranslation('');
@@ -279,7 +275,7 @@ function SideNav(): JSX.Element {
let updatedUserManagementItems: UserManagementMenuItems[] = [
manageLicenseMenuItem,
];
if (isCloudUserVal || isEECloudUserVal) {
if (isCloudUserVal || isEECloudUser()) {
const isOnboardingEnabled =
featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING)
?.active || false;
@@ -334,7 +330,6 @@ function SideNav(): JSX.Element {
featureFlags,
isCloudUserVal,
isCurrentVersionError,
isEECloudUserVal,
isLatestVersion,
licenses?.licenses,
onClickVersionHandler,

View File

@@ -30,7 +30,7 @@ import getTimeString from 'lib/getTimeString';
import { isObject } from 'lodash-es';
import { Check, Copy, Info, Send, Undo } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
import { connect, useDispatch, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
@@ -314,6 +314,11 @@ function DateTimeSelection({
return `Refreshed ${secondsDiff} sec ago`;
}, [maxTime, minTime, selectedTime]);
const isLogsExplorerPage = useMemo(
() => location.pathname === ROUTES.LOGS_EXPLORER,
[location.pathname],
);
const onSelectHandler = useCallback(
(value: Time | CustomTimeType): void => {
if (isModalTimeSelection) {
@@ -342,13 +347,15 @@ function DateTimeSelection({
return;
}
urlQuery.delete('startTime');
urlQuery.delete('endTime');
if (!isLogsExplorerPage) {
urlQuery.delete('startTime');
urlQuery.delete('endTime');
urlQuery.set(QueryParams.relativeTime, value);
urlQuery.set(QueryParams.relativeTime, value);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
}
// For logs explorer - time range handling is managed in useCopyLogLink.ts:52
@@ -361,6 +368,7 @@ function DateTimeSelection({
},
[
initQueryBuilderData,
isLogsExplorerPage,
isModalTimeSelection,
location.pathname,
onTimeChange,
@@ -430,14 +438,16 @@ function DateTimeSelection({
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
urlQuery.set(
QueryParams.startTime,
startTime?.toDate().getTime().toString(),
);
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
if (!isLogsExplorerPage) {
urlQuery.set(
QueryParams.startTime,
startTime?.toDate().getTime().toString(),
);
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
}
}
}
};
@@ -456,13 +466,15 @@ function DateTimeSelection({
setIsValidteRelativeTime(true);
urlQuery.delete('startTime');
urlQuery.delete('endTime');
if (!isLogsExplorerPage) {
urlQuery.delete('startTime');
urlQuery.delete('endTime');
urlQuery.set(QueryParams.relativeTime, dateTimeStr);
urlQuery.set(QueryParams.relativeTime, dateTimeStr);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
}
if (!stagedQuery) {
return;

View File

@@ -0,0 +1,40 @@
import { useAppContext } from 'providers/App/App';
import { useCallback } from 'react';
import { extractDomain } from 'utils/app';
const useAnalytics = (): any => {
const { user } = useAppContext();
// Segment Page View - analytics.page([category], [name], [properties], [options], [callback]);
const trackPageView = useCallback(
(pageName: string): void => {
if (user && user.email) {
window.analytics.page(null, pageName, {
userId: user.email,
});
}
},
[user],
);
const trackEvent = (
eventName: string,
properties?: Record<string, unknown>,
): void => {
if (user && user.email) {
const context = {
context: {
groupId: extractDomain(user?.email),
},
};
const updatedProperties = { ...properties };
updatedProperties.userId = user.email;
window.analytics.track(eventName, properties, context);
}
};
return { trackPageView, trackEvent };
};
export default useAnalytics;

View File

@@ -5,6 +5,7 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
export type LogTimeRange = {
start: number;
end: number;
pageSize: number;
};
export type UseCopyLogLink = {
@@ -12,6 +13,7 @@ export type UseCopyLogLink = {
isLogsExplorerPage: boolean;
activeLogId: string | null;
onLogCopy: MouseEventHandler<HTMLElement>;
onTimeRangeChange: (newTimeRange: LogTimeRange | null) => void;
};
export type UseActiveLog = {

View File

@@ -3,6 +3,7 @@ import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import useUrlQueryData from 'hooks/useUrlQueryData';
import history from 'lib/history';
import {
MouseEventHandler,
useCallback,
@@ -17,7 +18,7 @@ import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { HIGHLIGHTED_DELAY } from './configs';
import { UseCopyLogLink } from './types';
import { LogTimeRange, UseCopyLogLink } from './types';
export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
const urlQuery = useUrlQuery();
@@ -30,8 +31,27 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
null,
);
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
const { selectedTime, minTime, maxTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const onTimeRangeChange = useCallback(
(newTimeRange: LogTimeRange | null): void => {
if (selectedTime !== 'custom') {
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.set(QueryParams.relativeTime, selectedTime);
} else {
urlQuery.set(QueryParams.startTime, newTimeRange?.start.toString() || '');
urlQuery.set(QueryParams.endTime, newTimeRange?.end.toString() || '');
urlQuery.delete(QueryParams.relativeTime);
}
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
},
[pathname, urlQuery, selectedTime],
);
const isActiveLog = useMemo(() => activeLogId === logId, [activeLogId, logId]);
@@ -81,5 +101,6 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
isLogsExplorerPage,
activeLogId,
onLogCopy,
onTimeRangeChange,
};
};

View File

@@ -1,47 +0,0 @@
import {
getMetricsList,
MetricsListPayload,
MetricsListResponse,
} from 'api/metricsExplorer/getMetricsList';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
type UseGetMetricsList = (
requestData: MetricsListPayload,
options?: UseQueryOptions<
SuccessResponse<MetricsListResponse> | ErrorResponse,
Error
>,
headers?: Record<string, string>,
) => UseQueryResult<
SuccessResponse<MetricsListResponse> | ErrorResponse,
Error
>;
export const useGetMetricsList: UseGetMetricsList = (
requestData,
options,
headers,
) => {
const queryKey = useMemo(() => {
if (options?.queryKey && Array.isArray(options.queryKey)) {
return [...options.queryKey];
}
if (options?.queryKey && typeof options.queryKey === 'string') {
return options.queryKey;
}
return [REACT_QUERY_KEY.GET_METRICS_LIST, requestData];
}, [options?.queryKey, requestData]);
return useQuery<SuccessResponse<MetricsListResponse> | ErrorResponse, Error>({
queryFn: ({ signal }) => getMetricsList(requestData, signal, headers),
...options,
queryKey,
});
};

View File

@@ -1,45 +0,0 @@
import {
getMetricsListFilterKeys,
MetricsListFilterKeysResponse,
} from 'api/metricsExplorer/getMetricsListFilterKeys';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
type UseGetMetricsListFilterKeys = (
options?: UseQueryOptions<
SuccessResponse<MetricsListFilterKeysResponse> | ErrorResponse,
Error
>,
headers?: Record<string, string>,
) => UseQueryResult<
SuccessResponse<MetricsListFilterKeysResponse> | ErrorResponse,
Error
>;
export const useGetMetricsListFilterKeys: UseGetMetricsListFilterKeys = (
options,
headers,
) => {
const queryKey = useMemo(() => {
if (options?.queryKey && Array.isArray(options.queryKey)) {
return [...options.queryKey];
}
if (options?.queryKey && typeof options.queryKey === 'string') {
return options.queryKey;
}
return [REACT_QUERY_KEY.GET_METRICS_LIST_FILTER_KEYS];
}, [options?.queryKey]);
return useQuery<
SuccessResponse<MetricsListFilterKeysResponse> | ErrorResponse,
Error
>({
queryFn: ({ signal }) => getMetricsListFilterKeys(signal, headers),
...options,
queryKey,
});
};

View File

@@ -1,50 +0,0 @@
import {
getMetricsTreeMap,
MetricsTreeMapPayload,
MetricsTreeMapResponse,
} from 'api/metricsExplorer/getMetricsTreeMap';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
type UseGetMetricsTreeMap = (
requestData: MetricsTreeMapPayload,
options?: UseQueryOptions<
SuccessResponse<MetricsTreeMapResponse> | ErrorResponse,
Error
>,
headers?: Record<string, string>,
) => UseQueryResult<
SuccessResponse<MetricsTreeMapResponse> | ErrorResponse,
Error
>;
export const useGetMetricsTreeMap: UseGetMetricsTreeMap = (
requestData,
options,
headers,
) => {
const queryKey = useMemo(() => {
if (options?.queryKey && Array.isArray(options.queryKey)) {
return [...options.queryKey];
}
if (options?.queryKey && typeof options.queryKey === 'string') {
return options.queryKey;
}
return [REACT_QUERY_KEY.GET_METRICS_TREE_MAP, requestData];
}, [options?.queryKey, requestData]);
return useQuery<
SuccessResponse<MetricsTreeMapResponse> | ErrorResponse,
Error
>({
queryFn: ({ signal }) => getMetricsTreeMap(requestData, signal, headers),
...options,
queryKey,
});
};

View File

@@ -31,7 +31,6 @@ export const useAutoComplete = (
shouldUseSuggestions?: boolean,
isInfraMonitoring?: boolean,
entity?: K8sCategory | null,
isMetricsExplorer?: boolean,
): IAutoComplete => {
const [searchValue, setSearchValue] = useState<string>('');
const [searchKey, setSearchKey] = useState<string>('');
@@ -43,7 +42,6 @@ export const useAutoComplete = (
shouldUseSuggestions,
isInfraMonitoring,
entity,
isMetricsExplorer,
);
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);

View File

@@ -1,5 +1,4 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { getMetricsListFilterValues } from 'api/metricsExplorer/getMetricsListFilterValues';
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import {
@@ -11,7 +10,6 @@ import {
getTagToken,
isInNInOperator,
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { useGetMetricsListFilterKeys } from 'hooks/metricsExplorer/useGetMetricsListFilterKeys';
import useDebounceValue from 'hooks/useDebounce';
import { cloneDeep, isEqual, uniqWith, unset } from 'lodash-es';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -52,7 +50,6 @@ export const useFetchKeysAndValues = (
shouldUseSuggestions?: boolean,
isInfraMonitoring?: boolean,
entity?: K8sCategory | null,
isMetricsExplorer?: boolean,
): IuseFetchKeysAndValues => {
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
const [exampleQueries, setExampleQueries] = useState<TagFilter[]>([]);
@@ -101,17 +98,10 @@ export const useFetchKeysAndValues = (
const isQueryEnabled = useMemo(
() =>
query.dataSource === DataSource.METRICS &&
!isInfraMonitoring &&
!isMetricsExplorer
query.dataSource === DataSource.METRICS && !isInfraMonitoring
? !!query.dataSource && !!query.aggregateAttribute.dataType
: true,
[
isInfraMonitoring,
isMetricsExplorer,
query.aggregateAttribute.dataType,
query.dataSource,
],
[isInfraMonitoring, query.aggregateAttribute.dataType, query.dataSource],
);
const { data, isFetching, status } = useGetAggregateKeys(
@@ -149,14 +139,6 @@ export const useFetchKeysAndValues = (
},
);
const {
data: metricsListFilterKeysData,
isFetching: isFetchingMetricsListFilterKeys,
status: fetchingMetricsListFilterKeysStatus,
} = useGetMetricsListFilterKeys({
enabled: isMetricsExplorer && isQueryEnabled && !shouldUseSuggestions,
});
/**
* Fetches the options to be displayed based on the selected value
* @param value - the selected value
@@ -200,15 +182,6 @@ export const useFetchKeysAndValues = (
: tagValue?.toString() ?? '',
});
payload = response.payload;
} else if (isMetricsExplorer) {
const response = await getMetricsListFilterValues({
searchText: searchKey,
filterKey: filterAttributeKey?.key ?? tagKey,
filterAttributeKeyDataType:
filterAttributeKey?.dataType ?? DataTypes.EMPTY,
limit: 10,
});
payload = response.payload?.data;
} else {
const response = await getAttributesValues({
aggregateOperator: query.aggregateOperator,
@@ -265,32 +238,6 @@ export const useFetchKeysAndValues = (
}
}, [data?.payload?.attributeKeys, status]);
useEffect(() => {
if (
isMetricsExplorer &&
fetchingMetricsListFilterKeysStatus === 'success' &&
!isFetchingMetricsListFilterKeys &&
metricsListFilterKeysData?.payload?.data?.attributeKeys
) {
setKeys(metricsListFilterKeysData.payload.data.attributeKeys);
setSourceKeys((prevState) =>
uniqWith(
[
...(metricsListFilterKeysData.payload.data.attributeKeys ?? []),
...prevState,
],
isEqual,
),
);
}
}, [
metricsListFilterKeysData?.payload?.data?.attributeKeys,
fetchingMetricsListFilterKeysStatus,
isMetricsExplorer,
metricsListFilterKeysData,
isFetchingMetricsListFilterKeys,
]);
useEffect(() => {
if (
fetchingSuggestionsStatus === 'success' &&

View File

@@ -1,15 +0,0 @@
import { useAppContext } from 'providers/App/App';
import { LicensePlatform } from 'types/api/licensesV3/getActive';
export const useGetTenantLicense = (): {
isCloudUser: boolean;
isEECloudUser: boolean;
} => {
const { activeLicenseV3 } = useAppContext();
return {
isCloudUser: activeLicenseV3?.platform === LicensePlatform.CLOUD || false,
isEECloudUser:
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED || false,
};
};

View File

@@ -19,6 +19,7 @@ import {
import { QueryDataV3 } from 'types/api/widgets/getQuery';
import { GlobalReducer } from 'types/reducer/globalTime';
import { LogTimeRange } from './logs/types';
import { useCopyLogLink } from './logs/useCopyLogLink';
import { useGetExplorerQueryRange } from './queryBuilder/useGetExplorerQueryRange';
import useUrlQueryData from './useUrlQueryData';
@@ -128,7 +129,7 @@ export const useLogsData = ({
return data;
};
const { activeLogId } = useCopyLogLink();
const { activeLogId, onTimeRangeChange } = useCopyLogLink();
const { data, isFetching } = useGetExplorerQueryRange(
requestData,
@@ -149,6 +150,7 @@ export const useLogsData = ({
);
useEffect(() => {
const currentParams = data?.params as Omit<LogTimeRange, 'pageSize'>;
const currentData = data?.payload?.data?.newResult?.data?.result || [];
if (currentData.length > 0 && currentData[0].list) {
const currentLogs: ILog[] = currentData[0].list.map((item) => ({
@@ -158,6 +160,11 @@ export const useLogsData = ({
const newLogs = [...logs, ...currentLogs];
setLogs(newLogs);
onTimeRangeChange({
start: currentParams?.start,
end: currentParams?.end,
pageSize: newLogs.length,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -12,10 +12,6 @@ interface SafeNavigateParams {
search?: string;
}
interface UseSafeNavigateProps {
preventSameUrlNavigation?: boolean;
}
const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
if (url1.pathname !== url2.pathname) return false;
@@ -82,11 +78,7 @@ const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
return newKeys.length > 0;
};
export const useSafeNavigate = (
{ preventSameUrlNavigation }: UseSafeNavigateProps = {
preventSameUrlNavigation: true,
},
): {
export const useSafeNavigate = (): {
safeNavigate: (
to: string | SafeNavigateParams,
options?: NavigateOptions,
@@ -116,7 +108,7 @@ export const useSafeNavigate = (
const urlsAreSame = areUrlsEffectivelySame(currentUrl, targetUrl);
const isDefaultParamsNavigation = isDefaultNavigation(currentUrl, targetUrl);
if (preventSameUrlNavigation && urlsAreSame) {
if (urlsAreSame) {
return;
}
@@ -137,7 +129,7 @@ export const useSafeNavigate = (
);
}
},
[navigate, location.pathname, location.search, preventSameUrlNavigation],
[navigate, location.pathname, location.search],
);
return { safeNavigate };

View File

@@ -49,10 +49,12 @@
/>
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<meta name="robots" content="noindex">
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link rel="stylesheet" href="/css/uPlot.min.css" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -98,16 +100,32 @@
</script>
<script>
const CUSTOMERIO_ID = '<%= htmlWebpackPlugin.options.CUSTOMERIO_ID %>';
const CUSTOMERIO_SITE_ID =
'<%= htmlWebpackPlugin.options.CUSTOMERIO_SITE_ID %>';
const CUSTOMERIO_SITE_ID = '<%= htmlWebpackPlugin.options.CUSTOMERIO_SITE_ID %>';
!function(){var i="cioanalytics", analytics=(window[i]=window[i]||[]);if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.setAttribute('data-global-customerio-analytics-key', i);t.src="https://cdp.customer.io/v1/analytics-js/snippet/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._writeKey=key;analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.15.3";
analytics.load(
CUSTOMERIO_ID,
{
"integrations": {
"Customer.io In-App Plugin": {
siteId: CUSTOMERIO_SITE_ID
}
}
}
);
analytics.page();
}}();
</script>
<script>
//Set your SEGMENT_ID
const SEGMENT_ID = '<%= htmlWebpackPlugin.options.SEGMENT_ID %>';
!(function () {
var i = 'cioanalytics',
analytics = (window[i] = window[i] || []);
var analytics = (window.analytics = window.analytics || []);
if (!analytics.initialize)
if (analytics.invoked)
window.console &&
console.error &&
console.error('Snippet included twice.');
console.error('Segment snippet included twice.');
else {
analytics.invoked = !0;
analytics.methods = [
@@ -134,36 +152,35 @@
];
analytics.factory = function (e) {
return function () {
var t = Array.prototype.slice.call(arguments);
t.unshift(e);
analytics.push(t);
if (window.analytics.initialized)
return window.analytics[e].apply(window.analytics, arguments);
var i = Array.prototype.slice.call(arguments);
i.unshift(e);
analytics.push(i);
return analytics;
};
};
for (var e = 0; e < analytics.methods.length; e++) {
var key = analytics.methods[e];
for (var i = 0; i < analytics.methods.length; i++) {
var key = analytics.methods[i];
analytics[key] = analytics.factory(key);
}
analytics.load = function (key, e) {
analytics.load = function (key, i) {
var t = document.createElement('script');
t.type = 'text/javascript';
t.async = !0;
t.setAttribute('data-global-customerio-analytics-key', i);
t.src =
'https://cdp.customer.io/v1/analytics-js/snippet/' +
'https://analytics-cdn.signoz.io/analytics.js/v1/' +
key +
'/analytics.min.js';
var n = document.getElementsByTagName('script')[0];
n.parentNode.insertBefore(t, n);
analytics._writeKey = key;
analytics._loadOptions = e;
analytics._loadOptions = i;
};
analytics.SNIPPET_VERSION = '4.15.3';
analytics.load(CUSTOMERIO_ID, {
analytics._writeKey = SEGMENT_ID;
analytics.SNIPPET_VERSION = '4.16.1';
analytics.load(SEGMENT_ID, {
integrations: {
'Customer.io In-App Plugin': {
siteId: CUSTOMERIO_SITE_ID,
},
'Segment.io': { apiHost: 'analytics-api.signoz.io/v1' },
},
});
analytics.page();

View File

@@ -53,8 +53,8 @@ function ChannelsEdit(): JSX.Element {
};
}
if (value && 'msteamsv2_configs' in value) {
const msteamsConfig = value.msteamsv2_configs[0];
if (value && 'msteams_configs' in value) {
const msteamsConfig = value.msteams_configs[0];
channel = msteamsConfig;
return {
type: ChannelType.MsTeams,

View File

@@ -1,10 +1,14 @@
import './DashboardsListPage.styles.scss';
import { Space, Typography } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ListOfAllDashboard from 'container/ListOfDashboard';
import { LayoutGrid } from 'lucide-react';
import { useLocation } from 'react-router-dom';
function DashboardsListPage(): JSX.Element {
const location = useLocation();
return (
<Space
direction="vertical"
@@ -12,6 +16,7 @@ function DashboardsListPage(): JSX.Element {
style={{ width: '100%' }}
className="dashboard-list-page"
>
<ReleaseNote path={location.pathname} />
<div className="dashboard-header">
<LayoutGrid size={14} className="icon" />
<Typography.Text className="text">Dashboards</Typography.Text>

View File

@@ -7,9 +7,9 @@ import { Color } from '@signozhq/design-tokens';
import { Button, Flex, Skeleton, Typography } from 'antd';
import { useGetIntegration } from 'hooks/Integrations/useGetIntegration';
import { useGetIntegrationStatus } from 'hooks/Integrations/useGetIntegrationStatus';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { defaultTo } from 'lodash-es';
import { ArrowLeft, MoveUpRight, RotateCw } from 'lucide-react';
import { isCloudUser } from 'utils/app';
import { handleContactSupport } from '../utils';
import IntegrationDetailContent from './IntegrationDetailContent';
@@ -44,8 +44,6 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
integrationId: selectedIntegration,
});
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const {
data: integrationStatus,
isLoading: isStatusLoading,
@@ -106,7 +104,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
</Button>
<div
className="contact-support"
onClick={(): void => handleContactSupport(isCloudUserVal)}
onClick={(): void => handleContactSupport(isCloudUser())}
>
<Typography.Link className="text">Contact Support </Typography.Link>

View File

@@ -5,10 +5,10 @@ import './Integrations.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, List, Typography } from 'antd';
import { useGetAllIntegrations } from 'hooks/Integrations/useGetAllIntegrations';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { MoveUpRight, RotateCw } from 'lucide-react';
import { Dispatch, SetStateAction, useMemo } from 'react';
import { IntegrationsProps } from 'types/api/integrations/types';
import { isCloudUser } from 'utils/app';
import { handleContactSupport, INTEGRATION_TYPES } from './utils';
@@ -44,8 +44,6 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
refetch,
} = useGetAllIntegrations();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const filteredDataList = useMemo(() => {
let integrationsList: IntegrationsProps[] = [];
@@ -92,7 +90,7 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
</Button>
<div
className="contact-support"
onClick={(): void => handleContactSupport(isCloudUserVal)}
onClick={(): void => handleContactSupport(isCloudUser())}
>
<Typography.Link className="text">Contact Support </Typography.Link>

View File

@@ -8,10 +8,10 @@ import MessagingQueueHealthCheck from 'components/MessagingQueueHealthCheck/Mess
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { isCloudUser } from 'utils/app';
import {
KAFKA_SETUP_DOC_LINK,
@@ -34,7 +34,7 @@ function MessagingQueues(): JSX.Element {
);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
const getStartedRedirect = (link: string, sourceCard: string): void => {
logEvent('Messaging Queues: Get started clicked', {

View File

@@ -1,9 +1,15 @@
import { Space } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ServicesApplication from 'container/ServiceApplication';
import { useLocation } from 'react-router-dom';
function Metrics(): JSX.Element {
const location = useLocation();
return (
<Space direction="vertical" style={{ width: '100%' }}>
<ReleaseNote path={location.pathname} />
<ServicesApplication />
</Space>
);

View File

@@ -1,7 +1,6 @@
import RouteTab from 'components/RouteTab';
import { FeatureKeys } from 'constants/features';
import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useMemo } from 'react';
@@ -13,10 +12,6 @@ import { getRoutes } from './utils';
function SettingsPage(): JSX.Element {
const { pathname } = useLocation();
const { user, featureFlags, licenses } = useAppContext();
const {
isCloudUser: isCloudAccount,
isEECloudUser: isEECloudAccount,
} = useGetTenantLicense();
const isWorkspaceBlocked = licenses?.workSpaceBlock || false;
@@ -37,19 +32,9 @@ function SettingsPage(): JSX.Element {
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudAccount,
isEECloudAccount,
t,
),
[
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
isCloudAccount,
isEECloudAccount,
t,
],
[user.role, isCurrentOrgSettings, isGatewayEnabled, isWorkspaceBlocked, t],
);
return <RouteTab routes={routes} activeKey={pathname} history={history} />;

View File

@@ -1,6 +1,7 @@
import { RouteTabProps } from 'components/RouteTab/types';
import { TFunction } from 'i18next';
import { ROLES, USER_ROLES } from 'types/roles';
import { isCloudUser, isEECloudUser } from 'utils/app';
import {
alertChannels,
@@ -17,12 +18,13 @@ export const getRoutes = (
isCurrentOrgSettings: boolean,
isGatewayEnabled: boolean,
isWorkspaceBlocked: boolean,
isCloudAccount: boolean,
isEECloudAccount: boolean,
t: TFunction,
): RouteTabProps['routes'] => {
const settings = [];
const isCloudAccount = isCloudUser();
const isEECloudAccount = isEECloudUser();
const isAdmin = userRole === USER_ROLES.ADMIN;
const isEditor = userRole === USER_ROLES.EDITOR;

View File

@@ -1,13 +1,12 @@
import './NoData.styles.scss';
import { Button, Typography } from 'antd';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { LifeBuoy, RefreshCw } from 'lucide-react';
import { handleContactSupport } from 'pages/Integrations/utils';
import { isCloudUser } from 'utils/app';
function NoData(): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const isCloudUserVal = isCloudUser();
return (
<div className="not-found-trace">
<section className="description">

View File

@@ -21,6 +21,7 @@ import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeatures
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { UserFlags } from 'types/api/user/setFlags';
import { OrgPreference } from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
@@ -157,6 +158,13 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
}
}, [orgPreferencesData, isFetchingOrgPreferences]);
function setUserFlags(userflags: UserFlags): void {
setUser((prev) => ({
...prev,
flags: userflags,
}));
}
function updateUser(user: IUser): void {
setUser((prev) => ({
...prev,
@@ -244,6 +252,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
orgPreferencesFetchError,
licensesRefetch,
updateUser,
setUserFlags,
updateOrgPreferences,
updateOrg,
}),

View File

@@ -3,6 +3,7 @@ import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { PayloadProps as User } from 'types/api/user/getUser';
import { UserFlags } from 'types/api/user/setFlags';
import { OrgPreference } from 'types/reducer/app';
export interface IAppContext {
@@ -25,6 +26,7 @@ export interface IAppContext {
orgPreferencesFetchError: unknown;
licensesRefetch: () => void;
updateUser: (user: IUser) => void;
setUserFlags: (flags: UserFlags) => void;
updateOrgPreferences: (orgPreferences: OrgPreference[]) => void;
updateOrg(orgId: string, updatedOrgName: string): void;
}

View File

@@ -20,6 +20,7 @@ function getUserDefaults(): IUser {
name: '',
profilePictureURL: '',
createdAt: 0,
flags: {},
organization: '',
orgId: '',
role: 'VIEWER',

View File

@@ -763,12 +763,7 @@ export function QueryBuilderProvider({
[panelType, stagedQuery],
);
const { safeNavigate } = useSafeNavigate({
preventSameUrlNavigation: !(
initialDataSource === DataSource.LOGS ||
initialDataSource === DataSource.TRACES
),
});
const { safeNavigate } = useSafeNavigate();
const redirectWithQueryBuilderData = useCallback(
(

View File

@@ -16,7 +16,6 @@ import thunk from 'redux-thunk';
import store from 'store';
import {
LicenseEvent,
LicensePlatform,
LicenseState,
LicenseStatus,
} from 'types/api/licensesV3/getActive';
@@ -116,7 +115,6 @@ export function getAppContextMock(
key: 'does-not-matter',
state: LicenseState.ACTIVE,
status: LicenseStatus.VALID,
platform: LicensePlatform.CLOUD,
},
isFetchingActiveLicenseV3: false,
activeLicenseV3FetchError: null,
@@ -128,6 +126,7 @@ export function getAppContextMock(
name: 'John Doe',
profilePictureURL: '',
createdAt: 1732544623,
flags: {},
organization: 'Nightswatch',
orgId: 'does-not-matter-id',
role: role as ROLES,
@@ -325,6 +324,7 @@ export function getAppContextMock(
orgPreferencesFetchError: null,
isLoggedIn: true,
updateUser: jest.fn(),
setUserFlags: jest.fn(),
updateOrg: jest.fn(),
updateOrgPreferences: jest.fn(),
licensesRefetch: jest.fn(),

View File

@@ -13,11 +13,6 @@ export enum LicenseState {
ACTIVE = 'ACTIVE',
}
export enum LicensePlatform {
SELF_HOSTED = 'SELF_HOSTED',
CLOUD = 'CLOUD',
}
export type LicenseV3EventQueueResModel = {
event: LicenseEvent;
status: string;
@@ -31,5 +26,4 @@ export type LicenseV3ResModel = {
status: LicenseStatus;
state: LicenseState;
event_queue: LicenseV3EventQueueResModel;
platform: LicensePlatform;
};

View File

@@ -1,3 +1,4 @@
import { UserFlags } from 'types/api/user/setFlags';
import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
@@ -15,5 +16,6 @@ export interface PayloadProps {
profilePictureURL: string;
organization: string;
role: ROLES;
flags: UserFlags;
groupId: string;
}

View File

@@ -0,0 +1,12 @@
import { User } from 'types/reducer/app';
export interface UserFlags {
ReleaseNote0120Hide?: string;
}
export type PayloadProps = UserFlags;
export interface Props {
userId: User['userId'];
flags: UserFlags;
}

View File

@@ -13,6 +13,18 @@ export function extractDomain(email: string): string {
return emailParts[1];
}
export const isCloudUser = (): boolean => {
const { hostname } = window.location;
return hostname?.endsWith('signoz.cloud');
};
export const isEECloudUser = (): boolean => {
const { hostname } = window.location;
return hostname?.endsWith('signoz.io');
};
export const checkVersionState = (
currentVersion: string,
latestVersion: string,

View File

@@ -21,6 +21,7 @@ const plugins = [
new HtmlWebpackPlugin({
template: 'src/index.html.ejs',
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
CUSTOMERIO_SITE_ID: process.env.CUSTOMERIO_SITE_ID,
CUSTOMERIO_ID: process.env.CUSTOMERIO_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
@@ -40,6 +41,7 @@ const plugins = [
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
WEBSOCKET_API_ENDPOINT: process.env.WEBSOCKET_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
CUSTOMERIO_SITE_ID: process.env.CUSTOMERIO_SITE_ID,
CUSTOMERIO_ID: process.env.CUSTOMERIO_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,

View File

@@ -26,6 +26,7 @@ const plugins = [
new HtmlWebpackPlugin({
template: 'src/index.html.ejs',
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
CUSTOMERIO_SITE_ID: process.env.CUSTOMERIO_SITE_ID,
CUSTOMERIO_ID: process.env.CUSTOMERIO_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,
@@ -50,6 +51,7 @@ const plugins = [
FRONTEND_API_ENDPOINT: process.env.FRONTEND_API_ENDPOINT,
WEBSOCKET_API_ENDPOINT: process.env.WEBSOCKET_API_ENDPOINT,
INTERCOM_APP_ID: process.env.INTERCOM_APP_ID,
SEGMENT_ID: process.env.SEGMENT_ID,
CUSTOMERIO_SITE_ID: process.env.CUSTOMERIO_SITE_ID,
CUSTOMERIO_ID: process.env.CUSTOMERIO_ID,
POSTHOG_KEY: process.env.POSTHOG_KEY,

View File

@@ -3936,11 +3936,6 @@
dependencies:
"@types/geojson" "*"
"@types/d3-hierarchy@^1.1.6":
version "1.1.11"
resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-1.1.11.tgz#c3bd70d025621f73cb3319e97e08ae4c9051c791"
integrity sha512-lnQiU7jV+Gyk9oQYk0GGYccuexmQPTp08E0+4BidgFdiJivjEvf+esPSdZqCZ2C7UwTWejWpqetVaU8A+eX3FA==
"@types/d3-interpolate@3.0.1", "@types/d3-interpolate@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc"
@@ -4479,11 +4474,6 @@
resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz"
integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==
"@types/trusted-types@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
"@types/unist@*", "@types/unist@^3.0.0":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20"
@@ -4726,15 +4716,6 @@
"@types/d3-shape" "^1.3.1"
d3-shape "^1.0.6"
"@visx/group@3.12.0":
version "3.12.0"
resolved "https://registry.yarnpkg.com/@visx/group/-/group-3.12.0.tgz#2c69b810b52f1c1e69bf6f2fe923d184e32078c7"
integrity sha512-Dye8iS1alVXPv7nj/7M37gJe6sSKqJLH7x6sEWAsRQ9clI0kFvjbKcKgF+U3aAVQr0NCohheFV+DtR8trfK/Ag==
dependencies:
"@types/react" "*"
classnames "^2.3.1"
prop-types "^15.6.2"
"@visx/group@3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@visx/group/-/group-3.3.0.tgz#20c1b75c1ab31798c3c702b6f58c412c688a6373"
@@ -4744,18 +4725,6 @@
classnames "^2.3.1"
prop-types "^15.6.2"
"@visx/hierarchy@3.12.0":
version "3.12.0"
resolved "https://registry.yarnpkg.com/@visx/hierarchy/-/hierarchy-3.12.0.tgz#38295d2469cf957ed6d7700fe968aa16cbb878f0"
integrity sha512-+X1HOeLEOODxjAD7ixrWJ4KCVei4wFe8ra3dYU0uZ14RdPPgUeiuyBfdeXWZuAHM6Ix9qrryneatQjkC3h4mvA==
dependencies:
"@types/d3-hierarchy" "^1.1.6"
"@types/react" "*"
"@visx/group" "3.12.0"
classnames "^2.3.1"
d3-hierarchy "^1.1.4"
prop-types "^15.6.1"
"@visx/scale@3.5.0":
version "3.5.0"
resolved "https://registry.yarnpkg.com/@visx/scale/-/scale-3.5.0.tgz#c3db3863bbdd24d44781104ef5ee4cdc8df6f11d"
@@ -7126,16 +7095,6 @@ d3-geo@3.1.0:
dependencies:
d3-array "2.5.0 - 3"
d3-hierarchy@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
d3-hierarchy@^1.1.4:
version "1.1.9"
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83"
integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
@@ -7579,12 +7538,15 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
dompurify@3.2.4, dompurify@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.4.tgz#af5a5a11407524431456cf18836c55d13441cd8e"
integrity sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==
optionalDependencies:
"@types/trusted-types" "^2.0.7"
dompurify@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.3.tgz#cfe3ce4232c216d923832f68f2aa18b2fb9bd223"
integrity sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng==
dompurify@^3.0.0:
version "3.1.7"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.7.tgz#711a8c96479fb6ced93453732c160c3c72418a6a"
integrity sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==
domutils@^2.5.2, domutils@^2.8.0:
version "2.8.0"

6
go.mod
View File

@@ -69,8 +69,9 @@ require (
go.opentelemetry.io/otel/trace v1.34.0
go.uber.org/multierr v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0
golang.org/x/crypto v0.31.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.33.0
golang.org/x/oauth2 v0.24.0
golang.org/x/sync v0.10.0
golang.org/x/text v0.21.0
@@ -115,7 +116,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -266,7 +267,6 @@ require (
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.28.0 // indirect

12
go.sum
View File

@@ -262,8 +262,8 @@ github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7F
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -1108,8 +1108,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1332,8 +1332,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -49,7 +49,4 @@ type Alertmanager interface {
// GetConfig gets the config for the organization.
GetConfig(context.Context, string) (*alertmanagertypes.Config, error)
// SetDefaultConfig sets the default config for the organization.
SetDefaultConfig(context.Context, string) error
}

View File

@@ -12,10 +12,10 @@ import (
type Config struct {
// The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
// See https://github.com/prometheus/alertmanager/blob/3b06b97af4d146e141af92885a185891eb79a5b0/cmd/alertmanager/main.go#L155C54-L155C249
ExternalURL *url.URL `mapstructure:"external_url"`
ExternalUrl *url.URL `mapstructure:"external_url"`
// GlobalConfig is the global configuration for the alertmanager
Global alertmanagertypes.GlobalConfig `mapstructure:"global" yaml:"global"`
Global alertmanagertypes.GlobalConfig `mapstructure:"global"`
// Config of the root node of the routing tree.
Route alertmanagertypes.RouteConfig `mapstructure:"route"`
@@ -66,9 +66,8 @@ type NFLogConfig struct {
func NewConfig() Config {
return Config{
ExternalURL: &url.URL{
Scheme: "http",
Host: "localhost:8080",
ExternalUrl: &url.URL{
Host: "localhost:8080",
},
Global: alertmanagertypes.GlobalConfig{
// Corresponds to the default in upstream (https://github.com/prometheus/alertmanager/blob/3b06b97af4d146e141af92885a185891eb79a5b0/config/config.go#L727)

View File

@@ -223,7 +223,7 @@ func (server *Server) SetConfig(ctx context.Context, alertmanagerConfig *alertma
return err
}
server.tmpl.ExternalURL = server.srvConfig.ExternalURL
server.tmpl.ExternalURL = server.srvConfig.ExternalUrl
// Build the routing tree and record which receivers are used.
routes := dispatch.NewRoute(config.Route, nil)
@@ -314,7 +314,7 @@ func (server *Server) SetConfig(ctx context.Context, alertmanagerConfig *alertma
}
func (server *Server) TestReceiver(ctx context.Context, receiver alertmanagertypes.Receiver) error {
return alertmanagertypes.TestReceiver(ctx, receiver, server.alertmanagerConfig, server.tmpl, server.logger, alertmanagertypes.NewTestAlert(receiver, time.Now(), time.Now()))
return alertmanagertypes.TestReceiver(ctx, receiver, server.tmpl, server.logger, alertmanagertypes.NewTestAlert(receiver, time.Now(), time.Now()))
}
func (server *Server) TestAlert(ctx context.Context, postableAlert *alertmanagertypes.PostableAlert, receivers []string) error {
@@ -335,7 +335,7 @@ func (server *Server) TestAlert(ctx context.Context, postableAlert *alertmanager
ch <- err
return
}
ch <- alertmanagertypes.TestReceiver(ctx, receiver, server.alertmanagerConfig, server.tmpl, server.logger, alerts[0])
ch <- alertmanagertypes.TestReceiver(ctx, receiver, server.tmpl, server.logger, alerts[0])
}(receiverName)
}

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