Compare commits

..

11 Commits

Author SHA1 Message Date
grandwizard28
d05e279bcd Merge branch 'main' into alertmanager 2025-02-12 22:58:29 +05:30
grandwizard28
f1c5d873f7 feat(alertmanager): first attempt at bootstrap 2025-02-12 18:46:28 +05:30
grandwizard28
aec239cc7c feat(alertmanager): first attempt at bootstrap 2025-02-12 18:46:28 +05:30
grandwizard28
59e26652dc feat(alertmanager): first attempt at bootstrap 2025-02-12 18:46:27 +05:30
grandwizard28
e02afc5e97 feat(alertmanager): first attempt at bootstrap 2025-02-12 18:46:27 +05:30
grandwizard28
3eac8ac30b feat(alertmanager): first attempt at bootstrap 2025-02-12 18:46:27 +05:30
grandwizard28
382c4f58e1 refactor(alertmanager): add alertmanager 2025-02-12 18:46:27 +05:30
grandwizard28
73ea632a3f refactor(alertmanager): move to types package 2025-02-12 18:46:27 +05:30
grandwizard28
00fa8810c0 feat(alertmanager): add support for multi org 2025-02-12 18:46:26 +05:30
grandwizard28
6cee330d44 feat(alertmanager): add support for testing receiver 2025-02-12 18:46:26 +05:30
grandwizard28
871c6e642c feat(alertmanager): first iteration of alertmanager 2025-02-12 18:46:25 +05:30
220 changed files with 2559 additions and 10100 deletions

2
.gitignore vendored
View File

@@ -76,3 +76,5 @@ dist/
# ignore user_scripts that is fetched by init-clickhouse
deploy/common/clickhouse/user_scripts/
# queries.active
queries.active

View File

