Compare commits
23 Commits
v0.75.0-04
...
fix/allow-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df1bd64d7 | ||
|
|
1f33928bf9 | ||
|
|
8abba261a8 | ||
|
|
8f2e8cccb4 | ||
|
|
256fbfc180 | ||
|
|
d362f5bce3 | ||
|
|
42f7511e06 | ||
|
|
819428ad09 | ||
|
|
29fa5c3cf0 | ||
|
|
d09b85bea8 | ||
|
|
114a979b14 | ||
|
|
cb69cd91a0 | ||
|
|
2d73f91380 | ||
|
|
296a444bd8 | ||
|
|
36ebde5470 | ||
|
|
509d9c7fe5 | ||
|
|
816cae3aac | ||
|
|
cb2c492618 | ||
|
|
4177b88a4e | ||
|
|
b1e3f03bb5 | ||
|
|
7417392acb | ||
|
|
42589b4ed4 | ||
|
|
5f28f03d54 |
@@ -70,26 +70,74 @@ 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
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@ 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"
|
||||
@@ -20,6 +21,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -51,7 +53,7 @@ type APIHandler struct {
|
||||
}
|
||||
|
||||
// NewAPIHandler returns an APIHandler
|
||||
func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
|
||||
|
||||
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
|
||||
Reader: opts.DataConnector,
|
||||
@@ -67,6 +69,8 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
FluxInterval: opts.FluxInterval,
|
||||
UseLogsNewSchema: opts.UseLogsNewSchema,
|
||||
UseTraceNewSchema: opts.UseTraceNewSchema,
|
||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||
Signoz: signoz,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -134,7 +134,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
_, registerError := baseauth.Register(ctx, req)
|
||||
_, registerError := baseauth.Register(ctx, req, ah.Signoz.Alertmanager)
|
||||
if !registerError.IsNil() {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
|
||||
@@ -18,6 +18,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,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:",
|
||||
@@ -124,7 +125,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
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,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,
|
||||
@@ -154,7 +155,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
||||
|
||||
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
||||
ctx context.Context, orgId string, cloudProvider string,
|
||||
) (*basemodel.User, *basemodel.ApiError) {
|
||||
) (*types.User, *basemodel.ApiError) {
|
||||
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
|
||||
|
||||
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
|
||||
@@ -171,19 +172,21 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
)
|
||||
|
||||
newUser := &basemodel.User{
|
||||
Id: cloudIntegrationUserId,
|
||||
Name: fmt.Sprintf("%s integration", cloudProvider),
|
||||
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
OrgId: orgId,
|
||||
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,
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@ 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"
|
||||
|
||||
@@ -44,7 +47,6 @@ 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"
|
||||
@@ -175,8 +177,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
<-readerReady
|
||||
rm, err := makeRulesManager(serverOptions.PromConfigPath,
|
||||
baseconst.GetAlertManagerApiPrefix(),
|
||||
rm, err := makeRulesManager(
|
||||
serverOptions.PromConfigPath,
|
||||
serverOptions.RuleRepoURL,
|
||||
serverOptions.SigNoz.SQLStore.SQLxDB(),
|
||||
reader,
|
||||
@@ -185,6 +187,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
lm,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
serverOptions.SigNoz.Alertmanager,
|
||||
serverOptions.SigNoz.SQLStore,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -267,7 +271,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
JWT: serverOptions.Jwt,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts, serverOptions.SigNoz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -340,14 +344,14 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
r := baseapp.NewRouter()
|
||||
|
||||
// add auth middleware
|
||||
getUserFromRequest := func(ctx context.Context) (*basemodel.UserPayload, error) {
|
||||
getUserFromRequest := func(ctx context.Context) (*types.GettableUser, 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"))
|
||||
}
|
||||
|
||||
@@ -529,7 +533,6 @@ func (s *Server) Stop() error {
|
||||
|
||||
func makeRulesManager(
|
||||
promConfigPath,
|
||||
alertManagerURL string,
|
||||
ruleRepoURL string,
|
||||
db *sqlx.DB,
|
||||
ch baseint.Reader,
|
||||
@@ -537,39 +540,34 @@ func makeRulesManager(
|
||||
disableRules bool,
|
||||
fm baseint.FeatureLookup,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool) (*baserules.Manager, error) {
|
||||
|
||||
useTraceNewSchema bool,
|
||||
alertmanager alertmanager.Alertmanager,
|
||||
sqlstore sqlstore.SQLStore,
|
||||
) (*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{
|
||||
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(),
|
||||
|
||||
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
|
||||
|
||||
@@ -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) (*basemodel.UserPayload, error) {
|
||||
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*types.GettableUser, 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 &basemodel.UserPayload{
|
||||
user.User.GroupID = group.ID
|
||||
user.User.ID = pat.Id
|
||||
return &types.GettableUser{
|
||||
User: user.User,
|
||||
Role: pat.Role,
|
||||
}, nil
|
||||
|
||||
@@ -10,6 +10,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -39,7 +40,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) (*basemodel.UserPayload, basemodel.BaseApiError)
|
||||
GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError)
|
||||
ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError)
|
||||
RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ 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) (*basemodel.User, basemodel.BaseApiError) {
|
||||
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*types.User, basemodel.BaseApiError) {
|
||||
// get auth domain from email domain
|
||||
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||||
if apierr != nil {
|
||||
@@ -42,15 +43,17 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
user := &basemodel.User{
|
||||
Id: uuid.NewString(),
|
||||
Name: "",
|
||||
Email: email,
|
||||
Password: hash,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
user := &types.User{
|
||||
ID: uuid.NewString(),
|
||||
Name: "",
|
||||
Email: email,
|
||||
Password: hash,
|
||||
TimeAuditable: types.TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
ProfilePictureURL: "", // Currently unused
|
||||
GroupId: group.ID,
|
||||
OrgId: domain.OrgId,
|
||||
GroupID: group.ID,
|
||||
OrgID: domain.OrgId,
|
||||
}
|
||||
|
||||
user, apiErr = m.CreateUser(ctx, user, false)
|
||||
@@ -73,7 +76,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
return "", model.BadRequestStr("invalid user email received from the auth provider")
|
||||
}
|
||||
|
||||
user := &basemodel.User{}
|
||||
user := &types.User{}
|
||||
|
||||
if userPayload == nil {
|
||||
newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
|
||||
@@ -95,7 +98,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
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -42,10 +43,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,
|
||||
CreatedAt: createdByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
}
|
||||
@@ -95,10 +96,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,
|
||||
CreatedAt: createdByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: createdByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
}
|
||||
@@ -111,10 +112,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,
|
||||
CreatedAt: updatedByUser.CreatedAt.Unix(),
|
||||
ProfilePictureURL: updatedByUser.ProfilePictureURL,
|
||||
NotFound: false,
|
||||
}
|
||||
@@ -170,8 +171,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basem
|
||||
}
|
||||
|
||||
// deprecated
|
||||
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
|
||||
users := []basemodel.UserPayload{}
|
||||
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*types.GettableUser, basemodel.BaseApiError) {
|
||||
users := []types.GettableUser{}
|
||||
|
||||
query := `SELECT
|
||||
u.id,
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
@@ -150,7 +149,14 @@ func main() {
|
||||
zap.L().Fatal("Failed to create config", zap.Error(err))
|
||||
}
|
||||
|
||||
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
|
||||
signoz, err := signoz.New(
|
||||
context.Background(),
|
||||
config,
|
||||
signoz.NewCacheProviderFactories(),
|
||||
signoz.NewWebProviderFactories(),
|
||||
signoz.NewSQLStoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||
}
|
||||
@@ -198,16 +204,19 @@ func main() {
|
||||
zap.L().Fatal("Failed to initialize auth cache", zap.Error(err))
|
||||
}
|
||||
|
||||
signalsChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)
|
||||
signoz.Start(context.Background())
|
||||
|
||||
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()
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ type OrgDomain struct {
|
||||
SamlConfig *SamlConfig `json:"samlConfig"`
|
||||
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
|
||||
|
||||
Org *basemodel.Organization
|
||||
Org *types.Organization
|
||||
}
|
||||
|
||||
func (od *OrgDomain) String() string {
|
||||
|
||||
@@ -28,6 +28,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.UseLogsNewSchema,
|
||||
opts.UseTraceNewSchema,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -48,6 +49,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.PqlEngine,
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -68,6 +70,7 @@ 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
|
||||
@@ -126,6 +129,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
opts.UseTraceNewSchema,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -144,6 +148,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
opts.ManagerOpts.PqlEngine,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -160,6 +165,7 @@ 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))
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"css-minimizer-webpack-plugin": "5.0.1",
|
||||
"d3-hierarchy": "3.1.2",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "3.1.3",
|
||||
"dompurify": "3.2.4",
|
||||
"dotenv": "8.2.0",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -13,7 +14,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
||||
|
||||
|
||||
@@ -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, isCloudUser, isEECloudUser } from 'utils/app';
|
||||
import { extractDomain } from 'utils/app';
|
||||
|
||||
import PrivateRoute from './Private';
|
||||
import defaultRoutes, {
|
||||
@@ -50,11 +50,12 @@ function App(): JSX.Element {
|
||||
} = useAppContext();
|
||||
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
|
||||
|
||||
const { trackPageView } = useAnalytics();
|
||||
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
const isCloudUserVal = isCloudUser();
|
||||
const {
|
||||
isCloudUser: isCloudUserVal,
|
||||
isEECloudUser: isEECloudUserVal,
|
||||
} = useGetTenantLicense();
|
||||
|
||||
const enableAnalytics = useCallback(
|
||||
(user: IUser): void => {
|
||||
@@ -65,18 +66,21 @@ function App(): JSX.Element {
|
||||
|
||||
const { name, email, role } = user;
|
||||
|
||||
const domain = extractDomain(email);
|
||||
const hostNameParts = hostname.split('.');
|
||||
|
||||
const identifyPayload = {
|
||||
email,
|
||||
name,
|
||||
company_name: orgName,
|
||||
role,
|
||||
tenant_id: hostNameParts[0],
|
||||
data_region: hostNameParts[1],
|
||||
tenant_url: hostname,
|
||||
company_domain: domain,
|
||||
source: 'signoz-ui',
|
||||
role,
|
||||
};
|
||||
|
||||
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
|
||||
const domain = extractDomain(email);
|
||||
const hostNameParts = hostname.split('.');
|
||||
|
||||
const groupTraits = {
|
||||
name: orgName,
|
||||
tenant_id: hostNameParts[0],
|
||||
@@ -86,8 +90,13 @@ function App(): JSX.Element {
|
||||
source: 'signoz-ui',
|
||||
};
|
||||
|
||||
window.analytics.identify(email, sanitizedIdentifyPayload);
|
||||
window.analytics.group(domain, groupTraits);
|
||||
if (email) {
|
||||
logEvent('Email Identified', identifyPayload, 'identify');
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
logEvent('Domain Identified', groupTraits, 'group');
|
||||
}
|
||||
|
||||
posthog?.identify(email, {
|
||||
email,
|
||||
@@ -150,7 +159,7 @@ function App(): JSX.Element {
|
||||
|
||||
let updatedRoutes = defaultRoutes;
|
||||
// if the user is a cloud user
|
||||
if (isCloudUserVal || isEECloudUser()) {
|
||||
if (isCloudUserVal || isEECloudUserVal) {
|
||||
// if the user is on basic plan then remove billing
|
||||
if (isOnBasicPlan) {
|
||||
updatedRoutes = updatedRoutes.filter(
|
||||
@@ -175,6 +184,7 @@ function App(): JSX.Element {
|
||||
isCloudUserVal,
|
||||
isFetchingLicenses,
|
||||
isFetchingUser,
|
||||
isEECloudUserVal,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -187,9 +197,7 @@ function App(): JSX.Element {
|
||||
hide_default_launcher: false,
|
||||
});
|
||||
}
|
||||
|
||||
trackPageView(pathname);
|
||||
}, [pathname, trackPageView]);
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
// feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete
|
||||
|
||||
@@ -13,13 +13,11 @@ 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: amData.data,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -10,7 +10,7 @@ const create = async (
|
||||
try {
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
|
||||
@@ -9,19 +9,21 @@ 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 (props.username !== '' && props.password !== '') {
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const editMsTeams = async (
|
||||
try {
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
|
||||
@@ -9,18 +9,21 @@ const editWebhook = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
if (props.username !== '' && props.password !== '') {
|
||||
const username = props.username ? props.username.trim() : '';
|
||||
const password = props.password ? props.password.trim() : '';
|
||||
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const testMsTeams = async (
|
||||
try {
|
||||
const response = await axios.post('/testChannel', {
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
webhook_url: props.webhook_url,
|
||||
|
||||
@@ -9,19 +9,21 @@ 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 (props.username !== '' && props.password !== '') {
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,11 +7,15 @@ 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 {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
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;
|
||||
@@ -1,7 +1,6 @@
|
||||
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';
|
||||
@@ -70,9 +69,6 @@ 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' && {
|
||||
|
||||
@@ -6,6 +6,7 @@ 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';
|
||||
@@ -16,7 +17,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const { notifications } = useNotifications();
|
||||
const {
|
||||
licenses,
|
||||
@@ -77,7 +77,6 @@ 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)
|
||||
@@ -99,6 +98,7 @@ function LaunchChatSupport({
|
||||
}, [
|
||||
featureFlags,
|
||||
featureFlagsFetchError,
|
||||
isCloudUserVal,
|
||||
isFetchingFeatureFlags,
|
||||
isLoggedIn,
|
||||
licenses,
|
||||
|
||||
@@ -16,6 +16,7 @@ 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,
|
||||
@@ -23,7 +24,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export default interface ReleaseNoteProps {
|
||||
path?: string;
|
||||
release?: string;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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;
|
||||
@@ -23,6 +23,7 @@ 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';
|
||||
@@ -54,7 +55,6 @@ 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,6 +122,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation(['titles']);
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
|
||||
{
|
||||
queryFn: getUserVersion,
|
||||
@@ -354,7 +356,6 @@ 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)
|
||||
@@ -376,6 +377,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}, [
|
||||
featureFlags,
|
||||
featureFlagsFetchError,
|
||||
isCloudUserVal,
|
||||
isFetchingFeatureFlags,
|
||||
isLoggedIn,
|
||||
licenses,
|
||||
@@ -390,11 +392,16 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
LOCALSTORAGE.DONT_SHOW_SLOW_API_WARNING,
|
||||
);
|
||||
|
||||
logEvent(`Slow API Warning`, {
|
||||
duration: `${data.duration}ms`,
|
||||
url: data.url,
|
||||
threshold: data.threshold,
|
||||
});
|
||||
logEvent(
|
||||
`Slow API Warning`,
|
||||
{
|
||||
durationMs: data.duration,
|
||||
url: data.url,
|
||||
thresholdMs: data.threshold,
|
||||
},
|
||||
'track',
|
||||
true, // rate limited - controlled by Backend
|
||||
);
|
||||
|
||||
const isDontShowSlowApiWarning = dontShowSlowApiWarning === 'true';
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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';
|
||||
@@ -33,7 +34,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const processUsageData = useCallback(
|
||||
(data: any): void => {
|
||||
|
||||
@@ -173,6 +173,7 @@ function EditAlertChannels({
|
||||
const prepareEmailRequest = useCallback(
|
||||
() => ({
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
to: selectedConfig.to || '',
|
||||
html: selectedConfig.html || '',
|
||||
headers: selectedConfig.headers || {},
|
||||
@@ -208,6 +209,7 @@ 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,
|
||||
@@ -261,6 +263,7 @@ 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 || '',
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -23,7 +24,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const renderConfig = [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Col, Row, Select } from 'antd';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { find } from 'lodash-es';
|
||||
import {
|
||||
ChangeEvent,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { isCloudUser } from 'utils/app';
|
||||
|
||||
import {
|
||||
Input,
|
||||
@@ -39,6 +39,9 @@ function Retention({
|
||||
initialValue,
|
||||
);
|
||||
const interacted = useRef(false);
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
useEffect(() => {
|
||||
if (!interacted.current) setSelectedValue(initialValue);
|
||||
}, [initialValue]);
|
||||
@@ -91,8 +94,6 @@ function Retention({
|
||||
return null;
|
||||
}
|
||||
|
||||
const isCloudUserVal = isCloudUser();
|
||||
|
||||
return (
|
||||
<RetentionContainer>
|
||||
<Row justify="space-between">
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
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,
|
||||
@@ -70,7 +67,6 @@ function ListAlertRules(): JSX.Element {
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<ReleaseNote path={location.pathname} />
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: data.payload,
|
||||
|
||||
@@ -34,6 +34,7 @@ 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';
|
||||
@@ -82,7 +83,6 @@ import {
|
||||
WidgetRow,
|
||||
Widgets,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { isCloudUser } from 'utils/app';
|
||||
|
||||
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
||||
import ImportJSON from './ImportJSON';
|
||||
@@ -111,6 +111,8 @@ function DashboardsList(): JSX.Element {
|
||||
setListSortOrder: setSortOrder,
|
||||
} = useDashboard();
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const [searchString, setSearchString] = useState<string>(
|
||||
sortOrder.search || '',
|
||||
);
|
||||
@@ -694,7 +696,7 @@ function DashboardsList(): JSX.Element {
|
||||
Create and manage dashboards for your workspace.
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
{isCloudUser() && (
|
||||
{isCloudUserVal && (
|
||||
<div className="integrations-container">
|
||||
<div className="integrations-content">
|
||||
<RequestDashboardBtn />
|
||||
@@ -735,7 +737,7 @@ function DashboardsList(): JSX.Element {
|
||||
<Button
|
||||
type="text"
|
||||
className="learn-more"
|
||||
onClick={(): void => handleContactSupport(isCloudUser())}
|
||||
onClick={(): void => handleContactSupport(isCloudUserVal)}
|
||||
>
|
||||
Contact Support
|
||||
</Button>
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
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 (isCloudUser()) {
|
||||
if (isCloudUserVal) {
|
||||
history.push('/support');
|
||||
} else {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
|
||||
@@ -30,7 +30,6 @@ 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';
|
||||
@@ -104,7 +103,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, onTimeRangeChange } = useCopyLogLink();
|
||||
const { activeLogId } = useCopyLogLink();
|
||||
|
||||
const { queryData: pageSize } = useUrlQueryData(
|
||||
QueryParams.pageSize,
|
||||
@@ -562,7 +561,6 @@ 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) => ({
|
||||
@@ -572,11 +570,6 @@ 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
|
||||
@@ -612,7 +605,6 @@ function LogsExplorerViews({
|
||||
pageSize,
|
||||
minTime,
|
||||
activeLogId,
|
||||
onTimeRangeChange,
|
||||
panelType,
|
||||
selectedView,
|
||||
]);
|
||||
|
||||
@@ -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,14 +14,15 @@ export default function NoLogs({
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
}): JSX.Element {
|
||||
const cloudUser = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const handleLinkClick = (
|
||||
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
|
||||
): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (cloudUser) {
|
||||
if (isCloudUserVal) {
|
||||
if (dataSource === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Navigate to onboarding', {});
|
||||
} else if (dataSource === DataSource.LOGS) {
|
||||
|
||||
@@ -19,10 +19,6 @@ 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(
|
||||
|
||||
@@ -281,7 +281,7 @@ function Members(): JSX.Element {
|
||||
const { joinedOn } = record;
|
||||
return (
|
||||
<Typography>
|
||||
{dayjs.unix(Number(joinedOn)).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)}
|
||||
{dayjs(joinedOn).format(DATE_TIME_FORMATS.MONTH_DATE_FULL)}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ const useSampleLogs = ({
|
||||
filters: filter || initialFilters,
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
limit: count || DEFAULT_SAMPLE_LOGS_COUNT,
|
||||
pageSize: count || DEFAULT_SAMPLE_LOGS_COUNT,
|
||||
};
|
||||
return q;
|
||||
}, [count, filter]);
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -14,7 +15,6 @@ 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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const queries = useGetQueriesRange(queryRangeRequestData, ENTITY_VERSION_V4, {
|
||||
queryKey: [
|
||||
|
||||
@@ -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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const tableColumns = useMemo(() => getColumns(search, false), [search]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -11,6 +11,7 @@ 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 {
|
||||
@@ -28,7 +29,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, isCloudUser, isEECloudUser } from 'utils/app';
|
||||
import { checkVersionState } from 'utils/app';
|
||||
|
||||
import { routeConfig } from './config';
|
||||
import { getQueryString } from './helper';
|
||||
@@ -86,7 +87,10 @@ function SideNav(): JSX.Element {
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
const isCloudUserVal = isCloudUser();
|
||||
const {
|
||||
isCloudUser: isCloudUserVal,
|
||||
isEECloudUser: isEECloudUserVal,
|
||||
} = useGetTenantLicense();
|
||||
|
||||
const { t } = useTranslation('');
|
||||
|
||||
@@ -275,7 +279,7 @@ function SideNav(): JSX.Element {
|
||||
let updatedUserManagementItems: UserManagementMenuItems[] = [
|
||||
manageLicenseMenuItem,
|
||||
];
|
||||
if (isCloudUserVal || isEECloudUser()) {
|
||||
if (isCloudUserVal || isEECloudUserVal) {
|
||||
const isOnboardingEnabled =
|
||||
featureFlags?.find((feature) => feature.name === FeatureKeys.ONBOARDING)
|
||||
?.active || false;
|
||||
@@ -330,6 +334,7 @@ function SideNav(): JSX.Element {
|
||||
featureFlags,
|
||||
isCloudUserVal,
|
||||
isCurrentVersionError,
|
||||
isEECloudUserVal,
|
||||
isLatestVersion,
|
||||
licenses?.licenses,
|
||||
onClickVersionHandler,
|
||||
|
||||
@@ -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, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
@@ -314,11 +314,6 @@ 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) {
|
||||
@@ -347,15 +342,13 @@ function DateTimeSelection({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLogsExplorerPage) {
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
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
|
||||
|
||||
@@ -368,7 +361,6 @@ function DateTimeSelection({
|
||||
},
|
||||
[
|
||||
initQueryBuilderData,
|
||||
isLogsExplorerPage,
|
||||
isModalTimeSelection,
|
||||
location.pathname,
|
||||
onTimeChange,
|
||||
@@ -438,16 +430,14 @@ function DateTimeSelection({
|
||||
|
||||
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -466,15 +456,13 @@ function DateTimeSelection({
|
||||
|
||||
setIsValidteRelativeTime(true);
|
||||
|
||||
if (!isLogsExplorerPage) {
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
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;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
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;
|
||||
@@ -5,7 +5,6 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
export type LogTimeRange = {
|
||||
start: number;
|
||||
end: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
export type UseCopyLogLink = {
|
||||
@@ -13,7 +12,6 @@ export type UseCopyLogLink = {
|
||||
isLogsExplorerPage: boolean;
|
||||
activeLogId: string | null;
|
||||
onLogCopy: MouseEventHandler<HTMLElement>;
|
||||
onTimeRangeChange: (newTimeRange: LogTimeRange | null) => void;
|
||||
};
|
||||
|
||||
export type UseActiveLog = {
|
||||
|
||||
@@ -3,7 +3,6 @@ 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,
|
||||
@@ -18,7 +17,7 @@ import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { HIGHLIGHTED_DELAY } from './configs';
|
||||
import { LogTimeRange, UseCopyLogLink } from './types';
|
||||
import { UseCopyLogLink } from './types';
|
||||
|
||||
export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
const urlQuery = useUrlQuery();
|
||||
@@ -31,27 +30,8 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
null,
|
||||
);
|
||||
|
||||
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 { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const isActiveLog = useMemo(() => activeLogId === logId, [activeLogId, logId]);
|
||||
@@ -101,6 +81,5 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
isLogsExplorerPage,
|
||||
activeLogId,
|
||||
onLogCopy,
|
||||
onTimeRangeChange,
|
||||
};
|
||||
};
|
||||
|
||||
15
frontend/src/hooks/useGetTenantLicense.ts
Normal file
15
frontend/src/hooks/useGetTenantLicense.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -19,7 +19,6 @@ 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';
|
||||
@@ -129,7 +128,7 @@ export const useLogsData = ({
|
||||
return data;
|
||||
};
|
||||
|
||||
const { activeLogId, onTimeRangeChange } = useCopyLogLink();
|
||||
const { activeLogId } = useCopyLogLink();
|
||||
|
||||
const { data, isFetching } = useGetExplorerQueryRange(
|
||||
requestData,
|
||||
@@ -150,7 +149,6 @@ 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) => ({
|
||||
@@ -160,11 +158,6 @@ 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
|
||||
|
||||
@@ -12,6 +12,10 @@ interface SafeNavigateParams {
|
||||
search?: string;
|
||||
}
|
||||
|
||||
interface UseSafeNavigateProps {
|
||||
preventSameUrlNavigation?: boolean;
|
||||
}
|
||||
|
||||
const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
|
||||
if (url1.pathname !== url2.pathname) return false;
|
||||
|
||||
@@ -78,7 +82,11 @@ const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
|
||||
|
||||
return newKeys.length > 0;
|
||||
};
|
||||
export const useSafeNavigate = (): {
|
||||
export const useSafeNavigate = (
|
||||
{ preventSameUrlNavigation }: UseSafeNavigateProps = {
|
||||
preventSameUrlNavigation: true,
|
||||
},
|
||||
): {
|
||||
safeNavigate: (
|
||||
to: string | SafeNavigateParams,
|
||||
options?: NavigateOptions,
|
||||
@@ -108,7 +116,7 @@ export const useSafeNavigate = (): {
|
||||
const urlsAreSame = areUrlsEffectivelySame(currentUrl, targetUrl);
|
||||
const isDefaultParamsNavigation = isDefaultNavigation(currentUrl, targetUrl);
|
||||
|
||||
if (urlsAreSame) {
|
||||
if (preventSameUrlNavigation && urlsAreSame) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,7 +137,7 @@ export const useSafeNavigate = (): {
|
||||
);
|
||||
}
|
||||
},
|
||||
[navigate, location.pathname, location.search],
|
||||
[navigate, location.pathname, location.search, preventSameUrlNavigation],
|
||||
);
|
||||
|
||||
return { safeNavigate };
|
||||
|
||||
@@ -49,12 +49,10 @@
|
||||
/>
|
||||
<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>
|
||||
@@ -100,32 +98,16 @@
|
||||
</script>
|
||||
<script>
|
||||
const CUSTOMERIO_ID = '<%= htmlWebpackPlugin.options.CUSTOMERIO_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 %>';
|
||||
|
||||
const CUSTOMERIO_SITE_ID =
|
||||
'<%= htmlWebpackPlugin.options.CUSTOMERIO_SITE_ID %>';
|
||||
!(function () {
|
||||
var analytics = (window.analytics = window.analytics || []);
|
||||
var i = 'cioanalytics',
|
||||
analytics = (window[i] = window[i] || []);
|
||||
if (!analytics.initialize)
|
||||
if (analytics.invoked)
|
||||
window.console &&
|
||||
console.error &&
|
||||
console.error('Segment snippet included twice.');
|
||||
console.error('Snippet included twice.');
|
||||
else {
|
||||
analytics.invoked = !0;
|
||||
analytics.methods = [
|
||||
@@ -152,35 +134,36 @@
|
||||
];
|
||||
analytics.factory = function (e) {
|
||||
return function () {
|
||||
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);
|
||||
var t = Array.prototype.slice.call(arguments);
|
||||
t.unshift(e);
|
||||
analytics.push(t);
|
||||
return analytics;
|
||||
};
|
||||
};
|
||||
for (var i = 0; i < analytics.methods.length; i++) {
|
||||
var key = analytics.methods[i];
|
||||
for (var e = 0; e < analytics.methods.length; e++) {
|
||||
var key = analytics.methods[e];
|
||||
analytics[key] = analytics.factory(key);
|
||||
}
|
||||
analytics.load = function (key, i) {
|
||||
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://analytics-cdn.signoz.io/analytics.js/v1/' +
|
||||
'https://cdp.customer.io/v1/analytics-js/snippet/' +
|
||||
key +
|
||||
'/analytics.min.js';
|
||||
var n = document.getElementsByTagName('script')[0];
|
||||
n.parentNode.insertBefore(t, n);
|
||||
analytics._loadOptions = i;
|
||||
analytics._writeKey = key;
|
||||
analytics._loadOptions = e;
|
||||
};
|
||||
analytics._writeKey = SEGMENT_ID;
|
||||
analytics.SNIPPET_VERSION = '4.16.1';
|
||||
analytics.load(SEGMENT_ID, {
|
||||
analytics.SNIPPET_VERSION = '4.15.3';
|
||||
analytics.load(CUSTOMERIO_ID, {
|
||||
integrations: {
|
||||
'Segment.io': { apiHost: 'analytics-api.signoz.io/v1' },
|
||||
'Customer.io In-App Plugin': {
|
||||
siteId: CUSTOMERIO_SITE_ID,
|
||||
},
|
||||
},
|
||||
});
|
||||
analytics.page();
|
||||
|
||||
@@ -53,8 +53,8 @@ function ChannelsEdit(): JSX.Element {
|
||||
};
|
||||
}
|
||||
|
||||
if (value && 'msteams_configs' in value) {
|
||||
const msteamsConfig = value.msteams_configs[0];
|
||||
if (value && 'msteamsv2_configs' in value) {
|
||||
const msteamsConfig = value.msteamsv2_configs[0];
|
||||
channel = msteamsConfig;
|
||||
return {
|
||||
type: ChannelType.MsTeams,
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
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"
|
||||
@@ -16,7 +12,6 @@ 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>
|
||||
|
||||
@@ -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,6 +44,8 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
|
||||
integrationId: selectedIntegration,
|
||||
});
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const {
|
||||
data: integrationStatus,
|
||||
isLoading: isStatusLoading,
|
||||
@@ -104,7 +106,7 @@ function IntegrationDetailPage(props: IntegrationDetailPageProps): JSX.Element {
|
||||
</Button>
|
||||
<div
|
||||
className="contact-support"
|
||||
onClick={(): void => handleContactSupport(isCloudUser())}
|
||||
onClick={(): void => handleContactSupport(isCloudUserVal)}
|
||||
>
|
||||
<Typography.Link className="text">Contact Support </Typography.Link>
|
||||
|
||||
|
||||
@@ -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,6 +44,8 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
|
||||
refetch,
|
||||
} = useGetAllIntegrations();
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const filteredDataList = useMemo(() => {
|
||||
let integrationsList: IntegrationsProps[] = [];
|
||||
|
||||
@@ -90,7 +92,7 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
|
||||
</Button>
|
||||
<div
|
||||
className="contact-support"
|
||||
onClick={(): void => handleContactSupport(isCloudUser())}
|
||||
onClick={(): void => handleContactSupport(isCloudUserVal)}
|
||||
>
|
||||
<Typography.Link className="text">Contact Support </Typography.Link>
|
||||
|
||||
|
||||
@@ -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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const getStartedRedirect = (link: string, sourceCard: string): void => {
|
||||
logEvent('Messaging Queues: Get started clicked', {
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
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>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -12,6 +13,10 @@ 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;
|
||||
|
||||
@@ -32,9 +37,19 @@ function SettingsPage(): JSX.Element {
|
||||
isCurrentOrgSettings,
|
||||
isGatewayEnabled,
|
||||
isWorkspaceBlocked,
|
||||
isCloudAccount,
|
||||
isEECloudAccount,
|
||||
t,
|
||||
),
|
||||
[user.role, isCurrentOrgSettings, isGatewayEnabled, isWorkspaceBlocked, t],
|
||||
[
|
||||
user.role,
|
||||
isCurrentOrgSettings,
|
||||
isGatewayEnabled,
|
||||
isWorkspaceBlocked,
|
||||
isCloudAccount,
|
||||
isEECloudAccount,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
return <RouteTab routes={routes} activeKey={pathname} history={history} />;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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,
|
||||
@@ -18,13 +17,12 @@ 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;
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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 isCloudUserVal = isCloudUser();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
return (
|
||||
<div className="not-found-trace">
|
||||
<section className="description">
|
||||
|
||||
@@ -21,7 +21,6 @@ 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';
|
||||
|
||||
@@ -158,13 +157,6 @@ 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,
|
||||
@@ -252,7 +244,6 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
orgPreferencesFetchError,
|
||||
licensesRefetch,
|
||||
updateUser,
|
||||
setUserFlags,
|
||||
updateOrgPreferences,
|
||||
updateOrg,
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 {
|
||||
@@ -26,7 +25,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ function getUserDefaults(): IUser {
|
||||
name: '',
|
||||
profilePictureURL: '',
|
||||
createdAt: 0,
|
||||
flags: {},
|
||||
organization: '',
|
||||
orgId: '',
|
||||
role: 'VIEWER',
|
||||
|
||||
@@ -763,7 +763,12 @@ export function QueryBuilderProvider({
|
||||
[panelType, stagedQuery],
|
||||
);
|
||||
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const { safeNavigate } = useSafeNavigate({
|
||||
preventSameUrlNavigation: !(
|
||||
initialDataSource === DataSource.LOGS ||
|
||||
initialDataSource === DataSource.TRACES
|
||||
),
|
||||
});
|
||||
|
||||
const redirectWithQueryBuilderData = useCallback(
|
||||
(
|
||||
|
||||
@@ -16,6 +16,7 @@ import thunk from 'redux-thunk';
|
||||
import store from 'store';
|
||||
import {
|
||||
LicenseEvent,
|
||||
LicensePlatform,
|
||||
LicenseState,
|
||||
LicenseStatus,
|
||||
} from 'types/api/licensesV3/getActive';
|
||||
@@ -115,6 +116,7 @@ export function getAppContextMock(
|
||||
key: 'does-not-matter',
|
||||
state: LicenseState.ACTIVE,
|
||||
status: LicenseStatus.VALID,
|
||||
platform: LicensePlatform.CLOUD,
|
||||
},
|
||||
isFetchingActiveLicenseV3: false,
|
||||
activeLicenseV3FetchError: null,
|
||||
@@ -126,7 +128,6 @@ export function getAppContextMock(
|
||||
name: 'John Doe',
|
||||
profilePictureURL: '',
|
||||
createdAt: 1732544623,
|
||||
flags: {},
|
||||
organization: 'Nightswatch',
|
||||
orgId: 'does-not-matter-id',
|
||||
role: role as ROLES,
|
||||
@@ -324,7 +325,6 @@ export function getAppContextMock(
|
||||
orgPreferencesFetchError: null,
|
||||
isLoggedIn: true,
|
||||
updateUser: jest.fn(),
|
||||
setUserFlags: jest.fn(),
|
||||
updateOrg: jest.fn(),
|
||||
updateOrgPreferences: jest.fn(),
|
||||
licensesRefetch: jest.fn(),
|
||||
|
||||
@@ -13,6 +13,11 @@ export enum LicenseState {
|
||||
ACTIVE = 'ACTIVE',
|
||||
}
|
||||
|
||||
export enum LicensePlatform {
|
||||
SELF_HOSTED = 'SELF_HOSTED',
|
||||
CLOUD = 'CLOUD',
|
||||
}
|
||||
|
||||
export type LicenseV3EventQueueResModel = {
|
||||
event: LicenseEvent;
|
||||
status: string;
|
||||
@@ -26,4 +31,5 @@ export type LicenseV3ResModel = {
|
||||
status: LicenseStatus;
|
||||
state: LicenseState;
|
||||
event_queue: LicenseV3EventQueueResModel;
|
||||
platform: LicensePlatform;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { UserFlags } from 'types/api/user/setFlags';
|
||||
import { User } from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
@@ -16,6 +15,5 @@ export interface PayloadProps {
|
||||
profilePictureURL: string;
|
||||
organization: string;
|
||||
role: ROLES;
|
||||
flags: UserFlags;
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
|
||||
export interface UserFlags {
|
||||
ReleaseNote0120Hide?: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = UserFlags;
|
||||
|
||||
export interface Props {
|
||||
userId: User['userId'];
|
||||
flags: UserFlags;
|
||||
}
|
||||
@@ -13,18 +13,6 @@ 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,
|
||||
|
||||
@@ -21,7 +21,6 @@ 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,
|
||||
@@ -41,7 +40,6 @@ 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,
|
||||
|
||||
@@ -26,7 +26,6 @@ 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,
|
||||
@@ -51,7 +50,6 @@ 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,
|
||||
|
||||
@@ -4479,6 +4479,11 @@
|
||||
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"
|
||||
@@ -7574,15 +7579,12 @@ domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
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==
|
||||
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"
|
||||
|
||||
domutils@^2.5.2, domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
|
||||
6
go.mod
6
go.mod
@@ -69,9 +69,8 @@ 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.31.0
|
||||
golang.org/x/crypto v0.32.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
|
||||
@@ -116,7 +115,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.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // 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
|
||||
@@ -267,6 +266,7 @@ 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
12
go.sum
@@ -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.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
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-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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
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.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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/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=
|
||||
|
||||
@@ -49,4 +49,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
Global alertmanagertypes.GlobalConfig `mapstructure:"global" yaml:"global"`
|
||||
|
||||
// Config of the root node of the routing tree.
|
||||
Route alertmanagertypes.RouteConfig `mapstructure:"route"`
|
||||
@@ -66,8 +66,9 @@ type NFLogConfig struct {
|
||||
|
||||
func NewConfig() Config {
|
||||
return Config{
|
||||
ExternalUrl: &url.URL{
|
||||
Host: "localhost:8080",
|
||||
ExternalURL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "localhost:8080",
|
||||
},
|
||||
Global: alertmanagertypes.GlobalConfig{
|
||||
// Corresponds to the default in upstream (https://github.com/prometheus/alertmanager/blob/3b06b97af4d146e141af92885a185891eb79a5b0/config/config.go#L727)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestServerSetConfigAndStop(t *testing.T) {
|
||||
server, err := New(context.Background(), slog.New(slog.NewTextHandler(io.Discard, nil)), prometheus.NewRegistry(), NewConfig(), "1", alertmanagertypestest.NewStateStore())
|
||||
require.NoError(t, err)
|
||||
|
||||
amConfig, err := alertmanagertypes.NewDefaultConfig(alertmanagertypes.GlobalConfig{}, alertmanagertypes.RouteConfig{}, "1")
|
||||
amConfig, err := alertmanagertypes.NewDefaultConfig(alertmanagertypes.GlobalConfig{}, alertmanagertypes.RouteConfig{GroupInterval: 1 * time.Minute, RepeatInterval: 1 * time.Minute, GroupWait: 1 * time.Minute}, "1")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NoError(t, server.SetConfig(context.Background(), amConfig))
|
||||
@@ -37,7 +37,7 @@ func TestServerTestReceiverTypeWebhook(t *testing.T) {
|
||||
server, err := New(context.Background(), slog.New(slog.NewTextHandler(io.Discard, nil)), prometheus.NewRegistry(), NewConfig(), "1", alertmanagertypestest.NewStateStore())
|
||||
require.NoError(t, err)
|
||||
|
||||
amConfig, err := alertmanagertypes.NewDefaultConfig(alertmanagertypes.GlobalConfig{}, alertmanagertypes.RouteConfig{}, "1")
|
||||
amConfig, err := alertmanagertypes.NewDefaultConfig(alertmanagertypes.GlobalConfig{}, alertmanagertypes.RouteConfig{GroupInterval: 1 * time.Minute, RepeatInterval: 1 * time.Minute, GroupWait: 1 * time.Minute}, "1")
|
||||
require.NoError(t, err)
|
||||
|
||||
webhookListener, err := net.Listen("tcp", "localhost:0")
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +13,7 @@ type Config struct {
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
// Internal is the internal alertmanager configuration.
|
||||
Signoz Signoz `mapstructure:"signoz"`
|
||||
Signoz Signoz `mapstructure:"signoz" yaml:"signoz"`
|
||||
|
||||
// Legacy is the legacy alertmanager configuration.
|
||||
Legacy Legacy `mapstructure:"legacy"`
|
||||
@@ -26,12 +24,12 @@ type Signoz struct {
|
||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
||||
|
||||
// Config is the config for the alertmanager server.
|
||||
alertmanagerserver.Config `mapstructure:",squash"`
|
||||
alertmanagerserver.Config `mapstructure:",squash" yaml:",squash"`
|
||||
}
|
||||
|
||||
type Legacy struct {
|
||||
// ApiURL is the URL of the legacy signoz alertmanager.
|
||||
ApiURL string `mapstructure:"api_url"`
|
||||
ApiURL *url.URL `mapstructure:"api_url"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
@@ -42,26 +40,19 @@ func newConfig() factory.Config {
|
||||
return Config{
|
||||
Provider: "legacy",
|
||||
Legacy: Legacy{
|
||||
ApiURL: "http://alertmanager:9093/api",
|
||||
ApiURL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "alertmanager:9093",
|
||||
Path: "/api",
|
||||
},
|
||||
},
|
||||
Signoz: Signoz{
|
||||
PollInterval: 15 * time.Second,
|
||||
PollInterval: 1 * time.Minute,
|
||||
Config: alertmanagerserver.NewConfig(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.Provider == "legacy" {
|
||||
if c.Legacy.ApiURL == "" {
|
||||
return errors.New("api_url is required")
|
||||
}
|
||||
|
||||
_, err := url.Parse(c.Legacy.ApiURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("api_url %q is invalid: %w", c.Legacy.ApiURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package alertmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/config"
|
||||
@@ -14,6 +17,9 @@ import (
|
||||
func TestNewWithEnvProvider(t *testing.T) {
|
||||
t.Setenv("SIGNOZ_ALERTMANAGER_PROVIDER", "legacy")
|
||||
t.Setenv("SIGNOZ_ALERTMANAGER_LEGACY_API__URL", "http://localhost:9093/api")
|
||||
t.Setenv("SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_REPEAT__INTERVAL", "5m")
|
||||
t.Setenv("SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL", "https://example.com/test")
|
||||
t.Setenv("SIGNOZ_ALERTMANAGER_SIGNOZ_GLOBAL_RESOLVE__TIMEOUT", "10s")
|
||||
|
||||
conf, err := config.New(
|
||||
context.Background(),
|
||||
@@ -30,15 +36,26 @@ func TestNewWithEnvProvider(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := &Config{}
|
||||
err = conf.Unmarshal("alertmanager", actual)
|
||||
err = conf.Unmarshal("alertmanager", actual, "yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
def := NewConfigFactory().New().(Config)
|
||||
def.Signoz.Global.ResolveTimeout = model.Duration(10 * time.Second)
|
||||
def.Signoz.Route.RepeatInterval = 5 * time.Minute
|
||||
def.Signoz.ExternalURL = &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/test",
|
||||
}
|
||||
|
||||
expected := &Config{
|
||||
Provider: "legacy",
|
||||
Legacy: Legacy{
|
||||
ApiURL: "http://localhost:9093/api",
|
||||
ApiURL: &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "localhost:9093",
|
||||
Path: "/api",
|
||||
},
|
||||
},
|
||||
Signoz: def.Signoz,
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type provider struct {
|
||||
configStore alertmanagertypes.ConfigStore
|
||||
batcher *alertmanagerbatcher.Batcher
|
||||
url *url.URL
|
||||
orgID string
|
||||
}
|
||||
|
||||
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
@@ -49,11 +50,6 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/alertmanager/legacyalertmanager")
|
||||
configStore := sqlalertmanagerstore.NewConfigStore(sqlstore)
|
||||
|
||||
url, err := url.Parse(config.Legacy.ApiURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &provider{
|
||||
config: config,
|
||||
settings: settings,
|
||||
@@ -62,7 +58,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
},
|
||||
configStore: configStore,
|
||||
batcher: alertmanagerbatcher.New(settings.Logger(), alertmanagerbatcher.NewConfig()),
|
||||
url: url,
|
||||
url: config.Legacy.ApiURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -73,8 +69,25 @@ func (provider *provider) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
for alerts := range provider.batcher.C {
|
||||
if err := provider.putAlerts(ctx, "", alerts); err != nil {
|
||||
provider.settings.Logger().Error("failed to send alerts to alertmanager", "error", err)
|
||||
// For the first time, we need to get the orgID from the config store.
|
||||
// Since this is the legacy alertmanager, we get the first org from the store.
|
||||
if provider.orgID == "" {
|
||||
orgIDs, err := provider.configStore.ListOrgs(ctx)
|
||||
if err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to send alerts to alertmanager", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(orgIDs) == 0 {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to send alerts to alertmanager", "error", "no orgs found")
|
||||
continue
|
||||
}
|
||||
|
||||
provider.orgID = orgIDs[0]
|
||||
}
|
||||
|
||||
if err := provider.putAlerts(ctx, provider.orgID, alerts); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to send alerts to alertmanager", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,17 +138,24 @@ func (provider *provider) putAlerts(ctx context.Context, orgID string, alerts al
|
||||
return err
|
||||
}
|
||||
|
||||
legacyAlerts := make([]postableAlert, len(alerts))
|
||||
for i, alert := range alerts {
|
||||
receivers, err := cfg.ReceiverNamesFromRuleID(alert.Alert.Labels["ruleID"])
|
||||
if err != nil {
|
||||
return err
|
||||
var legacyAlerts []postableAlert
|
||||
for _, alert := range alerts {
|
||||
ruleID, ok := alert.Alert.Labels[alertmanagertypes.RuleIDMatcherName]
|
||||
if !ok {
|
||||
provider.settings.Logger().WarnContext(ctx, "cannot find ruleID for alert, skipping sending alert to alertmanager", "alert", alert)
|
||||
continue
|
||||
}
|
||||
|
||||
legacyAlerts[i] = postableAlert{
|
||||
receivers := cfg.ReceiverNamesFromRuleID(ruleID)
|
||||
if len(receivers) == 0 {
|
||||
provider.settings.Logger().WarnContext(ctx, "cannot find receivers for alert, skipping sending alert to alertmanager", "ruleID", ruleID, "alert", alert)
|
||||
continue
|
||||
}
|
||||
|
||||
legacyAlerts = append(legacyAlerts, postableAlert{
|
||||
PostableAlert: alert,
|
||||
Receivers: receivers,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
url := provider.url.JoinPath(alertsPath)
|
||||
@@ -169,7 +189,7 @@ func (provider *provider) putAlerts(ctx context.Context, orgID string, alerts al
|
||||
func (provider *provider) TestReceiver(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver) error {
|
||||
url := provider.url.JoinPath(testReceiverPath)
|
||||
|
||||
body, err := json.Marshal(receiver)
|
||||
body, err := json.Marshal(alertmanagertypes.MSTeamsV2ReceiverToMSTeamsReceiver(receiver))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,12 +218,13 @@ func (provider *provider) TestReceiver(ctx context.Context, orgID string, receiv
|
||||
func (provider *provider) TestAlert(ctx context.Context, orgID string, alert *alertmanagertypes.PostableAlert, receivers []string) error {
|
||||
url := provider.url.JoinPath(alertsPath)
|
||||
|
||||
legacyAlert := postableAlert{
|
||||
legacyAlerts := make([]postableAlert, 1)
|
||||
legacyAlerts[0] = postableAlert{
|
||||
PostableAlert: alert,
|
||||
Receivers: receivers,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(legacyAlert)
|
||||
body, err := json.Marshal(legacyAlerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -234,7 +255,18 @@ func (provider *provider) ListChannels(ctx context.Context, orgID string) ([]*al
|
||||
}
|
||||
|
||||
func (provider *provider) ListAllChannels(ctx context.Context) ([]*alertmanagertypes.Channel, error) {
|
||||
return provider.configStore.ListAllChannels(ctx)
|
||||
channels, err := provider.configStore.ListAllChannels(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
if err := channel.MSTeamsV2ToMSTeams(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetChannelByID(ctx context.Context, orgID string, channelID int) (*alertmanagertypes.Channel, error) {
|
||||
@@ -264,7 +296,7 @@ func (provider *provider) UpdateChannelByReceiverAndID(ctx context.Context, orgI
|
||||
err = provider.configStore.UpdateChannel(ctx, orgID, channel, alertmanagertypes.WithCb(func(ctx context.Context) error {
|
||||
url := provider.url.JoinPath(routesPath)
|
||||
|
||||
body, err := json.Marshal(receiver)
|
||||
body, err := json.Marshal(alertmanagertypes.MSTeamsV2ReceiverToMSTeamsReceiver(receiver))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -315,7 +347,7 @@ func (provider *provider) CreateChannel(ctx context.Context, orgID string, recei
|
||||
return provider.configStore.CreateChannel(ctx, channel, alertmanagertypes.WithCb(func(ctx context.Context) error {
|
||||
url := provider.url.JoinPath(routesPath)
|
||||
|
||||
body, err := json.Marshal(receiver)
|
||||
body, err := json.Marshal(alertmanagertypes.MSTeamsV2ReceiverToMSTeamsReceiver(receiver))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -407,3 +439,12 @@ func (provider *provider) Stop(ctx context.Context) error {
|
||||
func (provider *provider) GetConfig(ctx context.Context, orgID string) (*alertmanagertypes.Config, error) {
|
||||
return provider.configStore.Get(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) SetDefaultConfig(ctx context.Context, orgID string) error {
|
||||
config, err := alertmanagertypes.NewDefaultConfig(provider.config.Signoz.Config.Global, provider.config.Signoz.Config.Route, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.configStore.Set(ctx, config)
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ func (service *Service) SyncServers(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (service *Service) GetAlerts(ctx context.Context, orgID string, params alertmanagertypes.GettableAlertsParams) (alertmanagertypes.DeprecatedGettableAlerts, error) {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -99,6 +102,9 @@ func (service *Service) GetAlerts(ctx context.Context, orgID string, params aler
|
||||
}
|
||||
|
||||
func (service *Service) PutAlerts(ctx context.Context, orgID string, alerts alertmanagertypes.PostableAlerts) error {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -108,6 +114,9 @@ func (service *Service) PutAlerts(ctx context.Context, orgID string, alerts aler
|
||||
}
|
||||
|
||||
func (service *Service) TestReceiver(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver) error {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -117,6 +126,9 @@ func (service *Service) TestReceiver(ctx context.Context, orgID string, receiver
|
||||
}
|
||||
|
||||
func (service *Service) TestAlert(ctx context.Context, orgID string, alert *alertmanagertypes.PostableAlert, receivers []string) error {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -144,17 +156,6 @@ func (service *Service) newServer(ctx context.Context, orgID string) (*alertmana
|
||||
return nil, err
|
||||
}
|
||||
|
||||
beforeCompareAndSelectHash := config.StoreableConfig().Hash
|
||||
config, err = service.compareAndSelectConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if beforeCompareAndSelectHash == config.StoreableConfig().Hash {
|
||||
service.settings.Logger().Debug("skipping config store update for org", "orgID", orgID, "hash", config.StoreableConfig().Hash)
|
||||
return server, nil
|
||||
}
|
||||
|
||||
err = service.configStore.Set(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -174,56 +175,18 @@ func (service *Service) getConfig(ctx context.Context, orgID string) (*alertmana
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.SetGlobalConfig(service.config.Global)
|
||||
if config.AlertmanagerConfig().Route == nil {
|
||||
config.SetRouteConfig(service.config.Route)
|
||||
} else {
|
||||
config.UpdateRouteConfig(service.config.Route)
|
||||
}
|
||||
}
|
||||
|
||||
if err := config.SetGlobalConfig(service.config.Global); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.SetRouteConfig(service.config.Route)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// compareAndSelectConfig compares the existing config with the config derived from channels.
|
||||
// If the hash of the config and the channels mismatch, the config derived from channels is returned.
|
||||
func (service *Service) compareAndSelectConfig(ctx context.Context, incomingConfig *alertmanagertypes.Config) (*alertmanagertypes.Config, error) {
|
||||
channels, err := service.configStore.ListChannels(ctx, incomingConfig.StoreableConfig().OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matchers, err := service.configStore.GetMatchers(ctx, incomingConfig.StoreableConfig().OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := alertmanagertypes.NewConfigFromChannels(service.config.Global, service.config.Route, channels, incomingConfig.StoreableConfig().OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ruleID, receivers := range matchers {
|
||||
err = config.CreateRuleIDMatcher(ruleID, receivers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if incomingConfig.StoreableConfig().Hash != config.StoreableConfig().Hash {
|
||||
service.settings.Logger().InfoContext(ctx, "mismatch found, updating config to match channels and matchers")
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return incomingConfig, nil
|
||||
|
||||
}
|
||||
|
||||
// getServer returns the server for the given orgID. It should be called with the lock held.
|
||||
func (service *Service) getServer(orgID string) (*alertmanagerserver.Server, error) {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, ok := service.servers[orgID]
|
||||
if !ok {
|
||||
return nil, errors.Newf(errors.TypeNotFound, ErrCodeAlertmanagerNotFound, "alertmanager not found for org %s", orgID)
|
||||
|
||||
@@ -170,3 +170,12 @@ func (provider *provider) SetConfig(ctx context.Context, config *alertmanagertyp
|
||||
func (provider *provider) GetConfig(ctx context.Context, orgID string) (*alertmanagertypes.Config, error) {
|
||||
return provider.configStore.Get(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) SetDefaultConfig(ctx context.Context, orgID string) error {
|
||||
config, err := alertmanagertypes.NewDefaultConfig(provider.config.Signoz.Config.Global, provider.config.Signoz.Config.Route, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.configStore.Set(ctx, config)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/knadh/koanf/providers/confmap"
|
||||
"github.com/knadh/koanf/v2"
|
||||
yamlv2 "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -53,19 +57,30 @@ func (conf *Conf) MergeAt(input *Conf, path string) error {
|
||||
|
||||
// Unmarshal unmarshals the configuration at the given path into the input.
|
||||
// It uses a WeaklyTypedInput to allow for more flexible unmarshalling.
|
||||
func (conf *Conf) Unmarshal(path string, input any) error {
|
||||
dc := &mapstructure.DecoderConfig{
|
||||
TagName: "mapstructure",
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.TextUnmarshallerHookFunc(),
|
||||
),
|
||||
Result: input,
|
||||
func (conf *Conf) Unmarshal(path string, input any, tags ...string) error {
|
||||
tags = append([]string{"mapstructure"}, tags...)
|
||||
|
||||
for _, tag := range tags {
|
||||
dc := &mapstructure.DecoderConfig{
|
||||
TagName: tag,
|
||||
WeaklyTypedInput: true,
|
||||
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.TextUnmarshallerHookFunc(),
|
||||
StringToURLHookFunc(),
|
||||
YamlV2UnmarshalHookFunc(),
|
||||
),
|
||||
Result: input,
|
||||
}
|
||||
|
||||
err := conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: tag, DecoderConfig: dc})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: "mapstructure", DecoderConfig: dc})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set sets the configuration at the given key.
|
||||
@@ -88,3 +103,51 @@ func (conf *Conf) Set(key string, input any) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StringToURLHookFunc() mapstructure.DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{},
|
||||
) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
if t != reflect.TypeOf(url.URL{}) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Convert it by parsing
|
||||
u, err := url.Parse(data.(string))
|
||||
return u, err
|
||||
}
|
||||
}
|
||||
|
||||
func YamlV2UnmarshalHookFunc() mapstructure.DecodeHookFunc {
|
||||
return func(
|
||||
f reflect.Type,
|
||||
t reflect.Type,
|
||||
data interface{},
|
||||
) (interface{}, error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
result := reflect.New(t).Interface()
|
||||
_, ok := result.(yamlv2.Unmarshaler)
|
||||
if !ok {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
str, ok := data.(string)
|
||||
if !ok {
|
||||
str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String()
|
||||
}
|
||||
|
||||
if err := yamlv2.Unmarshal([]byte(str), result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func NewRegistry(logger *slog.Logger, services ...NamedService) (*Registry, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Registry) Start(ctx context.Context) error {
|
||||
func (r *Registry) Start(ctx context.Context) {
|
||||
for _, s := range r.services.GetInOrder() {
|
||||
go func(s NamedService) {
|
||||
r.logger.InfoContext(ctx, "starting service", "service", s.Name())
|
||||
@@ -49,7 +49,6 @@ func (r *Registry) Start(ctx context.Context) error {
|
||||
}(s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Registry) Wait(ctx context.Context) error {
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestRegistryWith2Services(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
require.NoError(t, registry.Start(ctx))
|
||||
registry.Start(ctx)
|
||||
require.NoError(t, registry.Wait(ctx))
|
||||
require.NoError(t, registry.Stop(ctx))
|
||||
}()
|
||||
@@ -62,7 +62,7 @@ func TestRegistryWith2ServicesWithoutWait(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
require.NoError(t, registry.Start(ctx))
|
||||
registry.Start(ctx)
|
||||
require.NoError(t, registry.Stop(ctx))
|
||||
}()
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ func (writer *nonFlushingBadResponseLoggingWriter) Write(data []byte) (int, erro
|
||||
// https://godoc.org/net/http#ResponseWriter
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// 204 No Content is a success response that indicates that the request has been successfully processed and that the response body is intentionally empty.
|
||||
if writer.statusCode == 204 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n, err := writer.rw.Write(data)
|
||||
if writer.logBody {
|
||||
writer.captureResponseBody(data)
|
||||
|
||||
@@ -23,6 +23,7 @@ type SDK struct {
|
||||
logger *slog.Logger
|
||||
sdk contribsdkconfig.SDK
|
||||
prometheusRegistry *prometheus.Registry
|
||||
startCh chan struct{}
|
||||
}
|
||||
|
||||
// New creates a new Instrumentation instance with configured providers.
|
||||
@@ -96,14 +97,17 @@ func New(ctx context.Context, build version.Build, cfg Config) (*SDK, error) {
|
||||
sdk: sdk,
|
||||
prometheusRegistry: prometheusRegistry,
|
||||
logger: NewLogger(cfg),
|
||||
startCh: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *SDK) Start(ctx context.Context) error {
|
||||
<-i.startCh
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *SDK) Stop(ctx context.Context) error {
|
||||
close(i.startCh)
|
||||
return i.sdk.Shutdown(ctx)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if !ok {
|
||||
RespondError(w, &model.ApiError{Err: errors.New("unauthorized"), Typ: model.ErrorUnauthorized}, nil)
|
||||
return
|
||||
}
|
||||
req, err := parseSetApdexScoreRequest(r)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := dao.DB().SetApdexSettings(context.Background(), req); err != nil {
|
||||
if err := dao.DB().SetApdexSettings(r.Context(), claims.OrgID, req); err != nil {
|
||||
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||
return
|
||||
}
|
||||
@@ -25,7 +31,12 @@ func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) {
|
||||
services := r.URL.Query().Get("services")
|
||||
apdexSet, err := dao.DB().GetApdexSettings(context.Background(), strings.Split(strings.TrimSpace(services), ","))
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if !ok {
|
||||
RespondError(w, &model.ApiError{Err: errors.New("unauthorized"), Typ: model.ErrorUnauthorized}, nil)
|
||||
return
|
||||
}
|
||||
apdexSet, err := dao.DB().GetApdexSettings(r.Context(), claims.OrgID, strings.Split(strings.TrimSpace(services), ","))
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil)
|
||||
return
|
||||
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
GetUserFromRequest func(r context.Context) (*model.UserPayload, error)
|
||||
GetUserFromRequest func(r context.Context) (*types.GettableUser, error)
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(f func(ctx context.Context) (*model.UserPayload, error)) *AuthMiddleware {
|
||||
func NewAuthMiddleware(f func(ctx context.Context) (*types.GettableUser, error)) *AuthMiddleware {
|
||||
return &AuthMiddleware{
|
||||
GetUserFromRequest: f,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ const (
|
||||
defaultTraceLocalTableName string = "signoz_index_v3"
|
||||
defaultTraceResourceTableV3 string = "distributed_traces_v3_resource"
|
||||
defaultTraceSummaryTable string = "distributed_trace_summary"
|
||||
|
||||
defaultMetadataDB string = "signoz_metadata"
|
||||
defaultMetadataTable string = "distributed_attributes_metadata"
|
||||
)
|
||||
|
||||
// NamespaceConfig is Clickhouse's internal configuration data
|
||||
@@ -88,6 +91,8 @@ type namespaceConfig struct {
|
||||
TraceLocalTableNameV3 string
|
||||
TraceResourceTableV3 string
|
||||
TraceSummaryTable string
|
||||
MetadataDB string
|
||||
MetadataTable string
|
||||
}
|
||||
|
||||
// Connecto defines how to connect to the database
|
||||
@@ -141,6 +146,8 @@ func NewOptions(
|
||||
TraceLocalTableNameV3: defaultTraceLocalTableName,
|
||||
TraceResourceTableV3: defaultTraceResourceTableV3,
|
||||
TraceSummaryTable: defaultTraceSummaryTable,
|
||||
MetadataDB: defaultMetadataDB,
|
||||
MetadataTable: defaultMetadataTable,
|
||||
},
|
||||
others: make(map[string]*namespaceConfig, len(otherNamespaces)),
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
chErrors "go.signoz.io/signoz/pkg/query-service/errors"
|
||||
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
"go.signoz.io/signoz/pkg/query-service/metrics"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
@@ -145,7 +144,6 @@ type ClickHouseReader struct {
|
||||
|
||||
promConfigFile string
|
||||
promConfig *config.Config
|
||||
alertManager am.Manager
|
||||
featureFlags interfaces.FeatureLookup
|
||||
|
||||
liveTailRefreshSeconds int
|
||||
@@ -164,6 +162,8 @@ type ClickHouseReader struct {
|
||||
|
||||
fluxIntervalForTraceDetail time.Duration
|
||||
cache cache.Cache
|
||||
metadataDB string
|
||||
metadataTable string
|
||||
}
|
||||
|
||||
// NewTraceReader returns a TraceReader for the database
|
||||
@@ -194,13 +194,6 @@ func NewReaderFromClickhouseConnection(
|
||||
fluxIntervalForTraceDetail time.Duration,
|
||||
cache cache.Cache,
|
||||
) *ClickHouseReader {
|
||||
alertManager, err := am.New()
|
||||
if err != nil {
|
||||
zap.L().Error("failed to initialize alert manager", zap.Error(err))
|
||||
zap.L().Error("check if the alert manager URL is correctly set and valid")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logsTableName := options.primary.LogsTable
|
||||
logsLocalTableName := options.primary.LogsLocalTable
|
||||
if useLogsNewSchema {
|
||||
@@ -219,7 +212,6 @@ func NewReaderFromClickhouseConnection(
|
||||
db: db,
|
||||
localDB: localDB,
|
||||
TraceDB: options.primary.TraceDB,
|
||||
alertManager: alertManager,
|
||||
operationsTable: options.primary.OperationsTable,
|
||||
indexTable: options.primary.IndexTable,
|
||||
errorTable: options.primary.ErrorTable,
|
||||
@@ -259,6 +251,8 @@ func NewReaderFromClickhouseConnection(
|
||||
|
||||
fluxIntervalForTraceDetail: fluxIntervalForTraceDetail,
|
||||
cache: cache,
|
||||
metadataDB: options.primary.MetadataDB,
|
||||
metadataTable: options.primary.MetadataTable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4126,6 +4120,97 @@ func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.Filt
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) FetchRelatedValues(ctx context.Context, req *v3.FilterAttributeValueRequest) ([]string, error) {
|
||||
var andConditions []string
|
||||
|
||||
andConditions = append(andConditions, fmt.Sprintf("unix_milli >= %d", req.StartTimeMillis))
|
||||
andConditions = append(andConditions, fmt.Sprintf("unix_milli <= %d", req.EndTimeMillis))
|
||||
|
||||
if len(req.ExistingFilterItems) != 0 {
|
||||
for _, item := range req.ExistingFilterItems {
|
||||
// we only support string for related values
|
||||
if item.Key.DataType != v3.AttributeKeyDataTypeString {
|
||||
continue
|
||||
}
|
||||
|
||||
var colName string
|
||||
switch item.Key.Type {
|
||||
case v3.AttributeKeyTypeResource:
|
||||
colName = "resource_attributes"
|
||||
case v3.AttributeKeyTypeTag:
|
||||
colName = "attributes"
|
||||
default:
|
||||
// we only support resource and tag for related values as of now
|
||||
continue
|
||||
}
|
||||
// IN doesn't make use of map value index, we convert it to = or !=
|
||||
operator := item.Operator
|
||||
if v3.FilterOperator(strings.ToLower(string(item.Operator))) == v3.FilterOperatorIn {
|
||||
operator = "="
|
||||
} else if v3.FilterOperator(strings.ToLower(string(item.Operator))) == v3.FilterOperatorNotIn {
|
||||
operator = "!="
|
||||
}
|
||||
addCondition := func(val string) {
|
||||
andConditions = append(andConditions, fmt.Sprintf("mapContains(%s, '%s') AND %s['%s'] %s %s", colName, item.Key.Key, colName, item.Key.Key, operator, val))
|
||||
}
|
||||
switch v := item.Value.(type) {
|
||||
case string:
|
||||
fmtVal := utils.ClickHouseFormattedValue(v)
|
||||
addCondition(fmtVal)
|
||||
case []string:
|
||||
for _, val := range v {
|
||||
fmtVal := utils.ClickHouseFormattedValue(val)
|
||||
addCondition(fmtVal)
|
||||
}
|
||||
case []interface{}:
|
||||
for _, val := range v {
|
||||
fmtVal := utils.ClickHouseFormattedValue(val)
|
||||
addCondition(fmtVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
whereClause := strings.Join(andConditions, " AND ")
|
||||
|
||||
var selectColumn string
|
||||
switch req.TagType {
|
||||
case v3.TagTypeResource:
|
||||
selectColumn = "resource_attributes" + "['" + req.FilterAttributeKey + "']"
|
||||
case v3.TagTypeTag:
|
||||
selectColumn = "attributes" + "['" + req.FilterAttributeKey + "']"
|
||||
default:
|
||||
selectColumn = "attributes" + "['" + req.FilterAttributeKey + "']"
|
||||
}
|
||||
|
||||
filterSubQuery := fmt.Sprintf(
|
||||
"SELECT DISTINCT %s FROM %s.%s WHERE %s LIMIT 100",
|
||||
selectColumn,
|
||||
r.metadataDB,
|
||||
r.metadataTable,
|
||||
whereClause,
|
||||
)
|
||||
zap.L().Debug("filterSubQuery for related values", zap.String("query", filterSubQuery))
|
||||
|
||||
rows, err := r.db.Query(ctx, filterSubQuery)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while executing query: %s", err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var attributeValues []string
|
||||
for rows.Next() {
|
||||
var value string
|
||||
if err := rows.Scan(&value); err != nil {
|
||||
return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
|
||||
}
|
||||
if value != "" {
|
||||
attributeValues = append(attributeValues, value)
|
||||
}
|
||||
}
|
||||
|
||||
return attributeValues, nil
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error) {
|
||||
var err error
|
||||
var filterValueColumn string
|
||||
@@ -4227,6 +4312,13 @@ func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.Fi
|
||||
}
|
||||
}
|
||||
|
||||
if req.IncludeRelated {
|
||||
relatedValues, _ := r.FetchRelatedValues(ctx, req)
|
||||
attributeValues.RelatedValues = &v3.FilterAttributeValueResponse{
|
||||
StringAttributeValues: relatedValues,
|
||||
}
|
||||
}
|
||||
|
||||
return &attributeValues, nil
|
||||
|
||||
}
|
||||
@@ -4907,6 +4999,13 @@ func (r *ClickHouseReader) GetTraceAttributeValues(ctx context.Context, req *v3.
|
||||
}
|
||||
}
|
||||
|
||||
if req.IncludeRelated {
|
||||
relatedValues, _ := r.FetchRelatedValues(ctx, req)
|
||||
attributeValues.RelatedValues = &v3.FilterAttributeValueResponse{
|
||||
StringAttributeValues: relatedValues,
|
||||
}
|
||||
}
|
||||
|
||||
return &attributeValues, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func (c *Controller) GenerateConnectionUrl(
|
||||
}
|
||||
|
||||
// TODO(Raj): parameterized this in follow up changes
|
||||
agentVersion := "0.0.1"
|
||||
agentVersion := "0.0.2"
|
||||
|
||||
connectionUrl := fmt.Sprintf(
|
||||
"https://%s.console.aws.amazon.com/cloudformation/home?region=%s#/stacks/quickcreate?",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#FA7E14" d="M7.983 8.37c-.053.073-.098.133-.141.194L5.775 11.5c-.64.91-1.282 1.82-1.924 2.73a.128.128 0 01-.092.051c-.906-.007-1.813-.017-2.719-.028-.01 0-.02-.003-.04-.006a.455.455 0 01.025-.053 13977.496 13977.496 0 015.446-8.146c.092-.138.188-.273.275-.413a.165.165 0 00.018-.124c-.167-.515-.338-1.03-.508-1.543-.073-.22-.15-.44-.218-.66-.022-.072-.059-.094-.134-.093-.57.002-1.136.001-1.704.001-.108 0-.108 0-.108-.103 0-.674 0-1.347-.002-2.021 0-.075.026-.092.099-.092 1.143.002 2.286.002 3.43 0a.113.113 0 01.076.017.107.107 0 01.045.061 18266.184 18266.184 0 003.92 9.51c.218.53.438 1.059.654 1.59.026.064.053.076.12.056.6-.178 1.2-.352 1.8-.531.075-.023.102-.008.126.064.204.62.412 1.239.62 1.858l.02.073c-.043.015-.083.032-.124.043l-4.085 1.25c-.065.02-.085 0-.106-.054l-1.25-3.048-1.226-2.984-.183-.449c-.01-.026-.023-.048-.043-.087z"/></svg>
|
||||
|
After Width: | Height: | Size: 965 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user