Compare commits
14 Commits
v0.73.0-cl
...
v0.73.0-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
918c8942c4 | ||
|
|
7e1301b8d2 | ||
|
|
eba2049a4d | ||
|
|
2a939e813d | ||
|
|
c3951afdfd | ||
|
|
fbf0e4efc7 | ||
|
|
1f52139ed3 | ||
|
|
e86c7c970a | ||
|
|
8bfca9b564 | ||
|
|
5a107f33f2 | ||
|
|
a6cfb63036 | ||
|
|
042f31116a | ||
|
|
e7587612e7 | ||
|
|
43a1fa53aa |
@@ -219,12 +219,18 @@ 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
|
||||
|
||||
|
||||
@@ -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.27
|
||||
image: signoz/signoz-otel-collector:0.111.28
|
||||
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.24
|
||||
image: signoz/signoz-schema-migrator:0.111.28
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -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.27
|
||||
image: signoz/signoz-otel-collector:0.111.28
|
||||
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.24
|
||||
image: signoz/signoz-schema-migrator:0.111.28
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -66,6 +66,8 @@ exporters:
|
||||
enabled: true
|
||||
clickhousemetricswrite/prometheus:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
signozclickhousemetrics:
|
||||
dsn: tcp://clickhouse:9000/signoz_metrics
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
timeout: 10s
|
||||
@@ -88,11 +90,11 @@ service:
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite]
|
||||
exporters: [clickhousemetricswrite, signozclickhousemetrics]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite/prometheus]
|
||||
exporters: [clickhousemetricswrite/prometheus, signozclickhousemetrics]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
|
||||
@@ -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.27}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.28}
|
||||
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.24}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
|
||||
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.24}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -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.27}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.28}
|
||||
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.24}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
|
||||
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.24}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.28}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -66,6 +66,8 @@ exporters:
|
||||
enabled: true
|
||||
clickhousemetricswrite/prometheus:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
signozclickhousemetrics:
|
||||
dsn: tcp://clickhouse:9000/signoz_metrics
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
timeout: 10s
|
||||
@@ -88,11 +90,11 @@ service:
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite]
|
||||
exporters: [clickhousemetricswrite, signozclickhousemetrics]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite/prometheus]
|
||||
exporters: [clickhousemetricswrite/prometheus, signozclickhousemetrics]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
|
||||
36
ee/http/middleware/pat.go
Normal file
36
ee/http/middleware/pat.go
Normal file
@@ -0,0 +1,36 @@
|
||||
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)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -20,6 +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"
|
||||
)
|
||||
|
||||
type APIHandlerOptions struct {
|
||||
@@ -41,6 +42,7 @@ type APIHandlerOptions struct {
|
||||
FluxInterval time.Duration
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
JWT *authtypes.JWT
|
||||
}
|
||||
|
||||
type APIHandler struct {
|
||||
|
||||
@@ -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)
|
||||
resp, err := baseauth.Login(ctx, &req, ah.opts.JWT)
|
||||
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)
|
||||
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, identity.Email, ah.opts.JWT)
|
||||
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)
|
||||
nextPage, err := ah.AppDao().PrepareSsoRedirect(ctx, redirectUri, email, ah.opts.JWT)
|
||||
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)
|
||||
|
||||
@@ -37,7 +37,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
||||
return
|
||||
}
|
||||
|
||||
currentUser, err := auth.GetUserFromRequest(r)
|
||||
currentUser, err := auth.GetUserFromReqContext(r.Context())
|
||||
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 {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
user, err := auth.GetUserFromRequest(r)
|
||||
user, err := auth.GetUserFromReqContext(r.Context())
|
||||
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.GetUserFromRequest(r)
|
||||
user, err := auth.GetUserFromReqContext(r.Context())
|
||||
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.GetUserFromRequest(r)
|
||||
user, err := auth.GetUserFromReqContext(r.Context())
|
||||
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.GetUserFromRequest(r)
|
||||
user, err := auth.GetUserFromReqContext(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorUnauthorized,
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
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"
|
||||
@@ -29,9 +24,8 @@ 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"
|
||||
@@ -80,6 +74,7 @@ type ServerOptions struct {
|
||||
GatewayUrl string
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
Jwt *authtypes.JWT
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -110,7 +105,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.SQLxDB())
|
||||
modelDao, err := dao.InitDao(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +128,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// initiate license manager
|
||||
lm, err := licensepkg.StartManager(serverOptions.SigNoz.SQLStore.SQLxDB())
|
||||
lm, err := licensepkg.StartManager(serverOptions.SigNoz.SQLStore.SQLxDB(), serverOptions.SigNoz.SQLStore.BunDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -202,14 +197,14 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
|
||||
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"couldn't create integrations controller: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
|
||||
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"couldn't create cloud provider integrations controller: %w", err,
|
||||
@@ -269,6 +264,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
GatewayUrl: serverOptions.GatewayUrl,
|
||||
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
||||
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
|
||||
JWT: serverOptions.Jwt,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
@@ -311,6 +307,8 @@ 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,
|
||||
@@ -342,8 +340,8 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
r := baseapp.NewRouter()
|
||||
|
||||
// add auth middleware
|
||||
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
|
||||
user, err := auth.GetUserFromRequest(r, apiHandler)
|
||||
getUserFromRequest := func(ctx context.Context) (*basemodel.UserPayload, error) {
|
||||
user, err := auth.GetUserFromRequestContext(ctx, apiHandler)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -357,6 +355,8 @@ 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,
|
||||
@@ -395,174 +395,6 @@ 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
|
||||
|
||||
@@ -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 GetUserFromRequest(r *http.Request, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) {
|
||||
patToken := r.Header.Get("SIGNOZ-API-KEY")
|
||||
if len(patToken) > 0 {
|
||||
func GetUserFromRequestContext(ctx context.Context, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) {
|
||||
patToken, ok := authtypes.UUIDFromContext(ctx)
|
||||
if ok && patToken != "" {
|
||||
zap.L().Debug("Received a non-zero length PAT token")
|
||||
ctx := context.Background()
|
||||
dao := apiHandler.AppDao()
|
||||
@@ -40,7 +40,7 @@ func GetUserFromRequest(r *http.Request, apiHandler *api.APIHandler) (*basemodel
|
||||
}
|
||||
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 GetUserFromRequest(r *http.Request, apiHandler *api.APIHandler) (*basemodel
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return baseauth.GetUserFromRequest(r)
|
||||
return baseauth.GetUserFromReqContext(ctx)
|
||||
}
|
||||
|
||||
@@ -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(inputDB *sqlx.DB) (ModelDao, error) {
|
||||
return sqlite.InitDB(inputDB)
|
||||
func InitDao(sqlStore sqlstore.SQLStore) (ModelDao, error) {
|
||||
return sqlite.InitDB(sqlStore)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
type ModelDao interface {
|
||||
@@ -22,7 +23,7 @@ type ModelDao interface {
|
||||
|
||||
// auth methods
|
||||
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
|
||||
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
|
||||
PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (redirectURL string, apierr basemodel.BaseApiError)
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
|
||||
|
||||
// org domain (auth domains) CRUD ops
|
||||
|
||||
@@ -14,6 +14,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,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,
|
||||
}
|
||||
|
||||
@@ -64,7 +65,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) (redirectURL string, apierr basemodel.BaseApiError) {
|
||||
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (redirectURL string, apierr basemodel.BaseApiError) {
|
||||
|
||||
userPayload, apierr := m.GetUserByEmail(ctx, email)
|
||||
if !apierr.IsNil() {
|
||||
@@ -85,7 +86,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
user = &userPayload.User
|
||||
}
|
||||
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(user)
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(user, jwt)
|
||||
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")
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 {
|
||||
@@ -29,8 +30,8 @@ func (m *modelDao) checkFeature(key string) error {
|
||||
}
|
||||
|
||||
// InitDB creates and extends base model DB repository
|
||||
func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
|
||||
dao, err := basedsql.InitDB(inputDB)
|
||||
func InitDB(sqlStore sqlstore.SQLStore) (*modelDao, error) {
|
||||
dao, err := basedsql.InitDB(sqlStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,21 +9,25 @@ 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
|
||||
db *sqlx.DB
|
||||
bundb *bun.DB
|
||||
}
|
||||
|
||||
// NewLicenseRepo initiates a new license repo
|
||||
func NewLicenseRepo(db *sqlx.DB) Repo {
|
||||
func NewLicenseRepo(db *sqlx.DB, bundb *bun.DB) Repo {
|
||||
return Repo{
|
||||
db: db,
|
||||
db: db,
|
||||
bundb: bundb,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,24 +169,25 @@ func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repo) CreateFeature(req *basemodel.Feature) *basemodel.ApiError {
|
||||
func (r *Repo) CreateFeature(req *types.FeatureStatus) *basemodel.ApiError {
|
||||
|
||||
_, 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)
|
||||
_, err := r.bundb.NewInsert().
|
||||
Model(req).
|
||||
Exec(context.Background())
|
||||
if err != nil {
|
||||
return &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repo) GetFeature(featureName string) (basemodel.Feature, error) {
|
||||
func (r *Repo) GetFeature(featureName string) (types.FeatureStatus, error) {
|
||||
var feature types.FeatureStatus
|
||||
|
||||
var feature basemodel.Feature
|
||||
err := r.bundb.NewSelect().
|
||||
Model(&feature).
|
||||
Where("name = ?", featureName).
|
||||
Scan(context.Background())
|
||||
|
||||
err := r.db.Get(&feature,
|
||||
`SELECT * FROM feature_status WHERE name = ?;`, featureName)
|
||||
if err != nil {
|
||||
return feature, err
|
||||
}
|
||||
@@ -205,18 +210,19 @@ func (r *Repo) GetAllFeatures() ([]basemodel.Feature, error) {
|
||||
return feature, nil
|
||||
}
|
||||
|
||||
func (r *Repo) UpdateFeature(req basemodel.Feature) error {
|
||||
func (r *Repo) UpdateFeature(req types.FeatureStatus) error {
|
||||
|
||||
_, 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)
|
||||
_, err := r.bundb.NewUpdate().
|
||||
Model(&req).
|
||||
Where("name = ?", req.Name).
|
||||
Exec(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
|
||||
func (r *Repo) InitFeatures(req []types.FeatureStatus) 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)
|
||||
@@ -229,7 +235,7 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
feature.Usage = currentFeature.Usage
|
||||
feature.Usage = int(currentFeature.Usage)
|
||||
if feature.Usage >= feature.UsageLimit && feature.UsageLimit != -1 {
|
||||
feature.Active = false
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ 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"
|
||||
@@ -43,12 +45,12 @@ type Manager struct {
|
||||
activeFeatures basemodel.FeatureSet
|
||||
}
|
||||
|
||||
func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
|
||||
func StartManager(db *sqlx.DB, bundb *bun.DB, features ...basemodel.Feature) (*Manager, error) {
|
||||
if LM != nil {
|
||||
return LM, nil
|
||||
}
|
||||
|
||||
repo := NewLicenseRepo(db)
|
||||
repo := NewLicenseRepo(db, bundb)
|
||||
m := &Manager{
|
||||
repo: &repo,
|
||||
}
|
||||
@@ -237,10 +239,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 {
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
|
||||
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
|
||||
map[string]interface{}{"err": errResponse.Err.Error()}, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -282,15 +284,41 @@ func (lm *Manager) GetFeatureFlags() (basemodel.FeatureSet, error) {
|
||||
}
|
||||
|
||||
func (lm *Manager) InitFeatures(features basemodel.FeatureSet) error {
|
||||
return lm.repo.InitFeatures(features)
|
||||
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)
|
||||
}
|
||||
|
||||
func (lm *Manager) UpdateFeatureFlag(feature basemodel.Feature) error {
|
||||
return lm.repo.UpdateFeature(feature)
|
||||
return lm.repo.UpdateFeature(types.FeatureStatus{
|
||||
Name: feature.Name,
|
||||
Active: feature.Active,
|
||||
Usage: int(feature.Usage),
|
||||
UsageLimit: int(feature.UsageLimit),
|
||||
Route: feature.Route,
|
||||
})
|
||||
}
|
||||
|
||||
func (lm *Manager) GetFeatureFlag(key string) (basemodel.Feature, error) {
|
||||
return lm.repo.GetFeature(key)
|
||||
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
|
||||
}
|
||||
|
||||
// GetRepo return the license repo
|
||||
|
||||
@@ -20,6 +20,7 @@ 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"
|
||||
|
||||
@@ -154,6 +155,16 @@ 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,
|
||||
@@ -171,15 +182,7 @@ func main() {
|
||||
GatewayUrl: gatewayUrl,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
}
|
||||
|
||||
// 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.")
|
||||
Jwt: jwt,
|
||||
}
|
||||
|
||||
server, err := app.NewServer(serverOptions)
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { FilterDropdownProps } from 'antd/lib/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
getQueueOverview,
|
||||
QueueOverviewResponse,
|
||||
@@ -458,6 +459,7 @@ export default function CeleryOverviewTable({
|
||||
|
||||
const handleRowClick = (record: RowData): void => {
|
||||
onRowClick(record);
|
||||
logEvent('MQ Overview Page: Right Panel', { ...record });
|
||||
};
|
||||
|
||||
const getFilteredData = useCallback(
|
||||
@@ -481,6 +483,22 @@ 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
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
@@ -98,6 +99,12 @@ 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}
|
||||
|
||||
@@ -42,10 +42,12 @@ import {
|
||||
function CeleryTaskBar({
|
||||
onClick,
|
||||
queryEnabled,
|
||||
checkIfDataExists,
|
||||
}: {
|
||||
onClick?: (task: CaptureDataProps) => void;
|
||||
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
@@ -187,6 +189,7 @@ function CeleryTaskBar({
|
||||
onGraphClick(celerySlowestTasksTableWidgetData, ...args)
|
||||
}
|
||||
customSeries={getCustomSeries}
|
||||
dataAvailable={checkIfDataExists}
|
||||
/>
|
||||
)}
|
||||
{barState === CeleryTaskState.Failed && (
|
||||
@@ -232,6 +235,7 @@ function CeleryTaskBar({
|
||||
|
||||
CeleryTaskBar.defaultProps = {
|
||||
onClick: (): void => {},
|
||||
checkIfDataExists: undefined,
|
||||
};
|
||||
|
||||
export default CeleryTaskBar;
|
||||
|
||||
@@ -35,6 +35,8 @@ function CeleryTaskGraph({
|
||||
customErrorMessage,
|
||||
start,
|
||||
end,
|
||||
checkIfDataExists,
|
||||
analyticsEvent,
|
||||
}: {
|
||||
widgetData: Widgets;
|
||||
onClick?: (task: CaptureDataProps) => void;
|
||||
@@ -48,6 +50,8 @@ function CeleryTaskGraph({
|
||||
customErrorMessage?: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
analyticsEvent?: string;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
@@ -125,6 +129,8 @@ function CeleryTaskGraph({
|
||||
customErrorMessage={customErrorMessage}
|
||||
start={start}
|
||||
end={end}
|
||||
dataAvailable={checkIfDataExists}
|
||||
analyticsEvent={analyticsEvent}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
@@ -141,6 +147,8 @@ CeleryTaskGraph.defaultProps = {
|
||||
customErrorMessage: undefined,
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
checkIfDataExists: undefined,
|
||||
analyticsEvent: undefined,
|
||||
};
|
||||
|
||||
export default CeleryTaskGraph;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -92,6 +93,15 @@ 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">
|
||||
@@ -124,6 +134,10 @@ 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">
|
||||
@@ -173,8 +187,19 @@ export default function CeleryTaskGraphGrid({
|
||||
</div>
|
||||
{!collapsedSections.traceBasedGraphs && (
|
||||
<>
|
||||
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
|
||||
<CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
|
||||
<CeleryTaskBar
|
||||
queryEnabled={queryEnabled}
|
||||
onClick={onClick}
|
||||
checkIfDataExists={(isDataAvailable): void =>
|
||||
checkIfDataExists(isDataAvailable, 'State Graph')
|
||||
}
|
||||
/>
|
||||
<CeleryTaskLatencyGraph
|
||||
queryEnabled={queryEnabled}
|
||||
checkIfDataExists={(isDataAvailable): void =>
|
||||
checkIfDataExists(isDataAvailable, 'Task Latency')
|
||||
}
|
||||
/>
|
||||
<div className="celery-task-graph-grid-bottom">
|
||||
{bottomWidgetData.map((widgetData, index) => (
|
||||
<CeleryTaskGraph
|
||||
@@ -184,6 +209,9 @@ export default function CeleryTaskGraphGrid({
|
||||
queryEnabled={queryEnabled}
|
||||
rightPanelTitle={rightPanelTitle[index]}
|
||||
applyCeleryTaskFilter
|
||||
checkIfDataExists={(isDataAvailable): void =>
|
||||
checkIfDataExists(isDataAvailable, rightPanelTitle[index])
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -40,8 +41,10 @@ export enum CeleryTaskGraphState {
|
||||
|
||||
function CeleryTaskLatencyGraph({
|
||||
queryEnabled,
|
||||
checkIfDataExists,
|
||||
}: {
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
@@ -61,6 +64,10 @@ 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(
|
||||
@@ -195,6 +202,7 @@ function CeleryTaskLatencyGraph({
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p99_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
dataAvailable={checkIfDataExists}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -215,6 +223,7 @@ function CeleryTaskLatencyGraph({
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p95_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
dataAvailable={checkIfDataExists}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -234,6 +243,7 @@ function CeleryTaskLatencyGraph({
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p90_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
dataAvailable={checkIfDataExists}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -243,3 +253,7 @@ function CeleryTaskLatencyGraph({
|
||||
}
|
||||
|
||||
export default CeleryTaskLatencyGraph;
|
||||
|
||||
CeleryTaskLatencyGraph.defaultProps = {
|
||||
checkIfDataExists: undefined,
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
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';
|
||||
@@ -44,12 +45,16 @@ 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(
|
||||
|
||||
@@ -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/integrations/aws/useAwsAccounts';
|
||||
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
getRegionPreviewText,
|
||||
useAccountSettingsModal,
|
||||
} from 'hooks/integrations/aws/useAccountSettingsModal';
|
||||
} from 'hooks/integration/aws/useAccountSettingsModal';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
|
||||
@@ -3,7 +3,7 @@ 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/integrations/aws/useIntegrationModal';
|
||||
import { useIntegrationModal } from 'hooks/integration/aws/useIntegrationModal';
|
||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Form } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { useAccountStatus } from 'hooks/integrations/aws/useAccountStatus';
|
||||
import { useAccountStatus } from 'hooks/integration/aws/useAccountStatus';
|
||||
import { useRef } from 'react';
|
||||
import { AccountStatusResponse } from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './RegionSelector.style.scss';
|
||||
|
||||
import { Checkbox } from 'antd';
|
||||
import { useRegionSelection } from 'hooks/integrations/aws/useRegionSelection';
|
||||
import { useRegionSelection } from 'hooks/integration/aws/useRegionSelection';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ServiceConfig,
|
||||
SupportedSignals,
|
||||
} from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useUpdateServiceConfig } from 'hooks/integrations/aws/useUpdateServiceConfig';
|
||||
import { useUpdateServiceConfig } from 'hooks/integration/aws/useUpdateServiceConfig';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
|
||||
@@ -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/integrations/aws/useServiceDetails';
|
||||
import { useServiceDetails } from 'hooks/integration/aws/useServiceDetails';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
|
||||
import { useGetAccountServices } from 'hooks/integration/aws/useGetAccountServices';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
@@ -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/integrations/aws';
|
||||
import { getAwsServices } from 'api/integration/aws';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -44,6 +45,7 @@ function GridCardGraph({
|
||||
customErrorMessage,
|
||||
start,
|
||||
end,
|
||||
analyticsEvent,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
@@ -219,6 +221,11 @@ function GridCardGraph({
|
||||
setIsInternalServerError(
|
||||
String(error.message).includes('API responded with 500'),
|
||||
);
|
||||
if (analyticsEvent) {
|
||||
logEvent(analyticsEvent, {
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
setDashboardQueryRangeCalled(true);
|
||||
},
|
||||
@@ -283,6 +290,7 @@ GridCardGraph.defaultProps = {
|
||||
threshold: undefined,
|
||||
headerMenuList: [MenuItemKeys.View],
|
||||
version: 'v3',
|
||||
analyticsEvent: undefined,
|
||||
};
|
||||
|
||||
export default memo(GridCardGraph);
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface GridCardGraphProps {
|
||||
customErrorMessage?: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
analyticsEvent?: string;
|
||||
}
|
||||
|
||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||
|
||||
@@ -150,11 +150,13 @@ function VariableItem({
|
||||
let allSelected = false;
|
||||
// The default value for multi-select is ALL and first value for
|
||||
// single select
|
||||
if (variableData.multiSelect) {
|
||||
value = newOptionsData;
|
||||
allSelected = true;
|
||||
} else {
|
||||
[value] = newOptionsData;
|
||||
if (valueNotInList) {
|
||||
if (variableData.multiSelect) {
|
||||
value = newOptionsData;
|
||||
allSelected = true;
|
||||
} else {
|
||||
[value] = newOptionsData;
|
||||
}
|
||||
}
|
||||
|
||||
if (variableData && variableData?.name && variableData?.id) {
|
||||
@@ -226,6 +228,9 @@ function VariableItem({
|
||||
}
|
||||
setErrorMessage(message);
|
||||
}
|
||||
setVariablesToGetUpdated((prev) =>
|
||||
prev.filter((v) => v !== variableData.name),
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Form } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useUpdateAccountConfig } from 'hooks/integrations/aws/useUpdateAccountConfig';
|
||||
import { useUpdateAccountConfig } from 'hooks/integration/aws/useUpdateAccountConfig';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import {
|
||||
Dispatch,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAwsAccounts } from 'api/integrations/aws';
|
||||
import { getAwsAccounts } from 'api/integration/aws';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { CloudAccount } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConnectionParams } from 'api/integrations/aws';
|
||||
import { getConnectionParams } from 'api/integration/aws';
|
||||
import { AxiosError } from 'axios';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateConnectionUrl } from 'api/integrations/aws';
|
||||
import { generateConnectionUrl } from 'api/integration/aws';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAwsServices } from 'api/integrations/aws';
|
||||
import { getAwsServices } from 'api/integration/aws';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { Service } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getServiceDetails } from 'api/integrations/aws';
|
||||
import { getServiceDetails } from 'api/integration/aws';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { ServiceData } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { updateAccountConfig } from 'api/integrations/aws';
|
||||
import { updateAccountConfig } from 'api/integration/aws';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import {
|
||||
AccountConfigPayload,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { updateServiceConfig } from 'api/integrations/aws';
|
||||
import { updateServiceConfig } from 'api/integration/aws';
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
|
||||
interface UpdateServiceConfigPayload {
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -9,7 +10,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, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
@@ -123,6 +124,26 @@ 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">
|
||||
@@ -130,7 +151,9 @@ export default function OverviewRightPanelGraph({
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_request_rate_button"
|
||||
onClick={(): void => goToTraces(requestRateWidget)}
|
||||
onClick={(): void => {
|
||||
goToTraces(requestRateWidget);
|
||||
}}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -140,6 +163,9 @@ 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">
|
||||
@@ -157,6 +183,9 @@ 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">
|
||||
@@ -173,6 +202,9 @@ export default function OverviewRightPanelGraph({
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('Celery_avg_latency')}
|
||||
dataAvailable={(isDataAvailable: boolean): void => {
|
||||
setAvgLatencyStatus(isDataAvailable);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
@@ -113,6 +114,9 @@ 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}>
|
||||
@@ -133,7 +137,15 @@ export default function ValueInfo({
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void => navigateToTrace(filters ?? [])}
|
||||
onClick={(): void => {
|
||||
logEvent(mqAnalyticsTitle, {
|
||||
filters,
|
||||
minTime,
|
||||
maxTime,
|
||||
source: 'request rate',
|
||||
});
|
||||
navigateToTrace(filters ?? []);
|
||||
}}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -155,7 +167,13 @@ export default function ValueInfo({
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void =>
|
||||
onClick={(): void => {
|
||||
logEvent(mqAnalyticsTitle, {
|
||||
filters,
|
||||
minTime,
|
||||
maxTime,
|
||||
source: 'error rate',
|
||||
});
|
||||
navigateToTrace([
|
||||
...(filters ?? []),
|
||||
{
|
||||
@@ -171,8 +189,8 @@ export default function ValueInfo({
|
||||
op: '=',
|
||||
value: 'true',
|
||||
},
|
||||
])
|
||||
}
|
||||
]);
|
||||
}}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
@@ -194,7 +212,15 @@ export default function ValueInfo({
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void => navigateToTrace(filters ?? [])}
|
||||
onClick={(): void => {
|
||||
logEvent(mqAnalyticsTitle, {
|
||||
filters,
|
||||
minTime,
|
||||
maxTime,
|
||||
source: 'average latency',
|
||||
});
|
||||
navigateToTrace(filters ?? []);
|
||||
}}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
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 { useState } from 'react';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useEffect, useRef, 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);
|
||||
|
||||
@@ -94,10 +94,7 @@ function LogsExplorer(): JSX.Element {
|
||||
if (!data) return null;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const hasValidColumns = Array.isArray(parsed?.selectColumns);
|
||||
|
||||
return hasValidColumns ? parsed.selectColumns : null;
|
||||
return JSON.parse(data);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -132,16 +129,11 @@ function LogsExplorer(): JSX.Element {
|
||||
// Skip if already migrated
|
||||
if (query.version) return query;
|
||||
|
||||
// Case 1: query has columns
|
||||
if (query.selectColumns.length > 0) {
|
||||
return {
|
||||
...query,
|
||||
version: 1,
|
||||
selectColumns: mergeWithRequiredColumns(query.selectColumns),
|
||||
};
|
||||
if (logListOptionsFromLocalStorage?.version) {
|
||||
return logListOptionsFromLocalStorage;
|
||||
}
|
||||
|
||||
// Case 2: No query columns in but we have localStorage columns
|
||||
// Case 1: we have localStorage columns
|
||||
if (logListOptionsFromLocalStorage?.selectColumns?.length > 0) {
|
||||
return {
|
||||
...query,
|
||||
@@ -152,6 +144,15 @@ function LogsExplorer(): JSX.Element {
|
||||
};
|
||||
}
|
||||
|
||||
// Case 2: No query columns in localStorage in but query has columns
|
||||
if (query.selectColumns.length > 0) {
|
||||
return {
|
||||
...query,
|
||||
version: 1,
|
||||
selectColumns: mergeWithRequiredColumns(query.selectColumns),
|
||||
};
|
||||
}
|
||||
|
||||
// Case 3: No columns anywhere, use defaults
|
||||
return {
|
||||
...query,
|
||||
@@ -159,7 +160,7 @@ function LogsExplorer(): JSX.Element {
|
||||
selectColumns: defaultLogsSelectedColumns,
|
||||
};
|
||||
},
|
||||
[mergeWithRequiredColumns, logListOptionsFromLocalStorage?.selectColumns],
|
||||
[mergeWithRequiredColumns, logListOptionsFromLocalStorage],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import '../MQDetails.style.scss';
|
||||
|
||||
import { Table, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { MessagingQueueServicePayload } from 'api/messagingQueues/getConsumerLagDetails';
|
||||
import { getKafkaSpanEval } from 'api/messagingQueues/getKafkaSpanEval';
|
||||
import axios from 'axios';
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
MessagingQueuesViewType,
|
||||
RowData,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -93,6 +94,9 @@ export function getColumns(
|
||||
className="traceid-text"
|
||||
onClick={(): void => {
|
||||
window.open(`${ROUTES.TRACE}/${item}`, '_blank');
|
||||
logEvent(`MQ Kafka: Drop Rate - traceid navigation`, {
|
||||
item,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
@@ -227,6 +231,22 @@ function DropRateView(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, evaluationTime]);
|
||||
|
||||
const prevTableDataRef = useRef<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length > 0) {
|
||||
const currentTableData = JSON.stringify(tableData);
|
||||
|
||||
if (currentTableData !== prevTableDataRef.current) {
|
||||
logEvent(`MQ Kafka: Drop Rate View`, {
|
||||
dataRender: tableData.length,
|
||||
});
|
||||
prevTableDataRef.current = currentTableData;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(tableData)]);
|
||||
|
||||
return (
|
||||
<div className={cx('mq-overview-container', 'droprate-view')}>
|
||||
<div className="mq-overview-title">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import './MQTables.styles.scss';
|
||||
|
||||
import { Skeleton, Table, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
MessagingQueueServicePayload,
|
||||
MessagingQueuesPayloadProps,
|
||||
@@ -23,11 +24,12 @@ import {
|
||||
MessagingQueueServiceDetailType,
|
||||
MessagingQueuesViewType,
|
||||
MessagingQueuesViewTypeOptions,
|
||||
ProducerLatencyOptions,
|
||||
RowData,
|
||||
SelectedTimelineQuery,
|
||||
setConfigDetail,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
@@ -130,6 +132,7 @@ function MessagingQueuesTable({
|
||||
tableApi,
|
||||
validConfigPresent = false,
|
||||
type = 'Detail',
|
||||
option = ProducerLatencyOptions.Producers,
|
||||
}: {
|
||||
currentTab?: MessagingQueueServiceDetailType;
|
||||
selectedView: MessagingQueuesViewTypeOptions;
|
||||
@@ -141,6 +144,7 @@ function MessagingQueuesTable({
|
||||
>;
|
||||
validConfigPresent?: boolean;
|
||||
type?: 'Detail' | 'Overview';
|
||||
option?: ProducerLatencyOptions;
|
||||
}): JSX.Element {
|
||||
const [columns, setColumns] = useState<any[]>([]);
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
@@ -262,6 +266,43 @@ function MessagingQueuesTable({
|
||||
configDetailQueryData?.topic || ''
|
||||
} ${configDetailQueryData?.partition || ''}`;
|
||||
|
||||
const prevTableDataRef = useRef<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length > 0 && type === 'Overview') {
|
||||
const currentTableData = JSON.stringify(tableData);
|
||||
|
||||
if (currentTableData !== prevTableDataRef.current) {
|
||||
logEvent(`MQ Kafka: ${MessagingQueuesViewType[selectedView].label}`, {
|
||||
dataRender: tableData.length,
|
||||
});
|
||||
prevTableDataRef.current = currentTableData;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [option, JSON.stringify(tableData), selectedView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableData.length > 0 && type === 'Detail') {
|
||||
const currentTableData = JSON.stringify(tableData);
|
||||
|
||||
if (currentTableData !== prevTableDataRef.current) {
|
||||
logEvent(
|
||||
`MQ Kafka: ${MessagingQueuesViewType[selectedView].label} - details`,
|
||||
{
|
||||
dataRender: tableData.length,
|
||||
activeTab: currentTab,
|
||||
topic: configDetailQueryData?.topic,
|
||||
partition: configDetailQueryData?.partition,
|
||||
serviceName: configDetailQueryData?.service_name,
|
||||
},
|
||||
);
|
||||
prevTableDataRef.current = currentTableData;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentTab, JSON.stringify(tableData), selectedView]);
|
||||
|
||||
return (
|
||||
<div className="mq-tables-container">
|
||||
{!validConfigPresent ? (
|
||||
|
||||
@@ -112,6 +112,7 @@ function MessagingQueueOverview({
|
||||
tableApi={getTableApi(selectedView)}
|
||||
validConfigPresent
|
||||
type="Overview"
|
||||
option={option}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,12 +26,14 @@ interface MetricSectionProps {
|
||||
title: string;
|
||||
description: string;
|
||||
graphCount: Widgets[];
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}
|
||||
|
||||
function MetricSection({
|
||||
title,
|
||||
description,
|
||||
graphCount,
|
||||
checkIfDataExists,
|
||||
}: MetricSectionProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
@@ -50,6 +52,7 @@ function MetricSection({
|
||||
<MetricPageGridGraph
|
||||
key={`graph-${widgetData.id}`}
|
||||
widgetData={widgetData}
|
||||
checkIfDataExists={checkIfDataExists}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -57,7 +60,15 @@ function MetricSection({
|
||||
);
|
||||
}
|
||||
|
||||
function MetricColumnGraphs(): JSX.Element {
|
||||
MetricSection.defaultProps = {
|
||||
checkIfDataExists: (): void => {},
|
||||
};
|
||||
|
||||
function MetricColumnGraphs({
|
||||
checkIfDataExists,
|
||||
}: {
|
||||
checkIfDataExists: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const { t } = useTranslation('messagingQueues');
|
||||
|
||||
const metricsData = [
|
||||
@@ -106,6 +117,7 @@ function MetricColumnGraphs(): JSX.Element {
|
||||
title={metric.title}
|
||||
description={metric.description}
|
||||
graphCount={metric?.graphCount || []}
|
||||
checkIfDataExists={checkIfDataExists}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import './MetricPage.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { CardContainer } from 'container/GridCardLayout/styles';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
@@ -28,6 +29,7 @@ interface CollapsibleMetricSectionProps {
|
||||
graphCount: Widgets[];
|
||||
isCollapsed: boolean;
|
||||
onToggle: () => void;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}
|
||||
|
||||
function CollapsibleMetricSection({
|
||||
@@ -36,6 +38,7 @@ function CollapsibleMetricSection({
|
||||
graphCount,
|
||||
isCollapsed,
|
||||
onToggle,
|
||||
checkIfDataExists,
|
||||
}: CollapsibleMetricSectionProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
@@ -63,6 +66,7 @@ function CollapsibleMetricSection({
|
||||
<MetricPageGridGraph
|
||||
key={`graph-${widgetData.id}`}
|
||||
widgetData={widgetData}
|
||||
checkIfDataExists={checkIfDataExists}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -72,6 +76,10 @@ function CollapsibleMetricSection({
|
||||
);
|
||||
}
|
||||
|
||||
CollapsibleMetricSection.defaultProps = {
|
||||
checkIfDataExists: undefined,
|
||||
};
|
||||
|
||||
function MetricPage(): JSX.Element {
|
||||
const [collapsedSections, setCollapsedSections] = useState<{
|
||||
[key: string]: boolean;
|
||||
@@ -114,9 +122,27 @@ function MetricPage(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const [renderedGraphCount, setRenderedGraphCount] = useState(0);
|
||||
const hasLoggedRef = useRef(false);
|
||||
|
||||
const checkIfDataExists = (isDataAvailable: boolean): void => {
|
||||
if (isDataAvailable) {
|
||||
const newCount = renderedGraphCount + 1;
|
||||
setRenderedGraphCount(newCount);
|
||||
|
||||
// Only log when first graph has rendered and we haven't logged yet
|
||||
if (newCount === 1 && !hasLoggedRef.current) {
|
||||
logEvent('MQ Kafka: Metric view', {
|
||||
graphRendered: true,
|
||||
});
|
||||
hasLoggedRef.current = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="metric-page">
|
||||
<MetricColumnGraphs />
|
||||
<MetricColumnGraphs checkIfDataExists={checkIfDataExists} />
|
||||
{metricSections.map(({ key, title, description, graphCount }) => (
|
||||
<CollapsibleMetricSection
|
||||
key={key}
|
||||
@@ -125,6 +151,7 @@ function MetricPage(): JSX.Element {
|
||||
graphCount={graphCount}
|
||||
isCollapsed={collapsedSections[key]}
|
||||
onToggle={(): void => toggleCollapse(key)}
|
||||
checkIfDataExists={checkIfDataExists}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,10 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
function MetricPageGridGraph({
|
||||
widgetData,
|
||||
checkIfDataExists,
|
||||
}: {
|
||||
widgetData: Widgets;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
@@ -51,9 +53,14 @@ function MetricPageGridGraph({
|
||||
widget={widgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
dataAvailable={checkIfDataExists}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
MetricPageGridGraph.defaultProps = {
|
||||
checkIfDataExists: (): void => {},
|
||||
};
|
||||
|
||||
export default MetricPageGridGraph;
|
||||
|
||||
7
go.mod
7
go.mod
@@ -13,17 +13,17 @@ require (
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
github.com/auth0/go-jwt-middleware v1.0.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-co-op/gocron v1.30.1
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
github.com/go-openapi/strfmt v0.23.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-redis/redismock/v8 v8.11.5
|
||||
github.com/go-viper/mapstructure/v2 v2.1.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
@@ -111,7 +111,6 @@ require (
|
||||
github.com/expr-lang/expr v1.16.9 // indirect
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
@@ -126,13 +125,11 @@ require (
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@@ -124,8 +124,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/auth0/go-jwt-middleware v1.0.1 h1:/fsQ4vRr4zod1wKReUH+0A3ySRjGiT9G34kypO/EKwI=
|
||||
github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM=
|
||||
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
@@ -246,9 +244,6 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=
|
||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -337,8 +332,6 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -444,13 +437,10 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gophercloud/gophercloud v1.14.0 h1:Bt9zQDhPrbd4qX7EILGmy+i7GP35cc+AAL2+wIJpUE8=
|
||||
github.com/gophercloud/gophercloud v1.14.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
@@ -864,9 +854,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
|
||||
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||
@@ -920,8 +907,6 @@ github.com/uptrace/bun/dialect/pgdialect v1.2.9 h1:caf5uFbOGiXvadV6pA5gn87k0awFF
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.9/go.mod h1:m7L9JtOp/Lt8HccET70ULxplMweE/u0S9lNUSxz2duo=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9 h1:HLzGWXBh07sT8zhVPy6veYbbGrAtYq0KzyRHXBj+GjA=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9/go.mod h1:dUR+ecoCWA0FIa9vhQVRnGtYYPpuCLJoEEtX9E1aiBU=
|
||||
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4=
|
||||
@@ -1366,7 +1351,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
|
||||
25
pkg/alertmanager/alertmanager.go
Normal file
25
pkg/alertmanager/alertmanager.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.signoz.io/signoz/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeAlertmanagerNotFound = errors.MustNewCode("alertmanager_not_found")
|
||||
)
|
||||
|
||||
type Alertmanager interface {
|
||||
factory.Service
|
||||
// GetAlerts gets the alerts from the alertmanager per organization.
|
||||
GetAlerts(context.Context, string, alertmanagertypes.GettableAlertsParams) (alertmanagertypes.GettableAlerts, error)
|
||||
|
||||
// PutAlerts puts the alerts into the alertmanager per organization.
|
||||
PutAlerts(context.Context, string, alertmanagertypes.PostableAlerts) error
|
||||
|
||||
// TestReceiver sends a test alert to a receiver.
|
||||
TestReceiver(context.Context, string, alertmanagertypes.Receiver) error
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package sqlalertmanagerstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"go.signoz.io/signoz/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewConfigStore(sqlstore sqlstore.SQLStore) alertmanagertypes.ConfigStore {
|
||||
return &config{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
// Get implements alertmanagertypes.ConfigStore.
|
||||
func (store *config) Get(ctx context.Context, orgID string) (*alertmanagertypes.Config, error) {
|
||||
storeableConfig := new(alertmanagertypes.StoreableConfig)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storeableConfig).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.Newf(errors.TypeNotFound, alertmanagertypes.ErrCodeAlertmanagerConfigNotFound, "cannot find alertmanager config for orgID %s", orgID)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg, err := alertmanagertypes.NewConfigFromStoreableConfig(storeableConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Set implements alertmanagertypes.ConfigStore.
|
||||
func (store *config) Set(ctx context.Context, config *alertmanagertypes.Config) error {
|
||||
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback() //nolint:errcheck
|
||||
|
||||
if _, err = tx.
|
||||
NewInsert().
|
||||
Model(config.StoreableConfig()).
|
||||
On("CONFLICT (org_id) DO UPDATE").
|
||||
Set("config = ?", string(config.StoreableConfig().Config)).
|
||||
Set("updated_at = ?", config.StoreableConfig().UpdatedAt).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
channels := config.Channels()
|
||||
if len(channels) != 0 {
|
||||
if _, err = tx.NewInsert().
|
||||
Model(&channels).
|
||||
On("CONFLICT (name) DO UPDATE").
|
||||
Set("data = EXCLUDED.data").
|
||||
Set("updated_at = EXCLUDED.updated_at").
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *config) ListOrgs(ctx context.Context) ([]string, error) {
|
||||
var orgIDs []string
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Table("organizations").
|
||||
ColumnExpr("id").
|
||||
Scan(ctx, &orgIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orgIDs, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package sqlalertmanagerstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"go.signoz.io/signoz/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStateStore(sqlstore sqlstore.SQLStore) alertmanagertypes.StateStore {
|
||||
return &state{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
// Get implements alertmanagertypes.StateStore.
|
||||
func (store *state) Get(ctx context.Context, orgID string) (*alertmanagertypes.StoreableState, error) {
|
||||
storeableState := new(alertmanagertypes.StoreableState)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storeableState).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.Newf(errors.TypeNotFound, alertmanagertypes.ErrCodeAlertmanagerStateNotFound, "cannot find alertmanager state for org %s", orgID)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return storeableState, nil
|
||||
}
|
||||
|
||||
// Set implements alertmanagertypes.StateStore.
|
||||
func (store *state) Set(ctx context.Context, orgID string, storeableState *alertmanagertypes.StoreableState) error {
|
||||
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback() //nolint:errcheck
|
||||
|
||||
_, err = tx.
|
||||
NewInsert().
|
||||
Model(storeableState).
|
||||
On("CONFLICT (org_id) DO UPDATE").
|
||||
Set("silences = EXCLUDED.silences").
|
||||
Set("nflog = EXCLUDED.nflog").
|
||||
Set("updated_at = EXCLUDED.updated_at").
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
254
pkg/alertmanager/api.go
Normal file
254
pkg/alertmanager/api.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"go.signoz.io/signoz/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/http/render"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
configStore alertmanagertypes.ConfigStore
|
||||
alertmanager Alertmanager
|
||||
}
|
||||
|
||||
func NewAPI(configStore alertmanagertypes.ConfigStore, alertmanager Alertmanager) *API {
|
||||
return &API{
|
||||
configStore: configStore,
|
||||
alertmanager: alertmanager,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) GetAlerts(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
params, err := alertmanagertypes.NewGettableAlertsParams(req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
alerts, err := api.alertmanager.GetAlerts(ctx, claims.OrgID, params)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, alerts)
|
||||
}
|
||||
|
||||
func (api *API) TestReceiver(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
defer req.Body.Close() //nolint:errcheck
|
||||
|
||||
receiver, err := alertmanagertypes.NewReceiver(string(body))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.alertmanager.TestReceiver(ctx, claims.OrgID, receiver)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (api *API) GetChannels(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
config, err := api.configStore.Get(ctx, claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
channels := config.Channels()
|
||||
|
||||
channelList := make([]*alertmanagertypes.Channel, 0, len(channels))
|
||||
for _, channel := range channels {
|
||||
channelList = append(channelList, channel)
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, channelList)
|
||||
}
|
||||
|
||||
func (api *API) GetChannelByID(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
if vars == nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required in path"))
|
||||
return
|
||||
}
|
||||
|
||||
idString, ok := vars["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required in path"))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idString)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid integer"))
|
||||
return
|
||||
}
|
||||
|
||||
config, err := api.configStore.Get(ctx, claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
channels := config.Channels()
|
||||
channel, err := alertmanagertypes.GetChannelByID(channels, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, channel)
|
||||
}
|
||||
|
||||
func (api *API) UpdateChannelByID(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
defer req.Body.Close() //nolint:errcheck
|
||||
|
||||
receiver, err := alertmanagertypes.NewReceiver(string(body))
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
config, err := api.configStore.Get(ctx, claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = config.UpdateReceiver(alertmanagertypes.NewRouteFromReceiver(receiver), receiver)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.configStore.Set(ctx, config)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (api *API) DeleteChannelByID(req *http.Request, rw http.ResponseWriter) {
|
||||
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(req)
|
||||
if vars == nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required in path"))
|
||||
return
|
||||
}
|
||||
|
||||
idString, ok := vars["id"]
|
||||
if !ok {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is required in path"))
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idString)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid integer"))
|
||||
return
|
||||
}
|
||||
|
||||
config, err := api.configStore.Get(ctx, claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
channels := config.Channels()
|
||||
channel, err := alertmanagertypes.GetChannelByID(channels, id)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = config.DeleteReceiver(channel.Name)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.configStore.Set(ctx, config)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
42
pkg/alertmanager/config.go
Normal file
42
pkg/alertmanager/config.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/pkg/alertmanager/server"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Config is the config for the alertmanager server.
|
||||
server.Config `mapstructure:",squash"`
|
||||
|
||||
// Provider is the provider for the alertmanager service.
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
// Internal is the internal alertmanager configuration.
|
||||
Internal Internal `mapstructure:"internal"`
|
||||
}
|
||||
|
||||
type Internal struct {
|
||||
// PollInterval is the interval at which the alertmanager is synced.
|
||||
PollInterval time.Duration `mapstructure:"poll_interval"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("alertmanager"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
return Config{
|
||||
Config: server.NewConfig(),
|
||||
Provider: "internal",
|
||||
Internal: Internal{
|
||||
PollInterval: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
70
pkg/alertmanager/internalalertmanager/provider.go
Normal file
70
pkg/alertmanager/internalalertmanager/provider.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package internalalertmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/pkg/alertmanager"
|
||||
"go.signoz.io/signoz/pkg/alertmanager/alertmanagerstore/sqlalertmanagerstore"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
service *alertmanager.Service
|
||||
config alertmanager.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
}
|
||||
|
||||
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[alertmanager.Alertmanager, alertmanager.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("internal"), func(ctx context.Context, settings factory.ProviderSettings, config alertmanager.Config) (alertmanager.Alertmanager, error) {
|
||||
return New(ctx, settings, config, sqlstore)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config alertmanager.Config, sqlstore sqlstore.SQLStore) (alertmanager.Alertmanager, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/alertmanager/internalalertmanager")
|
||||
return &provider{
|
||||
service: alertmanager.New(
|
||||
ctx,
|
||||
settings,
|
||||
config,
|
||||
sqlalertmanagerstore.NewStateStore(sqlstore),
|
||||
sqlalertmanagerstore.NewConfigStore(sqlstore),
|
||||
),
|
||||
settings: settings,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
ticker := time.NewTicker(provider.config.Internal.PollInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
if err := provider.service.SyncServers(ctx); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to sync alertmanager servers", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
return provider.service.Stop(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) GetAlerts(ctx context.Context, orgID string, params alertmanagertypes.GettableAlertsParams) (alertmanagertypes.GettableAlerts, error) {
|
||||
return provider.service.GetAlerts(ctx, orgID, params)
|
||||
}
|
||||
|
||||
func (provider *provider) PutAlerts(ctx context.Context, orgID string, alerts alertmanagertypes.PostableAlerts) error {
|
||||
return provider.service.PutAlerts(ctx, orgID, alerts)
|
||||
}
|
||||
|
||||
func (provider *provider) TestReceiver(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver) error {
|
||||
return provider.service.TestReceiver(ctx, orgID, receiver)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ type Server struct {
|
||||
logger *slog.Logger
|
||||
|
||||
// registry is the prometheus registry for the alertmanager
|
||||
registry *prometheus.Registry
|
||||
registry prometheus.Registerer
|
||||
|
||||
// srvConfig is the server config for the alertmanager
|
||||
srvConfig Config
|
||||
@@ -64,7 +64,7 @@ type Server struct {
|
||||
stopc chan struct{}
|
||||
}
|
||||
|
||||
func New(ctx context.Context, logger *slog.Logger, registry *prometheus.Registry, srvConfig Config, orgID string, stateStore alertmanagertypes.StateStore) (*Server, error) {
|
||||
func New(ctx context.Context, logger *slog.Logger, registry prometheus.Registerer, srvConfig Config, orgID string, stateStore alertmanagertypes.StateStore) (*Server, error) {
|
||||
server := &Server{
|
||||
logger: logger.With("pkg", "go.signoz.io/pkg/alertmanager/server"),
|
||||
registry: registry,
|
||||
@@ -77,20 +77,18 @@ func New(ctx context.Context, logger *slog.Logger, registry *prometheus.Registry
|
||||
server.marker = alertmanagertypes.NewMarker(server.registry)
|
||||
|
||||
// get silences for initial state
|
||||
silencesstate, err := server.stateStore.Get(ctx, server.orgID, alertmanagertypes.SilenceStateName)
|
||||
state, err := server.stateStore.Get(ctx, server.orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get nflog for initial state
|
||||
nflogstate, err := server.stateStore.Get(ctx, server.orgID, alertmanagertypes.NFLogStateName)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
silencesSnapshot := ""
|
||||
if state != nil {
|
||||
silencesSnapshot = state.Silences
|
||||
}
|
||||
|
||||
// Initialize silences
|
||||
server.silences, err = silence.New(silence.Options{
|
||||
SnapshotReader: strings.NewReader(silencesstate),
|
||||
SnapshotReader: strings.NewReader(silencesSnapshot),
|
||||
Retention: srvConfig.Silences.Retention,
|
||||
Limits: silence.Limits{
|
||||
MaxSilences: func() int { return srvConfig.Silences.Max },
|
||||
@@ -103,9 +101,14 @@ func New(ctx context.Context, logger *slog.Logger, registry *prometheus.Registry
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nflogSnapshot := ""
|
||||
if state != nil {
|
||||
nflogSnapshot = state.NFLog
|
||||
}
|
||||
|
||||
// Initialize notification log
|
||||
server.nflog, err = nflog.New(nflog.Options{
|
||||
SnapshotReader: strings.NewReader(nflogstate),
|
||||
SnapshotReader: strings.NewReader(nflogSnapshot),
|
||||
Retention: server.srvConfig.NFLog.Retention,
|
||||
Metrics: server.registry,
|
||||
Logger: server.logger,
|
||||
@@ -125,7 +128,21 @@ func New(ctx context.Context, logger *slog.Logger, registry *prometheus.Registry
|
||||
// Don't return here - we need to snapshot our state first.
|
||||
}
|
||||
|
||||
return server.stateStore.Set(ctx, server.orgID, alertmanagertypes.SilenceStateName, server.silences)
|
||||
state, err := server.stateStore.Get(ctx, server.orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if state == nil {
|
||||
state = alertmanagertypes.NewStoreableState(server.orgID)
|
||||
}
|
||||
|
||||
c, err := state.Set(alertmanagertypes.SilenceStateName, server.silences)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c, server.stateStore.Set(ctx, server.orgID, state)
|
||||
})
|
||||
|
||||
}()
|
||||
@@ -140,7 +157,21 @@ func New(ctx context.Context, logger *slog.Logger, registry *prometheus.Registry
|
||||
// Don't return without saving the current state.
|
||||
}
|
||||
|
||||
return server.stateStore.Set(ctx, server.orgID, alertmanagertypes.NFLogStateName, server.nflog)
|
||||
state, err := server.stateStore.Get(ctx, server.orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if state == nil {
|
||||
state = alertmanagertypes.NewStoreableState(server.orgID)
|
||||
}
|
||||
|
||||
c, err := state.Set(alertmanagertypes.NFLogStateName, server.nflog)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c, server.stateStore.Set(ctx, server.orgID, state)
|
||||
})
|
||||
}()
|
||||
|
||||
|
||||
137
pkg/alertmanager/service.go
Normal file
137
pkg/alertmanager/service.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"go.signoz.io/signoz/pkg/alertmanager/server"
|
||||
"go.signoz.io/signoz/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/factory"
|
||||
"go.signoz.io/signoz/pkg/types/alertmanagertypes"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
// config is the config for the alertmanager service
|
||||
config Config
|
||||
|
||||
// stateStore is the state store for the alertmanager service
|
||||
stateStore alertmanagertypes.StateStore
|
||||
|
||||
// configStore is the config store for the alertmanager service
|
||||
configStore alertmanagertypes.ConfigStore
|
||||
|
||||
// settings is the settings for the alertmanager service
|
||||
settings factory.ScopedProviderSettings
|
||||
|
||||
// Map of organization id to alertmanager server
|
||||
servers map[string]*server.Server
|
||||
|
||||
// Mutex to protect the servers map
|
||||
serversMtx sync.RWMutex
|
||||
}
|
||||
|
||||
func New(ctx context.Context, settings factory.ScopedProviderSettings, config Config, stateStore alertmanagertypes.StateStore, configStore alertmanagertypes.ConfigStore) *Service {
|
||||
service := &Service{
|
||||
config: config,
|
||||
stateStore: stateStore,
|
||||
configStore: configStore,
|
||||
settings: settings,
|
||||
servers: make(map[string]*server.Server),
|
||||
serversMtx: sync.RWMutex{},
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func (service *Service) SyncServers(ctx context.Context) error {
|
||||
orgIDs, err := service.configStore.ListOrgs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.serversMtx.Lock()
|
||||
for _, orgID := range orgIDs {
|
||||
config, err := service.getConfig(ctx, orgID)
|
||||
if err != nil {
|
||||
service.settings.Logger().Error("failed to get alertmanagerconfig for org", "orgID", orgID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
service.servers[orgID], err = server.New(ctx, service.settings.Logger(), service.settings.PrometheusRegisterer(), server.Config{}, orgID, service.stateStore)
|
||||
if err != nil {
|
||||
service.settings.Logger().Error("failed to create alertmanagerserver", "orgID", orgID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = service.servers[orgID].SetConfig(ctx, config)
|
||||
if err != nil {
|
||||
service.settings.Logger().Error("failed to set config for alertmanager server", "orgID", orgID, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
service.serversMtx.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) GetAlerts(ctx context.Context, orgID string, params alertmanagertypes.GettableAlertsParams) (alertmanagertypes.GettableAlerts, error) {
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server.GetAlerts(ctx, params)
|
||||
}
|
||||
|
||||
func (service *Service) PutAlerts(ctx context.Context, orgID string, alerts alertmanagertypes.PostableAlerts) error {
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return server.PutAlerts(ctx, alerts)
|
||||
}
|
||||
|
||||
func (service *Service) TestReceiver(ctx context.Context, orgID string, receiver alertmanagertypes.Receiver) error {
|
||||
server, err := service.getServer(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return server.TestReceiver(ctx, receiver)
|
||||
}
|
||||
|
||||
func (service *Service) Stop(ctx context.Context) error {
|
||||
for _, server := range service.servers {
|
||||
server.Stop(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) getConfig(ctx context.Context, orgID string) (*alertmanagertypes.Config, error) {
|
||||
config, err := service.configStore.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err = alertmanagertypes.NewDefaultConfig(service.config.Global, service.config.Route, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (service *Service) getServer(orgID string) (*server.Server, error) {
|
||||
server, ok := service.servers[orgID]
|
||||
if !ok {
|
||||
return nil, errors.Newf(errors.TypeNotFound, ErrCodeAlertmanagerNotFound, "alertmanager not found for org %s", orgID)
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
@@ -5,6 +5,16 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
CodeInvalidInput code = code{"invalid_input"}
|
||||
CodeInternal = code{"internal"}
|
||||
CodeUnsupported = code{"unsupported"}
|
||||
CodeNotFound = code{"not_found"}
|
||||
CodeMethodNotAllowed = code{"method_not_allowed"}
|
||||
CodeAlreadyExists = code{"already_exists"}
|
||||
CodeUnauthenticated = code{"unauthenticated"}
|
||||
)
|
||||
|
||||
var (
|
||||
codeRegex = regexp.MustCompile(`^[a-z_]+$`)
|
||||
)
|
||||
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
// TODO(remove): Remove auth packages
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -30,18 +28,16 @@ func NewAnalytics(logger *zap.Logger) *Analytics {
|
||||
|
||||
func (a *Analytics) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := auth.AttachJwtToContext(r.Context(), r)
|
||||
r = r.WithContext(ctx)
|
||||
route := mux.CurrentRoute(r)
|
||||
path, _ := route.GetPathTemplate()
|
||||
|
||||
queryRangeData, metadataExists := a.extractQueryRangeData(path, r)
|
||||
a.getActiveLogs(path, r)
|
||||
|
||||
badResponseBuffer := new(bytes.Buffer)
|
||||
writer := newBadResponseLoggingWriter(w, badResponseBuffer)
|
||||
next.ServeHTTP(writer, r)
|
||||
|
||||
queryRangeData, metadataExists := a.extractQueryRangeData(path, r)
|
||||
a.getActiveLogs(path, r)
|
||||
|
||||
data := map[string]interface{}{"path": path, "statusCode": writer.StatusCode()}
|
||||
if metadataExists {
|
||||
for key, value := range queryRangeData {
|
||||
@@ -50,9 +46,9 @@ func (a *Analytics) Wrap(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
if _, ok := telemetry.EnabledPaths()[path]; ok {
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +134,8 @@ func (a *Analytics) extractQueryRangeData(path string, r *http.Request) (map[str
|
||||
data["queryType"] = queryInfoResult.QueryType
|
||||
data["panelType"] = queryInfoResult.PanelType
|
||||
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
// switch case to set data["screen"] based on the referrer
|
||||
switch {
|
||||
case dashboardMatched:
|
||||
@@ -154,7 +150,7 @@ func (a *Analytics) extractQueryRangeData(path string, r *http.Request) (map[str
|
||||
data["screen"] = "unknown"
|
||||
return data, true
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_API, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_API, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
return data, true
|
||||
|
||||
44
pkg/http/middleware/auth.go
Normal file
44
pkg/http/middleware/auth.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
logger *zap.Logger
|
||||
jwt *authtypes.JWT
|
||||
headers []string
|
||||
}
|
||||
|
||||
func NewAuth(logger *zap.Logger, jwt *authtypes.JWT, headers []string) *Auth {
|
||||
if logger == nil {
|
||||
panic("cannot build auth middleware, logger is empty")
|
||||
}
|
||||
|
||||
return &Auth{logger: logger, jwt: jwt, headers: headers}
|
||||
}
|
||||
|
||||
func (a *Auth) Wrap(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var values []string
|
||||
for _, header := range a.headers {
|
||||
values = append(values, r.Header.Get(header))
|
||||
}
|
||||
|
||||
ctx, err := a.jwt.ContextFromRequest(
|
||||
r.Context(),
|
||||
values...)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -133,7 +133,11 @@ func (middleware *Logging) getLogCommentKVs(r *http.Request) map[string]string {
|
||||
client = "api"
|
||||
}
|
||||
|
||||
email, _ := auth.GetEmailFromJwt(r.Context())
|
||||
var email string
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
email = claims.Email
|
||||
}
|
||||
|
||||
kvs := map[string]string{
|
||||
"path": path,
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
GetUserFromRequest func(r *http.Request) (*model.UserPayload, error)
|
||||
GetUserFromRequest func(r context.Context) (*model.UserPayload, error)
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(f func(r *http.Request) (*model.UserPayload, error)) *AuthMiddleware {
|
||||
func NewAuthMiddleware(f func(ctx context.Context) (*model.UserPayload, error)) *AuthMiddleware {
|
||||
return &AuthMiddleware{
|
||||
GetUserFromRequest: f,
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func (am *AuthMiddleware) OpenAccess(f func(http.ResponseWriter, *http.Request))
|
||||
|
||||
func (am *AuthMiddleware) ViewAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := am.GetUserFromRequest(r)
|
||||
user, err := am.GetUserFromRequest(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorUnauthorized,
|
||||
@@ -53,7 +53,7 @@ func (am *AuthMiddleware) ViewAccess(f func(http.ResponseWriter, *http.Request))
|
||||
|
||||
func (am *AuthMiddleware) EditAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := am.GetUserFromRequest(r)
|
||||
user, err := am.GetUserFromRequest(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorUnauthorized,
|
||||
@@ -76,7 +76,7 @@ func (am *AuthMiddleware) EditAccess(f func(http.ResponseWriter, *http.Request))
|
||||
|
||||
func (am *AuthMiddleware) SelfAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := am.GetUserFromRequest(r)
|
||||
user, err := am.GetUserFromRequest(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorUnauthorized,
|
||||
@@ -100,7 +100,7 @@ func (am *AuthMiddleware) SelfAccess(f func(http.ResponseWriter, *http.Request))
|
||||
|
||||
func (am *AuthMiddleware) AdminAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := am.GetUserFromRequest(r)
|
||||
user, err := am.GetUserFromRequest(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorUnauthorized,
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/cache"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
|
||||
promModel "github.com/prometheus/common/model"
|
||||
"go.uber.org/zap"
|
||||
@@ -43,7 +44,6 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/resource"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/services"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/traces/tracedetail"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
chErrors "go.signoz.io/signoz/pkg/query-service/errors"
|
||||
@@ -1164,23 +1164,23 @@ func (r *ClickHouseReader) SearchTracesV2(ctx context.Context, params *model.Sea
|
||||
if traceSummary.NumSpans > uint64(params.MaxSpansInTrace) {
|
||||
zap.L().Error("Max spans allowed in a trace limit reached", zap.Int("MaxSpansInTrace", params.MaxSpansInTrace),
|
||||
zap.Uint64("Count", traceSummary.NumSpans))
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": traceSummary.NumSpans,
|
||||
"maxSpansInTraceLimit": params.MaxSpansInTrace,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED, data, claims.Email, true, false)
|
||||
}
|
||||
return nil, fmt.Errorf("max spans allowed in trace limit reached, please contact support for more details")
|
||||
}
|
||||
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": traceSummary.NumSpans,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, claims.Email, true, false)
|
||||
}
|
||||
|
||||
var startTime, endTime, durationNano uint64
|
||||
@@ -1266,13 +1266,13 @@ func (r *ClickHouseReader) SearchTracesV2(ctx context.Context, params *model.Sea
|
||||
}
|
||||
end = time.Now()
|
||||
zap.L().Debug("smartTraceAlgo took: ", zap.Duration("duration", end.Sub(start)))
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": len(searchScanResponses),
|
||||
"spansRenderLimit": params.SpansRenderLimit,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LARGE_TRACE_OPENED, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LARGE_TRACE_OPENED, data, claims.Email, true, false)
|
||||
}
|
||||
} else {
|
||||
for i, item := range searchSpanResponses {
|
||||
@@ -1306,23 +1306,23 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
|
||||
if countSpans > uint64(params.MaxSpansInTrace) {
|
||||
zap.L().Error("Max spans allowed in a trace limit reached", zap.Int("MaxSpansInTrace", params.MaxSpansInTrace),
|
||||
zap.Uint64("Count", countSpans))
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": countSpans,
|
||||
"maxSpansInTraceLimit": params.MaxSpansInTrace,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED, data, claims.Email, true, false)
|
||||
}
|
||||
return nil, fmt.Errorf("max spans allowed in trace limit reached, please contact support for more details")
|
||||
}
|
||||
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": countSpans,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, data, claims.Email, true, false)
|
||||
}
|
||||
|
||||
var startTime, endTime, durationNano uint64
|
||||
@@ -1379,13 +1379,13 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc
|
||||
}
|
||||
end = time.Now()
|
||||
zap.L().Debug("smartTraceAlgo took: ", zap.Duration("duration", end.Sub(start)))
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
data := map[string]interface{}{
|
||||
"traceSize": len(searchScanResponses),
|
||||
"spansRenderLimit": params.SpansRenderLimit,
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LARGE_TRACE_OPENED, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LARGE_TRACE_OPENED, data, claims.Email, true, false)
|
||||
}
|
||||
} else {
|
||||
for i, item := range searchSpanResponses {
|
||||
@@ -1455,7 +1455,7 @@ func (r *ClickHouseReader) GetWaterfallSpansForTraceWithMetadata(ctx context.Con
|
||||
var serviceNameIntervalMap = map[string][]tracedetail.Interval{}
|
||||
var hasMissingSpans bool
|
||||
|
||||
userEmail , emailErr := auth.GetEmailFromJwt(ctx)
|
||||
claims, claimsPresent := authtypes.ClaimsFromContext(ctx)
|
||||
cachedTraceData, err := r.GetWaterfallSpansForTraceWithMetadataCache(ctx, traceID)
|
||||
if err == nil {
|
||||
startTime = cachedTraceData.StartTime
|
||||
@@ -1468,8 +1468,8 @@ func (r *ClickHouseReader) GetWaterfallSpansForTraceWithMetadata(ctx context.Con
|
||||
totalErrorSpans = cachedTraceData.TotalErrorSpans
|
||||
hasMissingSpans = cachedTraceData.HasMissingSpans
|
||||
|
||||
if emailErr == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, map[string]interface{}{"traceSize": totalSpans}, userEmail, true, false)
|
||||
if claimsPresent {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, map[string]interface{}{"traceSize": totalSpans}, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1485,8 +1485,8 @@ func (r *ClickHouseReader) GetWaterfallSpansForTraceWithMetadata(ctx context.Con
|
||||
}
|
||||
totalSpans = uint64(len(searchScanResponses))
|
||||
|
||||
if emailErr == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, map[string]interface{}{"traceSize": totalSpans}, userEmail, true, false)
|
||||
if claimsPresent {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_TRACE_DETAIL_API, map[string]interface{}{"traceSize": totalSpans}, claims.Email, true, false)
|
||||
}
|
||||
|
||||
processingBeforeCache := time.Now()
|
||||
@@ -1531,8 +1531,8 @@ func (r *ClickHouseReader) GetWaterfallSpansForTraceWithMetadata(ctx context.Con
|
||||
if startTime == 0 || startTimeUnixNano < startTime {
|
||||
startTime = startTimeUnixNano
|
||||
}
|
||||
if endTime == 0 || (startTimeUnixNano + jsonItem.DurationNano ) > endTime {
|
||||
endTime = (startTimeUnixNano + jsonItem.DurationNano )
|
||||
if endTime == 0 || (startTimeUnixNano+jsonItem.DurationNano) > endTime {
|
||||
endTime = (startTimeUnixNano + jsonItem.DurationNano)
|
||||
}
|
||||
if durationNano == 0 || jsonItem.DurationNano > durationNano {
|
||||
durationNano = jsonItem.DurationNano
|
||||
@@ -1709,12 +1709,12 @@ func (r *ClickHouseReader) GetFlamegraphSpansForTrace(ctx context.Context, trace
|
||||
}
|
||||
|
||||
// metadata calculation
|
||||
startTimeUnixNano := uint64(item.TimeUnixNano.UnixNano())
|
||||
startTimeUnixNano := uint64(item.TimeUnixNano.UnixNano())
|
||||
if startTime == 0 || startTimeUnixNano < startTime {
|
||||
startTime = startTimeUnixNano
|
||||
}
|
||||
if endTime == 0 || ( startTimeUnixNano + jsonItem.DurationNano ) > endTime {
|
||||
endTime = (startTimeUnixNano + jsonItem.DurationNano )
|
||||
if endTime == 0 || (startTimeUnixNano+jsonItem.DurationNano) > endTime {
|
||||
endTime = (startTimeUnixNano + jsonItem.DurationNano)
|
||||
}
|
||||
if durationNano == 0 || jsonItem.DurationNano > durationNano {
|
||||
durationNano = jsonItem.DurationNano
|
||||
@@ -1778,7 +1778,7 @@ func (r *ClickHouseReader) GetFlamegraphSpansForTrace(ctx context.Context, trace
|
||||
|
||||
trace.Spans = selectedSpansForRequest
|
||||
trace.StartTimestampMillis = startTime / 1000000
|
||||
trace.EndTimestampMillis = endTime / 1000000
|
||||
trace.EndTimestampMillis = endTime / 1000000
|
||||
return trace, nil
|
||||
}
|
||||
|
||||
@@ -3464,9 +3464,9 @@ func (r *ClickHouseReader) GetLogs(ctx context.Context, params *model.LogsFilter
|
||||
"lenFilters": lenFilters,
|
||||
}
|
||||
if lenFilters != 0 {
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3506,9 +3506,9 @@ func (r *ClickHouseReader) TailLogs(ctx context.Context, client *model.LogsTailC
|
||||
"lenFilters": lenFilters,
|
||||
}
|
||||
if lenFilters != 0 {
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3598,9 +3598,9 @@ func (r *ClickHouseReader) AggregateLogs(ctx context.Context, params *model.Logs
|
||||
"lenFilters": lenFilters,
|
||||
}
|
||||
if lenFilters != 0 {
|
||||
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_LOGS_FILTERS, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
@@ -30,15 +30,15 @@ type Controller struct {
|
||||
serviceConfigRepo serviceConfigRepository
|
||||
}
|
||||
|
||||
func NewController(db *sqlx.DB) (
|
||||
func NewController(sqlStore sqlstore.SQLStore) (
|
||||
*Controller, error,
|
||||
) {
|
||||
accountsRepo, err := newCloudProviderAccountsRepository(db)
|
||||
accountsRepo, err := newCloudProviderAccountsRepository(sqlStore.SQLxDB())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create cloud provider accounts repo: %w", err)
|
||||
}
|
||||
|
||||
serviceConfigRepo, err := newServiceConfigRepository(db)
|
||||
serviceConfigRepo, err := newServiceConfigRepository(sqlStore.SQLxDB())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create cloud provider service config repo: %w", err)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
||||
require := require.New(t)
|
||||
testDB, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(testDB)
|
||||
sqlStore, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
// should be able to generate connection url for
|
||||
@@ -56,8 +56,8 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
||||
|
||||
func TestAgentCheckIns(t *testing.T) {
|
||||
require := require.New(t)
|
||||
testDB, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(testDB)
|
||||
sqlStore, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
// An agent should be able to check in from a cloud account even
|
||||
@@ -139,8 +139,8 @@ func TestAgentCheckIns(t *testing.T) {
|
||||
|
||||
func TestCantDisconnectNonExistentAccount(t *testing.T) {
|
||||
require := require.New(t)
|
||||
testDB, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(testDB)
|
||||
sqlStore, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
// Attempting to disconnect a non-existent account should return error
|
||||
@@ -154,8 +154,8 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
|
||||
|
||||
func TestConfigureService(t *testing.T) {
|
||||
require := require.New(t)
|
||||
testDB, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(testDB)
|
||||
sqlStore, _ := utils.NewTestSqliteDB(t)
|
||||
controller, err := NewController(sqlStore)
|
||||
require.NoError(err)
|
||||
|
||||
testCloudAccountId := "546311234"
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -125,13 +125,13 @@ func CreateView(ctx context.Context, view v3.SavedView) (string, error) {
|
||||
createdAt := time.Now()
|
||||
updatedAt := time.Now()
|
||||
|
||||
email, err := auth.GetEmailFromJwt(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("error in getting email from context")
|
||||
}
|
||||
|
||||
createBy := email
|
||||
updatedBy := email
|
||||
createBy := claims.Email
|
||||
updatedBy := claims.Email
|
||||
|
||||
_, err = db.Exec(
|
||||
"INSERT INTO saved_views (uuid, name, category, created_at, created_by, updated_at, updated_by, source_page, tags, data, extra_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
@@ -186,13 +186,13 @@ func UpdateView(ctx context.Context, uuid_ string, view v3.SavedView) error {
|
||||
return fmt.Errorf("error in marshalling explorer query data: %s", err.Error())
|
||||
}
|
||||
|
||||
email, err := auth.GetEmailFromJwt(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("error in getting email from context")
|
||||
}
|
||||
|
||||
updatedAt := time.Now()
|
||||
updatedBy := email
|
||||
updatedBy := claims.Email
|
||||
|
||||
_, err = db.Exec("UPDATE saved_views SET updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ? WHERE uuid = ?",
|
||||
updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData, uuid_)
|
||||
|
||||
@@ -49,6 +49,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/contextlinks"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -126,6 +127,8 @@ type APIHandler struct {
|
||||
jobsRepo *inframetrics.JobsRepo
|
||||
|
||||
pvcsRepo *inframetrics.PvcsRepo
|
||||
|
||||
JWT *authtypes.JWT
|
||||
}
|
||||
|
||||
type APIHandlerOpts struct {
|
||||
@@ -165,6 +168,8 @@ type APIHandlerOpts struct {
|
||||
UseLogsNewSchema bool
|
||||
|
||||
UseTraceNewSchema bool
|
||||
|
||||
JWT *authtypes.JWT
|
||||
}
|
||||
|
||||
// NewAPIHandler returns an APIHandler
|
||||
@@ -237,6 +242,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
||||
statefulsetsRepo: statefulsetsRepo,
|
||||
jobsRepo: jobsRepo,
|
||||
pvcsRepo: pvcsRepo,
|
||||
JWT: opts.JWT,
|
||||
}
|
||||
|
||||
logsQueryBuilder := logsv3.PrepareLogsQuery
|
||||
@@ -1616,9 +1622,9 @@ func (aH *APIHandler) submitFeedback(w http.ResponseWriter, r *http.Request) {
|
||||
"email": email,
|
||||
"message": message,
|
||||
}
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_INPRODUCT_FEEDBACK, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_INPRODUCT_FEEDBACK, data, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1628,9 +1634,9 @@ func (aH *APIHandler) registerEvent(w http.ResponseWriter, r *http.Request) {
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(request.EventName, request.Attributes, userEmail, request.RateLimited, true)
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(request.EventName, request.Attributes, claims.Email, request.RateLimited, true)
|
||||
aH.WriteJSON(w, r, map[string]string{"data": "Event Processed Successfully"})
|
||||
} else {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
||||
@@ -1734,9 +1740,9 @@ func (aH *APIHandler) getServices(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"number": len(*result),
|
||||
}
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_NUMBER_OF_SERVICES, data, userEmail, true, false)
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_NUMBER_OF_SERVICES, data, claims.Email, true, false)
|
||||
}
|
||||
|
||||
if (data["number"] != 0) && (data["number"] != telemetry.DEFAULT_NUMBER_OF_SERVICES) {
|
||||
@@ -2160,7 +2166,7 @@ func (aH *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
|
||||
// req.RefreshToken = c.Value
|
||||
// }
|
||||
|
||||
resp, err := auth.Login(context.Background(), req)
|
||||
resp, err := auth.Login(context.Background(), req, aH.JWT)
|
||||
if aH.HandleError(w, err, http.StatusUnauthorized) {
|
||||
return
|
||||
}
|
||||
@@ -2279,13 +2285,13 @@ func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
RespondError(w, apiErr, "Failed to get admin group")
|
||||
return
|
||||
}
|
||||
adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, adminGroup.Id)
|
||||
adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, adminGroup.ID)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, "Failed to get admin group users")
|
||||
return
|
||||
}
|
||||
|
||||
if user.GroupId == adminGroup.Id && len(adminUsers) == 1 {
|
||||
if user.GroupId == adminGroup.ID && len(adminUsers) == 1 {
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorInternal,
|
||||
Err: errors.New("cannot delete the last admin user")}, nil)
|
||||
@@ -2397,7 +2403,7 @@ func (aH *APIHandler) editRole(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
apiErr = dao.DB().UpdateUserGroup(context.Background(), user.Id, newGroup.Id)
|
||||
apiErr = dao.DB().UpdateUserGroup(context.Background(), user.Id, newGroup.ID)
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, "Failed to add user to group")
|
||||
return
|
||||
@@ -2442,11 +2448,11 @@ func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) {
|
||||
"isAnonymous": req.IsAnonymous,
|
||||
"organizationName": req.Name,
|
||||
}
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get user email from jwt", zap.Error(err))
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if !ok {
|
||||
zap.L().Error("failed to get user email from jwt")
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_ORG_SETTINGS, data, userEmail, true, false)
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_ORG_SETTINGS, data, claims.Email, true, false)
|
||||
|
||||
aH.WriteJSON(w, r, map[string]string{"data": "org updated successfully"})
|
||||
}
|
||||
@@ -5006,8 +5012,8 @@ func sendQueryResultEvents(r *http.Request, result []*v3.Result, queryRangeParam
|
||||
|
||||
if len(result) > 0 && (len(result[0].Series) > 0 || len(result[0].List) > 0) {
|
||||
|
||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
claims, ok := authtypes.ClaimsFromContext(r.Context())
|
||||
if ok {
|
||||
queryInfoResult := telemetry.GetInstance().CheckQueryInfo(queryRangeParams)
|
||||
if queryInfoResult.LogsUsed || queryInfoResult.MetricsUsed || queryInfoResult.TracesUsed {
|
||||
|
||||
@@ -5047,7 +5053,7 @@ func sendQueryResultEvents(r *http.Request, result []*v3.Result, queryRangeParam
|
||||
"filterApplied": queryInfoResult.FilterApplied,
|
||||
"dashboardId": dashboardID,
|
||||
"widgetId": widgetID,
|
||||
}, userEmail, true, false)
|
||||
}, claims.Email, true, false)
|
||||
}
|
||||
if alertMatched {
|
||||
var alertID string
|
||||
@@ -5074,7 +5080,7 @@ func sendQueryResultEvents(r *http.Request, result []*v3.Result, queryRangeParam
|
||||
"aggregateAttributeKey": queryInfoResult.AggregateAttributeKey,
|
||||
"filterApplied": queryInfoResult.FilterApplied,
|
||||
"alertId": alertID,
|
||||
}, userEmail, true, false)
|
||||
}, claims.Email, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,21 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/agentConf"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
mgr *Manager
|
||||
}
|
||||
|
||||
func NewController(db *sqlx.DB) (
|
||||
func NewController(sqlStore sqlstore.SQLStore) (
|
||||
*Controller, error,
|
||||
) {
|
||||
mgr, err := NewManager(db)
|
||||
mgr, err := NewManager(sqlStore.SQLxDB())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create integrations manager: %w", err)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
func NewTestIntegrationsManager(t *testing.T) *Manager {
|
||||
testDB := utils.NewQueryServiceDBForTests(t)
|
||||
|
||||
installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB)
|
||||
installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB.SQLxDB())
|
||||
if err != nil {
|
||||
t.Fatalf("could not init sqlite DB for installed integrations: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/agentConf"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -50,9 +50,9 @@ func (ic *LogParsingPipelineController) ApplyPipelines(
|
||||
postable []PostablePipeline,
|
||||
) (*PipelinesResponse, *model.ApiError) {
|
||||
// get user id from context
|
||||
userId, authErr := auth.ExtractUserIdFromContext(ctx)
|
||||
if authErr != nil {
|
||||
return nil, model.UnauthorizedError(errors.Wrap(authErr, "failed to get userId from context"))
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, model.UnauthorizedError(fmt.Errorf("failed to get userId from context"))
|
||||
}
|
||||
|
||||
var pipelines []Pipeline
|
||||
@@ -84,7 +84,7 @@ func (ic *LogParsingPipelineController) ApplyPipelines(
|
||||
}
|
||||
|
||||
// prepare config by calling gen func
|
||||
cfg, err := agentConf.StartNewVersion(ctx, userId, agentConf.ElementTypeLogPipelines, elements)
|
||||
cfg, err := agentConf.StartNewVersion(ctx, claims.UserID, agentConf.ElementTypeLogPipelines, elements)
|
||||
if err != nil || cfg == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -45,14 +45,9 @@ func (r *Repo) insertPipeline(
|
||||
))
|
||||
}
|
||||
|
||||
jwt, ok := auth.ExtractJwtFromContext(ctx)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, model.UnauthorizedError(err)
|
||||
}
|
||||
|
||||
claims, err := auth.ParseJWT(jwt)
|
||||
if err != nil {
|
||||
return nil, model.UnauthorizedError(err)
|
||||
return nil, model.UnauthorizedError(fmt.Errorf("failed to get email from context"))
|
||||
}
|
||||
|
||||
insertRow := &Pipeline{
|
||||
@@ -66,7 +61,7 @@ func (r *Repo) insertPipeline(
|
||||
Config: postable.Config,
|
||||
RawConfig: string(rawConfig),
|
||||
Creator: Creator{
|
||||
CreatedBy: claims["email"].(string),
|
||||
CreatedBy: claims.Email,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ type testbed struct {
|
||||
|
||||
func newTestbed(t *testing.T) *testbed {
|
||||
testDB := utils.NewQueryServiceDBForTests(t)
|
||||
_, err := model.InitDB(testDB)
|
||||
_, err := model.InitDB(testDB.SQLxDB())
|
||||
if err != nil {
|
||||
t.Fatalf("could not init opamp model: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
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"
|
||||
@@ -30,8 +24,8 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/preferences"
|
||||
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"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/app/explorer"
|
||||
@@ -68,6 +62,7 @@ type ServerOptions struct {
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
SigNoz *signoz.SigNoz
|
||||
Jwt *authtypes.JWT
|
||||
}
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
@@ -96,7 +91,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
// NewServer creates and initializes Server
|
||||
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
var err error
|
||||
if err := dao.InitDao(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
|
||||
if err := dao.InitDao(serverOptions.SigNoz.SQLStore); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -168,12 +163,12 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
|
||||
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create integrations controller: %w", err)
|
||||
}
|
||||
|
||||
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore.SQLxDB())
|
||||
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create cloud provider integrations controller: %w", err)
|
||||
}
|
||||
@@ -200,6 +195,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
FluxInterval: fluxInterval,
|
||||
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
||||
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
|
||||
JWT: serverOptions.Jwt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -254,6 +250,7 @@ func (s *Server) createPrivateServer(api *APIHandler) (*http.Server, error) {
|
||||
|
||||
r := NewRouter()
|
||||
|
||||
r.Use(middleware.NewAuth(zap.L(), s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewTimeout(zap.L(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@@ -284,6 +281,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
|
||||
r := NewRouter()
|
||||
|
||||
r.Use(middleware.NewAuth(zap.L(), s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewTimeout(zap.L(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@@ -293,8 +291,8 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
r.Use(middleware.NewLogging(zap.L(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
|
||||
// add auth middleware
|
||||
getUserFromRequest := func(r *http.Request) (*model.UserPayload, error) {
|
||||
user, err := auth.GetUserFromRequest(r)
|
||||
getUserFromRequest := func(ctx context.Context) (*model.UserPayload, error) {
|
||||
user, err := auth.GetUserFromReqContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -338,176 +336,6 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
}, 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 extractQueryRangeV3Data(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 := auth.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 := auth.AttachJwtToContext(r.Context(), r)
|
||||
r = r.WithContext(ctx)
|
||||
route := mux.CurrentRoute(r)
|
||||
path, _ := route.GetPathTemplate()
|
||||
|
||||
queryRangeV3data, metadataExists := extractQueryRangeV3Data(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 queryRangeV3data {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// if telemetry.GetInstance().IsSampled() {
|
||||
if _, ok := telemetry.EnabledPaths()[path]; ok {
|
||||
userEmail, err := auth.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
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
smtpservice "go.signoz.io/signoz/pkg/query-service/utils/smtpService"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -75,17 +75,12 @@ func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteRespons
|
||||
return nil, errors.Wrap(err, "invalid invite request")
|
||||
}
|
||||
|
||||
jwtAdmin, ok := ExtractJwtFromContext(ctx)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.Wrap(err, "failed to extract admin jwt token")
|
||||
return nil, errors.Wrap(err, "failed to extract admin user id")
|
||||
}
|
||||
|
||||
adminUser, err := validateUser(jwtAdmin)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate admin jwt token")
|
||||
}
|
||||
|
||||
au, apiErr := dao.DB().GetUser(ctx, adminUser.Id)
|
||||
au, apiErr := dao.DB().GetUser(ctx, claims.UserID)
|
||||
if apiErr != nil {
|
||||
return nil, errors.Wrap(err, "failed to query admin user from the DB")
|
||||
}
|
||||
@@ -123,17 +118,12 @@ func InviteUsers(ctx context.Context, req *model.BulkInviteRequest) (*model.Bulk
|
||||
FailedInvites: []model.FailedInvite{},
|
||||
}
|
||||
|
||||
jwtAdmin, ok := ExtractJwtFromContext(ctx)
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to extract admin jwt token")
|
||||
return nil, errors.New("failed to extract admin user id")
|
||||
}
|
||||
|
||||
adminUser, err := validateUser(jwtAdmin)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate admin jwt token")
|
||||
}
|
||||
|
||||
au, apiErr := dao.DB().GetUser(ctx, adminUser.Id)
|
||||
au, apiErr := dao.DB().GetUser(ctx, claims.UserID)
|
||||
if apiErr != nil {
|
||||
return nil, errors.Wrap(apiErr.Err, "failed to query admin user from the DB")
|
||||
}
|
||||
@@ -431,7 +421,7 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
|
||||
Password: hash,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
ProfilePictureURL: "", // Currently unused
|
||||
GroupId: group.Id,
|
||||
GroupId: group.ID,
|
||||
OrgId: org.Id,
|
||||
}
|
||||
|
||||
@@ -509,7 +499,7 @@ func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword b
|
||||
Password: hash,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
ProfilePictureURL: "", // Currently unused
|
||||
GroupId: group.Id,
|
||||
GroupId: group.ID,
|
||||
OrgId: invite.OrgId,
|
||||
}
|
||||
|
||||
@@ -550,16 +540,16 @@ func Register(ctx context.Context, req *RegisterRequest) (*model.User, *model.Ap
|
||||
}
|
||||
|
||||
// Login method returns access and refresh tokens on successful login, else it errors out.
|
||||
func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginResponse, error) {
|
||||
func Login(ctx context.Context, request *model.LoginRequest, jwt *authtypes.JWT) (*model.LoginResponse, error) {
|
||||
zap.L().Debug("Login method called for user", zap.String("email", request.Email))
|
||||
|
||||
user, err := authenticateLogin(ctx, request)
|
||||
user, err := authenticateLogin(ctx, request, jwt)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to authenticate login request", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userjwt, err := GenerateJWTForUser(&user.User)
|
||||
userjwt, err := GenerateJWTForUser(&user.User, jwt)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to generate JWT against login creds", zap.Error(err))
|
||||
return nil, err
|
||||
@@ -576,20 +566,36 @@ func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginRespon
|
||||
}, nil
|
||||
}
|
||||
|
||||
// authenticateLogin is responsible for querying the DB and validating the credentials.
|
||||
func authenticateLogin(ctx context.Context, req *model.LoginRequest) (*model.UserPayload, error) {
|
||||
func claimsToUserPayload(claims authtypes.Claims) (*model.UserPayload, error) {
|
||||
user := &model.UserPayload{
|
||||
User: model.User{
|
||||
Id: claims.UserID,
|
||||
GroupId: claims.GroupID,
|
||||
Email: claims.Email,
|
||||
OrgId: claims.OrgID,
|
||||
},
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// authenticateLogin is responsible for querying the DB and validating the credentials.
|
||||
func authenticateLogin(ctx context.Context, req *model.LoginRequest, jwt *authtypes.JWT) (*model.UserPayload, error) {
|
||||
// If refresh token is valid, then simply authorize the login request.
|
||||
if len(req.RefreshToken) > 0 {
|
||||
user, err := validateUser(req.RefreshToken)
|
||||
// parse the refresh token
|
||||
claims, err := jwt.Claims(req.RefreshToken)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate refresh token")
|
||||
return nil, errors.Wrap(err, "failed to parse refresh token")
|
||||
}
|
||||
|
||||
if user.OrgId == "" {
|
||||
if claims.OrgID == "" {
|
||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||
}
|
||||
|
||||
user, err := claimsToUserPayload(claims)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert claims to user payload")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -618,34 +624,17 @@ func passwordMatch(hash, password string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
|
||||
func GenerateJWTForUser(user *model.User, jwt *authtypes.JWT) (model.UserJwtObject, error) {
|
||||
j := model.UserJwtObject{}
|
||||
var err error
|
||||
j.AccessJwtExpiry = time.Now().Add(JwtExpiry).Unix()
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": user.Id,
|
||||
"gid": user.GroupId,
|
||||
"email": user.Email,
|
||||
"exp": j.AccessJwtExpiry,
|
||||
"orgId": user.OrgId,
|
||||
})
|
||||
|
||||
j.AccessJwt, err = token.SignedString([]byte(JwtSecret))
|
||||
j.AccessJwtExpiry = time.Now().Add(jwt.JwtExpiry).Unix()
|
||||
j.AccessJwt, err = jwt.AccessToken(user.OrgId, user.Id, user.GroupId, user.Email)
|
||||
if err != nil {
|
||||
return j, errors.Errorf("failed to encode jwt: %v", err)
|
||||
}
|
||||
|
||||
j.RefreshJwtExpiry = time.Now().Add(JwtRefresh).Unix()
|
||||
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": user.Id,
|
||||
"gid": user.GroupId,
|
||||
"email": user.Email,
|
||||
"exp": j.RefreshJwtExpiry,
|
||||
"orgId": user.OrgId,
|
||||
})
|
||||
|
||||
j.RefreshJwt, err = token.SignedString([]byte(JwtSecret))
|
||||
j.RefreshJwtExpiry = time.Now().Add(jwt.JwtRefresh).Unix()
|
||||
j.RefreshJwt, err = jwt.RefreshToken(user.OrgId, user.Id, user.GroupId, user.Email)
|
||||
if err != nil {
|
||||
return j, errors.Errorf("failed to encode jwt: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
jwtmiddleware "github.com/auth0/go-jwt-middleware"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
JwtSecret string
|
||||
JwtExpiry = 30 * time.Minute
|
||||
JwtRefresh = 30 * 24 * time.Hour
|
||||
)
|
||||
|
||||
func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
|
||||
// TODO[@vikrantgupta25] : to update this to the claims check function for better integrity of JWT
|
||||
// reference - https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Parser.ParseWithClaims
|
||||
token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.Errorf("unknown signing algo: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(JwtSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse jwt token")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, errors.Errorf("Not a valid jwt claim")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func validateUser(tok string) (*model.UserPayload, error) {
|
||||
claims, err := ParseJWT(tok)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
if !claims.VerifyExpiresAt(now, true) {
|
||||
return nil, model.ErrorTokenExpired
|
||||
}
|
||||
|
||||
var orgId string
|
||||
if claims["orgId"] != nil {
|
||||
orgId = claims["orgId"].(string)
|
||||
}
|
||||
|
||||
return &model.UserPayload{
|
||||
User: model.User{
|
||||
Id: claims["id"].(string),
|
||||
GroupId: claims["gid"].(string),
|
||||
Email: claims["email"].(string),
|
||||
OrgId: orgId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AttachJwtToContext attached the jwt token from the request header to the context.
|
||||
func AttachJwtToContext(ctx context.Context, r *http.Request) context.Context {
|
||||
token, err := ExtractJwtFromRequest(r)
|
||||
if err != nil {
|
||||
zap.L().Error("Error while getting token from header", zap.Error(err))
|
||||
return ctx
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, AccessJwtKey, token)
|
||||
}
|
||||
|
||||
func ExtractJwtFromContext(ctx context.Context) (string, bool) {
|
||||
jwtToken, ok := ctx.Value(AccessJwtKey).(string)
|
||||
return jwtToken, ok
|
||||
}
|
||||
|
||||
func ExtractJwtFromRequest(r *http.Request) (string, error) {
|
||||
authHeaderJwt, err := jwtmiddleware.FromAuthHeader(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(authHeaderJwt) > 0 {
|
||||
return authHeaderJwt, nil
|
||||
}
|
||||
|
||||
// We expect websocket connections to send auth JWT in the
|
||||
// `Sec-Websocket-Protocol` header.
|
||||
//
|
||||
// The standard js websocket API doesn't allow setting headers
|
||||
// other than the `Sec-WebSocket-Protocol` header, which is often
|
||||
// used for auth purposes as a result.
|
||||
return r.Header.Get("Sec-WebSocket-Protocol"), nil
|
||||
}
|
||||
|
||||
func ExtractUserIdFromContext(ctx context.Context) (string, error) {
|
||||
userId := ""
|
||||
jwt, ok := ExtractJwtFromContext(ctx)
|
||||
if !ok {
|
||||
return "", model.InternalError(fmt.Errorf("failed to extract jwt from context"))
|
||||
}
|
||||
|
||||
claims, err := ParseJWT(jwt)
|
||||
if err != nil {
|
||||
return "", model.InternalError(fmt.Errorf("failed get claims from jwt %v", err))
|
||||
}
|
||||
|
||||
if v, ok := claims["id"]; ok {
|
||||
userId = v.(string)
|
||||
}
|
||||
return userId, nil
|
||||
}
|
||||
|
||||
func GetEmailFromJwt(ctx context.Context) (string, error) {
|
||||
jwt, ok := ExtractJwtFromContext(ctx)
|
||||
if !ok {
|
||||
return "", model.InternalError(fmt.Errorf("failed to extract jwt from context"))
|
||||
}
|
||||
|
||||
claims, err := ParseJWT(jwt)
|
||||
if err != nil {
|
||||
return "", model.InternalError(fmt.Errorf("failed get claims from jwt %v", err))
|
||||
}
|
||||
|
||||
return claims["email"].(string), nil
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
@@ -31,7 +31,7 @@ func InitAuthCache(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err.Err, "failed to get group %s", groupName)
|
||||
}
|
||||
*dest = group.Id
|
||||
*dest = group.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -48,15 +48,19 @@ func InitAuthCache(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUserFromRequest(r *http.Request) (*model.UserPayload, error) {
|
||||
accessJwt, err := ExtractJwtFromRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func GetUserFromReqContext(ctx context.Context) (*model.UserPayload, error) {
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("no claims found in context")
|
||||
}
|
||||
|
||||
user, err := validateUser(accessJwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
user := &model.UserPayload{
|
||||
User: model.User{
|
||||
Id: claims.UserID,
|
||||
GroupId: claims.GroupID,
|
||||
Email: claims.Email,
|
||||
OrgId: claims.OrgID,
|
||||
},
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
var db ModelDao
|
||||
|
||||
func InitDao(inputDB *sqlx.DB) error {
|
||||
func InitDao(sqlStore sqlstore.SQLStore) error {
|
||||
var err error
|
||||
db, err = sqlite.InitDB(inputDB)
|
||||
db, err = sqlite.InitDB(sqlStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type ModelDao interface {
|
||||
@@ -22,7 +23,7 @@ type Queries interface {
|
||||
GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError)
|
||||
|
||||
GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError)
|
||||
GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError)
|
||||
GetGroupByName(ctx context.Context, name string) (*types.Group, *model.ApiError)
|
||||
GetGroups(ctx context.Context) ([]model.Group, *model.ApiError)
|
||||
|
||||
GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError)
|
||||
@@ -50,7 +51,7 @@ type Mutations interface {
|
||||
|
||||
UpdateUserFlags(ctx context.Context, userId string, flags map[string]string) (model.UserFlag, *model.ApiError)
|
||||
|
||||
CreateGroup(ctx context.Context, group *model.Group) (*model.Group, *model.ApiError)
|
||||
CreateGroup(ctx context.Context, group *types.Group) (*types.Group, *model.ApiError)
|
||||
DeleteGroup(ctx context.Context, id string) *model.ApiError
|
||||
|
||||
CreateOrg(ctx context.Context, org *model.Organization) (*model.Organization, *model.ApiError)
|
||||
|
||||
@@ -5,19 +5,23 @@ import (
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"go.signoz.io/signoz/pkg/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ModelDaoSqlite struct {
|
||||
db *sqlx.DB
|
||||
db *sqlx.DB
|
||||
bundb *bun.DB
|
||||
}
|
||||
|
||||
// InitDB sets up setting up the connection pool global variable.
|
||||
func InitDB(db *sqlx.DB) (*ModelDaoSqlite, error) {
|
||||
mds := &ModelDaoSqlite{db: db}
|
||||
func InitDB(sqlStore sqlstore.SQLStore) (*ModelDaoSqlite, error) {
|
||||
mds := &ModelDaoSqlite{db: sqlStore.SQLxDB(), bundb: sqlStore.BunDB()}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := mds.initializeOrgPreferences(ctx); err != nil {
|
||||
@@ -97,7 +101,7 @@ func (mds *ModelDaoSqlite) initializeRBAC(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (mds *ModelDaoSqlite) createGroupIfNotPresent(ctx context.Context,
|
||||
name string) (*model.Group, error) {
|
||||
name string) (*types.Group, error) {
|
||||
|
||||
group, err := mds.GetGroupByName(ctx, name)
|
||||
if err != nil {
|
||||
@@ -108,7 +112,7 @@ func (mds *ModelDaoSqlite) createGroupIfNotPresent(ctx context.Context,
|
||||
}
|
||||
|
||||
zap.L().Debug("group is not found, creating it", zap.String("group_name", name))
|
||||
group, cErr := mds.CreateGroup(ctx, &model.Group{Name: name})
|
||||
group, cErr := mds.CreateGroup(ctx, &types.Group{Name: name})
|
||||
if cErr != nil {
|
||||
return nil, cErr.Err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/telemetry"
|
||||
"go.signoz.io/signoz/pkg/types"
|
||||
)
|
||||
|
||||
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context,
|
||||
@@ -436,12 +437,13 @@ func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context,
|
||||
}
|
||||
|
||||
func (mds *ModelDaoSqlite) CreateGroup(ctx context.Context,
|
||||
group *model.Group) (*model.Group, *model.ApiError) {
|
||||
group *types.Group) (*types.Group, *model.ApiError) {
|
||||
|
||||
group.Id = uuid.NewString()
|
||||
group.ID = uuid.NewString()
|
||||
|
||||
q := `INSERT INTO groups (id, name) VALUES (?, ?);`
|
||||
if _, err := mds.db.ExecContext(ctx, q, group.Id, group.Name); err != nil {
|
||||
if _, err := mds.bundb.NewInsert().
|
||||
Model(group).
|
||||
Exec(ctx); err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||
}
|
||||
|
||||
@@ -478,10 +480,14 @@ func (mds *ModelDaoSqlite) GetGroup(ctx context.Context,
|
||||
}
|
||||
|
||||
func (mds *ModelDaoSqlite) GetGroupByName(ctx context.Context,
|
||||
name string) (*model.Group, *model.ApiError) {
|
||||
name string) (*types.Group, *model.ApiError) {
|
||||
|
||||
groups := []model.Group{}
|
||||
if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE name=?`, name); err != nil {
|
||||
groups := []types.Group{}
|
||||
err := mds.bundb.NewSelect().
|
||||
Model(&groups).
|
||||
Where("name = ?", name).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"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"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -98,6 +99,17 @@ func main() {
|
||||
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
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,
|
||||
HTTPHostPort: constants.HTTPHostPort,
|
||||
@@ -114,15 +126,7 @@ func main() {
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
SigNoz: signoz,
|
||||
}
|
||||
|
||||
// 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.")
|
||||
Jwt: jwt,
|
||||
}
|
||||
|
||||
server, err := app.NewServer(serverOptions)
|
||||
|
||||
@@ -10,11 +10,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"github.com/pkg/errors"
|
||||
"go.signoz.io/signoz/pkg/query-service/common"
|
||||
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/types/authtypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -267,10 +268,13 @@ func (r *ruleDB) GetPlannedMaintenanceByID(ctx context.Context, id string) (*Pla
|
||||
|
||||
func (r *ruleDB) CreatePlannedMaintenance(ctx context.Context, maintenance PlannedMaintenance) (int64, error) {
|
||||
|
||||
email, _ := auth.GetEmailFromJwt(ctx)
|
||||
maintenance.CreatedBy = email
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return 0, errors.New("no claims found in context")
|
||||
}
|
||||
maintenance.CreatedBy = claims.Email
|
||||
maintenance.CreatedAt = time.Now()
|
||||
maintenance.UpdatedBy = email
|
||||
maintenance.UpdatedBy = claims.Email
|
||||
maintenance.UpdatedAt = time.Now()
|
||||
|
||||
query := "INSERT INTO planned_maintenance (name, description, schedule, alert_ids, created_at, created_by, updated_at, updated_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)"
|
||||
@@ -298,8 +302,11 @@ func (r *ruleDB) DeletePlannedMaintenance(ctx context.Context, id string) (strin
|
||||
}
|
||||
|
||||
func (r *ruleDB) EditPlannedMaintenance(ctx context.Context, maintenance PlannedMaintenance, id string) (string, error) {
|
||||
email, _ := auth.GetEmailFromJwt(ctx)
|
||||
maintenance.UpdatedBy = email
|
||||
claims, ok := authtypes.ClaimsFromContext(ctx)
|
||||
if !ok {
|
||||
return "", errors.New("no claims found in context")
|
||||
}
|
||||
maintenance.UpdatedBy = claims.Email
|
||||
maintenance.UpdatedAt = time.Now()
|
||||
|
||||
query := "UPDATE planned_maintenance SET name=$1, description=$2, schedule=$3, alert_ids=$4, updated_at=$5, updated_by=$6 WHERE id=$7"
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
mockhouse "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/http/middleware"
|
||||
"go.signoz.io/signoz/pkg/query-service/app"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
@@ -292,20 +293,23 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||
testDB := utils.NewQueryServiceDBForTests(t)
|
||||
|
||||
fm := featureManager.StartManager()
|
||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB, fm)
|
||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB.SQLxDB(), fm)
|
||||
mockClickhouse.MatchExpectationsInOrder(false)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
FeatureFlags: fm,
|
||||
JWT: jwt,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("could not create a new ApiHandler: %v", err)
|
||||
}
|
||||
|
||||
router := app.NewRouter()
|
||||
am := app.NewAuthMiddleware(auth.GetUserFromRequest)
|
||||
//add the jwt middleware
|
||||
router.Use(middleware.NewAuth(zap.L(), jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
am := app.NewAuthMiddleware(auth.GetUserFromReqContext)
|
||||
apiHandler.RegisterRoutes(router, am)
|
||||
apiHandler.RegisterQueryRangeV3Routes(router, am)
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/open-telemetry/opamp-go/protobufs"
|
||||
"github.com/pkg/errors"
|
||||
@@ -22,13 +21,13 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opampModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
"go.signoz.io/signoz/pkg/query-service/queryBuilderToExpr"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -450,18 +449,18 @@ type LogPipelinesTestBed struct {
|
||||
}
|
||||
|
||||
// testDB can be injected for sharing a DB across multiple integration testbeds.
|
||||
func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed {
|
||||
if testDB == nil {
|
||||
testDB = utils.NewQueryServiceDBForTests(t)
|
||||
func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipelinesTestBed {
|
||||
if sqlStore == nil {
|
||||
sqlStore = utils.NewQueryServiceDBForTests(t)
|
||||
}
|
||||
|
||||
ic, err := integrations.NewController(testDB)
|
||||
ic, err := integrations.NewController(sqlStore)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create integrations controller: %v", err)
|
||||
}
|
||||
|
||||
controller, err := logparsingpipeline.NewLogParsingPipelinesController(
|
||||
testDB, ic.GetPipelinesForInstalledIntegrations,
|
||||
sqlStore.SQLxDB(), ic.GetPipelinesForInstalledIntegrations,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create a logparsingpipelines controller: %v", err)
|
||||
@@ -470,6 +469,7 @@ func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
AppDao: dao.DB(),
|
||||
LogsParsingPipelineController: controller,
|
||||
JWT: jwt,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("could not create a new ApiHandler: %v", err)
|
||||
@@ -481,7 +481,7 @@ func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed
|
||||
}
|
||||
|
||||
// Mock an available opamp agent
|
||||
testDB, err = opampModel.InitDB(testDB)
|
||||
testDB, err := opampModel.InitDB(sqlStore.SQLxDB())
|
||||
require.Nil(t, err, "failed to init opamp model")
|
||||
|
||||
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
|
||||
@@ -499,7 +499,7 @@ func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogPipelinesTestBed(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed {
|
||||
func NewLogPipelinesTestBed(t *testing.T, testDB sqlstore.SQLStore) *LogPipelinesTestBed {
|
||||
testbed := NewTestbedWithoutOpamp(t, testDB)
|
||||
|
||||
opampServer := opamp.InitializeServer(nil, testbed.agentConfMgr)
|
||||
@@ -540,7 +540,12 @@ func (tb *LogPipelinesTestBed) PostPipelinesToQSExpectingStatusCode(
|
||||
}
|
||||
|
||||
respWriter := httptest.NewRecorder()
|
||||
ctx := auth.AttachJwtToContext(req.Context(), req)
|
||||
|
||||
ctx, err := tb.apiHandler.JWT.ContextFromRequest(req.Context(), req.Header.Get("Authorization"))
|
||||
if err != nil {
|
||||
tb.t.Fatalf("couldn't get jwt from request: %v", err)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
tb.apiHandler.CreateLogsPipeline(respWriter, req)
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
mockhouse "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.signoz.io/signoz/pkg/http/middleware"
|
||||
"go.signoz.io/signoz/pkg/query-service/app"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/featureManager"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
"go.signoz.io/signoz/pkg/sqlstore"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestAWSIntegrationAccountLifecycle(t *testing.T) {
|
||||
@@ -342,7 +344,7 @@ type CloudIntegrationsTestBed struct {
|
||||
}
|
||||
|
||||
// testDB can be injected for sharing a DB across multiple integration testbeds.
|
||||
func NewCloudIntegrationsTestBed(t *testing.T, testDB *sqlx.DB) *CloudIntegrationsTestBed {
|
||||
func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudIntegrationsTestBed {
|
||||
if testDB == nil {
|
||||
testDB = utils.NewQueryServiceDBForTests(t)
|
||||
}
|
||||
@@ -353,7 +355,7 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB *sqlx.DB) *CloudIntegratio
|
||||
}
|
||||
|
||||
fm := featureManager.StartManager()
|
||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB, fm)
|
||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB.SQLxDB(), fm)
|
||||
mockClickhouse.MatchExpectationsInOrder(false)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
@@ -361,13 +363,15 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB *sqlx.DB) *CloudIntegratio
|
||||
AppDao: dao.DB(),
|
||||
CloudIntegrationsController: controller,
|
||||
FeatureFlags: fm,
|
||||
JWT: jwt,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("could not create a new ApiHandler: %v", err)
|
||||
}
|
||||
|
||||
router := app.NewRouter()
|
||||
am := app.NewAuthMiddleware(auth.GetUserFromRequest)
|
||||
router.Use(middleware.NewAuth(zap.L(), jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
am := app.NewAuthMiddleware(auth.GetUserFromReqContext)
|
||||
apiHandler.RegisterRoutes(router, am)
|
||||
apiHandler.RegisterCloudIntegrationsRoutes(router, am)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user