@@ -219,18 +219,12 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
- [Nityananda Gohain](https://github.com/nityanandagohain)
- [Srikanth Chekuri](https://github.com/srikanthccv)
- [Vishal Sharma](https://github.com/makeavish)
- [Shivanshu Raj Shrivastava](https://github.com/shivanshuraj1333)
- [Ekansh Gupta](https://github.com/eKuG)
- [Aniket Agarwal](https://github.com/aniketio-ctrl)
#### Frontend
- [Yunus M](https://github.com/YounixM)
- [Vikrant Gupta](https://github.com/vikrantgupta25)
- [Sagar Rajput](https://github.com/SagarRajput-7)
- [Shaheer Kochai](https://github.com/ahmadshaheer)
- [Amlan Kumar Nandy](https://github.com/amlannandy)
- [Sahil Khan](https://github.com/sawhil)
#### DevOps

View File

@@ -224,7 +224,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.28
image: signoz/signoz-otel-collector:0.111.27
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -248,7 +248,7 @@ services:
- query-service
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:0.111.28
image: signoz/signoz-schema-migrator:0.111.24
deploy:
restart_policy:
condition: on-failure

View File

@@ -160,7 +160,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.28
image: signoz/signoz-otel-collector:0.111.27
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -184,7 +184,7 @@ services:
- query-service
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:0.111.28
image: signoz/signoz-schema-migrator:0.111.24
deploy:
restart_policy:
condition: on-failure

View File

@@ -234,7 +234,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.27}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -260,7 +260,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.24}
container_name: schema-migrator-sync
command:
- sync
@@ -271,7 +271,7 @@ services:
condition: service_healthy
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.24}
container_name: schema-migrator-async
command:
- async

View File

@@ -166,7 +166,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.27}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -188,7 +188,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.24}
container_name: schema-migrator-sync
command:
- sync
@@ -199,7 +199,7 @@ services:
condition: service_healthy
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.24}
container_name: schema-migrator-async
command:
- async

View File

@@ -1,36 +0,0 @@
package middleware
import (
"net/http"
"go.signoz.io/signoz/pkg/types/authtypes"
)
type Pat struct {
uuid *authtypes.UUID
headers []string
}
func NewPat(headers []string) *Pat {
return &Pat{uuid: authtypes.NewUUID(), headers: headers}
}
func (p *Pat) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var values []string
for _, header := range p.headers {
values = append(values, r.Header.Get(header))
}
ctx, err := p.uuid.ContextFromRequest(r.Context(), values...)
if err != nil {
next.ServeHTTP(w, r)
return
}
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}

View File

@@ -20,7 +20,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/types/authtypes"
"go.signoz.io/signoz/pkg/signoz"
)
type APIHandlerOptions struct {
@@ -42,7 +42,7 @@ type APIHandlerOptions struct {
FluxInterval time.Duration
UseLogsNewSchema bool
UseTraceNewSchema bool
JWT *authtypes.JWT
SigNoz *signoz.SigNoz
}
type APIHandler struct {
@@ -67,6 +67,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
FluxInterval: opts.FluxInterval,
UseLogsNewSchema: opts.UseLogsNewSchema,
UseTraceNewSchema: opts.UseTraceNewSchema,
SigNoz: opts.SigNoz,
})
if err != nil {

View File

@@ -50,7 +50,7 @@ func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
}
// if all looks good, call auth
resp, err := baseauth.Login(ctx, &req, ah.opts.JWT)
resp, err := baseauth.Login(ctx, &req)
if ah.HandleError(w, err, http.StatusUnauthorized) {
return
}
@@ -253,7 +253,7 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
return
}
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, identity.Email, ah.opts.JWT)
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, identity.Email)
if err != nil {
zap.L().Error("[receiveGoogleAuth] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
handleSsoError(w, r, redirectUri)
@@ -331,7 +331,7 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
return
}
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, email, ah.opts.JWT)
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, email)
if err != nil {
zap.L().Error("[receiveSAML] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
handleSsoError(w, r, redirectUri)

View File

@@ -37,7 +37,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}
currentUser, err := auth.GetUserFromReqContext(r.Context())
currentUser, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, basemodel.UnauthorizedError(fmt.Errorf(
"couldn't deduce current user: %w", err,
@@ -183,7 +183,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
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

@@ -34,7 +34,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
RespondError(w, model.BadRequest(err), nil)
return
}
user, err := auth.GetUserFromReqContext(r.Context())
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
@@ -97,7 +97,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
return
}
user, err := auth.GetUserFromReqContext(r.Context())
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
@@ -127,7 +127,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
user, err := auth.GetUserFromReqContext(r.Context())
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
@@ -147,7 +147,7 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
id := mux.Vars(r)["id"]
user, err := auth.GetUserFromReqContext(r.Context())
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,

View File

@@ -1,20 +1,25 @@
package app
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
_ "net/http/pprof" // http profiler
"regexp"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/jmoiron/sqlx"
"github.com/rs/cors"
"github.com/soheilhy/cmux"
eemiddleware "go.signoz.io/signoz/ee/http/middleware"
"go.signoz.io/signoz/ee/query-service/app/api"
"go.signoz.io/signoz/ee/query-service/app/db"
"go.signoz.io/signoz/ee/query-service/auth"
@@ -24,8 +29,9 @@ import (
"go.signoz.io/signoz/ee/query-service/interfaces"
"go.signoz.io/signoz/ee/query-service/rules"
"go.signoz.io/signoz/pkg/http/middleware"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types/authtypes"
"go.signoz.io/signoz/pkg/web"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@@ -74,7 +80,6 @@ type ServerOptions struct {
GatewayUrl string
UseLogsNewSchema bool
UseTraceNewSchema bool
Jwt *authtypes.JWT
}
// Server runs HTTP api service
@@ -105,7 +110,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
// NewServer creates and initializes Server
func NewServer(serverOptions *ServerOptions) (*Server, error) {
modelDao, err := dao.InitDao(serverOptions.SigNoz.SQLStore)
modelDao, err := dao.InitDao(serverOptions.SigNoz.SQLStore.SQLxDB())
if err != nil {
return nil, err
}
@@ -128,7 +133,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// initiate license manager
lm, err := licensepkg.StartManager(serverOptions.SigNoz.SQLStore.SQLxDB(), serverOptions.SigNoz.SQLStore.BunDB())
lm, err := licensepkg.StartManager(serverOptions.SigNoz.SQLStore.SQLxDB())
if err != nil {
return nil, err
}
@@ -197,14 +202,14 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore)
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
if err != nil {
return nil, fmt.Errorf(
"couldn't create integrations controller: %w", err,
)
}
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore)
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
if err != nil {
return nil, fmt.Errorf(
"couldn't create cloud provider integrations controller: %w", err,
@@ -264,7 +269,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
GatewayUrl: serverOptions.GatewayUrl,
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
JWT: serverOptions.Jwt,
SigNoz: serverOptions.SigNoz,
}
apiHandler, err := api.NewAPIHandler(apiOpts)
@@ -307,8 +312,6 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
r := baseapp.NewRouter()
r.Use(middleware.NewAuth(zap.L(), s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
r.Use(eemiddleware.NewPat([]string{"SIGNOZ-API-KEY"}).Wrap)
r.Use(middleware.NewTimeout(zap.L(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default,
@@ -340,8 +343,8 @@ 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) {
user, err := auth.GetUserFromRequestContext(ctx, apiHandler)
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
user, err := auth.GetUserFromRequest(r, apiHandler)
if err != nil {
return nil, err
@@ -355,8 +358,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
}
am := baseapp.NewAuthMiddleware(getUserFromRequest)
r.Use(middleware.NewAuth(zap.L(), s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
r.Use(eemiddleware.NewPat([]string{"SIGNOZ-API-KEY"}).Wrap)
r.Use(middleware.NewTimeout(zap.L(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default,
@@ -374,7 +375,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
apiHandler.RegisterQueryRangeV4Routes(r, am)
apiHandler.RegisterWebSocketPaths(r, am)
apiHandler.RegisterMessagingQueuesRoutes(r, am)
apiHandler.MetricExplorerRoutes(r, am)
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
@@ -396,6 +396,174 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
}, nil
}
// TODO(remove): Implemented at pkg/http/middleware/logging.go
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}
// TODO(remove): Implemented at pkg/http/middleware/logging.go
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
// WriteHeader(int) is not called if our response implicitly returns 200 OK, so
// we default to that status code.
return &loggingResponseWriter{w, http.StatusOK}
}
// TODO(remove): Implemented at pkg/http/middleware/logging.go
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
// TODO(remove): Implemented at pkg/http/middleware/logging.go
// Flush implements the http.Flush interface.
func (lrw *loggingResponseWriter) Flush() {
lrw.ResponseWriter.(http.Flusher).Flush()
}
// TODO(remove): Implemented at pkg/http/middleware/logging.go
// Support websockets
func (lrw *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
h, ok := lrw.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, errors.New("hijack not supported")
}
return h.Hijack()
}
func extractQueryRangeData(path string, r *http.Request) (map[string]interface{}, bool) {
pathToExtractBodyFromV3 := "/api/v3/query_range"
pathToExtractBodyFromV4 := "/api/v4/query_range"
data := map[string]interface{}{}
var postData *v3.QueryRangeParamsV3
if (r.Method == "POST") && ((path == pathToExtractBodyFromV3) || (path == pathToExtractBodyFromV4)) {
if r.Body != nil {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
return nil, false
}
r.Body.Close() // must close
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
json.Unmarshal(bodyBytes, &postData)
} else {
return nil, false
}
} else {
return nil, false
}
referrer := r.Header.Get("Referer")
dashboardMatched, err := regexp.MatchString(`/dashboard/[a-zA-Z0-9\-]+/(new|edit)(?:\?.*)?$`, referrer)
if err != nil {
zap.L().Error("error while matching the referrer", zap.Error(err))
}
alertMatched, err := regexp.MatchString(`/alerts/(new|edit)(?:\?.*)?$`, referrer)
if err != nil {
zap.L().Error("error while matching the alert: ", zap.Error(err))
}
logsExplorerMatched, err := regexp.MatchString(`/logs/logs-explorer(?:\?.*)?$`, referrer)
if err != nil {
zap.L().Error("error while matching the logs explorer: ", zap.Error(err))
}
traceExplorerMatched, err := regexp.MatchString(`/traces-explorer(?:\?.*)?$`, referrer)
if err != nil {
zap.L().Error("error while matching the trace explorer: ", zap.Error(err))
}
queryInfoResult := telemetry.GetInstance().CheckQueryInfo(postData)
if (queryInfoResult.MetricsUsed || queryInfoResult.LogsUsed || queryInfoResult.TracesUsed) && (queryInfoResult.FilterApplied) {
if queryInfoResult.MetricsUsed {
telemetry.GetInstance().AddActiveMetricsUser()
}
if queryInfoResult.LogsUsed {
telemetry.GetInstance().AddActiveLogsUser()
}
if queryInfoResult.TracesUsed {
telemetry.GetInstance().AddActiveTracesUser()
}
data["metricsUsed"] = queryInfoResult.MetricsUsed
data["logsUsed"] = queryInfoResult.LogsUsed
data["tracesUsed"] = queryInfoResult.TracesUsed
data["filterApplied"] = queryInfoResult.FilterApplied
data["groupByApplied"] = queryInfoResult.GroupByApplied
data["aggregateOperator"] = queryInfoResult.AggregateOperator
data["aggregateAttributeKey"] = queryInfoResult.AggregateAttributeKey
data["numberOfQueries"] = queryInfoResult.NumberOfQueries
data["queryType"] = queryInfoResult.QueryType
data["panelType"] = queryInfoResult.PanelType
userEmail, err := baseauth.GetEmailFromJwt(r.Context())
if err == nil {
// switch case to set data["screen"] based on the referrer
switch {
case dashboardMatched:
data["screen"] = "panel"
case alertMatched:
data["screen"] = "alert"
case logsExplorerMatched:
data["screen"] = "logs-explorer"
case traceExplorerMatched:
data["screen"] = "traces-explorer"
default:
data["screen"] = "unknown"
return data, true
}
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_API, data, userEmail, true, false)
}
}
return data, true
}
func getActiveLogs(path string, r *http.Request) {
// if path == "/api/v1/dashboards/{uuid}" {
// telemetry.GetInstance().AddActiveMetricsUser()
// }
if path == "/api/v1/logs" {
hasFilters := len(r.URL.Query().Get("q"))
if hasFilters > 0 {
telemetry.GetInstance().AddActiveLogsUser()
}
}
}
func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := baseauth.AttachJwtToContext(r.Context(), r)
r = r.WithContext(ctx)
route := mux.CurrentRoute(r)
path, _ := route.GetPathTemplate()
queryRangeData, metadataExists := extractQueryRangeData(path, r)
getActiveLogs(path, r)
lrw := NewLoggingResponseWriter(w)
next.ServeHTTP(lrw, r)
data := map[string]interface{}{"path": path, "statusCode": lrw.statusCode}
if metadataExists {
for key, value := range queryRangeData {
data[key] = value
}
}
if _, ok := telemetry.EnabledPaths()[path]; ok {
userEmail, err := baseauth.GetEmailFromJwt(r.Context())
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail, true, false)
}
}
})
}
// initListeners initialises listeners of the server
func (s *Server) initListeners() error {
// listen on public port

View File

@@ -3,20 +3,20 @@ package auth
import (
"context"
"fmt"
"net/http"
"time"
"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/authtypes"
"go.uber.org/zap"
)
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) {
patToken, ok := authtypes.UUIDFromContext(ctx)
if ok && patToken != "" {
func GetUserFromRequest(r *http.Request, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) {
patToken := r.Header.Get("SIGNOZ-API-KEY")
if len(patToken) > 0 {
zap.L().Debug("Received a non-zero length PAT token")
ctx := context.Background()
dao := apiHandler.AppDao()
@@ -40,7 +40,7 @@ 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.GroupId = group.Id
user.User.Id = pat.Id
return &basemodel.UserPayload{
User: user.User,
@@ -52,5 +52,5 @@ func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler)
return nil, err
}
}
return baseauth.GetUserFromReqContext(ctx)
return baseauth.GetUserFromRequest(r)
}

View File

@@ -1,10 +1,10 @@
package dao
import (
"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/ee/query-service/dao/sqlite"
"go.signoz.io/signoz/pkg/sqlstore"
)
func InitDao(sqlStore sqlstore.SQLStore) (ModelDao, error) {
return sqlite.InitDB(sqlStore)
func InitDao(inputDB *sqlx.DB) (ModelDao, error) {
return sqlite.InitDB(inputDB)
}

View File

@@ -10,7 +10,6 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/types/authtypes"
)
type ModelDao interface {
@@ -23,7 +22,7 @@ type ModelDao interface {
// auth methods
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (redirectURL string, apierr basemodel.BaseApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
// org domain (auth domains) CRUD ops

View File

@@ -14,7 +14,6 @@ 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/authtypes"
"go.uber.org/zap"
)
@@ -49,7 +48,7 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
Password: hash,
CreatedAt: time.Now().Unix(),
ProfilePictureURL: "", // Currently unused
GroupId: group.ID,
GroupId: group.Id,
OrgId: domain.OrgId,
}
@@ -65,7 +64,7 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
// PrepareSsoRedirect prepares redirect page link after SSO response
// is successfully parsed (i.e. valid email is available)
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (redirectURL string, apierr basemodel.BaseApiError) {
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) {
userPayload, apierr := m.GetUserByEmail(ctx, email)
if !apierr.IsNil() {
@@ -86,7 +85,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
user = &userPayload.User
}
tokenStore, err := baseauth.GenerateJWTForUser(user, jwt)
tokenStore, err := baseauth.GenerateJWTForUser(user)
if err != nil {
zap.L().Error("failed to generate token for SSO login user", zap.Error(err))
return "", model.InternalErrorStr("failed to generate token for the user")

View File

@@ -7,7 +7,6 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao"
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/sqlstore"
)
type modelDao struct {
@@ -30,8 +29,8 @@ func (m *modelDao) checkFeature(key string) error {
}
// InitDB creates and extends base model DB repository
func InitDB(sqlStore sqlstore.SQLStore) (*modelDao, error) {
dao, err := basedsql.InitDB(sqlStore)
func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
dao, err := basedsql.InitDB(inputDB)
if err != nil {
return nil, err
}

View File

@@ -9,25 +9,21 @@ import (
"github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
"github.com/uptrace/bun"
"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"
)
// Repo is license repo. stores license keys in a secured DB
type Repo struct {
db *sqlx.DB
bundb *bun.DB
db *sqlx.DB
}
// NewLicenseRepo initiates a new license repo
func NewLicenseRepo(db *sqlx.DB, bundb *bun.DB) Repo {
func NewLicenseRepo(db *sqlx.DB) Repo {
return Repo{
db: db,
bundb: bundb,
db: db,
}
}
@@ -169,25 +165,24 @@ func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
return nil
}
func (r *Repo) CreateFeature(req *types.FeatureStatus) *basemodel.ApiError {
func (r *Repo) CreateFeature(req *basemodel.Feature) *basemodel.ApiError {
_, err := r.bundb.NewInsert().
Model(req).
Exec(context.Background())
_, err := r.db.Exec(
`INSERT INTO feature_status (name, active, usage, usage_limit, route)
VALUES (?, ?, ?, ?, ?);`,
req.Name, req.Active, req.Usage, req.UsageLimit, req.Route)
if err != nil {
return &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}
}
return nil
}
func (r *Repo) GetFeature(featureName string) (types.FeatureStatus, error) {
var feature types.FeatureStatus
func (r *Repo) GetFeature(featureName string) (basemodel.Feature, error) {
err := r.bundb.NewSelect().
Model(&feature).
Where("name = ?", featureName).
Scan(context.Background())
var feature basemodel.Feature
err := r.db.Get(&feature,
`SELECT * FROM feature_status WHERE name = ?;`, featureName)
if err != nil {
return feature, err
}
@@ -210,19 +205,18 @@ func (r *Repo) GetAllFeatures() ([]basemodel.Feature, error) {
return feature, nil
}
func (r *Repo) UpdateFeature(req types.FeatureStatus) error {
func (r *Repo) UpdateFeature(req basemodel.Feature) error {
_, err := r.bundb.NewUpdate().
Model(&req).
Where("name = ?", req.Name).
Exec(context.Background())
_, err := r.db.Exec(
`UPDATE feature_status SET active = ?, usage = ?, usage_limit = ?, route = ? WHERE name = ?;`,
req.Active, req.Usage, req.UsageLimit, req.Route, req.Name)
if err != nil {
return err
}
return nil
}
func (r *Repo) InitFeatures(req []types.FeatureStatus) error {
func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
// get a feature by name, if it doesn't exist, create it. If it does exist, update it.
for _, feature := range req {
currentFeature, err := r.GetFeature(feature.Name)
@@ -235,7 +229,7 @@ func (r *Repo) InitFeatures(req []types.FeatureStatus) error {
} else if err != nil {
return err
}
feature.Usage = int(currentFeature.Usage)
feature.Usage = currentFeature.Usage
if feature.Usage >= feature.UsageLimit && feature.UsageLimit != -1 {
feature.Active = false
}

View File

@@ -7,13 +7,11 @@ import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/uptrace/bun"
"sync"
"go.signoz.io/signoz/pkg/query-service/auth"
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/types"
"go.signoz.io/signoz/pkg/types/authtypes"
validate "go.signoz.io/signoz/ee/query-service/integrations/signozio"
"go.signoz.io/signoz/ee/query-service/model"
@@ -45,12 +43,12 @@ type Manager struct {
activeFeatures basemodel.FeatureSet
}
func StartManager(db *sqlx.DB, bundb *bun.DB, features ...basemodel.Feature) (*Manager, error) {
func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
if LM != nil {
return LM, nil
}
repo := NewLicenseRepo(db, bundb)
repo := NewLicenseRepo(db)
m := &Manager{
repo: &repo,
}
@@ -239,10 +237,10 @@ func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
defer func() {
if errResponse != nil {
claims, ok := authtypes.ClaimsFromContext(ctx)
if ok {
userEmail, err := auth.GetEmailFromJwt(ctx)
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
map[string]interface{}{"err": errResponse.Err.Error()}, claims.Email, true, false)
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
}
}
}()
@@ -284,41 +282,15 @@ func (lm *Manager) GetFeatureFlags() (basemodel.FeatureSet, error) {
}
func (lm *Manager) InitFeatures(features basemodel.FeatureSet) error {
featureStatus := make([]types.FeatureStatus, len(features))
for i, f := range features {
featureStatus[i] = types.FeatureStatus{
Name: f.Name,
Active: f.Active,
Usage: int(f.Usage),
UsageLimit: int(f.UsageLimit),
Route: f.Route,
}
}
return lm.repo.InitFeatures(featureStatus)
return lm.repo.InitFeatures(features)
}
func (lm *Manager) UpdateFeatureFlag(feature basemodel.Feature) error {
return lm.repo.UpdateFeature(types.FeatureStatus{
Name: feature.Name,
Active: feature.Active,
Usage: int(feature.Usage),
UsageLimit: int(feature.UsageLimit),
Route: feature.Route,
})
return lm.repo.UpdateFeature(feature)
}
func (lm *Manager) GetFeatureFlag(key string) (basemodel.Feature, error) {
featureStatus, err := lm.repo.GetFeature(key)
if err != nil {
return basemodel.Feature{}, err
}
return basemodel.Feature{
Name: featureStatus.Name,
Active: featureStatus.Active,
Usage: int64(featureStatus.Usage),
UsageLimit: int64(featureStatus.UsageLimit),
Route: featureStatus.Route,
}, nil
return lm.repo.GetFeature(key)
}
// GetRepo return the license repo

View File

@@ -7,7 +7,6 @@ import (
"os"
"os/signal"
"strconv"
"syscall"
"time"
"go.opentelemetry.io/otel/sdk/resource"
@@ -20,7 +19,6 @@ import (
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/types/authtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -88,6 +86,8 @@ func init() {
}
func main() {
ctx := context.Background()
var promConfigPath, skipTopLvlOpsPath string
// disables rule execution but allows change to the rule definition
@@ -155,16 +155,6 @@ func main() {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
}
jwtSecret := os.Getenv("SIGNOZ_JWT_SECRET")
if len(jwtSecret) == 0 {
zap.L().Warn("No JWT secret key is specified.")
} else {
zap.L().Info("JWT secret key set successfully.")
}
jwt := authtypes.NewJWT(jwtSecret, 30*time.Minute, 30*24*time.Hour)
serverOptions := &app.ServerOptions{
Config: config,
SigNoz: signoz,
@@ -182,7 +172,15 @@ func main() {
GatewayUrl: gatewayUrl,
UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema,
Jwt: jwt,
}
// Read the jwt secret key
auth.JwtSecret = os.Getenv("SIGNOZ_JWT_SECRET")
if len(auth.JwtSecret) == 0 {
zap.L().Warn("No JWT secret key is specified.")
} else {
zap.L().Info("JWT secret key set successfully.")
}
server, err := app.NewServer(serverOptions)
@@ -194,20 +192,20 @@ func main() {
zap.L().Fatal("Could not start server", zap.Error(err))
}
if err := auth.InitAuthCache(context.Background()); err != nil {
if err := auth.InitAuthCache(ctx); err != nil {
zap.L().Fatal("Failed to initialize auth cache", zap.Error(err))
}
signalsChannel := make(chan os.Signal, 1)
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)
if err := signoz.Start(ctx); err != nil {
zap.L().Fatal("Failed to start signoz", zap.Error(err))
}
for {
select {
case status := <-server.HealthCheckStatus():
zap.L().Info("Received HealthCheck status: ", zap.Int("status", int(status)))
case <-signalsChannel:
zap.L().Fatal("Received OS Interrupt Signal ... ")
server.Stop()
}
if err := signoz.Wait(ctx); err != nil {
zap.L().Fatal("Failed to wait for signoz", zap.Error(err))
}
server.Stop()
if err := signoz.Stop(ctx); err != nil {
zap.L().Fatal("Failed to stop signoz", zap.Error(err))
}
}

View File

@@ -157,6 +157,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
@@ -279,6 +286,13 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -415,4 +429,11 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -15,7 +15,6 @@ import {
Typography,
} from 'antd';
import { FilterDropdownProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import {
getQueueOverview,
QueueOverviewResponse,
@@ -459,7 +458,6 @@ export default function CeleryOverviewTable({
const handleRowClick = (record: RowData): void => {
onRowClick(record);
logEvent('MQ Overview Page: Right Panel', { ...record });
};
const getFilteredData = useCallback(
@@ -483,22 +481,6 @@ export default function CeleryOverviewTable({
tableData,
]);
const prevTableDataRef = useRef<string>();
useEffect(() => {
if (tableData.length > 0) {
const currentTableData = JSON.stringify(tableData);
if (currentTableData !== prevTableDataRef.current) {
logEvent(`MQ Overview Page: List rendered`, {
dataRender: tableData.length,
});
prevTableDataRef.current = currentTableData;
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(tableData)]);
return (
<div className="celery-overview-table-container">
<Input.Search

View File

@@ -2,7 +2,6 @@ import './CeleryTaskDetail.style.scss';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Divider, Drawer, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { PANEL_TYPES } from 'constants/queryBuilder';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -99,12 +98,6 @@ export default function CeleryTaskDetail({
...rowData,
[taskData.entity]: taskData.value,
});
logEvent('MQ Celery: navigation to trace page', {
filters,
startTime,
endTime,
source: widgetData.title,
});
navigateToTrace(filters, startTime, endTime);
}}
start={startTime}

View File

@@ -42,12 +42,10 @@ import {
function CeleryTaskBar({
onClick,
queryEnabled,
checkIfDataExists,
}: {
onClick?: (task: CaptureDataProps) => void;
queryEnabled: boolean;
checkIfDataExists?: (isDataAvailable: boolean) => void;
}): JSX.Element {
const history = useHistory();
const { pathname } = useLocation();
@@ -189,7 +187,6 @@ function CeleryTaskBar({
onGraphClick(celerySlowestTasksTableWidgetData, ...args)
}
customSeries={getCustomSeries}
dataAvailable={checkIfDataExists}
/>
)}
{barState === CeleryTaskState.Failed && (
@@ -235,7 +232,6 @@ function CeleryTaskBar({
CeleryTaskBar.defaultProps = {
onClick: (): void => {},
checkIfDataExists: undefined,
};
export default CeleryTaskBar;

View File

@@ -35,8 +35,6 @@ function CeleryTaskGraph({
customErrorMessage,
start,
end,
checkIfDataExists,
analyticsEvent,
}: {
widgetData: Widgets;
onClick?: (task: CaptureDataProps) => void;
@@ -50,8 +48,6 @@ function CeleryTaskGraph({
customErrorMessage?: string;
start?: number;
end?: number;
checkIfDataExists?: (isDataAvailable: boolean) => void;
analyticsEvent?: string;
}): JSX.Element {
const history = useHistory();
const { pathname } = useLocation();
@@ -129,8 +125,6 @@ function CeleryTaskGraph({
customErrorMessage={customErrorMessage}
start={start}
end={end}
dataAvailable={checkIfDataExists}
analyticsEvent={analyticsEvent}
/>
</Card>
);
@@ -147,8 +141,6 @@ CeleryTaskGraph.defaultProps = {
customErrorMessage: undefined,
start: undefined,
end: undefined,
checkIfDataExists: undefined,
analyticsEvent: undefined,
};
export default CeleryTaskGraph;

View File

@@ -1,7 +1,6 @@
import './CeleryTaskGraph.style.scss';
import { Card, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { CardContainer } from 'container/GridCardLayout/styles';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { ChevronDown, ChevronUp } from 'lucide-react';
@@ -93,15 +92,6 @@ export default function CeleryTaskGraphGrid({
}));
};
const checkIfDataExists = (isDataAvailable: boolean, title: string): void => {
if (isDataAvailable) {
logEvent(`MQ Celery: ${title} data exists`, {
graph: title,
isDataAvailable,
});
}
};
return (
<div className="celery-task-graph-grid-container">
<div className="metric-based-graphs">
@@ -134,10 +124,6 @@ export default function CeleryTaskGraphGrid({
widgetData={celeryActiveTasksData}
queryEnabled={queryEnabled}
customErrorMessage="Enable Flower metrics to view this graph"
checkIfDataExists={(isDataAvailable): void =>
checkIfDataExists(isDataAvailable, 'Active Tasks by worker')
}
analyticsEvent="MQ Celery: Flower metric not enabled"
/>
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
@@ -187,19 +173,8 @@ export default function CeleryTaskGraphGrid({
</div>
{!collapsedSections.traceBasedGraphs && (
<>
<CeleryTaskBar
queryEnabled={queryEnabled}
onClick={onClick}
checkIfDataExists={(isDataAvailable): void =>
checkIfDataExists(isDataAvailable, 'State Graph')
}
/>
<CeleryTaskLatencyGraph
queryEnabled={queryEnabled}
checkIfDataExists={(isDataAvailable): void =>
checkIfDataExists(isDataAvailable, 'Task Latency')
}
/>
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
<CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
<div className="celery-task-graph-grid-bottom">
{bottomWidgetData.map((widgetData, index) => (
<CeleryTaskGraph
@@ -209,9 +184,6 @@ export default function CeleryTaskGraphGrid({
queryEnabled={queryEnabled}
rightPanelTitle={rightPanelTitle[index]}
applyCeleryTaskFilter
checkIfDataExists={(isDataAvailable): void =>
checkIfDataExists(isDataAvailable, rightPanelTitle[index])
}
/>
))}
</div>

View File

@@ -1,7 +1,6 @@
import './CeleryTaskGraph.style.scss';
import { Col, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ViewMenuAction } from 'container/GridCardLayout/config';
@@ -41,10 +40,8 @@ export enum CeleryTaskGraphState {
function CeleryTaskLatencyGraph({
queryEnabled,
checkIfDataExists,
}: {
queryEnabled: boolean;
checkIfDataExists?: (isDataAvailable: boolean) => void;
}): JSX.Element {
const history = useHistory();
const { pathname } = useLocation();
@@ -64,10 +61,6 @@ function CeleryTaskLatencyGraph({
const handleTabClick = (key: CeleryTaskGraphState): void => {
setGraphState(key as CeleryTaskGraphState);
logEvent('MQ Celery: Task latency graph tab clicked', {
taskName: urlQuery.get(QueryParams.taskName),
graphState: key,
});
};
const onDragSelect = useCallback(
@@ -202,7 +195,6 @@ function CeleryTaskLatencyGraph({
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p99_latency')}
isQueryEnabled={queryEnabled}
dataAvailable={checkIfDataExists}
/>
</>
)}
@@ -223,7 +215,6 @@ function CeleryTaskLatencyGraph({
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p95_latency')}
isQueryEnabled={queryEnabled}
dataAvailable={checkIfDataExists}
/>
</>
)}
@@ -243,7 +234,6 @@ function CeleryTaskLatencyGraph({
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p90_latency')}
isQueryEnabled={queryEnabled}
dataAvailable={checkIfDataExists}
/>
</>
)}
@@ -253,7 +243,3 @@ function CeleryTaskLatencyGraph({
}
export default CeleryTaskLatencyGraph;
CeleryTaskLatencyGraph.defaultProps = {
checkIfDataExists: undefined,
};

View File

@@ -2,7 +2,6 @@
import './CeleryTaskGraph.style.scss';
import { Col, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Dispatch, SetStateAction, useMemo } from 'react';
@@ -45,16 +44,12 @@ function CeleryTaskStateGraphConfig({
{ label: 'Successful', key: CeleryTaskState.Successful },
];
const urlQuery = useUrlQuery();
const handleTabClick = (key: CeleryTaskState): void => {
setBarState(key as CeleryTaskState);
logEvent('MQ Celery: State graph tab clicked', {
taskName: urlQuery.get(QueryParams.taskName),
graphState: key,
});
};
const urlQuery = useUrlQuery();
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(

View File

@@ -18,9 +18,7 @@ import {
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query';
import { useOptionsMenu } from 'container/OptionsMenu';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
@@ -31,7 +29,6 @@ import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { popupContainer } from 'utils/selectPopupContainer';
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
@@ -86,20 +83,7 @@ function ExplorerCard({
const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || '';
const { options } = useOptionsMenu({
storageKey:
sourcepage === DataSource.TRACES
? LOCALSTORAGE.TRACES_LIST_OPTIONS
: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: sourcepage,
aggregateOperator: StringOperators.NOOP,
});
const isQueryUpdated = isStagedQueryUpdated(
viewsData?.data?.data,
viewKey,
options,
);
const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey);
const { mutateAsync: updateViewAsync } = useUpdateView({
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),

View File

@@ -6,24 +6,11 @@ import { DataSource } from 'types/common/queryBuilder';
import { viewMockData } from '../__mock__/viewData';
import ExplorerCard from '../ExplorerCard';
const historyReplace = jest.fn();
// eslint-disable-next-line sonarjs/no-duplicate-string
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
}),
useHistory: (): any => ({
...jest.requireActual('react-router-dom').useHistory(),
replace: historyReplace,
}),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({

View File

@@ -2,7 +2,6 @@ import { FormInstance } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import { AxiosResponse } from 'axios';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { OptionsQuery } from 'container/OptionsMenu/types';
import { UseMutateAsyncFunction } from 'react-query';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -37,7 +36,6 @@ export interface IsQueryUpdatedInViewProps {
data: ViewProps[] | undefined;
stagedQuery: Query | null;
currentPanelType: PANEL_TYPES | null;
options: OptionsQuery;
}
export interface SaveViewWithNameProps {

View File

@@ -80,13 +80,12 @@ export const isQueryUpdatedInView = ({
data,
stagedQuery,
currentPanelType,
options,
}: IsQueryUpdatedInViewProps): boolean => {
const currentViewDetails = getViewDetailsUsingViewKey(viewKey, data);
if (!currentViewDetails) {
return false;
}
const { query, panelType, extraData } = currentViewDetails;
const { query, panelType } = currentViewDetails;
// Omitting id from aggregateAttribute and groupBy
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
@@ -98,15 +97,12 @@ export const isQueryUpdatedInView = ({
) {
return false;
}
return (
panelType !== currentPanelType ||
!isEqual(query.builder, updatedCurrentQuery?.builder) ||
!isEqual(query.clickhouse_sql, updatedCurrentQuery?.clickhouse_sql) ||
!isEqual(query.promql, updatedCurrentQuery?.promql) ||
!isEqual(
options?.selectColumns,
extraData && JSON.parse(extraData)?.selectColumns,
)
!isEqual(query.promql, updatedCurrentQuery?.promql)
);
};

View File

@@ -6,8 +6,6 @@ import { ColumnGroupType, ColumnType } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { SlidersHorizontal } from 'lucide-react';
import { memo, useEffect, useState } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -27,12 +25,8 @@ function DynamicColumnTable({
onDragColumn,
facingIssueBtn,
shouldSendAlertsLogEvent,
pagination,
...restProps
}: DynamicColumnTableProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const urlQuery = useUrlQuery();
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
columns,
);
@@ -99,28 +93,6 @@ function DynamicColumnTable({
type: 'checkbox',
})) || [];
// Get current page from URL or default to 1
const currentPage = Number(urlQuery.get('page')) || 1;
const handlePaginationChange = (page: number, pageSize?: number): void => {
// Update URL with new page number while preserving other params
urlQuery.set('page', page.toString());
const newUrl = `${window.location.pathname}?${urlQuery.toString()}`;
safeNavigate(newUrl);
// Call original pagination handler if provided
if (pagination?.onChange && !!pageSize) {
pagination.onChange(page, pageSize);
}
};
const enhancedPagination = {
...pagination,
current: currentPage, // Ensure the pagination component shows the correct page
onChange: handlePaginationChange,
};
return (
<div className="DynamicColumnTable">
<Flex justify="flex-end" align="center" gap={8}>
@@ -144,7 +116,6 @@ function DynamicColumnTable({
<ResizeTable
columns={columnsData}
onDragColumn={onDragColumn}
pagination={enhancedPagination}
{...restProps}
/>
</div>

View File

@@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TableProps } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { PaginationProps } from 'antd/lib';
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
import { LaunchChatSupportProps } from 'components/LaunchChatSupport/LaunchChatSupport';
@@ -16,7 +15,6 @@ export interface DynamicColumnTableProps extends TableProps<any> {
onDragColumn?: (fromIndex: number, toIndex: number) => void;
facingIssueBtn?: LaunchChatSupportProps;
shouldSendAlertsLogEvent?: boolean;
pagination?: PaginationProps;
}
export type GetVisibleColumnsFunction = (

View File

@@ -23,4 +23,5 @@ export enum FeatureKeys {
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
AWS_INTEGRATION = 'AWS_INTEGRATION',
}

View File

@@ -3,7 +3,7 @@ import './AccountActions.style.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Select, Skeleton } from 'antd';
import { SelectProps } from 'antd/lib';
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, ChevronDown } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
@@ -159,9 +159,8 @@ function AccountActions(): JSX.Element {
useEffect(() => {
if (initialAccount !== null) {
setActiveAccount(initialAccount);
const latestUrlQuery = new URLSearchParams(window.location.search);
latestUrlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
navigate({ search: latestUrlQuery.toString() });
urlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
navigate({ search: urlQuery.toString() });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialAccount]);

View File

@@ -6,7 +6,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
getRegionPreviewText,
useAccountSettingsModal,
} from 'hooks/integration/aws/useAccountSettingsModal';
} from 'hooks/integrations/aws/useAccountSettingsModal';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { Dispatch, SetStateAction, useCallback } from 'react';

View File

@@ -2,11 +2,9 @@ import './CloudAccountSetupModal.style.scss';
import { Color } from '@signozhq/design-tokens';
import SignozModal from 'components/SignozModal/SignozModal';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useIntegrationModal } from 'hooks/integration/aws/useIntegrationModal';
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
import { SquareArrowOutUpRight } from 'lucide-react';
import { useCallback } from 'react';
import { useQueryClient } from 'react-query';
import {
ActiveViewEnum,
@@ -20,7 +18,6 @@ import { SuccessView } from './SuccessView';
function CloudAccountSetupModal({
onClose,
}: IntegrationModalProps): JSX.Element {
const queryClient = useQueryClient();
const {
form,
modalState,
@@ -113,10 +110,7 @@ function CloudAccountSetupModal({
</div>
),
block: true,
onOk: (): void => {
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
handleClose();
},
onOk: handleClose,
cancelButtonProps: { style: { display: 'none' } },
disabled: false,
};
@@ -157,7 +151,6 @@ function CloudAccountSetupModal({
activeView,
handleClose,
setActiveView,
queryClient,
]);
const modalConfig = getModalConfig();

View File

@@ -1,6 +1,6 @@
import { Form } from 'antd';
import cx from 'classnames';
import { useAccountStatus } from 'hooks/integration/aws/useAccountStatus';
import { useAccountStatus } from 'hooks/integrations/aws/useAccountStatus';
import { useRef } from 'react';
import { AccountStatusResponse } from 'types/api/integrations/aws';
import { regions } from 'utils/regions';

View File

@@ -1,7 +1,7 @@
import './RegionSelector.style.scss';
import { Checkbox } from 'antd';
import { useRegionSelection } from 'hooks/integration/aws/useRegionSelection';
import { useRegionSelection } from 'hooks/integrations/aws/useRegionSelection';
import { Dispatch, SetStateAction } from 'react';
import { regions } from 'utils/regions';

View File

@@ -7,7 +7,7 @@ import {
ServiceConfig,
SupportedSignals,
} from 'container/CloudIntegrationPage/ServicesSection/types';
import { useUpdateServiceConfig } from 'hooks/integration/aws/useUpdateServiceConfig';
import { useUpdateServiceConfig } from 'hooks/integrations/aws/useUpdateServiceConfig';
import { useCallback, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
@@ -34,19 +34,10 @@ function ConfigureServiceModal({
const [isLoading, setIsLoading] = useState(false);
// Track current form values
const initialValues = {
const [currentValues, setCurrentValues] = useState({
metrics: initialConfig?.metrics?.enabled || false,
logs: initialConfig?.logs?.enabled || false,
};
const [currentValues, setCurrentValues] = useState(initialValues);
const isSaveDisabled = useMemo(
() =>
// disable only if current values are same as the initial config
currentValues.metrics === initialValues.metrics &&
currentValues.logs === initialValues.logs,
[currentValues, initialValues.metrics, initialValues.logs],
);
});
const {
mutate: updateServiceConfig,
@@ -102,6 +93,11 @@ function ConfigureServiceModal({
onClose,
]);
const isSaveDisabled = useMemo(
() => currentValues.metrics === false && currentValues.logs === false,
[currentValues],
);
const handleClose = useCallback(() => {
form.resetFields();
onClose();

View File

@@ -5,7 +5,7 @@ import CloudServiceDashboards from 'container/CloudIntegrationPage/ServicesSecti
import CloudServiceDataCollected from 'container/CloudIntegrationPage/ServicesSection/CloudServiceDataCollected';
import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/types';
import dayjs from 'dayjs';
import { useServiceDetails } from 'hooks/integration/aws/useServiceDetails';
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo, useState } from 'react';
@@ -111,27 +111,24 @@ function ServiceDetails(): JSX.Element | null {
<div className="service-details__title-bar">
<div className="service-details__details-title">Details</div>
<div className="service-details__right-actions">
{isAnySignalConfigured && (
<ServiceStatus serviceStatus={serviceDetailsData.status} />
)}
<ServiceStatus serviceStatus={serviceDetailsData.status} />
{!!cloudAccountId &&
(isAnySignalConfigured ? (
<Button
className="configure-button configure-button--default"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Configure ({enabledSignals}/{totalSupportedSignals})
</Button>
) : (
<Button
type="primary"
className="configure-button configure-button--primary"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Enable Service
</Button>
))}
{!!cloudAccountId && isAnySignalConfigured ? (
<Button
className="configure-button configure-button--default"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Configure ({enabledSignals}/{totalSupportedSignals})
</Button>
) : (
<Button
type="primary"
className="configure-button configure-button--primary"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Enable Service
</Button>
)}
</div>
</div>
<div className="service-details__overview">

View File

@@ -1,5 +1,5 @@
import Spinner from 'components/Spinner';
import { useGetAccountServices } from 'hooks/integration/aws/useGetAccountServices';
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
@@ -24,11 +24,10 @@ function ServicesList({
const handleActiveService = useCallback(
(serviceId: string): void => {
const latestUrlQuery = new URLSearchParams(window.location.search);
latestUrlQuery.set('service', serviceId);
navigate({ search: latestUrlQuery.toString() });
urlQuery.set('service', serviceId);
navigate({ search: urlQuery.toString() });
},
[navigate],
[navigate, urlQuery],
);
const filteredServices = useMemo(() => {

View File

@@ -3,7 +3,7 @@ import './ServicesTabs.style.scss';
import { Color } from '@signozhq/design-tokens';
import type { SelectProps, TabsProps } from 'antd';
import { Select, Tabs } from 'antd';
import { getAwsServices } from 'api/integration/aws';
import { getAwsServices } from 'api/integrations/aws';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useUrlQuery from 'hooks/useUrlQuery';
import { ChevronDown } from 'lucide-react';

View File

@@ -27,12 +27,6 @@ jest.mock('uplot', () => {
};
});
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
let mockWindowOpen: jest.Mock;
window.ResizeObserver =

View File

@@ -36,11 +36,6 @@ window.ResizeObserver =
unobserve: jest.fn(),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
describe('Anomaly Alert Documentation Redirection', () => {
let mockWindowOpen: jest.Mock;

View File

@@ -212,46 +212,13 @@ function ExplorerOptions({
0.08,
);
const { options, handleOptionsChange } = useOptionsMenu({
storageKey:
sourcepage === DataSource.TRACES
? LOCALSTORAGE.TRACES_LIST_OPTIONS
: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: sourcepage,
aggregateOperator: StringOperators.NOOP,
});
const getUpdatedExtraData = (
extraData: string | undefined,
newSelectedColumns: BaseAutocompleteData[],
): string => {
let updatedExtraData;
if (extraData) {
const parsedExtraData = JSON.parse(extraData);
parsedExtraData.selectColumns = newSelectedColumns;
updatedExtraData = JSON.stringify(parsedExtraData);
} else {
updatedExtraData = JSON.stringify({
color: Color.BG_SIENNA_500,
selectColumns: newSelectedColumns,
});
}
return updatedExtraData;
};
const updatedExtraData = getUpdatedExtraData(
extraData,
options?.selectColumns,
);
const {
mutateAsync: updateViewAsync,
isLoading: isViewUpdating,
} = useUpdateView({
compositeQuery,
viewKey,
extraData: updatedExtraData,
extraData: extraData || JSON.stringify({ color: Color.BG_SIENNA_500 }),
sourcePage: sourcepage,
viewName,
});
@@ -263,11 +230,13 @@ function ExplorerOptions({
};
const onUpdateQueryHandler = (): void => {
const extraData = viewsData?.data?.data?.find((view) => view.uuid === viewKey)
?.extraData;
updateViewAsync(
{
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
viewKey,
extraData: updatedExtraData,
extraData: extraData || JSON.stringify({ color: Color.BG_SIENNA_500 }),
sourcePage: sourcepage,
viewName,
},
@@ -289,6 +258,15 @@ function ExplorerOptions({
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const { options, handleOptionsChange } = useOptionsMenu({
storageKey:
sourcepage === DataSource.TRACES
? LOCALSTORAGE.TRACES_LIST_OPTIONS
: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: sourcepage,
aggregateOperator: StringOperators.NOOP,
});
type ExtraData = {
selectColumns?: BaseAutocompleteData[];
version?: number;
@@ -444,11 +422,7 @@ function ExplorerOptions({
history.replace(DATASOURCE_VS_ROUTES[sourcepage]);
};
const isQueryUpdated = isStagedQueryUpdated(
viewsData?.data?.data,
viewKey,
options,
);
const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey);
const {
isLoading: isSaveViewLoading,

View File

@@ -17,8 +17,8 @@ import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEqual } from 'lodash-es';
@@ -87,7 +87,7 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { featureFlags } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const { selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
@@ -224,7 +224,7 @@ function FormAlertRules({
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [detectionMethod]);
@@ -295,8 +295,8 @@ function FormAlertRules({
urlQuery.delete(QueryParams.panelTypes);
urlQuery.delete(QueryParams.ruleId);
urlQuery.delete(QueryParams.relativeTime);
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, [safeNavigate, urlQuery]);
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, [urlQuery]);
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
@@ -515,7 +515,7 @@ function FormAlertRules({
urlQuery.delete(QueryParams.panelTypes);
urlQuery.delete(QueryParams.ruleId);
urlQuery.delete(QueryParams.relativeTime);
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, 2000);
} else {
logData = {

View File

@@ -20,11 +20,11 @@ import {
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useChartMutable } from 'hooks/useChartMutable';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import GetMinMax from 'lib/getMinMax';
import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -49,7 +49,6 @@ function FullView({
isDependedDataLoaded = false,
onToggleModelHandler,
}: FullViewProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const { selectedTime: globalSelectedTime } = useSelector<
AppState,
GlobalReducer
@@ -138,9 +137,9 @@ function FullView({
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
},
[dispatch, location.pathname, safeNavigate, urlQuery],
[dispatch, location.pathname, urlQuery],
);
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<

View File

@@ -23,12 +23,6 @@ jest.mock('react-router-dom', () => ({
}),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),

View File

@@ -10,9 +10,9 @@ import { placeWidgetAtBottom } from 'container/NewWidget/utils';
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import {
@@ -51,7 +51,6 @@ function WidgetGraphComponent({
customSeries,
customErrorMessage,
}: WidgetGraphComponentProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false);
const { notifications } = useNotifications();
@@ -174,7 +173,7 @@ function WidgetGraphComponent({
graphType: widget?.panelTypes,
widgetId: uuid,
};
safeNavigate(`${pathname}/new?${createQueryParams(queryParams)}`);
history.push(`${pathname}/new?${createQueryParams(queryParams)}`);
},
},
);
@@ -195,7 +194,7 @@ function WidgetGraphComponent({
const separator = existingSearch.toString() ? '&' : '';
const newSearch = `${existingSearch}${separator}${updatedSearch}`;
safeNavigate({
history.push({
pathname,
search: newSearch,
});
@@ -222,7 +221,7 @@ function WidgetGraphComponent({
});
setGraphVisibility(localStoredVisibilityState);
}
safeNavigate({
history.push({
pathname,
search: createQueryParams(updatedQueryParams),
});

View File

@@ -1,4 +1,3 @@
import logEvent from 'api/common/logEvent';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -45,7 +44,6 @@ function GridCardGraph({
customErrorMessage,
start,
end,
analyticsEvent,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
@@ -221,11 +219,6 @@ function GridCardGraph({
setIsInternalServerError(
String(error.message).includes('API responded with 500'),
);
if (analyticsEvent) {
logEvent(analyticsEvent, {
error: error.message,
});
}
}
setDashboardQueryRangeCalled(true);
},
@@ -290,7 +283,6 @@ GridCardGraph.defaultProps = {
threshold: undefined,
headerMenuList: [MenuItemKeys.View],
version: 'v3',
analyticsEvent: undefined,
};
export default memo(GridCardGraph);

View File

@@ -58,7 +58,6 @@ export interface GridCardGraphProps {
customErrorMessage?: string;
start?: number;
end?: number;
analyticsEvent?: string;
}
export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@@ -15,8 +15,8 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { defaultTo, isUndefined } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import {
@@ -55,7 +55,6 @@ interface GraphLayoutProps {
// eslint-disable-next-line sonarjs/cognitive-complexity
function GraphLayout(props: GraphLayoutProps): JSX.Element {
const { handle } = props;
const { safeNavigate } = useSafeNavigate();
const {
selectedDashboard,
layouts,
@@ -67,7 +66,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
setSelectedRowWidgetId,
isDashboardFetching,
} = useDashboard();
const { data } = selectedDashboard || {};
const { pathname } = useLocation();
@@ -216,13 +214,13 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, safeNavigate, urlQuery],
[dispatch, pathname, urlQuery],
);
useEffect(() => {
@@ -233,8 +231,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
!isEqual(layouts, dashboardLayout) &&
!isDashboardLocked &&
saveLayoutPermission &&
!updateDashboardMutation.isLoading &&
!isDashboardFetching
!updateDashboardMutation.isLoading
) {
onSaveHandler();
}

View File

@@ -18,8 +18,8 @@ import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
import useComponentPermission from 'hooks/useComponentPermission';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { isEmpty } from 'lodash-es';
import { CircleX, X } from 'lucide-react';
@@ -72,7 +72,6 @@ function WidgetHeader({
setSearchTerm,
}: IWidgetHeaderProps): JSX.Element | null {
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const onEditHandler = useCallback((): void => {
const widgetId = widget.id;
urlQuery.set(QueryParams.widgetId, widgetId);
@@ -82,8 +81,8 @@ function WidgetHeader({
encodeURIComponent(JSON.stringify(widget.query)),
);
const generatedUrl = `${window.location.pathname}/new?${urlQuery}`;
safeNavigate(generatedUrl);
}, [safeNavigate, urlQuery, widget.id, widget.panelTypes, widget.query]);
history.push(generatedUrl);
}, [urlQuery, widget.id, widget.panelTypes, widget.query]);
const onCreateAlertsHandler = useCreateAlerts(widget, 'dashboardView');

View File

@@ -35,7 +35,7 @@ import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import history from 'lib/history';
import { get, isEmpty, isUndefined } from 'lodash-es';
import {
ArrowDownWideNarrow,
@@ -74,7 +74,7 @@ import {
} from 'react';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { generatePath, Link } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import {
Dashboard,
@@ -105,7 +105,7 @@ function DashboardsList(): JSX.Element {
} = useGetAllDashboard();
const { user } = useAppContext();
const { safeNavigate } = useSafeNavigate();
const {
listSortOrder: sortOrder,
setListSortOrder: setSortOrder,
@@ -293,7 +293,7 @@ function DashboardsList(): JSX.Element {
});
if (response.statusCode === 200) {
safeNavigate(
history.push(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.payload.uuid,
}),
@@ -313,7 +313,7 @@ function DashboardsList(): JSX.Element {
errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
});
}
}, [newDashboardState, safeNavigate, t]);
}, [newDashboardState, t]);
const onModalHandler = (uploadedGrafana: boolean): void => {
logEvent('Dashboard List: Import JSON clicked', {});
@@ -418,7 +418,7 @@ function DashboardsList(): JSX.Element {
if (event.metaKey || event.ctrlKey) {
window.open(getLink(), '_blank');
} else {
safeNavigate(getLink());
history.push(getLink());
}
logEvent('Dashboard List: Clicked on dashboard', {
dashboardId: dashboard.id,
@@ -444,12 +444,10 @@ function DashboardsList(): JSX.Element {
placement="left"
overlayClassName="title-toolip"
>
<div
<Link
to={getLink()}
className="title-link"
onClick={(e): void => {
e.stopPropagation();
safeNavigate(getLink());
}}
onClick={(e): void => e.stopPropagation()}
>
<img
src={dashboard?.image || Base64Icons[0]}
@@ -462,7 +460,7 @@ function DashboardsList(): JSX.Element {
>
{dashboard.name}
</Typography.Text>
</div>
</Link>
</Tooltip>
</div>

View File

@@ -18,8 +18,8 @@ import createDashboard from 'api/dashboard/create';
import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
import history from 'lib/history';
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
// See more: https://github.com/lucide-icons/lucide/issues/94
@@ -33,7 +33,6 @@ function ImportJSON({
uploadedGrafana,
onModalHandler,
}: ImportJSONProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const [jsonData, setJsonData] = useState<Record<string, unknown>>();
const { t } = useTranslation(['dashboard', 'common']);
const [isUploadJSONError, setIsUploadJSONError] = useState<boolean>(false);
@@ -98,7 +97,7 @@ function ImportJSON({
});
if (response.statusCode === 200) {
safeNavigate(
history.push(
generatePath(ROUTES.DASHBOARD, {
dashboardId: response.payload.uuid,
}),

View File

@@ -2,12 +2,14 @@ import Graph from 'components/Graph';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { themeColors } from 'constants/theme';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
import useUrlQuery from 'hooks/useUrlQuery';
import getChartData, { GetChartDataProps } from 'lib/getChartData';
import GetMinMax from 'lib/getMinMax';
import { colors } from 'lib/getRandomColor';
import { memo, useCallback, useMemo } from 'react';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
@@ -26,7 +28,6 @@ function LogsExplorerChart({
const dispatch = useDispatch();
const urlQuery = useUrlQuery();
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const handleCreateDatasets: Required<GetChartDataProps>['createDataset'] = useCallback(
(element, index, allLabels) => ({
data: element,
@@ -61,13 +62,41 @@ function LogsExplorerChart({
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
},
[dispatch, location.pathname, safeNavigate, urlQuery],
[dispatch, location.pathname, urlQuery],
);
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
const startTime = searchParams.get(QueryParams.startTime);
const endTime = searchParams.get(QueryParams.endTime);
const relativeTime = searchParams.get(
QueryParams.relativeTime,
) as CustomTimeType;
if (relativeTime) {
dispatch(UpdateTimeInterval(relativeTime));
} else if (startTime && endTime && startTime !== endTime) {
dispatch(
UpdateTimeInterval('custom', [
parseInt(getTimeString(startTime), 10),
parseInt(getTimeString(endTime), 10),
]),
);
}
};
useEffect(() => {
window.addEventListener('popstate', handleBackNavigation);
return (): void => {
window.removeEventListener('popstate', handleBackNavigation);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const graphData = useMemo(
() =>
getChartData({

View File

@@ -38,7 +38,6 @@ import useAxiosError from 'hooks/useAxiosError';
import useClickOutside from 'hooks/useClickOutside';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { FlatLogData } from 'lib/logs/flatLogData';
import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData';
@@ -63,6 +62,7 @@ import {
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll';
import { ILog } from 'types/api/logs/log';
@@ -98,7 +98,7 @@ function LogsExplorerViews({
chartQueryKeyRef: MutableRefObject<any>;
}): JSX.Element {
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate();
const history = useHistory();
// this is to respect the panel type present in the URL rather than defaulting it to list always.
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
@@ -486,7 +486,7 @@ function LogsExplorerViews({
widgetId,
});
safeNavigate(dashboardEditView);
history.push(dashboardEditView);
},
onError: handleAxisError,
});
@@ -495,7 +495,7 @@ function LogsExplorerViews({
getUpdatedQueryForExport,
exportDefaultQuery,
options.selectColumns,
safeNavigate,
history,
notifications,
panelType,
updateDashboard,

View File

@@ -75,12 +75,6 @@ jest.mock('hooks/queryBuilder/useGetExplorerQueryRange', () => ({
useGetExplorerQueryRange: jest.fn(),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
// Set up the specific behavior for useGetExplorerQueryRange in individual test cases
beforeEach(() => {
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({

View File

@@ -13,7 +13,6 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
@@ -158,7 +157,6 @@ function DBCall(): JSX.Element {
servicename,
isDBCall: true,
});
const { safeNavigate } = useSafeNavigate();
return (
<Row gutter={24}>
@@ -173,7 +171,6 @@ function DBCall(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces
@@ -209,7 +206,6 @@ function DBCall(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces

View File

@@ -15,7 +15,6 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
@@ -221,8 +220,6 @@ function External(): JSX.Element {
isExternalCall: true,
});
const { safeNavigate } = useSafeNavigate();
return (
<>
<Row gutter={24}>
@@ -237,7 +234,6 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery: errorApmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces
@@ -274,7 +270,6 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces
@@ -314,7 +309,6 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces
@@ -351,7 +345,6 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces

View File

@@ -13,7 +13,6 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import history from 'lib/history';
@@ -291,7 +290,6 @@ function Application(): JSX.Element {
},
],
});
const { safeNavigate } = useSafeNavigate();
return (
<>
@@ -319,7 +317,6 @@ function Application(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces
@@ -349,7 +346,6 @@ function Application(): JSX.Element {
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
>
View Traces

View File

@@ -12,7 +12,6 @@ import { latency } from 'container/MetricsApplication/MetricsPageQueries/Overvie
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { resourceAttributesToTagFilterItems } from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { useAppContext } from 'providers/App/App';
import { useMemo } from 'react';
@@ -86,8 +85,6 @@ function ServiceOverview({
const apmToLogQuery = useGetAPMToLogsQueries({ servicename });
const { safeNavigate } = useSafeNavigate();
return (
<>
<GraphControlsPanel
@@ -99,7 +96,6 @@ function ServiceOverview({
apmToTraceQuery: apmToLogQuery,
isViewLogsClicked: true,
stepInterval,
safeNavigate,
})}
onViewTracesClick={onViewTracePopupClick({
servicename,
@@ -107,7 +103,6 @@ function ServiceOverview({
timestamp: selectedTimeStamp,
apmToTraceQuery,
stepInterval,
safeNavigate,
})}
/>
<Card data-testid="service_latency">

View File

@@ -1,6 +1,5 @@
import { Tooltip, Typography } from 'antd';
import { navigateToTrace } from 'container/MetricsApplication/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { v4 as uuid } from 'uuid';
@@ -15,7 +14,6 @@ function ColumnWithLink({
record,
}: LinkColumnProps): JSX.Element {
const text = record.toString();
const { safeNavigate } = useSafeNavigate();
const apmToTraceQuery = useGetAPMToTracesQueries({
servicename,
@@ -44,7 +42,6 @@ function ColumnWithLink({
maxTime,
selectedTraceTags,
apmToTraceQuery,
safeNavigate,
});
};

View File

@@ -6,6 +6,7 @@ import { getQueryString } from 'container/SideNav/helper';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { resourceAttributesToTracesFilterItems } from 'hooks/useResourceAttribute/utils';
import history from 'lib/history';
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
import { traceFilterKeys } from 'pages/TracesExplorer/Filter/filterUtils';
import { Dispatch, SetStateAction, useMemo } from 'react';
@@ -35,7 +36,6 @@ interface OnViewTracePopupClickProps {
apmToTraceQuery: Query;
isViewLogsClicked?: boolean;
stepInterval?: number;
safeNavigate: (url: string) => void;
}
export function generateExplorerPath(
@@ -63,7 +63,6 @@ export function onViewTracePopupClick({
apmToTraceQuery,
isViewLogsClicked,
stepInterval,
safeNavigate,
}: OnViewTracePopupClickProps): VoidFunction {
return (): void => {
const endTime = timestamp;
@@ -89,7 +88,7 @@ export function onViewTracePopupClick({
queryString,
);
safeNavigate(newPath);
history.push(newPath);
};
}
@@ -112,7 +111,7 @@ export function onGraphClickHandler(
buttonElement.style.display = 'block';
buttonElement.style.left = `${mouseX}px`;
buttonElement.style.top = `${mouseY}px`;
setSelectedTimeStamp(Math.floor(xValue * 1_000));
setSelectedTimeStamp(xValue);
}
} else if (buttonElement && buttonElement.style.display === 'block') {
buttonElement.style.display = 'none';

View File

@@ -8,7 +8,6 @@ import Download from 'container/Download/Download';
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { useRef } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
@@ -32,7 +31,7 @@ function TopOperationsTable({
}: TopOperationsTableProps): JSX.Element {
const searchInput = useRef<InputRef>(null);
const { servicename: encodedServiceName } = useParams<IServiceName>();
const { safeNavigate } = useSafeNavigate();
const servicename = decodeURIComponent(encodedServiceName);
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
@@ -88,7 +87,6 @@ function TopOperationsTable({
maxTime,
selectedTraceTags,
apmToTraceQuery: preparedQuery,
safeNavigate,
});
};
@@ -128,7 +126,7 @@ function TopOperationsTable({
key: 'p50',
width: 50,
sorter: (a: TopOperationList, b: TopOperationList): number => a.p50 - b.p50,
render: (value: number): string => (value / 1_000_000).toFixed(2),
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
title: 'P95 (in ms)',
@@ -136,7 +134,7 @@ function TopOperationsTable({
key: 'p95',
width: 50,
sorter: (a: TopOperationList, b: TopOperationList): number => a.p95 - b.p95,
render: (value: number): string => (value / 1_000_000).toFixed(2),
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
title: 'P99 (in ms)',
@@ -144,7 +142,7 @@ function TopOperationsTable({
key: 'p99',
width: 50,
sorter: (a: TopOperationList, b: TopOperationList): number => a.p99 - b.p99,
render: (value: number): string => (value / 1_000_000).toFixed(2),
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
title: 'Number of Calls',

View File

@@ -21,7 +21,6 @@ export interface NavigateToTraceProps {
maxTime: number;
selectedTraceTags: string;
apmToTraceQuery: Query;
safeNavigate: (path: string) => void;
}
export interface DatabaseCallsRPSProps extends DatabaseCallProps {

View File

@@ -1,5 +1,6 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { TopOperationList } from './TopOperationsTable';
import { NavigateToTraceProps } from './types';
@@ -18,14 +19,10 @@ export const navigateToTrace = ({
maxTime,
selectedTraceTags,
apmToTraceQuery,
safeNavigate,
}: NavigateToTraceProps): void => {
const urlParams = new URLSearchParams();
urlParams.set(
QueryParams.startTime,
Math.floor(minTime / 1_000_000).toString(),
);
urlParams.set(QueryParams.endTime, Math.floor(maxTime / 1_000_000).toString());
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
const JSONCompositeQuery = encodeURIComponent(JSON.stringify(apmToTraceQuery));
@@ -35,7 +32,7 @@ export const navigateToTrace = ({
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
safeNavigate(newTraceExplorerPath);
history.push(newTraceExplorerPath);
};
export const getNearestHighestBucketValue = (

View File

@@ -25,12 +25,6 @@ jest.mock(
},
);
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
describe('Dashboard landing page actions header tests', () => {
it('unlock dashboard should be disabled for integrations created dashboards', async () => {
const mockLocation = {

View File

@@ -21,8 +21,8 @@ import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { isEmpty } from 'lodash-es';
import {
Check,
@@ -89,7 +89,6 @@ export function sanitizeDashboardData(
// eslint-disable-next-line sonarjs/cognitive-complexity
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const { handle } = props;
const {
selectedDashboard,
@@ -312,7 +311,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${ROUTES.ALL_DASHBOARD}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
}
return (

View File

@@ -150,13 +150,11 @@ function VariableItem({
let allSelected = false;
// The default value for multi-select is ALL and first value for
// single select
if (valueNotInList) {
if (variableData.multiSelect) {
value = newOptionsData;
allSelected = true;
} else {
[value] = newOptionsData;
}
if (variableData.multiSelect) {
value = newOptionsData;
allSelected = true;
} else {
[value] = newOptionsData;
}
if (variableData && variableData?.name && variableData?.id) {
@@ -228,9 +226,6 @@ function VariableItem({
}
setErrorMessage(message);
}
setVariablesToGetUpdated((prev) =>
prev.filter((v) => v !== variableData.name),
);
},
},
);

View File

@@ -3,11 +3,11 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import {
Dispatch,
SetStateAction,
@@ -33,7 +33,6 @@ function WidgetGraph({
const dispatch = useDispatch();
const urlQuery = useUrlQuery();
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
@@ -72,9 +71,9 @@ function WidgetGraph({
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.push(generatedUrl);
},
[dispatch, location.pathname, safeNavigate, urlQuery],
[dispatch, location.pathname, urlQuery],
);
useEffect(() => {

View File

@@ -87,12 +87,6 @@ jest.mock('hooks/queryBuilder/useGetCompositeQueryParam', () => ({
useGetCompositeQueryParam: (): Query => compositeQueryParam as Query,
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
describe('Column unit selector panel unit test', () => {
it('unit selectors should be rendered for queries and formula', () => {
const mockLocation = {

View File

@@ -20,10 +20,10 @@ import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useAxiosError from 'hooks/useAxiosError';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import history from 'lib/history';
import { defaultTo, isEmpty, isUndefined } from 'lodash-es';
import { Check, X } from 'lucide-react';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
@@ -67,7 +67,6 @@ import {
} from './utils';
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const {
selectedDashboard,
setSelectedDashboard,
@@ -329,11 +328,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
}
const updatedQuery = { ...(stagedQuery || initialQueriesMap.metrics) };
updatedQuery.builder.queryData[0].pageSize = 10;
// If stagedQuery exists, don't re-run the query (e.g. when clicking on Add to Dashboard from logs and traces explorer)
if (!stagedQuery) {
redirectWithQueryBuilderData(updatedQuery);
}
redirectWithQueryBuilderData(updatedQuery);
return {
query: updatedQuery,
graphType: PANEL_TYPES.LIST,
@@ -474,7 +469,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setSelectedRowWidgetId(null);
setSelectedDashboard(dashboard);
setToScrollWidgetId(selectedWidget?.id || '');
safeNavigate({
history.push({
pathname: generatePath(ROUTES.DASHBOARD, { dashboardId }),
});
},
@@ -497,7 +492,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setSelectedDashboard,
setToScrollWidgetId,
setSelectedRowWidgetId,
safeNavigate,
dashboardId,
]);
@@ -506,12 +500,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
setDiscardModal(true);
return;
}
safeNavigate(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId, isQueryModified, safeNavigate]);
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId, isQueryModified]);
const discardChanges = useCallback(() => {
safeNavigate(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId, safeNavigate]);
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
}, [dashboardId]);
const setGraphHandler = (type: PANEL_TYPES): void => {
setIsLoadingPanelData(true);

View File

@@ -23,12 +23,6 @@ jest.mock('providers/Dashboard/Dashboard', () => ({
}),
}));
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
describe('QueryTable -', () => {
it('should render correctly with all the data rows', () => {
const { container } = render(<QueryTable {...QueryTableProps} />);

View File

@@ -23,18 +23,17 @@ import { QueryHistoryState } from 'container/LiveLogs/types';
import NewExplorerCTA from 'container/NewExplorerCTA';
import dayjs, { Dayjs } from 'dayjs';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax, { isValidTimeFormat } from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
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 { useQueryClient } from 'react-query';
import { connect, useDispatch, useSelector } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { useNavigationType } from 'react-router-dom-v5-compat';
import { useCopyToClipboard } from 'react-use';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
@@ -44,7 +43,6 @@ import AppActions from 'types/actions';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { normalizeTimeToMs } from 'utils/timeUtils';
import AutoRefresh from '../AutoRefreshV2';
import { DateTimeRangeType } from '../CustomDateTimeModal';
@@ -77,9 +75,6 @@ function DateTimeSelection({
modalSelectedInterval,
}: Props): JSX.Element {
const [formSelector] = Form.useForm();
const { safeNavigate } = useSafeNavigate();
const navigationType = useNavigationType(); // Returns 'POP' for back/forward navigation
const dispatch = useDispatch();
const [hasSelectedTimeError, setHasSelectedTimeError] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
@@ -194,8 +189,8 @@ function DateTimeSelection({
const path = `${ROUTES.LIVE_LOGS}?${QueryParams.compositeQuery}=${JSONCompositeQuery}`;
safeNavigate(path, { state: queryHistoryState });
}, [panelType, queryClient, safeNavigate, stagedQuery]);
history.push(path, queryHistoryState);
}, [panelType, queryClient, stagedQuery]);
const { maxTime, minTime, selectedTime } = useSelector<
AppState,
@@ -354,7 +349,7 @@ function DateTimeSelection({
urlQuery.set(QueryParams.relativeTime, value);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
}
// For logs explorer - time range handling is managed in useCopyLogLink.ts:52
@@ -373,7 +368,6 @@ function DateTimeSelection({
location.pathname,
onTimeChange,
refreshButtonHidden,
safeNavigate,
stagedQuery,
updateLocalStorageForRoutes,
updateTimeInterval,
@@ -446,7 +440,7 @@ function DateTimeSelection({
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
}
}
}
@@ -473,7 +467,7 @@ function DateTimeSelection({
urlQuery.set(QueryParams.relativeTime, dateTimeStr);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
}
if (!stagedQuery) {
@@ -515,77 +509,6 @@ function DateTimeSelection({
return time;
};
const handleAbsoluteTimeSync = useCallback(
(
startTime: string,
endTime: string,
currentMinTime: number,
currentMaxTime: number,
): void => {
const startTs = normalizeTimeToMs(startTime);
const endTs = normalizeTimeToMs(endTime);
const timeComparison = {
url: {
start: dayjs(startTs).startOf('minute'),
end: dayjs(endTs).startOf('minute'),
},
current: {
start: dayjs(normalizeTimeToMs(currentMinTime)).startOf('minute'),
end: dayjs(normalizeTimeToMs(currentMaxTime)).startOf('minute'),
},
};
const hasTimeChanged =
!timeComparison.current.start.isSame(timeComparison.url.start) ||
!timeComparison.current.end.isSame(timeComparison.url.end);
if (hasTimeChanged) {
dispatch(UpdateTimeInterval('custom', [startTs, endTs]));
}
},
[dispatch],
);
const handleRelativeTimeSync = useCallback(
(relativeTime: string): void => {
updateTimeInterval(relativeTime as Time);
setIsValidteRelativeTime(true);
setRefreshButtonHidden(false);
},
[updateTimeInterval],
);
// Sync time picker state with URL on browser navigation
useEffect(() => {
if (navigationType !== 'POP') return;
if (searchStartTime && searchEndTime) {
handleAbsoluteTimeSync(searchStartTime, searchEndTime, minTime, maxTime);
return;
}
if (
relativeTimeFromUrl &&
isValidTimeFormat(relativeTimeFromUrl) &&
relativeTimeFromUrl !== selectedTime
) {
handleRelativeTimeSync(relativeTimeFromUrl);
}
}, [
navigationType,
searchStartTime,
searchEndTime,
relativeTimeFromUrl,
selectedTime,
minTime,
maxTime,
dispatch,
updateTimeInterval,
handleAbsoluteTimeSync,
handleRelativeTimeSync,
]);
// this is triggred when we change the routes and based on that we are changing the default options
useEffect(() => {
const metricsTimeDuration = getLocalStorageKey(
@@ -601,16 +524,6 @@ function DateTimeSelection({
const currentRoute = location.pathname;
// Give priority to relativeTime from URL if it exists and start /end time are not present in the url, to sync the relative time in URL param with the time picker
if (
!searchStartTime &&
!searchEndTime &&
relativeTimeFromUrl &&
isValidTimeFormat(relativeTimeFromUrl)
) {
handleRelativeTimeSync(relativeTimeFromUrl);
}
// set the default relative time for alert history and overview pages if relative time is not specified
if (
(!urlQuery.has(QueryParams.startTime) ||
@@ -622,7 +535,7 @@ function DateTimeSelection({
updateTimeInterval(defaultRelativeTime);
urlQuery.set(QueryParams.relativeTime, defaultRelativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
return;
}
@@ -660,7 +573,7 @@ function DateTimeSelection({
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname, updateTimeInterval, globalTimeLoading]);

View File

@@ -12,7 +12,6 @@ import {
transformDataWithDate,
} from 'container/TracesExplorer/ListView/utils';
import { Pagination } from 'hooks/queryPagination';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import history from 'lib/history';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
@@ -40,7 +39,6 @@ function TracesTableComponent({
offset: 0,
limit: 10,
});
const { safeNavigate } = useSafeNavigate();
useEffect(() => {
setRequestData((prev) => ({
@@ -89,25 +87,6 @@ function TracesTableComponent({
[],
);
const handlePaginationChange = useCallback(
(newPagination: Pagination) => {
const urlQuery = new URLSearchParams(window.location.search);
// Update URL with new pagination values
urlQuery.set('offset', newPagination.offset.toString());
urlQuery.set('limit', newPagination.limit.toString());
// Update URL without page reload
safeNavigate({
search: urlQuery.toString(),
});
// Update component state
setPagination(newPagination);
},
[safeNavigate],
);
if (queryResponse.isError) {
return <div>{SOMETHING_WENT_WRONG}</div>;
}
@@ -137,19 +116,19 @@ function TracesTableComponent({
offset={pagination.offset}
countPerPage={pagination.limit}
handleNavigatePrevious={(): void => {
handlePaginationChange({
setPagination({
...pagination,
offset: pagination.offset - pagination.limit,
});
}}
handleNavigateNext={(): void => {
handlePaginationChange({
setPagination({
...pagination,
offset: pagination.offset + pagination.limit,
});
}}
handleCountItemsPerPageChange={(value): void => {
handlePaginationChange({
setPagination({
...pagination,
limit: value,
offset: 0,

View File

@@ -1,7 +1,7 @@
import { Form } from 'antd';
import { FormInstance } from 'antd/lib';
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
import { useUpdateAccountConfig } from 'hooks/integration/aws/useUpdateAccountConfig';
import { useUpdateAccountConfig } from 'hooks/integrations/aws/useUpdateAccountConfig';
import { isEqual } from 'lodash-es';
import {
Dispatch,

View File

@@ -1,4 +1,4 @@
import { getAwsAccounts } from 'api/integration/aws';
import { getAwsAccounts } from 'api/integrations/aws';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
import { useQuery, UseQueryResult } from 'react-query';

View File

@@ -1,4 +1,4 @@
import { getConnectionParams } from 'api/integration/aws';
import { getConnectionParams } from 'api/integrations/aws';
import { AxiosError } from 'axios';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';

View File

@@ -1,4 +1,4 @@
import { generateConnectionUrl } from 'api/integration/aws';
import { generateConnectionUrl } from 'api/integrations/aws';
import { AxiosError } from 'axios';
import { useMutation, UseMutationResult } from 'react-query';
import {

View File

@@ -1,4 +1,4 @@
import { getAwsServices } from 'api/integration/aws';
import { getAwsServices } from 'api/integrations/aws';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { Service } from 'container/CloudIntegrationPage/ServicesSection/types';
import { useQuery, UseQueryResult } from 'react-query';

View File

@@ -1,4 +1,4 @@
import { getServiceDetails } from 'api/integration/aws';
import { getServiceDetails } from 'api/integrations/aws';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { ServiceData } from 'container/CloudIntegrationPage/ServicesSection/types';
import { useQuery, UseQueryResult } from 'react-query';

View File

@@ -1,4 +1,4 @@
import { updateAccountConfig } from 'api/integration/aws';
import { updateAccountConfig } from 'api/integrations/aws';
import { useMutation, UseMutationResult } from 'react-query';
import {
AccountConfigPayload,

View File

@@ -1,4 +1,4 @@
import { updateServiceConfig } from 'api/integration/aws';
import { updateServiceConfig } from 'api/integrations/aws';
import { useMutation, UseMutationResult } from 'react-query';
interface UpdateServiceConfigPayload {

View File

@@ -1,10 +1,10 @@
import { useMachine } from '@xstate/react';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { encode } from 'js-base64';
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import history from 'lib/history';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { whilelistedKeys } from './config';
@@ -32,7 +32,6 @@ function ResourceProvider({ children }: Props): JSX.Element {
const [queries, setQueries] = useState<IResourceAttribute[]>(
getResourceAttributeQueriesFromURL(),
);
const { safeNavigate } = useSafeNavigate();
const urlQuery = useUrlQuery();
const [optionsData, setOptionsData] = useState<OptionsData>({
@@ -40,12 +39,6 @@ function ResourceProvider({ children }: Props): JSX.Element {
options: [],
});
// Watch for URL query changes
useEffect(() => {
const queriesFromUrl = getResourceAttributeQueriesFromURL();
setQueries(queriesFromUrl);
}, [urlQuery]);
const handleLoading = (isLoading: boolean): void => {
setLoading(isLoading);
if (isLoading) {
@@ -60,10 +53,10 @@ function ResourceProvider({ children }: Props): JSX.Element {
encode(JSON.stringify(queries)),
);
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
setQueries(queries);
},
[pathname, safeNavigate, urlQuery],
[pathname, urlQuery],
);
const [state, send] = useMachine(ResourceAttributesFilterMachine, {

View File

@@ -5,12 +5,6 @@ import { Router } from 'react-router-dom';
import ResourceProvider from '../ResourceProvider';
import useResourceAttribute from '../useResourceAttribute';
jest.mock('hooks/useSafeNavigate', () => ({
useSafeNavigate: (): any => ({
safeNavigate: jest.fn(),
}),
}));
describe('useResourceAttribute component hook', () => {
it('should not change other query params except for resourceAttribute', async () => {
const history = createMemoryHistory({

View File

@@ -1,136 +0,0 @@
import { cloneDeep, isEqual } from 'lodash-es';
import { useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
interface NavigateOptions {
replace?: boolean;
state?: any;
}
interface SafeNavigateParams {
pathname?: string;
search?: string;
}
const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
if (url1.pathname !== url2.pathname) return false;
const params1 = new URLSearchParams(url1.search);
const params2 = new URLSearchParams(url2.search);
const allParams = new Set([
...Array.from(params1.keys()),
...Array.from(params2.keys()),
]);
return Array.from(allParams).every((param) => {
if (param === 'compositeQuery') {
try {
const query1 = params1.get('compositeQuery');
const query2 = params2.get('compositeQuery');
if (!query1 || !query2) return false;
const decoded1 = JSON.parse(decodeURIComponent(query1));
const decoded2 = JSON.parse(decodeURIComponent(query2));
const filtered1 = cloneDeep(decoded1);
const filtered2 = cloneDeep(decoded2);
delete filtered1.id;
delete filtered2.id;
return isEqual(filtered1, filtered2);
} catch (error) {
console.warn('Error comparing compositeQuery:', error);
return false;
}
}
return params1.get(param) === params2.get(param);
});
};
/**
* Determines if this navigation is adding default/initial parameters
* Returns true if:
* 1. We're staying on the same page (same pathname)
* 2. Either:
* - Current URL has no params and target URL has params, or
* - Target URL has new params that didn't exist in current URL
*/
const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
// Different pathnames means it's not a default navigation
if (currentUrl.pathname !== targetUrl.pathname) return false;
const currentParams = new URLSearchParams(currentUrl.search);
const targetParams = new URLSearchParams(targetUrl.search);
// Case 1: Clean URL getting params for the first time
if (!currentParams.toString() && targetParams.toString()) return true;
// Case 2: Check for new params that didn't exist before
const currentKeys = new Set(Array.from(currentParams.keys()));
const targetKeys = new Set(Array.from(targetParams.keys()));
// Find keys that exist in target but not in current
const newKeys = Array.from(targetKeys).filter((key) => !currentKeys.has(key));
return newKeys.length > 0;
};
export const useSafeNavigate = (): {
safeNavigate: (
to: string | SafeNavigateParams,
options?: NavigateOptions,
) => void;
} => {
const navigate = useNavigate();
const location = useLocation();
const safeNavigate = useCallback(
(to: string | SafeNavigateParams, options?: NavigateOptions) => {
const currentUrl = new URL(
`${location.pathname}${location.search}`,
window.location.origin,
);
let targetUrl: URL;
if (typeof to === 'string') {
targetUrl = new URL(to, window.location.origin);
} else {
targetUrl = new URL(
`${to.pathname || location.pathname}${to.search || ''}`,
window.location.origin,
);
}
const urlsAreSame = areUrlsEffectivelySame(currentUrl, targetUrl);
const isDefaultParamsNavigation = isDefaultNavigation(currentUrl, targetUrl);
if (urlsAreSame) {
return;
}
const navigationOptions = {
...options,
replace: isDefaultParamsNavigation || options?.replace,
};
if (typeof to === 'string') {
navigate(to, navigationOptions);
} else {
navigate(
{
pathname: to.pathname || location.pathname,
search: to.search,
},
navigationOptions,
);
}
},
[navigate, location.pathname, location.search],
);
return { safeNavigate };
};

View File

@@ -1,16 +1,15 @@
import { useCallback, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { useHistory, useLocation } from 'react-router-dom';
import { useSafeNavigate } from './useSafeNavigate';
import useUrlQuery from './useUrlQuery';
const useUrlQueryData = <T>(
queryKey: string,
defaultData?: T,
): UseUrlQueryData<T> => {
const history = useHistory();
const location = useLocation();
const urlQuery = useUrlQuery();
const { safeNavigate } = useSafeNavigate();
const query = useMemo(() => urlQuery.get(queryKey), [urlQuery, queryKey]);
@@ -33,9 +32,9 @@ const useUrlQueryData = <T>(
// Construct the new URL by combining the current pathname with the updated query string
const generatedUrl = `${location.pathname}?${currentUrlQuery.toString()}`;
safeNavigate(generatedUrl);
history.replace(generatedUrl);
},
[location.pathname, queryKey, safeNavigate],
[history, location.pathname, queryKey],
);
return {

View File

@@ -20,7 +20,6 @@ import { urlKey } from 'container/AllError/utils';
import { RelativeTimeMap } from 'container/TopNav/DateTimeSelection/config';
import useAxiosError from 'hooks/useAxiosError';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import GetMinMax from 'lib/getMinMax';
@@ -322,8 +321,6 @@ export const useTimelineTable = ({
extra: any,
) => void;
} => {
const { safeNavigate } = useSafeNavigate();
const { pathname } = useLocation();
const { search } = useLocation();
@@ -346,7 +343,7 @@ export const useTimelineTable = ({
const updatedOrder = order === 'ascend' ? 'asc' : 'desc';
const params = new URLSearchParams(window.location.search);
safeNavigate(
history.replace(
`${pathname}?${createQueryParams({
...Object.fromEntries(params),
order: updatedOrder,
@@ -356,7 +353,7 @@ export const useTimelineTable = ({
);
}
},
[pathname, safeNavigate],
[pathname],
);
const offsetInt = parseInt(offset, 10);

View File

@@ -5,8 +5,8 @@ import ROUTES from 'constants/routes';
import AllAlertRules from 'container/ListAlertRules';
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
import TriggeredAlerts from 'container/TriggeredAlerts';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { GalleryVerticalEnd, Pyramid } from 'lucide-react';
import AlertDetails from 'pages/AlertDetails';
import { useLocation } from 'react-router-dom';
@@ -14,7 +14,6 @@ import { useLocation } from 'react-router-dom';
function AllAlertList(): JSX.Element {
const urlQuery = useUrlQuery();
const location = useLocation();
const { safeNavigate } = useSafeNavigate();
const tab = urlQuery.get('tab');
const isAlertHistory = location.pathname === ROUTES.ALERT_HISTORY;
@@ -68,7 +67,7 @@ function AllAlertList(): JSX.Element {
if (search) {
params += `&search=${search}`;
}
safeNavigate(`/alerts?${params}`);
history.replace(`/alerts?${params}`);
}}
className={`${
isAlertHistory || isAlertOverview ? 'alert-details-tabs' : ''

View File

@@ -1,6 +1,5 @@
import { Color } from '@signozhq/design-tokens';
import { Card } from 'antd';
import logEvent from 'api/common/logEvent';
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
import { QueryParams } from 'constants/query';
@@ -10,7 +9,7 @@ import { Button } from 'container/MetricsApplication/Tabs/styles';
import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
import useUrlQuery from 'hooks/useUrlQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
@@ -124,26 +123,6 @@ export default function OverviewRightPanelGraph({
},
});
const [requestRateStatus, setRequestRateStatus] = useState<boolean | null>(
null,
);
const [errorRateStatus, setErrorRateStatus] = useState<boolean | null>(null);
const [avgLatencyStatus, setAvgLatencyStatus] = useState<boolean | null>(null);
useEffect(() => {
if (
requestRateStatus !== null &&
errorRateStatus !== null &&
avgLatencyStatus !== null
) {
logEvent('MQ Overview Page: Right Drawer - graphs', {
requestRate: requestRateStatus,
errorRate: errorRateStatus,
avgLatency: avgLatencyStatus,
});
}
}, [requestRateStatus, errorRateStatus, avgLatencyStatus]);
return (
<Card className="overview-right-panel-graph-card">
<div className="request-rate-card">
@@ -151,9 +130,7 @@ export default function OverviewRightPanelGraph({
type="default"
size="small"
id="Celery_request_rate_button"
onClick={(): void => {
goToTraces(requestRateWidget);
}}
onClick={(): void => goToTraces(requestRateWidget)}
>
View Traces
</Button>
@@ -163,9 +140,6 @@ export default function OverviewRightPanelGraph({
onDragSelect={onDragSelect}
onClickHandler={handleGraphClick('Celery_request_rate')}
customSeries={getCustomSeries}
dataAvailable={(isDataAvailable: boolean): void => {
setRequestRateStatus(isDataAvailable);
}}
/>
</div>
<div className="error-rate-card">
@@ -183,9 +157,6 @@ export default function OverviewRightPanelGraph({
onDragSelect={onDragSelect}
onClickHandler={handleGraphClick('Celery_error_rate')}
customSeries={getCustomSeries}
dataAvailable={(isDataAvailable: boolean): void => {
setErrorRateStatus(isDataAvailable);
}}
/>
</div>
<div className="avg-latency-card">
@@ -202,9 +173,6 @@ export default function OverviewRightPanelGraph({
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={handleGraphClick('Celery_avg_latency')}
dataAvailable={(isDataAvailable: boolean): void => {
setAvgLatencyStatus(isDataAvailable);
}}
/>
</div>
</Card>

View File

@@ -2,7 +2,6 @@ import './ValueInfo.styles.scss';
import { FileSearchOutlined } from '@ant-design/icons';
import { Button, Card, Col, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -114,9 +113,6 @@ export default function ValueInfo({
return 'red';
};
const mqAnalyticsTitle =
'MQ Overview Page: Right drawer navigation to trace page';
return (
<Card className="value-info-card">
<Row gutter={16}>
@@ -137,15 +133,7 @@ export default function ValueInfo({
icon={<FileSearchOutlined />}
className="trace-button"
disabled={isLoading}
onClick={(): void => {
logEvent(mqAnalyticsTitle, {
filters,
minTime,
maxTime,
source: 'request rate',
});
navigateToTrace(filters ?? []);
}}
onClick={(): void => navigateToTrace(filters ?? [])}
>
View Traces
</Button>
@@ -167,13 +155,7 @@ export default function ValueInfo({
icon={<FileSearchOutlined />}
className="trace-button"
disabled={isLoading}
onClick={(): void => {
logEvent(mqAnalyticsTitle, {
filters,
minTime,
maxTime,
source: 'error rate',
});
onClick={(): void =>
navigateToTrace([
...(filters ?? []),
{
@@ -189,8 +171,8 @@ export default function ValueInfo({
op: '=',
value: 'true',
},
]);
}}
])
}
>
View Traces
</Button>
@@ -212,15 +194,7 @@ export default function ValueInfo({
icon={<FileSearchOutlined />}
className="trace-button"
disabled={isLoading}
onClick={(): void => {
logEvent(mqAnalyticsTitle, {
filters,
minTime,
maxTime,
source: 'average latency',
});
navigateToTrace(filters ?? []);
}}
onClick={(): void => navigateToTrace(filters ?? [])}
>
View Traces
</Button>

View File

@@ -1,30 +1,15 @@
import './CeleryTask.styles.scss';
import logEvent from 'api/common/logEvent';
import CeleryTaskConfigOptions from 'components/CeleryTask/CeleryTaskConfigOptions/CeleryTaskConfigOptions';
import CeleryTaskDetail, {
CaptureDataProps,
} from 'components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail';
import CeleryTaskGraphGrid from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid';
import { QueryParams } from 'constants/query';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import useUrlQuery from 'hooks/useUrlQuery';
import { useEffect, useRef, useState } from 'react';
import { useState } from 'react';
export default function CeleryTask(): JSX.Element {
const [task, setTask] = useState<CaptureDataProps | null>(null);
const loggedRef = useRef(false);
const taskName = useUrlQuery().get(QueryParams.taskName);
useEffect(() => {
if (taskName && !loggedRef.current) {
logEvent('MQ Celery: Task name filter', {
taskName,
});
loggedRef.current = true;
}
}, [taskName]);
const onTaskClick = (captureData: CaptureDataProps): void => {
setTask(captureData);

View File

@@ -4,8 +4,8 @@ import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import NewWidget from 'container/NewWidget';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useEffect, useState } from 'react';
import { generatePath, useLocation, useParams } from 'react-router-dom';
@@ -14,7 +14,6 @@ import { Widgets } from 'types/api/dashboard/getAll';
function DashboardWidget(): JSX.Element | null {
const { search } = useLocation();
const { dashboardId } = useParams<DashboardWidgetPageParams>();
const { safeNavigate } = useSafeNavigate();
const [selectedGraph, setSelectedGraph] = useState<PANEL_TYPES>();
@@ -33,11 +32,11 @@ function DashboardWidget(): JSX.Element | null {
const graphType = params.get('graphType') as PANEL_TYPES | null;
if (graphType === null) {
safeNavigate(generatePath(ROUTES.DASHBOARD, { dashboardId }));
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
} else {
setSelectedGraph(graphType);
}
}, [dashboardId, safeNavigate, search]);
}, [dashboardId, search]);
if (selectedGraph === undefined || dashboardResponse.isLoading) {
return <Spinner tip="Loading.." />;

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