Compare commits

...

23 Commits

Author SHA1 Message Date
Shaheer Kochai
2df1bd64d7 Merge branch 'main' into fix/allow-custom-input-in-range-picker 2025-03-10 11:32:51 +04:30
Vibhu Pandey
1f33928bf9 feat(alertmanager): integrate with ruler (#7222)
### Summary

Integrate the new implementations of the alertmanager along with changes to the ruler. This change can be broadly categoried into 3 parts:

#### Frontend
- The earlier `/api/v1/alerts` api was double encoding the response in json and sending it to the frontend. This PR fixes the json response object. 

For instance, we have gone from the response `{
    "status": "success",
    "data": "{\"status\":\"success\",\"data\":[{\"labels\":{\"alertname\":\"[platform][consumer] consumer is above 100% memory utilization\",\"bu\":\"platform\",\"......
}` to the response `{"status":"success","data":[{"labels":{"alertname":"[Metrics] Pod CP......`

- `msteams` has been changed to `msteamsv2` wherever applicable

#### Ruler
The following changes have been done in the ruler component:

- Removal of the old alertmanager and notifier
- The RuleDB methods `Create`, `Edit` and `Delete` have been made transactional
- Introduction of a new `testPrepareNotifyFunc` for sending test notifications
- Integration with the new alertmanager

#### Alertmanager
Although a huge chunk of the alertmanagers have been merged in previous PRs (the list can be found at https://github.com/SigNoz/platform-pod/issues/404), this PR takes care of changes needed in order to incorporate it with the ruler

- Addition of ruleId based matching
- Support for marshalling the global configuration directly from the upstream alertmanager
- Addition of orgId to the legacy alertmanager
- Support for always adding defaults to both routes and receivers while creating them
- Migration to create the required alertmanager tables
- Migration for msteams to msteamsv2 has been added. We will start using msteamv2 config for the new alertmanager and keep using msteams for the old one.

#### Related Issues / PR's

Closes https://github.com/SigNoz/platform-pod/issues/404
Closes https://github.com/SigNoz/platform-pod/issues/176
2025-03-09 20:00:42 +00:00
Srikanth Chekuri
8abba261a8 fix: add missing send_resolved for email/pager/opsgenie edit payload (#7240) 2025-03-08 18:09:57 +05:30
Shivanshu Raj Shrivastava
8f2e8cccb4 New autocomplete endpoint with filters (#7241)
* feat: new autocomplete endpoint with filters
Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-03-08 11:42:20 +05:30
Vishal Sharma
256fbfc180 chore: add new user preferences for welcome checklist (#7239) 2025-03-07 19:48:18 +05:30
Yunus M
d362f5bce3 feat: update logEvent to pass eventType and replace segment calls wit… (#7209)
* feat: update logEvent to pass eventType and replace segment calls with logEvent

* feat: update logEvent to handle rate limiting

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2025-03-07 15:42:51 +05:30
dependabot[bot]
42f7511e06 chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.2 to 4.0.5 (#7180)
Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.2 to 4.0.5.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.2...v4.0.5)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-07 03:12:55 +00:00
Vishal Sharma
819428ad09 chore: add identify and group event support to /event API (#7219)
* chore: add identify and group event support to /event API

* chore: minor refactor
2025-03-07 00:23:47 +05:30
Shaheer Kochai
29fa5c3cf0 fix the issue of logs preview and count mismatch in pipelines by updating sample logs query param from limit to pageSize (#7231) 2025-03-06 14:55:23 +00:00
Raj Kamal Singh
d09b85bea8 feat: aws integration: support for lambda (#7196)
* feat: aws integration: add service definition for lambda

* feat: aws integration: lambda: add details of metrics collected

* feat: aws integrations: lambda overview: use sum for relevant metrics
2025-03-06 13:03:46 +00:00
Sahil
114a979b14 feat: disabled same url check for traces query builder as well 2025-03-06 16:01:09 +05:30
Sahil
cb69cd91a0 fix: disabled same url check for redirect in logs explorer 2025-03-06 16:01:09 +05:30
Nityananda Gohain
2d73f91380 Fix: Multitenancy support for ORG (#7155)
* fix: support multitenancy in org

* fix: register and login working now

* fix: changes to migration

* fix: migrations run both on sqlite and postgres

* fix: remove user flags from fe and be

* fix: remove ingestion keys from update

* fix: multitenancy support for apdex settings

* fix: render ts for users correctly

* fix: fix migration to run for new tenants

* fix: clean up migrations

* fix: address comments

* Update pkg/sqlmigration/013_update_organization.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: fix build

* fix: force invites with org id

* Update pkg/query-service/auth/auth.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: address comments

* fix: address comments

* fix: provier with their own dialect

* fix: update dialects

* fix: remove unwanted change

* Update pkg/query-service/app/http_handler.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: different files for types

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-03-06 15:39:45 +05:30
Shaheer Kochai
296a444bd8 fix(logs): centralize time range handling in DateTimeSelectionV2 (#7175)
- Remove custom time range handling logic from logs components
- Use unified time range selection through DateTimeSelectionV2 component
2025-03-06 06:15:11 +00:00
dependabot[bot]
36ebde5470 chore(deps): bump dompurify from 3.1.3 to 3.2.4 in /frontend (#7124)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.1.3 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.1.3...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-06 11:05:59 +05:30
Raj Kamal Singh
509d9c7fe5 feat: upgrade aws integration agent version (#7223)
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-03-06 04:52:13 +00:00
Srikanth Chekuri
816cae3aac fix: update bearer to capital case and handle undefined (#7226) 2025-03-06 04:00:30 +00:00
Yunus M
cb2c492618 chore: use platform property to evaluate type of user, update all references (#7162)
* feat: use platform property to evaluate type of user, update all references
2025-03-05 21:50:29 +05:30
Vibhu Pandey
4177b88a4e fix(alertmanager): fix tests for alertmanager (#7225) 2025-03-05 15:54:54 +00:00
Nityananda Gohain
b1e3f03bb5 fix: new implementation for finding missing timerange (#7201)
* fix: new implementation for finding missing timerange

* fix: remove unwanted code

* fix: update if condition

* fix: update logic and the test cases

* fix: correct name

* fix: fix overlapping test case

* fix: add comments

* Update pkg/query-service/querycache/query_range_cache.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix: use step ms

* fix: add one more test case

* fix: test case

* fix: merge missing ranger

* Update pkg/query-service/querycache/query_range_cache.go

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-03-05 20:26:37 +05:30
ahmadshaheer
7417392acb fix: fix the issue of not being able to type in to/from of custom range picker 2025-01-28 10:12:07 +04:30
ahmadshaheer
42589b4ed4 refactor: centralize uPlot x-axis values formatting 2025-01-28 10:12:07 +04:30
ahmadshaheer
5f28f03d54 feat: change the format of uplot x axis to 24 hours 2025-01-28 10:08:54 +04:30
167 changed files with 5779 additions and 2731 deletions

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -23,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

View File

@@ -7,14 +7,14 @@ import (
"go.signoz.io/signoz/ee/query-service/app/api"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/telemetry"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.uber.org/zap"
)
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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))
}
}

View File

@@ -11,7 +11,7 @@ import (
saml2 "github.com/russellhaering/gosaml2"
"go.signoz.io/signoz/ee/query-service/sso"
"go.signoz.io/signoz/ee/query-service/sso/saml"
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 {

View File

@@ -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))

View File

@@ -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",

View File

@@ -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);

View File

@@ -1,20 +1,20 @@
import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import useAnalytics from 'hooks/analytics/useAnalytics';
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useThemeConfig } from 'hooks/useDarkMode';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { identity, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import AlertRuleProvider from 'providers/Alert';
import { useAppContext } from 'providers/App/App';
@@ -24,7 +24,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useCallback, useEffect, useState } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat';
import { extractDomain, 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

View File

@@ -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);

View File

@@ -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,

View File

@@ -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,
},
};
}

View File

@@ -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,

View File

@@ -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,
},
};
}

View File

@@ -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,

View File

@@ -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,
},
};
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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' && {

View File

@@ -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,

View File

@@ -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(() => {

View File

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

View File

@@ -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>
}
/>
);
}

View File

@@ -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;

View File

@@ -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';

View File

@@ -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 => {

View File

@@ -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 || '',

View File

@@ -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 = [
{

View File

@@ -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">

View File

@@ -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,

View File

@@ -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>

View File

@@ -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');

View File

@@ -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,
]);

View File

@@ -3,10 +3,10 @@ import './NoLogs.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
import { ArrowUpRight } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder';
import { isCloudUser } from 'utils/app';
import DOCLINKS from 'utils/docLinks';
export default function NoLogs({
@@ -14,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) {

View File

@@ -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(

View File

@@ -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>
);
},

View File

@@ -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]);

View File

@@ -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: [

View File

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

View File

@@ -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,

View File

@@ -30,7 +30,7 @@ import getTimeString from 'lib/getTimeString';
import { isObject } from 'lodash-es';
import { Check, Copy, Info, Send, Undo } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, 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;

View File

@@ -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;

View File

@@ -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 = {

View File

@@ -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,
};
};

View 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,
};
};

View File

@@ -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

View File

@@ -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 };

View File

@@ -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();

View File

@@ -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,

View File

@@ -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>

View File

@@ -7,9 +7,9 @@ import { Color } from '@signozhq/design-tokens';
import { Button, Flex, Skeleton, Typography } from 'antd';
import { useGetIntegration } from 'hooks/Integrations/useGetIntegration';
import { useGetIntegrationStatus } from 'hooks/Integrations/useGetIntegrationStatus';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { defaultTo } from 'lodash-es';
import { ArrowLeft, MoveUpRight, RotateCw } from 'lucide-react';
import { isCloudUser } from 'utils/app';
import { handleContactSupport } from '../utils';
import IntegrationDetailContent from './IntegrationDetailContent';
@@ -44,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>

View File

@@ -5,10 +5,10 @@ import './Integrations.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, List, Typography } from 'antd';
import { useGetAllIntegrations } from 'hooks/Integrations/useGetAllIntegrations';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { MoveUpRight, RotateCw } from 'lucide-react';
import { Dispatch, SetStateAction, useMemo } from 'react';
import { IntegrationsProps } from 'types/api/integrations/types';
import { isCloudUser } from 'utils/app';
import { handleContactSupport, INTEGRATION_TYPES } from './utils';
@@ -44,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>

View File

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

View File

@@ -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>
);

View File

@@ -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} />;

View File

@@ -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;

View File

@@ -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">

View File

@@ -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,
}),

View File

@@ -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;
}

View File

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

View File

@@ -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(
(

View File

@@ -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(),

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
View File

@@ -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
View File

@@ -262,8 +262,8 @@ github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7F
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.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=

View File

@@ -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
}

View File

@@ -12,10 +12,10 @@ import (
type Config struct {
// The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
// See https://github.com/prometheus/alertmanager/blob/3b06b97af4d146e141af92885a185891eb79a5b0/cmd/alertmanager/main.go#L155C54-L155C249
ExternalUrl *url.URL `mapstructure:"external_url"`
ExternalURL *url.URL `mapstructure:"external_url"`
// GlobalConfig is the global configuration for the alertmanager
Global alertmanagertypes.GlobalConfig `mapstructure:"global"`
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)

View File

@@ -223,7 +223,7 @@ func (server *Server) SetConfig(ctx context.Context, alertmanagerConfig *alertma
return err
}
server.tmpl.ExternalURL = server.srvConfig.ExternalUrl
server.tmpl.ExternalURL = server.srvConfig.ExternalURL
// Build the routing tree and record which receivers are used.
routes := dispatch.NewRoute(config.Route, nil)

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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,
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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))
}()

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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)),
}

View File

@@ -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
}

View File

@@ -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?",

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -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