Compare commits

..

2 Commits

Author SHA1 Message Date
Srikanth Chekuri
a58d70079b Merge branch 'develop' into error-response 2024-06-15 20:17:12 +05:30
Srikanth Chekuri
2ae341cfa9 chore: error response std and improved error messages 2024-06-13 01:19:40 +05:30
607 changed files with 4084 additions and 26530 deletions

View File

@@ -158,7 +158,6 @@ jobs:
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
- name: Install dependencies
working-directory: frontend
run: yarn install

View File

@@ -30,7 +30,6 @@ jobs:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
@@ -52,4 +51,4 @@ jobs:
make build-frontend-amd64
make run-testing
EOF
gcloud beta compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

View File

@@ -30,7 +30,6 @@ jobs:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
@@ -53,4 +52,4 @@ jobs:
make build-frontend-amd64
make run-testing
EOF
gcloud beta compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

View File

@@ -347,7 +347,7 @@ curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod-
```bash
kubectl -n sample-application run strzal --image=djbingham/curl \
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
'user_count=6' -F 'spawn_rate=2' http://locust-master:8089/swarm
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
```
**5.1.3 To stop the load generation:**

View File

@@ -188,4 +188,3 @@ test:
go test ./pkg/query-service/tests/integration/...
go test ./pkg/query-service/rules/...
go test ./pkg/query-service/collectorsimulator/...
go test ./pkg/query-service/postprocess/...

View File

@@ -198,14 +198,14 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
#### Frontend
- [Palash Gupta](https://github.com/palashgdev)
- [Yunus M](https://github.com/YounixM)
- [Vikrant Gupta](https://github.com/vikrantgupta25)
- [Sagar Rajput](https://github.com/SagarRajput-7)
- [Rajat Dabade](https://github.com/Rajat-Dabade)
#### DevOps
- [Prashant Shahi](https://github.com/prashant-shahi)
- [Vibhu Pandey](https://github.com/grandwizard28)
- [Dhawal Sanghvi](https://github.com/dhawal1248)
<br /><br />

View File

@@ -146,7 +146,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.49.1
image: signoz/query-service:0.46.0
command:
[
"-config=/root/config/prometheus.yml",
@@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.48.0
image: signoz/frontend:0.46.0
deploy:
restart_policy:
condition: on-failure
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.102.2
image: signoz/signoz-otel-collector:0.88.24
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -211,7 +211,6 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
- DOCKER_MULTI_NODE_CLUSTER=false
@@ -238,7 +237,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.102.2
image: signoz/signoz-schema-migrator:0.88.24
deploy:
restart_policy:
condition: on-failure

View File

@@ -36,7 +36,6 @@ receivers:
# endpoint: 0.0.0.0:6832
hostmetrics:
collection_interval: 30s
root_path: /hostfs
scrapers:
cpu: {}
load: {}

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -81,7 +81,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.102.2
image: signoz/signoz-otel-collector:0.88.24
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -93,8 +93,6 @@ services:
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
ports:

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
container_name: signoz-query-service
command:
[
@@ -204,7 +204,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -216,7 +216,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -230,7 +230,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
container_name: signoz-otel-collector
command:
[
@@ -244,7 +244,6 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
- DOCKER_MULTI_NODE_CLUSTER=false

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
container_name: signoz-query-service
command:
[
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
container_name: signoz-otel-collector
command:
[
@@ -243,7 +243,6 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
- DOCKER_MULTI_NODE_CLUSTER=false

View File

@@ -36,7 +36,6 @@ receivers:
# endpoint: 0.0.0.0:6832
hostmetrics:
collection_interval: 30s
root_path: /hostfs
scrapers:
cpu: {}
load: {}

View File

@@ -389,7 +389,7 @@ trap bye EXIT
URL="https://api.segment.io/v1/track"
HEADER_1="Content-Type: application/json"
HEADER_2="Authorization: Basic OWtScko3b1BDR1BFSkxGNlFqTVBMdDVibGpGaFJRQnI="
HEADER_2="Authorization: Basic NEdtb2E0aXhKQVVIeDJCcEp4c2p3QTFiRWZud0VlUno6"
send_event() {
error=""

View File

@@ -24,6 +24,7 @@ import (
type APIHandlerOptions struct {
DataConnector interfaces.DataConnector
SkipConfig *basemodel.SkipConfig
PreferDelta bool
PreferSpanMetrics bool
MaxIdleConns int
MaxOpenConns int
@@ -52,6 +53,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
SkipConfig: opts.SkipConfig,
PerferDelta: opts.PreferDelta,
PreferSpanMetrics: opts.PreferSpanMetrics,
MaxIdleConns: opts.MaxIdleConns,
MaxOpenConns: opts.MaxOpenConns,

View File

@@ -35,14 +35,14 @@ func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
req := basemodel.LoginRequest{}
err := parseRequest(r, &req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
ctx := context.Background()
if req.Email != "" && ah.CheckFeature(model.SSO) {
var apierr basemodel.BaseApiError
var apierr *basemodel.ApiError
_, apierr = ah.AppDao().CanUsePassword(ctx, req.Email)
if apierr != nil && !apierr.IsNil() {
RespondError(w, apierr, nil)
@@ -74,7 +74,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
requestBody, err := io.ReadAll(r.Body)
if err != nil {
zap.L().Error("received no input in api", zap.Error(err))
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
@@ -82,7 +82,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
if err != nil {
zap.L().Error("received invalid user registration request", zap.Error(err))
RespondError(w, model.BadRequest(fmt.Errorf("failed to register user")), nil)
RespondError(w, basemodel.BadRequest(fmt.Errorf("failed to register user")), nil)
return
}
@@ -90,13 +90,13 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
invite, err := baseauth.ValidateInvite(ctx, req)
if err != nil {
zap.L().Error("failed to validate invite token", zap.Error(err))
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
if invite == nil {
zap.L().Error("failed to validate invite token: it is either empty or invalid", zap.Error(err))
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
RespondError(w, basemodel.BadRequest(basemodel.ErrSignupFailed{}), nil)
return
}
@@ -104,7 +104,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
domain, apierr := ah.AppDao().GetDomainByEmail(ctx, invite.Email)
if apierr != nil {
zap.L().Error("failed to get domain from email", zap.Error(apierr))
RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil)
RespondError(w, basemodel.InternalError(basemodel.ErrSignupFailed{}), nil)
}
precheckResp := &basemodel.PrecheckResponse{
@@ -120,7 +120,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
var precheckError basemodel.BaseApiError
var precheckError *basemodel.ApiError
precheckResp, precheckError = ah.AppDao().PrecheckLogin(ctx, user.Email, req.SourceUrl)
if precheckError != nil {
@@ -130,7 +130,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
} else {
// no-sso, validate password
if err := baseauth.ValidatePassword(req.Password); err != nil {
RespondError(w, model.InternalError(fmt.Errorf("password is not in a valid format")), nil)
RespondError(w, basemodel.InternalError(fmt.Errorf("password is not in a valid format")), nil)
return
}
@@ -155,7 +155,7 @@ func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
inviteObject, err := baseauth.GetInvite(context.Background(), token)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}

View File

@@ -1,9 +1,7 @@
package api
import (
"errors"
"net/http"
"strings"
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
@@ -31,10 +29,6 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
// Get the dashboard UUID from the request
uuid := mux.Vars(r)["uuid"]
if strings.HasPrefix(uuid,"integration") {
RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: errors.New("dashboards created by integrations cannot be unlocked")}, "You are not authorized to lock/unlock this dashboard")
return
}
dashboard, err := dashboards.GetDashboard(r.Context(), uuid)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())

View File

@@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
func (ah *APIHandler) listDomainsByOrg(w http.ResponseWriter, r *http.Request) {
@@ -27,12 +28,12 @@ func (ah *APIHandler) postDomain(w http.ResponseWriter, r *http.Request) {
req := model.OrgDomain{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
if err := req.ValidNew(); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
@@ -50,18 +51,18 @@ func (ah *APIHandler) putDomain(w http.ResponseWriter, r *http.Request) {
domainIdStr := mux.Vars(r)["id"]
domainId, err := uuid.Parse(domainIdStr)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
req := model.OrgDomain{Id: domainId}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
req.Id = domainId
if err := req.Valid(nil); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
}
if apierr := ah.AppDao().UpdateDomain(ctx, &req); apierr != nil {
@@ -77,7 +78,7 @@ func (ah *APIHandler) deleteDomain(w http.ResponseWriter, r *http.Request) {
domainId, err := uuid.Parse(domainIdStr)
if err != nil {
RespondError(w, model.BadRequest(fmt.Errorf("invalid domain id")), nil)
RespondError(w, basemodel.BadRequest(fmt.Errorf("invalid domain id")), nil)
return
}

View File

@@ -9,6 +9,7 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -71,12 +72,12 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
var l model.License
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
if l.Key == "" {
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
RespondError(w, basemodel.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
license, apiError := ah.LM().Activate(r.Context(), l.Key)
@@ -100,20 +101,20 @@ func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/checkout", r.Body)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
licenseResp, err := hClient.Do(req)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
// decode response body
var resp checkoutResponse
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
@@ -124,7 +125,7 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
licenseKey := r.URL.Query().Get("licenseKey")
if licenseKey == "" {
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
RespondError(w, basemodel.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
@@ -133,20 +134,20 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("GET", billingURL, nil)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
billingResp, err := hClient.Do(req)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
// decode response body
var billingResponse billingDetails
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
@@ -251,20 +252,20 @@ func (ah *APIHandler) portalSession(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/portal", r.Body)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
licenseResp, err := hClient.Do(req)
if err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}
// decode response body
var resp checkoutResponse
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
RespondError(w, model.InternalError(err), nil)
RespondError(w, basemodel.InternalError(err), nil)
return
}

View File

@@ -31,13 +31,13 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
req := model.CreatePATRequestBody{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -49,7 +49,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}
err = validatePATRequest(pat)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
@@ -66,7 +66,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}
zap.L().Info("Got Create PAT request", zap.Any("pat", pat))
var apierr basemodel.BaseApiError
var apierr *basemodel.ApiError
if pat, apierr = ah.AppDao().CreatePAT(ctx, pat); apierr != nil {
RespondError(w, apierr, nil)
return
@@ -93,14 +93,14 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
req := model.PAT{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -108,7 +108,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
err = validatePATRequest(req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
RespondError(w, basemodel.BadRequest(err), nil)
return
}
@@ -116,7 +116,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now().Unix()
zap.L().Info("Got Update PAT request", zap.Any("pat", req))
var apierr basemodel.BaseApiError
var apierr *basemodel.ApiError
if apierr = ah.AppDao().UpdatePAT(ctx, req, id); apierr != nil {
RespondError(w, apierr, nil)
return
@@ -129,8 +129,8 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -149,8 +149,8 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
Err: err,
}, nil)
return

View File

@@ -7,6 +7,6 @@ import (
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
func RespondError(w http.ResponseWriter, apiErr basemodel.BaseApiError, data interface{}) {
baseapp.RespondError(w, apiErr, data)
func RespondError(w http.ResponseWriter, apiErr *basemodel.ApiError, data interface{}) {
baseapp.RespondError(w, apiErr)
}

View File

@@ -4,7 +4,6 @@ import (
"net/http"
"go.signoz.io/signoz/ee/query-service/app/db"
"go.signoz.io/signoz/ee/query-service/model"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
@@ -19,7 +18,7 @@ func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
}
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, "Error reading params")
return
}

View File

@@ -21,7 +21,7 @@ import (
// GetMetricResultEE runs the query and returns list of time series
func (r *ClickhouseReader) GetMetricResultEE(ctx context.Context, query string) ([]*basemodel.Series, string, error) {
defer utils.Elapsed("GetMetricResult", nil)()
defer utils.Elapsed("GetMetricResult")()
zap.L().Info("Executing metric result query: ", zap.String("query", query))
var hash string

View File

@@ -4,11 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
_ "net/http/pprof" // http profiler
"os"
"regexp"
@@ -28,8 +28,6 @@ import (
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@@ -43,7 +41,6 @@ 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/app/preferences"
"go.signoz.io/signoz/pkg/query-service/cache"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
@@ -67,6 +64,7 @@ type ServerOptions struct {
// alert specific params
DisableRules bool
RuleRepoURL string
PreferDelta bool
PreferSpanMetrics bool
MaxIdleConns int
MaxOpenConns int
@@ -113,10 +111,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
baseexplorer.InitWithDSN(baseconst.RELATIONAL_DATASOURCE_PATH)
if err := preferences.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err
}
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
if err != nil {
@@ -125,13 +119,33 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
localDB.SetMaxOpenConns(10)
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err
gatewayFeature := basemodel.Feature{
Name: "GATEWAY",
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
}
//Activate this feature if the url is not empty
var gatewayProxy *httputil.ReverseProxy
if serverOptions.GatewayUrl == "" {
gatewayFeature.Active = false
gatewayProxy, err = gateway.NewNoopProxy()
if err != nil {
return nil, err
}
} else {
zap.L().Info("Enabling gateway feature flag ...")
gatewayFeature.Active = true
gatewayProxy, err = gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err
}
}
// initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB)
lm, err := licensepkg.StartManager("sqlite", localDB, gatewayFeature)
if err != nil {
return nil, err
}
@@ -180,13 +194,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
go func() {
err = migrate.ClickHouseMigrate(reader.GetConn(), serverOptions.Cluster)
if err != nil {
zap.L().Error("error while running clickhouse migrations", zap.Error(err))
}
}()
// initiate opamp
_, err = opAmpModel.InitDB(localDB)
if err != nil {
@@ -249,6 +256,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
SkipConfig: skipConfig,
PreferDelta: serverOptions.PreferDelta,
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
MaxIdleConns: serverOptions.MaxIdleConns,
MaxOpenConns: serverOptions.MaxOpenConns,
@@ -334,17 +342,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
// add auth middleware
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
user, err := auth.GetUserFromRequest(r, apiHandler)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
return auth.GetUserFromRequest(r, apiHandler)
}
am := baseapp.NewAuthMiddleware(getUserFromRequest)
@@ -736,7 +734,6 @@ func makeRulesManager(
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
EvalDelay: baseconst.GetEvalDelay(),
}
// create Manager

View File

@@ -21,24 +21,24 @@ type ModelDao interface {
DB() *sqlx.DB
// auth methods
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
CanUsePassword(ctx context.Context, email string) (bool, *basemodel.ApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr *basemodel.ApiError)
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
// org domain (auth domains) CRUD ops
ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError)
GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError)
CreateDomain(ctx context.Context, d *model.OrgDomain) basemodel.BaseApiError
UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, *basemodel.ApiError)
GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, *basemodel.ApiError)
CreateDomain(ctx context.Context, d *model.OrgDomain) *basemodel.ApiError
UpdateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError
DeleteDomain(ctx context.Context, id uuid.UUID) *basemodel.ApiError
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, *basemodel.ApiError)
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *basemodel.ApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) *basemodel.ApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, *basemodel.ApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) *basemodel.ApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, *basemodel.ApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, *basemodel.ApiError)
ListPATs(ctx context.Context) ([]model.PAT, *basemodel.ApiError)
RevokePAT(ctx context.Context, id string, userID string) *basemodel.ApiError
}

View File

@@ -17,22 +17,19 @@ import (
"go.uber.org/zap"
)
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, *basemodel.ApiError) {
// get auth domain from email domain
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
zap.L().Error("failed to get domain from email", zap.Error(apierr))
return nil, model.InternalErrorStr("failed to get domain from email")
}
if domain == nil {
zap.L().Error("email domain does not match any authenticated domain", zap.String("email", email))
return nil, model.InternalErrorStr("email domain does not match any authenticated domain")
return nil, basemodel.InternalError(fmt.Errorf("failed to get domain from email"))
}
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
if err != nil {
zap.L().Error("failed to generate password hash when registering a user via SSO redirect", zap.Error(err))
return nil, model.InternalErrorStr("failed to generate password hash")
return nil, basemodel.InternalError(fmt.Errorf("failed to generate password hash"))
}
group, apiErr := m.GetGroupByName(ctx, baseconst.ViewerGroup)
@@ -64,12 +61,12 @@ 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) (redirectURL string, apierr *basemodel.ApiError) {
userPayload, apierr := m.GetUserByEmail(ctx, email)
if !apierr.IsNil() {
zap.L().Error("failed to get user with email received from auth provider", zap.String("error", apierr.Error()))
return "", model.BadRequestStr("invalid user email received from the auth provider")
return "", basemodel.BadRequest(fmt.Errorf("invalid user email received from the auth provider"))
}
user := &basemodel.User{}
@@ -88,7 +85,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
tokenStore, err := baseauth.GenerateJWTForUser(user)
if err != nil {
zap.L().Error("failed to generate token for SSO login user", zap.Error(err))
return "", model.InternalErrorStr("failed to generate token for the user")
return "", basemodel.InternalError(fmt.Errorf("failed to generate token for the user"))
}
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
@@ -98,7 +95,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
tokenStore.RefreshJwt), nil
}
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError) {
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, *basemodel.ApiError) {
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
return false, apierr
@@ -113,7 +110,7 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, base
}
if userPayload.Role != baseconst.AdminGroup {
return false, model.BadRequest(fmt.Errorf("auth method not supported"))
return false, basemodel.BadRequest(fmt.Errorf("auth method not supported"))
}
}
@@ -123,7 +120,7 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, base
// PrecheckLogin is called when the login or signup page is loaded
// to check sso login is to be prompted
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) {
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, *basemodel.ApiError) {
// assume user is valid unless proven otherwise
resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false}
@@ -147,7 +144,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
ssoAvailable = false
default:
zap.L().Error("feature check failed", zap.String("featureKey", model.SSO), zap.Error(err))
return resp, model.BadRequestStr(err.Error())
return resp, &basemodel.ApiError{Err: err, Typ: basemodel.ErrorBadData}
}
}
@@ -180,7 +177,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
siteUrl, err := url.Parse(escapedUrl)
if err != nil {
zap.L().Error("failed to parse referer", zap.Error(err))
return resp, model.InternalError(fmt.Errorf("failed to generate login request"))
return resp, basemodel.InternalError(fmt.Errorf("failed to generate login request"))
}
// build Idp URL that will authenticat the user
@@ -189,7 +186,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
if err != nil {
zap.L().Error("failed to prepare saml request for domain", zap.String("domain", orgDomain.Name), zap.Error(err))
return resp, model.InternalError(err)
return resp, basemodel.InternalError(err)
}
// set SSO to true, as the url is generated correctly

View File

@@ -76,47 +76,47 @@ func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url
}
// GetDomainByName returns org domain for a given domain name
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, basemodel.BaseApiError) {
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, *basemodel.ApiError) {
stored := StoredDomain{}
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE name=$1 LIMIT 1`, name)
if err != nil {
if err == sql.ErrNoRows {
return nil, model.BadRequest(fmt.Errorf("invalid domain name"))
return nil, basemodel.BadRequest(fmt.Errorf("invalid domain name"))
}
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
return domain, nil
}
// GetDomain returns org domain for a given domain id
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError) {
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, *basemodel.ApiError) {
stored := StoredDomain{}
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE id=$1 LIMIT 1`, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, model.BadRequest(fmt.Errorf("invalid domain id"))
return nil, basemodel.BadRequest(fmt.Errorf("invalid domain id"))
}
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
return domain, nil
}
// ListDomains gets the list of auth domains by org id
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError) {
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, *basemodel.ApiError) {
domains := []model.OrgDomain{}
stored := []StoredDomain{}
@@ -126,7 +126,7 @@ func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDo
if err == sql.ErrNoRows {
return []model.OrgDomain{}, nil
}
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
for _, s := range stored {
@@ -141,20 +141,20 @@ func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDo
}
// CreateDomain creates a new auth domain
func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError {
if domain.Id == uuid.Nil {
domain.Id = uuid.New()
}
if domain.OrgId == "" || domain.Name == "" {
return model.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgId, Name "))
return basemodel.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgId, Name "))
}
configJson, err := json.Marshal(domain)
if err != nil {
zap.L().Error("failed to unmarshal domain config", zap.Error(err))
return model.InternalError(fmt.Errorf("domain creation failed"))
return basemodel.InternalError(fmt.Errorf("domain creation failed"))
}
_, err = m.DB().ExecContext(ctx,
@@ -168,24 +168,24 @@ func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) ba
if err != nil {
zap.L().Error("failed to insert domain in db", zap.Error(err))
return model.InternalError(fmt.Errorf("domain creation failed"))
return basemodel.InternalError(fmt.Errorf("domain creation failed"))
}
return nil
}
// UpdateDomain updates stored config params for a domain
func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError {
if domain.Id == uuid.Nil {
zap.L().Error("domain update failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
return model.InternalError(fmt.Errorf("domain update failed"))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
}
configJson, err := json.Marshal(domain)
if err != nil {
zap.L().Error("domain update failed", zap.Error(err))
return model.InternalError(fmt.Errorf("domain update failed"))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
}
_, err = m.DB().ExecContext(ctx,
@@ -196,18 +196,18 @@ func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) ba
if err != nil {
zap.L().Error("domain update failed", zap.Error(err))
return model.InternalError(fmt.Errorf("domain update failed"))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
}
return nil
}
// DeleteDomain deletes an org domain
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError {
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) *basemodel.ApiError {
if id == uuid.Nil {
zap.L().Error("domain delete failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
return model.InternalError(fmt.Errorf("domain delete failed"))
return basemodel.InternalError(fmt.Errorf("domain delete failed"))
}
_, err := m.DB().ExecContext(ctx,
@@ -216,21 +216,21 @@ func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.Bas
if err != nil {
zap.L().Error("domain delete failed", zap.Error(err))
return model.InternalError(fmt.Errorf("domain delete failed"))
return basemodel.InternalError(fmt.Errorf("domain delete failed"))
}
return nil
}
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError) {
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, *basemodel.ApiError) {
if email == "" {
return nil, model.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
return nil, basemodel.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
}
components := strings.Split(email, "@")
if len(components) < 2 {
return nil, model.BadRequest(fmt.Errorf("invalid email address"))
return nil, basemodel.BadRequest(fmt.Errorf("invalid email address"))
}
parsedDomain := components[1]
@@ -242,12 +242,12 @@ func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.O
if err == sql.ErrNoRows {
return nil, nil
}
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
return domain, nil
}

View File

@@ -11,7 +11,7 @@ import (
"go.uber.org/zap"
)
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) {
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *basemodel.ApiError) {
result, err := m.DB().ExecContext(ctx,
"INSERT INTO personal_access_tokens (user_id, token, role, name, created_at, expires_at, updated_at, updated_by_user_id, last_used, revoked) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
p.UserID,
@@ -27,12 +27,12 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
)
if err != nil {
zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
return model.PAT{}, basemodel.InternalError(fmt.Errorf("PAT insertion failed"))
}
id, err := result.LastInsertId()
if err != nil {
zap.L().Error("Failed to get last inserted id, err: %v", zap.Error(err))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
return model.PAT{}, basemodel.InternalError(fmt.Errorf("PAT insertion failed"))
}
p.Id = strconv.Itoa(int(id))
createdByUser, _ := m.GetUser(ctx, p.UserID)
@@ -53,7 +53,7 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem
return p, nil
}
func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError {
func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) *basemodel.ApiError {
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET role=$1, name=$2, updated_at=$3, updated_by_user_id=$4 WHERE id=$5 and revoked=false;",
p.Role,
@@ -63,29 +63,29 @@ func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) basemo
id)
if err != nil {
zap.L().Error("Failed to update PAT in db, err: %v", zap.Error(err))
return model.InternalError(fmt.Errorf("PAT update failed"))
return basemodel.InternalError(fmt.Errorf("PAT update failed"))
}
return nil
}
func (m *modelDao) UpdatePATLastUsed(ctx context.Context, token string, lastUsed int64) basemodel.BaseApiError {
func (m *modelDao) UpdatePATLastUsed(ctx context.Context, token string, lastUsed int64) *basemodel.ApiError {
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET last_used=$1 WHERE token=$2 and revoked=false;",
lastUsed,
token)
if err != nil {
zap.L().Error("Failed to update PAT last used in db, err: %v", zap.Error(err))
return model.InternalError(fmt.Errorf("PAT last used update failed"))
return basemodel.InternalError(fmt.Errorf("PAT last used update failed"))
}
return nil
}
func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError) {
func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, *basemodel.ApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, "SELECT * FROM personal_access_tokens WHERE revoked=false ORDER by updated_at DESC;"); err != nil {
zap.L().Error("Failed to fetch PATs err: %v", zap.Error(err))
return nil, model.InternalError(fmt.Errorf("failed to fetch PATs"))
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PATs"))
}
for i := range pats {
createdByUser, _ := m.GetUser(ctx, pats[i].UserID)
@@ -123,28 +123,28 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApi
return pats, nil
}
func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError {
func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) *basemodel.ApiError {
updatedAt := time.Now().Unix()
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET revoked=true, updated_by_user_id = $1, updated_at=$2 WHERE id=$3",
userID, updatedAt, id)
if err != nil {
zap.L().Error("Failed to revoke PAT in db, err: %v", zap.Error(err))
return model.InternalError(fmt.Errorf("PAT revoke failed"))
return basemodel.InternalError(fmt.Errorf("PAT revoke failed"))
}
return nil
}
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) {
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, *basemodel.ApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE token=? and revoked=false;`, token); err != nil {
return nil, model.InternalError(fmt.Errorf("failed to fetch PAT"))
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PAT"))
}
if len(pats) != 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf("found zero or multiple PATs with same token, %s", token),
}
}
@@ -152,16 +152,16 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemo
return &pats[0], nil
}
func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) {
func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, *basemodel.ApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE id=? and revoked=false;`, id); err != nil {
return nil, model.InternalError(fmt.Errorf("failed to fetch PAT"))
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PAT"))
}
if len(pats) != 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf("found zero or multiple PATs with same token"),
}
}
@@ -170,7 +170,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basem
}
// deprecated
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, *basemodel.ApiError) {
users := []basemodel.UserPayload{}
query := `SELECT
@@ -186,12 +186,12 @@ func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.U
WHERE u.id = p.user_id and p.token=? and p.expires_at >= strftime('%s', 'now');`
if err := m.DB().Select(&users, query, token); err != nil {
return nil, model.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))
}
if len(users) != 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf("found zero or multiple users with same PAT token"),
}
}

View File

@@ -5,5 +5,5 @@ import (
)
func NewNoopProxy() (*httputil.ReverseProxy, error) {
return &httputil.ReverseProxy{}, nil
return nil, nil
}

View File

@@ -13,6 +13,7 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
var C *Client
@@ -37,7 +38,7 @@ func init() {
}
// ActivateLicense sends key to license.signoz.io and gets activation data
func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError) {
func ActivateLicense(key, siteId string) (*ActivationResponse, *basemodel.ApiError) {
licenseReq := map[string]string{
"key": key,
"siteId": siteId,
@@ -48,13 +49,13 @@ func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError)
if err != nil {
zap.L().Error("failed to connect to license.signoz.io", zap.Error(err))
return nil, model.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
return nil, basemodel.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
}
httpBody, err := io.ReadAll(httpResponse.Body)
if err != nil {
zap.L().Error("failed to read activation response from license.signoz.io", zap.Error(err))
return nil, model.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
return nil, basemodel.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
}
defer httpResponse.Body.Close()
@@ -64,22 +65,22 @@ func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError)
err = json.Unmarshal(httpBody, &result)
if err != nil {
zap.L().Error("failed to marshal activation response from license.signoz.io", zap.Error(err))
return nil, model.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
return nil, basemodel.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
}
switch httpResponse.StatusCode {
case 200, 201:
return result.Data, nil
case 400, 401:
return nil, model.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
return nil, basemodel.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
default:
return nil, model.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
return nil, basemodel.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
}
}
// ValidateLicense validates the license key
func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError) {
func ValidateLicense(activationId string) (*ActivationResponse, *basemodel.ApiError) {
validReq := map[string]string{
"activationId": activationId,
}
@@ -88,12 +89,12 @@ func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError)
response, err := http.Post(C.Prefix+"/licenses/validate", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
return nil, basemodel.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
return nil, basemodel.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
}
defer response.Body.Close()
@@ -103,14 +104,14 @@ func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError)
a := ActivationResult{}
err = json.Unmarshal(body, &a)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
return nil, basemodel.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
}
return a.Data, nil
case 400, 401:
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
return nil, basemodel.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
"bad request error received from license.signoz.io"))
default:
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
return nil, basemodel.InternalError(errors.Wrap(fmt.Errorf(string(body)),
"internal error received from license.signoz.io"))
}
@@ -127,21 +128,21 @@ func NewPostRequestWithCtx(ctx context.Context, url string, contentType string,
}
// SendUsage reports the usage of signoz to license server
func SendUsage(ctx context.Context, usage model.UsagePayload) *model.ApiError {
func SendUsage(ctx context.Context, usage model.UsagePayload) *basemodel.ApiError {
reqString, _ := json.Marshal(usage)
req, err := NewPostRequestWithCtx(ctx, C.Prefix+"/usage", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
return model.BadRequest(errors.Wrap(err, "unable to create http request"))
return basemodel.BadRequest(errors.Wrap(err, "unable to create http request"))
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
return basemodel.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
}
body, err := io.ReadAll(res.Body)
if err != nil {
return model.BadRequest(errors.Wrap(err, "failed to read usage response from license.signoz.io"))
return basemodel.BadRequest(errors.Wrap(err, "failed to read usage response from license.signoz.io"))
}
defer res.Body.Close()
@@ -150,10 +151,10 @@ func SendUsage(ctx context.Context, usage model.UsagePayload) *model.ApiError {
case 200, 201:
return nil
case 400, 401:
return model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
return basemodel.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
"bad request error received from license.signoz.io"))
default:
return model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
return basemodel.InternalError(errors.Wrap(fmt.Errorf(string(body)),
"internal error received from license.signoz.io"))
}
}

View File

@@ -137,11 +137,11 @@ func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
return nil
}
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *basemodel.ApiError) {
licenses, err := lm.repo.GetLicenses(ctx)
if err != nil {
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
for _, l := range licenses {
@@ -212,8 +212,8 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
response, apiError := validate.ValidateLicense(lm.activeLicense.ActivationId)
if apiError != nil {
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
return apiError.Err
zap.L().Error("failed to validate license", zap.Any("apiError", apiError))
return apiError
}
if response.PlanDetails == lm.activeLicense.PlanDetails {
@@ -255,7 +255,7 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
}
// Activate activates a license key with signoz server
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *basemodel.ApiError) {
defer func() {
if errResponse != nil {
userEmail, err := auth.GetEmailFromJwt(ctx)
@@ -268,7 +268,7 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
response, apiError := validate.ActivateLicense(key, "")
if apiError != nil {
zap.L().Error("failed to activate license", zap.Error(apiError.Err))
zap.L().Error("failed to activate license", zap.Any("apiError", apiError))
return nil, apiError
}
@@ -283,14 +283,14 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
// store the license before activating it
err = lm.repo.InsertLicense(ctx, l)
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, model.InternalError(err)
return nil, basemodel.InternalError(err)
}
// license is valid, activate it

View File

@@ -89,6 +89,7 @@ func main() {
var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool
var preferDelta bool
var preferSpanMetrics bool
var maxIdleConns int
@@ -99,13 +100,14 @@ func main() {
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
flag.BoolVar(&preferDelta, "prefer-delta", false, "(prefer delta over cumulative metrics)")
flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)")
flag.IntVar(&maxIdleConns, "max-idle-conns", 50, "(number of connections to maintain in the pool.)")
flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)")
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)")
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
@@ -123,6 +125,7 @@ func main() {
HTTPHostPort: baseconst.HTTPHostPort,
PromConfigPath: promConfigPath,
SkipTopLvlOpsPath: skipTopLvlOpsPath,
PreferDelta: preferDelta,
PreferSpanMetrics: preferSpanMetrics,
PrivateHostPort: baseconst.PrivateHostPort,
DisableRules: disableRules,

View File

@@ -1,107 +1,5 @@
package model
import (
"fmt"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
type ApiError struct {
Typ basemodel.ErrorType
Err error
}
func (a *ApiError) Type() basemodel.ErrorType {
return a.Typ
}
func (a *ApiError) ToError() error {
if a != nil {
return a.Err
}
return a.Err
}
func (a *ApiError) Error() string {
return a.Err.Error()
}
func (a *ApiError) IsNil() bool {
return a == nil || a.Err == nil
}
// NewApiError returns a ApiError object of given type
func NewApiError(typ basemodel.ErrorType, err error) *ApiError {
return &ApiError{
Typ: typ,
Err: err,
}
}
// BadRequest returns a ApiError object of bad request
func BadRequest(err error) *ApiError {
return &ApiError{
Typ: basemodel.ErrorBadData,
Err: err,
}
}
// BadRequestStr returns a ApiError object of bad request for string input
func BadRequestStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorBadData,
Err: fmt.Errorf(s),
}
}
// InternalError returns a ApiError object of internal type
func InternalError(err error) *ApiError {
return &ApiError{
Typ: basemodel.ErrorInternal,
Err: err,
}
}
// InternalErrorStr returns a ApiError object of internal type for string input
func InternalErrorStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf(s),
}
}
var (
ErrorNone basemodel.ErrorType = ""
ErrorTimeout basemodel.ErrorType = "timeout"
ErrorCanceled basemodel.ErrorType = "canceled"
ErrorExec basemodel.ErrorType = "execution"
ErrorBadData basemodel.ErrorType = "bad_data"
ErrorInternal basemodel.ErrorType = "internal"
ErrorUnavailable basemodel.ErrorType = "unavailable"
ErrorNotFound basemodel.ErrorType = "not_found"
ErrorNotImplemented basemodel.ErrorType = "not_implemented"
ErrorUnauthorized basemodel.ErrorType = "unauthorized"
ErrorForbidden basemodel.ErrorType = "forbidden"
ErrorConflict basemodel.ErrorType = "conflict"
ErrorStreamingNotSupported basemodel.ErrorType = "streaming is not supported"
)
func init() {
ErrorNone = basemodel.ErrorNone
ErrorTimeout = basemodel.ErrorTimeout
ErrorCanceled = basemodel.ErrorCanceled
ErrorExec = basemodel.ErrorExec
ErrorBadData = basemodel.ErrorBadData
ErrorInternal = basemodel.ErrorInternal
ErrorUnavailable = basemodel.ErrorUnavailable
ErrorNotFound = basemodel.ErrorNotFound
ErrorNotImplemented = basemodel.ErrorNotImplemented
ErrorUnauthorized = basemodel.ErrorUnauthorized
ErrorForbidden = basemodel.ErrorForbidden
ErrorConflict = basemodel.ErrorConflict
ErrorStreamingNotSupported = basemodel.ErrorStreamingNotSupported
}
type ErrUnsupportedAuth struct{}
func (errUnsupportedAuth ErrUnsupportedAuth) Error() string {

View File

@@ -11,7 +11,6 @@ const Enterprise = "ENTERPRISE_PLAN"
const DisableUpsell = "DISABLE_UPSELL"
const Onboarding = "ONBOARDING"
const ChatSupport = "CHAT_SUPPORT"
const Gateway = "GATEWAY"
var BasicPlan = basemodel.FeatureSet{
basemodel.Feature{
@@ -112,13 +111,6 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
@@ -213,13 +205,6 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -328,11 +313,4 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -190,7 +190,7 @@ func (lm *Manager) UploadUsageWithExponentalBackOff(ctx context.Context, payload
} else if apiErr != nil {
// sleeping for exponential backoff
sleepDuration := RetryInterval * time.Duration(i)
zap.L().Error("failed to upload snapshot retrying after %v secs : %v", zap.Duration("sleepDuration", sleepDuration), zap.Error(apiErr.Err))
zap.L().Error("failed to upload snapshot retrying after %v secs : %v", zap.Duration("sleepDuration", sleepDuration), zap.Any("apiErr", apiErr))
time.Sleep(sleepDuration)
} else {
break

View File

@@ -9,7 +9,6 @@ const config: Config.InitialOptions = {
modulePathIgnorePatterns: ['dist'],
moduleNameMapper: {
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
},
globals: {
extensionsToTreatAsEsm: ['.ts'],

View File

@@ -88,7 +88,6 @@
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"posthog-js": "1.142.1",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",
@@ -110,8 +109,6 @@
"react-syntax-highlighter": "15.5.0",
"react-use": "^17.3.2",
"react-virtuoso": "4.0.3",
"overlayscrollbars-react": "^0.5.6",
"overlayscrollbars": "^2.8.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"rehype-raw": "7.0.0",

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="2.94" y1="3.74" x2="8.67" y2="3.74" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="b" x1="9.13" y1="3.79" x2="14.85" y2="3.79" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="c" x1=".01" y1="9.12" x2="5.73" y2="9.12" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="d" x1="6.18" y1="9.08" x2="11.9" y2="9.08" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="e" x1="12.35" y1="9.13" x2="18.08" y2="9.13" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="f" x1="2.87" y1="14.56" x2="8.6" y2="14.56" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="g" x1="9.05" y1="14.6" x2="14.78" y2="14.6" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient></defs><path fill="url(#a)" d="M5.8 1.22l-2.86.53v3.9l2.86.61 2.87-1.15V2.2L5.8 1.22z"/><path d="M5.91 6.2l2.62-1.06A.2.2 0 008.65 5V2.36a.21.21 0 00-.13-.18l-2.65-.9h-.12l-2.6.48a.2.2 0 00-.15.18v3.53a.19.19 0 00.15.19l2.63.55a.32.32 0 00.13-.01z" fill="none"/><path d="M2.94 1.75v3.9l2.89.61v-5zm1.22 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2l.93-.16z" fill="#341a6e"/><path fill="url(#b)" d="M11.99 1.27l-2.86.53v3.9l2.86.61 2.86-1.16v-2.9l-2.86-.98z"/><path d="M9.13 1.8v3.9l2.87.61v-5zm1.21 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2.05l.93-.17z" fill="#341a6e"/><path fill="url(#c)" d="M2.87 6.6l-2.86.53v3.9l2.86.61 2.87-1.15V7.58L2.87 6.6z"/><path d="M0 7.13V11l2.89.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.27.26l-.93-.15V7.38l.93-.16z" fill="#341a6e"/><path fill="url(#d)" d="M9.04 6.56l-2.86.53v3.9l2.86.62 2.86-1.16V7.54l-2.86-.98z"/><path d="M6.18 7.09V11l2.88.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.34l.93-.16z" fill="#341a6e"/><path fill="url(#e)" d="M15.21 6.61l-2.86.53v3.9l2.86.61 2.87-1.15V7.59l-2.87-.98z"/><path d="M12.35 7.14V11l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.39l.93-.16z" fill="#341a6e"/><path fill="url(#f)" d="M5.73 12.04l-2.86.52v3.9l2.86.62 2.87-1.16v-2.9l-2.87-.98z"/><path d="M5.84 17l2.61-1a.18.18 0 00.12-.18v-2.6a.2.2 0 00-.13-.22l-2.64-.9a.17.17 0 00-.12 0l-2.6.47a.19.19 0 00-.16.19v3.54a.19.19 0 00.15.19L5.7 17a.23.23 0 00.14 0z" fill="none"/><path d="M2.87 12.56v3.9l2.89.62V12zm1.22 3.61L3.28 16v-3l.81-.14zm1.26.23l-.93-.15v-3.44l.93-.16z" fill="#341a6e"/><path fill="url(#g)" d="M11.91 12.08l-2.86.53v3.9l2.86.61 2.87-1.15v-2.91l-2.87-.98z"/><path d="M9.05 12.61v3.9l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15v-3.43l.93-.16z" fill="#341a6e"/></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="b" x1="4.4" y1="11.48" x2="4.37" y2="7.53" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="c" x1="10.13" y1="15.45" x2="10.13" y2="11.9" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="d" x1="14.18" y1="11.15" x2="14.18" y2="7.38" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><radialGradient id="a" cx="13428.81" cy="3518.86" r="56.67" gradientTransform="matrix(.15 0 0 .15 -2005.33 -518.83)" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#5ea0ef"/><stop offset="1" stop-color="#0078d4"/></radialGradient></defs><path d="M14.21 15.72A8.5 8.5 0 013.79 2.28l.09-.06a8.5 8.5 0 0110.33 13.5" fill="url(#a)"/><path d="M6.69 7.23a13 13 0 018.91-3.58 8.47 8.47 0 00-1.49-1.44 14.34 14.34 0 00-4.69 1.1 12.54 12.54 0 00-4.08 2.82 2.76 2.76 0 011.35 1.1zM2.48 10.65a17.86 17.86 0 00-.83 2.62 7.82 7.82 0 00.62.92c.18.23.35.44.55.65a17.94 17.94 0 011.08-3.47 2.76 2.76 0 01-1.42-.72z" fill="#fff" opacity=".6"/><path d="M3.46 6.11a12 12 0 01-.69-2.94 8.15 8.15 0 00-1.1 1.45A12.69 12.69 0 002.24 7a2.69 2.69 0 011.22-.89z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><path d="M8.36 13.67a1.77 1.77 0 01.54-1.27 11.88 11.88 0 01-2.53-1.86 2.74 2.74 0 01-1.49.83 13.1 13.1 0 001.45 1.28 12.12 12.12 0 002.05 1.25 1.79 1.79 0 01-.02-.23zM14.66 13.88a12 12 0 01-2.76-.32.41.41 0 010 .11 1.75 1.75 0 01-.51 1.24 13.69 13.69 0 003.42.24A8.21 8.21 0 0016 13.81a11.5 11.5 0 01-1.34.07z" fill="#f2f2f2" opacity=".55"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/><path d="M12.32 8.93a1.83 1.83 0 01.61-1 25.5 25.5 0 01-4.46-4.14 16.91 16.91 0 01-2-2.92 7.64 7.64 0 00-1.09.42 18.14 18.14 0 002.15 3.18 26.44 26.44 0 004.79 4.46z" fill="#f2f2f2" opacity=".7"/><circle cx="14.18" cy="9.27" r="1.89" fill="url(#d)"/><path d="M17.35 10.54l-.35-.17-.3-.16h-.06l-.26-.21h-.07L16 9.8a1.76 1.76 0 01-.64.92c.12.08.25.15.38.22l.08.05.35.19.86.45a8.63 8.63 0 00.29-1.11z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="b27f1ad0-7d11-4247-9da3-91bce6211f32" x1="8.798" y1="8.703" x2="14.683" y2="8.703" gradientUnits="userSpaceOnUse"><stop offset="0.001" stop-color="#773adc" /><stop offset="1" stop-color="#552f99" /></linearGradient><linearGradient id="b2f92112-4ca9-4b17-a019-c9f26c1a4a8f" x1="5.764" y1="3.777" x2="5.764" y2="13.78" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a67af4" /><stop offset="0.999" stop-color="#773adc" /></linearGradient></defs><g id="b8a0486a-5501-4d92-b540-a766c4b3b548"><g><g><g><path d="M16.932,11.578a8.448,8.448,0,0,1-7.95,5.59,8.15,8.15,0,0,1-2.33-.33,2.133,2.133,0,0,0,.18-.83c.01,0,.03.01.04.01a7.422,7.422,0,0,0,2.11.3,7.646,7.646,0,0,0,6.85-4.28l.01-.01Z" fill="#32bedd" /><path d="M3.582,14.068a2.025,2.025,0,0,0-.64.56,8.6,8.6,0,0,1-1.67-2.44l1.04.23v.26a.6.6,0,0,0,.47.59l.14.03a6.136,6.136,0,0,0,.62.73Z" fill="#32bedd" /><path d="M12.352.958a2.28,2.28,0,0,0-.27.81c-.02-.01-.05-.02-.07-.03a7.479,7.479,0,0,0-3.03-.63,7.643,7.643,0,0,0-5.9,2.8l-.29.06a.6.6,0,0,0-.48.58v.46l-1.02.19A8.454,8.454,0,0,1,8.982.268,8.6,8.6,0,0,1,12.352.958Z" fill="#32bedd" /><path d="M16.872,5.7l-1.09-.38a6.6,6.6,0,0,0-.72-1.16c-.02-.03-.04-.05-.05-.07a2.083,2.083,0,0,0,.72-.45A7.81,7.81,0,0,1,16.872,5.7Z" fill="#32bedd" /><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="#fff" /><g><g id="e918f286-5032-4942-ad29-ea17e6f1cc90"><path d="M1.1,5.668l1.21-.23v6.55l-1.23-.27-.99-.22a.111.111,0,0,1-.09-.12v-5.4a.12.12,0,0,1,.09-.12Z" fill="#a67af4" /></g><g><g id="a47a99dd-4d47-4c70-8c42-c5ac274ce496"><g><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="url(#b27f1ad0-7d11-4247-9da3-91bce6211f32)" /><path d="M8.586,3.3,2.878,4.378a.177.177,0,0,0-.14.175V12.68a.177.177,0,0,0,.137.174L8.581,14.1a.176.176,0,0,0,.21-.174V3.478A.175.175,0,0,0,8.619,3.3Z" fill="url(#b2f92112-4ca9-4b17-a019-c9f26c1a4a8f)" /></g></g><polygon points="5.948 4.921 5.948 12.483 7.934 12.814 7.934 4.564 5.948 4.921" fill="#b796f9" opacity="0.5" /><polygon points="3.509 5.329 3.509 11.954 5.238 12.317 5.238 5.031 3.509 5.329" fill="#b796f9" opacity="0.5" /></g></g></g><path d="M16,2.048a1.755,1.755,0,1,1-1.76-1.76A1.756,1.756,0,0,1,16,2.048Z" fill="#32bedd" /><circle cx="4.65" cy="15.973" r="1.759" fill="#32bedd" /></g><path d="M18,6.689v3.844a.222.222,0,0,1-.133.2l-.766.316-3.07,1.268-.011,0a.126.126,0,0,1-.038,0,.1.1,0,0,1-.1-.1V5.234a.1.1,0,0,1,.054-.088l0,0,.019,0a.031.031,0,0,1,.019,0,.055.055,0,0,1,.034.008l.011,0,.012,0L17.05,6.2l.8.282A.213.213,0,0,1,18,6.689Z" fill="#773adc" /><path d="M13.959,5.14l-3.8.715a.118.118,0,0,0-.093.117v5.409a.118.118,0,0,0,.091.116l3.8.831a.115.115,0,0,0,.137-.09.109.109,0,0,0,0-.026V5.256a.117.117,0,0,0-.115-.118A.082.082,0,0,0,13.959,5.14Z" fill="#a67af4" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="-175.993" y1="-343.723" x2="-175.993" y2="-359.232" gradientTransform="matrix(1.156 0 0 1.029 212.573 370.548)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fea11b"/><stop offset=".284" stop-color="#fea51a"/><stop offset=".547" stop-color="#feb018"/><stop offset=".8" stop-color="#ffc314"/><stop offset="1" stop-color="#ffd70f"/></linearGradient></defs><path d="M5.54 13.105l-.586.588a.267.267 0 01-.377 0L.223 9.353a.533.533 0 010-.755l.588-.59 4.732 4.718a.267.267 0 010 .378z" fill="#50e6ff"/><path d="M4.863 4.305l.59.588a.267.267 0 010 .378L.806 9.932l-.59-.589a.533.533 0 01-.001-.754l4.273-4.285a.267.267 0 01.376 0z" fill="#1490df"/><path d="M17.19 8.012l.588.59a.533.533 0 01-.001.754l-4.354 4.34a.267.267 0 01-.377 0l-.586-.587a.267.267 0 010-.377l4.732-4.718z" fill="#50e6ff"/><path d="M17.782 9.34l-.59.589-4.648-4.662a.267.267 0 010-.377l.59-.588a.267.267 0 01.378 0l4.273 4.286a.533.533 0 010 .753z" fill="#1490df"/><path d="M8.459 9.9H4.87a.193.193 0 01-.2-.181.166.166 0 01.018-.075L8.991 1.13a.206.206 0 01.186-.106h4.245a.193.193 0 01.2.181.165.165 0 01-.035.1L8.534 7.966h4.928a.193.193 0 01.2.181.176.176 0 01-.052.122l-8.189 8.519c-.077.046-.624.5-.356-.189z" fill="url(#a)"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><radialGradient id="b" cx="9.36" cy="10.57" r="7.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2"/><stop offset=".58" stop-color="#eee"/><stop offset="1" stop-color="#e6e6e6"/></radialGradient><linearGradient id="a" x1="2.59" y1="10.16" x2="15.41" y2="10.16" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005ba1"/><stop offset=".07" stop-color="#0060a9"/><stop offset=".36" stop-color="#0071c8"/><stop offset=".52" stop-color="#0078d4"/><stop offset=".64" stop-color="#0074cd"/><stop offset=".82" stop-color="#006abb"/><stop offset="1" stop-color="#005ba1"/></linearGradient></defs><path d="M9 5.14c-3.54 0-6.41-1-6.41-2.32v12.36c0 1.27 2.82 2.3 6.32 2.32H9c3.54 0 6.41-1 6.41-2.32V2.82c0 1.29-2.87 2.32-6.41 2.32z" fill="url(#a)"/><path d="M15.41 2.82c0 1.29-2.87 2.32-6.41 2.32s-6.41-1-6.41-2.32S5.46.5 9 .5s6.41 1 6.41 2.32" fill="#e8e8e8"/><path d="M13.92 2.63c0 .82-2.21 1.48-4.92 1.48s-4.92-.66-4.92-1.48S6.29 1.16 9 1.16s4.92.66 4.92 1.47" fill="#50e6ff"/><path d="M9 3a11.55 11.55 0 00-3.89.57A11.42 11.42 0 009 4.11a11.15 11.15 0 003.89-.58A11.84 11.84 0 009 3z" fill="#198ab3"/><path d="M12.9 11.4V8H12v4.13h2.46v-.73zM5.76 9.73a1.83 1.83 0 01-.51-.31.44.44 0 01-.12-.32.34.34 0 01.15-.3.68.68 0 01.42-.12 1.62 1.62 0 011 .29v-.86a2.58 2.58 0 00-1-.16 1.64 1.64 0 00-1.09.34 1.08 1.08 0 00-.42.89c0 .51.32.91 1 1.21a2.88 2.88 0 01.62.36.42.42 0 01.15.32.38.38 0 01-.16.31.81.81 0 01-.45.11 1.66 1.66 0 01-1.09-.42V12a2.17 2.17 0 001.07.24 1.88 1.88 0 001.18-.33 1.08 1.08 0 00.33-.91 1.05 1.05 0 00-.25-.7 2.42 2.42 0 00-.83-.57zM11 11.32a2.34 2.34 0 00.33-1.26A2.32 2.32 0 0011 9a1.81 1.81 0 00-.7-.75 2 2 0 00-1-.26 2.11 2.11 0 00-1.08.27 1.86 1.86 0 00-.73.74 2.46 2.46 0 00-.26 1.14 2.26 2.26 0 00.24 1 1.76 1.76 0 00.69.74 2.06 2.06 0 001 .3l.86 1h1.21L10 12.08a1.79 1.79 0 001-.76zm-1-.25a.94.94 0 01-.76.35.92.92 0 01-.76-.36 1.52 1.52 0 01-.29-1 1.53 1.53 0 01.29-1 1 1 0 01.78-.37.87.87 0 01.75.37 1.62 1.62 0 01.27 1 1.46 1.46 0 01-.28 1.01z" fill="url(#b)"/></svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="8.88" y1="12.21" x2="8.88" y2=".21" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0078d4"/><stop offset=".82" stop-color="#5ea0ef"/></linearGradient><linearGradient id="b" x1="8.88" y1="16.84" x2="8.88" y2="12.21" gradientUnits="userSpaceOnUse"><stop offset=".15" stop-color="#ccc"/><stop offset="1" stop-color="#707070"/></linearGradient></defs><rect x="-.12" y=".21" width="18" height="12" rx=".6" fill="url(#a)"/><path fill="#50e6ff" d="M11.88 4.46v3.49l-3 1.76v-3.5l3-1.75z"/><path fill="#c3f1ff" d="M11.88 4.46l-3 1.76-3-1.76 3-1.75 3 1.75z"/><path fill="#9cebff" d="M8.88 6.22v3.49l-3-1.76V4.46l3 1.76z"/><path fill="#c3f1ff" d="M5.88 7.95l3-1.74v3.5l-3-1.76z"/><path fill="#9cebff" d="M11.88 7.95l-3-1.74v3.5l3-1.76z"/><path d="M12.49 15.84c-1.78-.28-1.85-1.56-1.85-3.63H7.11c0 2.07-.06 3.35-1.84 3.63a1 1 0 00-.89 1h9a1 1 0 00-.89-1z" fill="url(#b)"/></svg>

Before

Width:  |  Height:  |  Size: 973 B

View File

@@ -1,8 +0,0 @@
{
"invite_user": "Invite your teammates",
"invite": "Invite",
"skip": "Skip",
"invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.",
"select_use_case": "Select a use-case to get started",
"get_started": "Get Started"
}

View File

@@ -6,6 +6,5 @@
"share": "Share",
"save": "Save",
"edit": "Edit",
"logged_in": "Logged In",
"pending_data_placeholder": "Just a bit of patience, just a little bits enough ⎯ were getting your {{dataSource}}!"
"logged_in": "Logged In"
}

View File

@@ -1,8 +0,0 @@
{
"invite_user": "Invite your teammates",
"invite": "Invite",
"skip": "Skip",
"invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.",
"select_use_case": "Select a use-case to get started",
"get_started": "Get Started"
}

View File

@@ -8,7 +8,6 @@
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
"GET_STARTED_AWS_MONITORING": "SigNoz | Get Started | AWS",
"GET_STARTED_AZURE_MONITORING": "SigNoz | Get Started | AZURE",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",

View File

@@ -76,8 +76,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
isUserFetching: false,
},
});
if (!isLoggedIn) {
history.push(ROUTES.LOGIN, { from: pathname });
history.push(ROUTES.LOGIN);
}
};

View File

@@ -1,7 +1,6 @@
import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
@@ -18,7 +17,6 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { identity, pick, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
@@ -40,7 +38,7 @@ import defaultRoutes, {
function App(): JSX.Element {
const themeConfig = useThemeConfig();
const { data: licenseData } = useLicense();
const { data } = useLicense();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { role, isLoggedIn: isLoggedInState, user, org } = useSelector<
AppState,
@@ -49,7 +47,7 @@ function App(): JSX.Element {
const dispatch = useDispatch<Dispatch<AppActions>>();
const { trackPageView } = useAnalytics();
const { trackPageView, trackEvent } = useAnalytics();
const { hostname, pathname } = window.location;
@@ -94,10 +92,10 @@ function App(): JSX.Element {
});
const isOnBasicPlan =
licenseData?.payload?.licenses?.some(
data?.payload?.licenses?.some(
(license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || licenseData?.payload?.licenses === null;
) || data?.payload?.licenses === null;
const enableAnalytics = (user: User): void => {
const orgName =
@@ -114,7 +112,9 @@ function App(): JSX.Element {
};
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const groupTraits = {
@@ -127,30 +127,10 @@ function App(): JSX.Element {
};
window.analytics.identify(email, sanitizedIdentifyPayload);
window.analytics.group(domain, groupTraits);
window.clarity('identify', email, name);
posthog?.identify(email, {
email,
name,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
posthog?.group('company', domain, {
name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
};
useEffect(() => {
@@ -164,6 +144,10 @@ function App(): JSX.Element {
!isIdentifiedUser
) {
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
if (isCloudUserVal) {
enableAnalytics(user);
}
}
if (
@@ -200,7 +184,7 @@ function App(): JSX.Element {
LOCALSTORAGE.THEME_ANALYTICS_V1,
);
if (!isThemeAnalyticsSent) {
logEvent('Theme Analytics', {
trackEvent('Theme Analytics', {
theme: isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT,
user: pick(user, ['email', 'userId', 'name']),
org,
@@ -211,11 +195,6 @@ function App(): JSX.Element {
console.error('Failed to parse local storage theme analytics event');
}
}
if (isCloudUserVal && user && user.email) {
enableAnalytics(user);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);

View File

@@ -9,9 +9,9 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
// making the error status code as standard Error Status Code
const statusCode = response.status as ErrorStatusCode;
const { data } = response as AxiosResponse;
if (statusCode >= 400 && statusCode < 500) {
const { data } = response as AxiosResponse;
if (statusCode === 404) {
return {
statusCode,
@@ -34,11 +34,12 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
body: JSON.stringify((response.data as any).data),
};
}
return {
statusCode,
payload: null,
error: 'Something went wrong',
message: data?.error,
message: null,
};
}
if (request) {

View File

@@ -3,7 +3,7 @@ const apiV1 = '/api/v1/';
export const apiV2 = '/api/v2/';
export const apiV3 = '/api/v3/';
export const apiV4 = '/api/v4/';
export const gatewayApiV1 = '/api/gateway/v1/';
export const apiAlertManager = '/api/alertmanager/';
export const gatewayApiV1 = '/api/gateway/v1';
export const apiAlertManager = '/api/alertmanager';
export default apiV1;

View File

@@ -1,4 +1,4 @@
import { ApiBaseInstance as axios } from 'api';
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@@ -21,7 +21,6 @@ const logEvent = async (
payload: response.data.data,
};
} catch (error) {
console.error(error);
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -96,10 +96,6 @@ const interceptorRejected = async (
}
};
const interceptorRejectedBase = async (
value: AxiosResponse<any>,
): Promise<AxiosResponse<any>> => Promise.reject(value);
const instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
});
@@ -144,18 +140,6 @@ ApiV4Instance.interceptors.response.use(
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
//
// axios Base
export const ApiBaseInstance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
});
ApiBaseInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejectedBase,
);
ApiBaseInstance.interceptors.request.use(interceptorsRequestResponse);
//
// gateway Api V1
export const GatewayApiV1Instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV1}`,

View File

@@ -1,20 +1,7 @@
import axios from 'api';
import { isNil } from 'lodash-es';
interface GetTopLevelOperationsProps {
service?: string;
start?: number;
end?: number;
}
const getTopLevelOperations = async (
props: GetTopLevelOperationsProps,
): Promise<ServiceDataProps> => {
const response = await axios.post(`/service/top_level_operations`, {
start: !isNil(props.start) ? `${props.start}` : undefined,
end: !isNil(props.end) ? `${props.end}` : undefined,
service: props.service,
});
const getTopLevelOperations = async (): Promise<ServiceDataProps> => {
const response = await axios.post(`/service/top_level_operations`);
return response.data;
};

View File

@@ -1,7 +1,6 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { Dayjs } from 'dayjs';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Recurrence } from './getAllDowntimeSchedules';
@@ -12,8 +11,8 @@ export interface DowntimeSchedulePayload {
alertIds: string[];
schedule: {
timezone?: string;
startTime?: string | Dayjs;
endTime?: string | Dayjs;
startTime?: string;
endTime?: string;
recurrence?: Recurrence;
};
}

View File

@@ -1,6 +1,6 @@
import axios from 'api';
import { AxiosError, AxiosResponse } from 'axios';
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
import { Option } from 'container/PlannedDowntime/DropdownWithSubMenu/DropdownWithSubMenu';
import { useQuery, UseQueryResult } from 'react-query';
export type Recurrence = {
@@ -28,7 +28,6 @@ export interface DowntimeSchedules {
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
kind: string | null;
}
export type PayloadProps = { data: DowntimeSchedules[] };

View File

@@ -287,7 +287,7 @@ function CustomTimePicker({
)
}
arrow={false}
trigger="click"
trigger="hover"
open={open}
onOpenChange={handleOpenChange}
style={{

View File

@@ -3,15 +3,8 @@ import './DropDown.styles.scss';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useState } from 'react';
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
@@ -21,25 +14,12 @@ function DropDown({
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
return (
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
<Dropdown menu={{ items }}>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
onClick={(e): void => e.preventDefault()}
>
<EllipsisOutlined className="dropdown-icon" />
</Button>
@@ -47,8 +27,4 @@ function DropDown({
);
}
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown;

View File

@@ -52,7 +52,7 @@
.log-body {
font-family: 'SF Mono';
font-family: 'Geist Mono';
font-family: 'Space Mono', monospace;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);

View File

@@ -2,7 +2,7 @@ import './AddToQueryHOC.styles.scss';
import { Popover } from 'antd';
import { OPERATORS } from 'constants/queryBuilder';
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
import { memo, ReactNode, useCallback, useMemo } from 'react';
function AddToQueryHOC({
fieldKey,
@@ -10,10 +10,9 @@ function AddToQueryHOC({
onAddToQuery,
children,
}: AddToQueryHOCProps): JSX.Element {
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
event.stopPropagation();
const handleQueryAdd = useCallback(() => {
onAddToQuery(fieldKey, fieldValue, OPERATORS.IN);
};
}, [fieldKey, fieldValue, onAddToQuery]);
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey,

View File

@@ -28,17 +28,12 @@ export const SEVERITY_TEXT_TYPE = {
FATAL2: 'FATAL2',
FATAL3: 'FATAL3',
FATAL4: 'FATAL4',
UNKNOWN: 'UNKNOWN',
} as const;
export const LogType = {
TRACE: 'TRACE',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARN: 'WARN',
WARNING: 'WARNING',
ERROR: 'ERROR',
FATAL: 'FATAL',
UNKNOWN: 'UNKNOWN',
} as const;
function LogStateIndicator({

View File

@@ -1,10 +1,9 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ILog } from 'types/api/logs/log';
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';
describe('getLogIndicatorType', () => {
it('severity_number should be given priority over severity_text', () => {
it('should return severity type for valid log with severityText', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
@@ -21,57 +20,11 @@ describe('getLogIndicatorType', () => {
attributesInt: {},
attributesFloat: {},
severity_text: 'INFO',
severity_number: 2,
};
// severity_number should get priority over severity_text
expect(getLogIndicatorType(log)).toBe('TRACE');
expect(getLogIndicatorType(log)).toBe('INFO');
});
it('severity_text should be used when severity_number is absent ', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'FATAL',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('case insensitive severity_text should be valid', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'fatAl',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('should return log level if severityText and severityNumber is missing', () => {
it('should return log level if severityText is missing', () => {
const log: ILog = {
date: '2024-02-29T12:34:58Z',
timestamp: 1646115296,
@@ -83,16 +36,13 @@ describe('getLogIndicatorType', () => {
body: 'Sample log',
resources_string: {},
attributesString: {},
attributes_string: {
log_level: 'INFO' as never,
},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'some_random',
severity_text: 'FATAL',
severityText: '',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('INFO');
expect(getLogIndicatorType(log)).toBe('FATAL');
});
});
@@ -105,7 +55,6 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 2,
severity_number: 2,
body: 'Sample log message',
resources_string: {},
@@ -115,7 +64,7 @@ describe('getLogIndicatorTypeForTable', () => {
attributesFloat: {},
severity_text: 'WARN',
};
expect(getLogIndicatorTypeForTable(log)).toBe('TRACE');
expect(getLogIndicatorTypeForTable(log)).toBe('WARN');
});
it('should return log level if severityText is missing', () => {
@@ -126,8 +75,7 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 0,
severity_number: 0,
severityNumber: 2,
body: 'Sample log message',
resources_string: {},
attributesString: {},
@@ -139,47 +87,3 @@ describe('getLogIndicatorTypeForTable', () => {
expect(getLogIndicatorTypeForTable(log)).toBe('INFO');
});
});
describe('logIndicatorBySeverityNumber', () => {
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const logLevelExpectations = [
{ minSevNumber: 1, maxSevNumber: 4, expectedIndicatorType: 'TRACE' },
{ minSevNumber: 5, maxSevNumber: 8, expectedIndicatorType: 'DEBUG' },
{ minSevNumber: 9, maxSevNumber: 12, expectedIndicatorType: 'INFO' },
{ minSevNumber: 13, maxSevNumber: 16, expectedIndicatorType: 'WARN' },
{ minSevNumber: 17, maxSevNumber: 20, expectedIndicatorType: 'ERROR' },
{ minSevNumber: 21, maxSevNumber: 24, expectedIndicatorType: 'FATAL' },
];
logLevelExpectations.forEach((e) => {
for (let sevNum = e.minSevNumber; sevNum <= e.maxSevNumber; sevNum++) {
const sevText = (Math.random() + 1).toString(36).substring(2);
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: sevText,
severityNumber: sevNum,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: sevText,
severity_number: sevNum,
};
it(`getLogIndicatorType should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorType(log)).toBe(e.expectedIndicatorType);
});
it(`getLogIndicatorTypeForTable should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorTypeForTable(log)).toBe(e.expectedIndicatorType);
});
}
});
});

View File

@@ -2,112 +2,56 @@ import { ILog } from 'types/api/logs/log';
import { LogType, SEVERITY_TEXT_TYPE } from './LogStateIndicator';
const getLogTypeBySeverityText = (severityText: string): string => {
const getSeverityType = (severityText: string): string => {
switch (severityText) {
case SEVERITY_TEXT_TYPE.TRACE:
case SEVERITY_TEXT_TYPE.TRACE2:
case SEVERITY_TEXT_TYPE.TRACE3:
case SEVERITY_TEXT_TYPE.TRACE4:
return LogType.TRACE;
return SEVERITY_TEXT_TYPE.TRACE;
case SEVERITY_TEXT_TYPE.DEBUG:
case SEVERITY_TEXT_TYPE.DEBUG2:
case SEVERITY_TEXT_TYPE.DEBUG3:
case SEVERITY_TEXT_TYPE.DEBUG4:
return LogType.DEBUG;
return SEVERITY_TEXT_TYPE.DEBUG;
case SEVERITY_TEXT_TYPE.INFO:
case SEVERITY_TEXT_TYPE.INFO2:
case SEVERITY_TEXT_TYPE.INFO3:
case SEVERITY_TEXT_TYPE.INFO4:
return LogType.INFO;
return SEVERITY_TEXT_TYPE.INFO;
case SEVERITY_TEXT_TYPE.WARN:
case SEVERITY_TEXT_TYPE.WARN2:
case SEVERITY_TEXT_TYPE.WARN3:
case SEVERITY_TEXT_TYPE.WARN4:
case SEVERITY_TEXT_TYPE.WARNING:
return LogType.WARN;
return SEVERITY_TEXT_TYPE.WARN;
case SEVERITY_TEXT_TYPE.ERROR:
case SEVERITY_TEXT_TYPE.ERROR2:
case SEVERITY_TEXT_TYPE.ERROR3:
case SEVERITY_TEXT_TYPE.ERROR4:
return LogType.ERROR;
return SEVERITY_TEXT_TYPE.ERROR;
case SEVERITY_TEXT_TYPE.FATAL:
case SEVERITY_TEXT_TYPE.FATAL2:
case SEVERITY_TEXT_TYPE.FATAL3:
case SEVERITY_TEXT_TYPE.FATAL4:
return LogType.FATAL;
return SEVERITY_TEXT_TYPE.FATAL;
default:
return LogType.UNKNOWN;
return SEVERITY_TEXT_TYPE.INFO;
}
};
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const getLogTypeBySeverityNumber = (severityNumber: number): string => {
if (severityNumber < 1) {
return LogType.UNKNOWN;
}
if (severityNumber < 5) {
return LogType.TRACE;
}
if (severityNumber < 9) {
return LogType.DEBUG;
}
if (severityNumber < 13) {
return LogType.INFO;
}
if (severityNumber < 17) {
return LogType.WARN;
}
if (severityNumber < 21) {
return LogType.ERROR;
}
if (severityNumber < 25) {
return LogType.FATAL;
}
return LogType.UNKNOWN;
};
const getLogType = (
severityText: string,
severityNumber: number,
defaultType: string,
): string => {
// give priority to the severityNumber
if (severityNumber) {
const logType = getLogTypeBySeverityNumber(severityNumber);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
// is severityNumber is not present then rely on the severityText
if (severityText) {
const logType = getLogTypeBySeverityText(severityText);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
return defaultType;
};
export const getLogIndicatorType = (logData: ILog): string => {
const defaultType = logData.attributes_string?.log_level || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
logData?.severity_text?.toUpperCase(),
logData?.severity_number || 0,
defaultType,
);
if (logData.severity_text) {
return getSeverityType(logData.severity_text);
}
return logData.attributes_string?.log_level || LogType.INFO;
};
export const getLogIndicatorTypeForTable = (
log: Record<string, unknown>,
): string => {
const defaultType = (log.log_level as string) || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
(log?.severity_text as string)?.toUpperCase(),
(log?.severity_number as number) || 0,
defaultType,
);
if (log.severity_text) {
return getSeverityType(log.severity_text as string);
}
return (log.log_level as string) || LogType.INFO;
};

View File

@@ -62,6 +62,8 @@ function RawLogView({
const isDarkMode = useIsDarkMode();
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
const severityText = data.severity_text ? `${data.severity_text} |` : '';
const logType = getLogIndicatorType(data);
const updatedSelecedFields = useMemo(
@@ -86,16 +88,17 @@ function RawLogView({
attributesText += ' | ';
}
const text = useMemo(() => {
const date =
const text = useMemo(
() =>
typeof data.timestamp === 'string'
? dayjs(data.timestamp)
: dayjs(data.timestamp / 1e6);
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
data.body
}`;
}, [data.timestamp, data.body, attributesText]);
? `${dayjs(data.timestamp).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`
: `${dayjs(data.timestamp / 1e6).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText, attributesText],
);
const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return;

View File

@@ -49,7 +49,7 @@ export const ExpandIconWrapper = styled(Col)`
export const RawLogContent = styled.div<RawLogContentProps>`
margin-bottom: 0;
font-family: 'SF Mono', monospace;
font-family: 'Geist Mono';
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 400;
text-align: left;

View File

@@ -1,54 +0,0 @@
import TypicalOverlayScrollbar from 'components/TypicalOverlayScrollbar/TypicalOverlayScrollbar';
import VirtuosoOverlayScrollbar from 'components/VirtuosoOverlayScrollbar/VirtuosoOverlayScrollbar';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { PartialOptions } from 'overlayscrollbars';
import { CSSProperties, ReactElement, useMemo } from 'react';
type Props = {
children: ReactElement;
isVirtuoso?: boolean;
style?: CSSProperties;
options?: PartialOptions;
};
function OverlayScrollbar({
children,
isVirtuoso,
style,
options: customOptions,
}: Props): any {
const isDarkMode = useIsDarkMode();
const options = useMemo(
() =>
({
scrollbars: {
autoHide: 'scroll',
theme: isDarkMode ? 'os-theme-light' : 'os-theme-dark',
},
...(customOptions || {}),
} as PartialOptions),
[customOptions, isDarkMode],
);
if (isVirtuoso) {
return (
<VirtuosoOverlayScrollbar style={style} options={options}>
{children}
</VirtuosoOverlayScrollbar>
);
}
return (
<TypicalOverlayScrollbar style={style} options={options}>
{children}
</TypicalOverlayScrollbar>
);
}
OverlayScrollbar.defaultProps = {
isVirtuoso: false,
style: {},
options: {},
};
export default OverlayScrollbar;

View File

@@ -2,9 +2,7 @@
import './DynamicColumnTable.syles.scss';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { ColumnGroupType, ColumnType } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { SlidersHorizontal } from 'lucide-react';
import { memo, useEffect, useState } from 'react';
@@ -24,7 +22,6 @@ function DynamicColumnTable({
dynamicColumns,
onDragColumn,
facingIssueBtn,
shouldSendAlertsLogEvent,
...restProps
}: DynamicColumnTableProps): JSX.Element {
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
@@ -50,18 +47,11 @@ function DynamicColumnTable({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, dynamicColumns]);
const onToggleHandler = (
index: number,
column: ColumnGroupType<any> | ColumnType<any>,
) => (checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
const onToggleHandler = (index: number) => (
checked: boolean,
event: React.MouseEvent<HTMLButtonElement>,
): void => {
event.stopPropagation();
if (shouldSendAlertsLogEvent) {
logEvent('Alert: Column toggled', {
column: column?.title,
action: checked ? 'Enable' : 'Disable',
});
}
setVisibleColumns({
tablesource,
dynamicColumns,
@@ -85,7 +75,7 @@ function DynamicColumnTable({
<div>{column.title?.toString()}</div>
<Switch
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
onChange={onToggleHandler(index, column)}
onChange={onToggleHandler(index)}
/>
</div>
),

View File

@@ -3,7 +3,6 @@
import { Table } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { dragColumnParams } from 'hooks/useDragColumns/configs';
import { set } from 'lodash-es';
import {
SyntheticEvent,
useCallback,
@@ -21,7 +20,6 @@ import { ResizeTableProps } from './types';
function ResizeTable({
columns,
onDragColumn,
pagination,
...restProps
}: ResizeTableProps): JSX.Element {
const [columnsData, setColumns] = useState<ColumnsType>([]);
@@ -60,21 +58,14 @@ function ResizeTable({
[columnsData, onDragColumn, handleResize],
);
const tableParams = useMemo(() => {
const props = {
const tableParams = useMemo(
() => ({
...restProps,
components: { header: { cell: ResizableHeader } },
columns: mergedColumns,
};
set(
props,
'pagination',
pagination ? { ...pagination, hideOnSinglePage: true } : false,
);
return props;
}, [mergedColumns, pagination, restProps]);
}),
[mergedColumns, restProps],
);
useEffect(() => {
if (columns) {

View File

@@ -14,7 +14,6 @@ export interface DynamicColumnTableProps extends TableProps<any> {
dynamicColumns: TableProps<any>['columns'];
onDragColumn?: (fromIndex: number, toIndex: number) => void;
facingIssueBtn?: FacingIssueBtnProps;
shouldSendAlertsLogEvent?: boolean;
}
export type GetVisibleColumnsFunction = (

View File

@@ -5,6 +5,7 @@ import { Button } from 'antd';
import { Tag } from 'antd/lib';
import Input from 'components/Input';
import { Check, X } from 'lucide-react';
import { TweenOneGroup } from 'rc-tween-one';
import React, { Dispatch, SetStateAction, useState } from 'react';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
@@ -45,19 +46,41 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
func(value);
};
const forMap = (tag: string): React.ReactElement => (
<span key={tag} style={{ display: 'inline-block' }}>
<Tag
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
{tag}
</Tag>
</span>
);
const tagChild = tags.map(forMap);
const renderTagsAnimated = (): React.ReactElement => (
<TweenOneGroup
appear={false}
className="tags"
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
onEnd={(e): void => {
if (e.type === 'appear' || e.type === 'enter') {
(e.target as any).style = 'display: inline-block';
}
}}
>
{tagChild}
</TweenOneGroup>
);
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
<span>{tag}</span>
</Tag>
))}
{renderTagsAnimated()}
{inputVisible && (
<div className="add-tag-container">
<Input

View File

@@ -9,6 +9,7 @@ import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useMemo } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
import { style } from './constant';
@@ -63,7 +64,7 @@ function TextToolTip({
);
return (
<Tooltip overlay={overlay}>
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
{useFilledIcon ? (
<QuestionCircleFilled style={iconStyle} />
) : (

View File

@@ -1,31 +0,0 @@
import './typicalOverlayScrollbar.scss';
import { PartialOptions } from 'overlayscrollbars';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { CSSProperties, ReactElement } from 'react';
interface Props {
children: ReactElement;
style?: CSSProperties;
options?: PartialOptions;
}
export default function TypicalOverlayScrollbar({
children,
style,
options,
}: Props): ReturnType<typeof OverlayScrollbarsComponent> {
return (
<OverlayScrollbarsComponent
defer
options={options}
style={style}
className="overlay-scrollbar"
data-overlayscrollbars-initialize
>
{children}
</OverlayScrollbarsComponent>
);
}
TypicalOverlayScrollbar.defaultProps = { style: {}, options: {} };

View File

@@ -1,3 +0,0 @@
.overlay-scrollbar {
height: 100%;
}

View File

@@ -1,7 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Uplot.styles.scss';
import * as Sentry from '@sentry/react';
import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
@@ -14,6 +13,7 @@ import {
useImperativeHandle,
useRef,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import UPlot from 'uplot';
import { dataMatch, optionsUpdateState } from './utils';
@@ -139,7 +139,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
}
return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<div className="uplot-graph-container" ref={targetRef}>
{data && data[0] && data[0]?.length === 0 ? (
<div className="not-found">
@@ -147,7 +147,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
</div>
) : null}
</div>
</Sentry.ErrorBoundary>
</ErrorBoundary>
);
},
);

View File

@@ -49,10 +49,7 @@ function ValueGraph({
}
>
<Tooltip title={t('this_value_satisfies_multiple_thresholds')}>
<ExclamationCircleFilled
className="value-graph-icon"
data-testid="conflicting-thresholds"
/>
<ExclamationCircleFilled className="value-graph-icon" />
</Tooltip>
</div>
)}

View File

@@ -1,37 +0,0 @@
import './virtuosoOverlayScrollbar.scss';
import useInitializeOverlayScrollbar from 'hooks/useInitializeOverlayScrollbar/useInitializeOverlayScrollbar';
import { PartialOptions } from 'overlayscrollbars';
import React, { CSSProperties, ReactElement } from 'react';
interface VirtuosoOverlayScrollbarProps {
children: ReactElement;
style?: CSSProperties;
options: PartialOptions;
}
export default function VirtuosoOverlayScrollbar({
children,
style,
options,
}: VirtuosoOverlayScrollbarProps): JSX.Element {
const { rootRef, setScroller } = useInitializeOverlayScrollbar(options);
const enhancedChild = React.cloneElement(children, {
scrollerRef: setScroller,
'data-overlayscrollbars-initialize': true,
});
return (
<div
data-overlayscrollbars-initialize
ref={rootRef}
className="overlay-scroll-wrapper"
style={style}
>
{enhancedChild}
</div>
);
}
VirtuosoOverlayScrollbar.defaultProps = { style: {} };

View File

@@ -1,5 +0,0 @@
.overlay-scroll-wrapper {
height: 100%;
width: 100%;
overflow: auto;
}

View File

@@ -16,7 +16,6 @@ export interface FacingIssueBtnProps {
buttonText?: string;
className?: string;
onHoverText?: string;
intercomMessageDisabled?: boolean;
}
function FacingIssueBtn({
@@ -26,12 +25,11 @@ function FacingIssueBtn({
buttonText = '',
className = '',
onHoverText = '',
intercomMessageDisabled = false,
}: FacingIssueBtnProps): JSX.Element | null {
const handleFacingIssuesClick = (): void => {
logEvent(eventName, attributes);
if (window.Intercom && !intercomMessageDisabled) {
if (window.Intercom) {
window.Intercom('showNewMessage', defaultTo(message, ''));
}
};
@@ -64,7 +62,6 @@ FacingIssueBtn.defaultProps = {
buttonText: '',
className: '',
onHoverText: '',
intercomMessageDisabled: false,
};
export default FacingIssueBtn;

View File

@@ -19,5 +19,6 @@ export enum FeatureKeys {
OSS = 'OSS',
ONBOARDING = 'ONBOARDING',
CHAT_SUPPORT = 'CHAT_SUPPORT',
PLANNED_MAINTENANCE = 'PLANNED_MAINTENANCE',
GATEWAY = 'GATEWAY',
}

View File

@@ -40,5 +40,4 @@ export const getComponentForPanelType = (
export const AVAILABLE_EXPORT_PANEL_TYPES = [
PANEL_TYPES.TIME_SERIES,
PANEL_TYPES.TABLE,
PANEL_TYPES.LIST,
];

View File

@@ -23,10 +23,6 @@ export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
value: QueryFunctionsTypes.ABSOLUTE,
label: 'Absolute',
},
{
value: QueryFunctionsTypes.RUNNING_DIFF,
label: 'Running Diff',
},
{
value: QueryFunctionsTypes.LOG_2,
label: 'Log2',
@@ -107,9 +103,6 @@ export const queryFunctionsTypesConfig: QueryFunctionConfigType = {
absolute: {
showInput: false,
},
runningDiff: {
showInput: false,
},
log2: {
showInput: false,
},

View File

@@ -13,7 +13,6 @@ const ROUTES = {
GET_STARTED_INFRASTRUCTURE_MONITORING:
'/get-started/infrastructure-monitoring',
GET_STARTED_AWS_MONITORING: '/get-started/aws-monitoring',
GET_STARTED_AZURE_MONITORING: '/get-started/azure-monitoring',
USAGE_EXPLORER: '/usage-explorer',
APPLICATION: '/services',
ALL_DASHBOARD: '/dashboard',

View File

@@ -1,78 +0,0 @@
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: jest.fn(),
},
})),
}));
describe('Alert Channels Settings List page', () => {
beforeEach(() => {
render(<AlertChannels />);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Should display the Alert Channels page properly', () => {
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
});
it('Should check if "New Alert Channel" Button is visble ', () => {
expect(screen.getByText('button_new_channel')).toBeInTheDocument();
});
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
const helpIcon = screen.getByLabelText('question-circle');
fireEvent.mouseOver(helpIcon);
await waitFor(() => {
const tooltip = screen.getByText('tooltip_notification_channels');
expect(tooltip).toBeInTheDocument();
});
});
});
describe('Should check if the channels table is properly displayed', () => {
it('Should check if the table columns are properly displayed', () => {
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
expect(screen.getByText('column_channel_action')).toBeInTheDocument();
});
it('Should check if the data in the table is displayed properly', () => {
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
expect(screen.getAllByText('column_channel_edit')[0]).toBeInTheDocument();
expect(screen.getAllByText('Delete')[0]).toBeInTheDocument();
});
it('Should check if clicking on Delete displays Success Toast "Channel Deleted Successfully"', async () => {
const deleteButton = screen.getAllByRole('button', { name: 'Delete' })[0];
expect(deleteButton).toBeInTheDocument();
act(() => {
fireEvent.click(deleteButton);
});
await waitFor(() => {
expect(successNotification).toBeCalledWith({
message: 'Success',
description: 'channel_delete_success',
});
});
});
});
});

View File

@@ -1,72 +0,0 @@
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: jest.fn(),
},
})),
}));
jest.mock('hooks/useComponentPermission', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => [false]),
}));
describe('Alert Channels Settings List page (Normal User)', () => {
beforeEach(() => {
render(<AlertChannels />);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Should display the Alert Channels page properly', () => {
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
});
it('Should check if "New Alert Channel" Button is visble and disabled', () => {
const newAlertButton = screen.getByRole('button', {
name: 'plus button_new_channel',
});
expect(newAlertButton).toBeInTheDocument();
expect(newAlertButton).toBeDisabled();
});
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
const helpIcon = screen.getByLabelText('question-circle');
fireEvent.mouseOver(helpIcon);
await waitFor(() => {
const tooltip = screen.getByText('tooltip_notification_channels');
expect(tooltip).toBeInTheDocument();
});
});
});
describe('Should check if the channels table is properly displayed', () => {
it('Should check if the table columns are properly displayed', () => {
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
expect(screen.queryByText('column_channel_action')).not.toBeInTheDocument();
});
it('Should check if the data in the table is displayed properly', () => {
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
expect(screen.queryByText('column_channel_edit')).not.toBeInTheDocument();
expect(screen.queryByText('Delete')).not.toBeInTheDocument();
});
});
});

View File

@@ -1,424 +0,0 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
import CreateAlertChannels from 'container/CreateAlertChannels';
import { ChannelType } from 'container/CreateAlertChannels/config';
import {
opsGenieDescriptionDefaultValue,
opsGenieMessageDefaultValue,
opsGeniePriorityDefaultValue,
pagerDutyAdditionalDetailsDefaultValue,
pagerDutyDescriptionDefaultVaule,
pagerDutySeverityTextDefaultValue,
slackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
const successNotification = jest.fn();
const errorNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: errorNotification,
},
})),
}));
jest.mock('hooks/useFeatureFlag', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
active: true,
})),
}));
describe('Create Alert Channel', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Slack} />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('Should check if the title is "New Notification Channels"', () => {
expect(screen.getByText('page_title_create')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
// Default Channel type (Slack) fields
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
it('Should check if saving the form without filling the name displays "Something went wrong"', async () => {
const saveButton = screen.getByRole('button', {
name: 'button_save_channel',
});
fireEvent.click(saveButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
description: 'Something went wrong',
message: 'Error',
}),
);
});
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
server.use(
rest.post('http://localhost/api/v1/testChannel', (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
status: 'success',
data: 'test alert sent',
}),
),
),
);
const testButton = screen.getByRole('button', {
name: 'button_test_channel',
});
fireEvent.click(testButton);
await waitFor(() =>
expect(successNotification).toHaveBeenCalledWith({
message: 'Success',
description: 'channel_test_done',
}),
);
});
it('Should check if clicking on Test button shows "Something went wrong" error message if testing fails', async () => {
const testButton = screen.getByRole('button', {
name: 'button_test_channel',
});
fireEvent.click(testButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
message: 'Error',
description: 'channel_test_failed',
}),
);
});
});
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
describe('Webhook', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
});
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
expect(screen.getByText('Webhook')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_username',
testId: 'webhook-username-textbox',
helpText: 'help_webhook_username',
});
});
it('Should check if Password label and textbox, and help text are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'Password (optional)',
testId: 'webhook-password-textbox',
helpText: 'help_webhook_password',
});
});
});
describe('PagerDuty', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
});
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
});
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_routing_key',
testId: 'pager-routing-key-textbox',
});
});
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_description',
testId: 'pager-description-textarea',
helpText: 'help_pager_description',
});
});
it('Should check if the description contains default template', () => {
const descriptionTextArea = screen.getByTestId(
'pager-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
pagerDutyDescriptionDefaultVaule,
);
});
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_severity',
testId: 'pager-severity-textbox',
helpText: 'help_pager_severity',
});
});
it('Should check if Severity contains the default template', () => {
const severityTextbox = screen.getByTestId('pager-severity-textbox');
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
});
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_details',
testId: 'pager-additional-details-textarea',
helpText: 'help_pager_details',
});
});
it('Should check if Additional Information contains the default template', () => {
const detailsTextArea = screen.getByTestId(
'pager-additional-details-textarea',
);
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
});
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_group',
testId: 'pager-group-textarea',
helpText: 'help_pager_group',
});
});
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_class',
testId: 'pager-class-textarea',
helpText: 'help_pager_class',
});
});
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client',
testId: 'pager-client-textarea',
helpText: 'help_pager_client',
});
});
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
const clientTextArea = screen.getByTestId('pager-client-textarea');
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
});
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client_url',
testId: 'pager-client-url-textarea',
helpText: 'help_pager_client_url',
});
});
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
expect(clientUrlTextArea).toHaveValue(
'https://enter-signoz-host-n-port-here/alerts',
);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
});
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
});
it('Should check if API key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_api_key',
testId: 'opsgenie-api-key-textbox',
required: true,
});
});
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_message',
testId: 'opsgenie-message-textarea',
helpText: 'help_opsgenie_message',
required: true,
});
});
it('Should check if Message contains the default template ', () => {
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_description',
testId: 'opsgenie-description-textarea',
helpText: 'help_opsgenie_description',
required: true,
});
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
const descriptionTextArea = screen.getByTestId(
'opsgenie-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
opsGenieDescriptionDefaultValue,
);
});
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_priority',
testId: 'opsgenie-priority-textarea',
helpText: 'help_opsgenie_priority',
required: true,
});
});
it('Should check if Message contains the default template', () => {
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Email} />);
});
it('Should check if the selected item in the type dropdown has text "Email"', () => {
expect(screen.getByText('Email')).toBeInTheDocument();
});
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
helpText: 'help_email_to',
required: true,
});
});
});
describe('Microsoft Teams', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
});
it('Should check if the selected item in the type dropdown has text "msteams"', () => {
expect(screen.getByText('msteams')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
});
});
});

View File

@@ -1,348 +0,0 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
import CreateAlertChannels from 'container/CreateAlertChannels';
import { ChannelType } from 'container/CreateAlertChannels/config';
import {
opsGenieDescriptionDefaultValue,
opsGenieMessageDefaultValue,
opsGeniePriorityDefaultValue,
pagerDutyAdditionalDetailsDefaultValue,
pagerDutyDescriptionDefaultVaule,
pagerDutySeverityTextDefaultValue,
slackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
describe('Create Alert Channel (Normal User)', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Slack} />);
});
it('Should check if the title is "New Notification Channels"', () => {
expect(screen.getByText('page_title_create')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
// Default Channel type (Slack) fields
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
});
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
describe('Webhook', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
});
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
expect(screen.getByText('Webhook')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_username',
testId: 'webhook-username-textbox',
helpText: 'help_webhook_username',
});
});
it('Should check if Password label and textbox, and help text are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'Password (optional)',
testId: 'webhook-password-textbox',
helpText: 'help_webhook_password',
});
});
});
describe('PagerDuty', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
});
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
});
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_routing_key',
testId: 'pager-routing-key-textbox',
});
});
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_description',
testId: 'pager-description-textarea',
helpText: 'help_pager_description',
});
});
it('Should check if the description contains default template', () => {
const descriptionTextArea = screen.getByTestId(
'pager-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
pagerDutyDescriptionDefaultVaule,
);
});
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_severity',
testId: 'pager-severity-textbox',
helpText: 'help_pager_severity',
});
});
it('Should check if Severity contains the default template', () => {
const severityTextbox = screen.getByTestId('pager-severity-textbox');
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
});
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_details',
testId: 'pager-additional-details-textarea',
helpText: 'help_pager_details',
});
});
it('Should check if Additional Information contains the default template', () => {
const detailsTextArea = screen.getByTestId(
'pager-additional-details-textarea',
);
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
});
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_group',
testId: 'pager-group-textarea',
helpText: 'help_pager_group',
});
});
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_class',
testId: 'pager-class-textarea',
helpText: 'help_pager_class',
});
});
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client',
testId: 'pager-client-textarea',
helpText: 'help_pager_client',
});
});
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
const clientTextArea = screen.getByTestId('pager-client-textarea');
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
});
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client_url',
testId: 'pager-client-url-textarea',
helpText: 'help_pager_client_url',
});
});
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
expect(clientUrlTextArea).toHaveValue(
'https://enter-signoz-host-n-port-here/alerts',
);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
});
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
});
it('Should check if API key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_api_key',
testId: 'opsgenie-api-key-textbox',
required: true,
});
});
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_message',
testId: 'opsgenie-message-textarea',
helpText: 'help_opsgenie_message',
required: true,
});
});
it('Should check if Message contains the default template ', () => {
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_description',
testId: 'opsgenie-description-textarea',
helpText: 'help_opsgenie_description',
required: true,
});
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
const descriptionTextArea = screen.getByTestId(
'opsgenie-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
opsGenieDescriptionDefaultValue,
);
});
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_priority',
testId: 'opsgenie-priority-textarea',
helpText: 'help_opsgenie_priority',
required: true,
});
});
it('Should check if Message contains the default template', () => {
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Email} />);
});
it('Should check if the selected item in the type dropdown has text "Email"', () => {
expect(screen.getByText('Email')).toBeInTheDocument();
});
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
helpText: 'help_email_to',
required: true,
});
});
});
describe('Microsoft Teams', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
});
it('Should check if the selected item in the type dropdown has text "Microsoft Teams (Supported in Paid Plans Only)"', () => {
expect(
screen.getByText('Microsoft Teams (Supported in Paid Plans Only)'),
).toBeInTheDocument();
});
it('Should check if the upgrade plan message is shown', () => {
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
expect(
screen.getByText(/This feature is available for paid plans only./),
).toBeInTheDocument();
const link = screen.getByRole('link', { name: 'Click here' });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', SIGNOZ_UPGRADE_PLAN_URL);
expect(screen.getByText(/to Upgrade/)).toBeInTheDocument();
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(
screen.getByRole('button', { name: 'button_save_channel' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'button_test_channel' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'button_return' }),
).toBeInTheDocument();
});
it('Should check if save and test buttons are disabled', () => {
expect(
screen.getByRole('button', { name: 'button_save_channel' }),
).toBeDisabled();
expect(
screen.getByRole('button', { name: 'button_test_channel' }),
).toBeDisabled();
});
});
});
});

View File

@@ -1,118 +0,0 @@
import EditAlertChannels from 'container/EditAlertChannels';
import {
editAlertChannelInitialValue,
editSlackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
const successNotification = jest.fn();
const errorNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: errorNotification,
},
})),
}));
jest.mock('hooks/useFeatureFlag', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
active: true,
})),
}));
describe('Should check if the edit alert channel is properly displayed ', () => {
beforeEach(() => {
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('Should check if the title is "Edit Notification Channels"', () => {
expect(screen.getByText('page_title_edit')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
value: 'Dummy-Channel',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly and the checkbox is checked ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
expect(screen.getByTestId('field-send-resolved-checkbox')).toBeChecked();
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
value:
'https://discord.com/api/webhooks/dummy_webhook_id/dummy_webhook_token/slack',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
value: '#dummy_channel',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(
editSlackDescriptionDefaultValue,
);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
});

View File

@@ -1,31 +0,0 @@
import { screen } from 'tests/test-utils';
export const testLabelInputAndHelpValue = ({
labelText,
testId,
helpText,
required = false,
value,
}: {
labelText: string;
testId: string;
helpText?: string;
required?: boolean;
value?: string;
}): void => {
const label = screen.getByText(labelText);
expect(label).toBeInTheDocument();
const input = screen.getByTestId(testId);
expect(input).toBeInTheDocument();
if (helpText !== undefined) {
expect(screen.getByText(helpText)).toBeInTheDocument();
}
if (required) {
expect(input).toBeRequired();
}
if (value) {
expect(input).toHaveValue(value);
}
};

View File

@@ -1,15 +1,13 @@
import { PlusOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useCallback, useEffect } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -33,14 +31,6 @@ function AlertChannels(): JSX.Element {
const { loading, payload, error, errorMessage } = useFetch(getAll);
useEffect(() => {
if (!isUndefined(payload)) {
logEvent('Alert Channel: Channel list page visited', {
number: payload?.length,
});
}
}, [payload]);
if (error) {
return <Typography>{errorMessage}</Typography>;
}

View File

@@ -12,7 +12,6 @@ import { ColumnType, TablePaginationConfig } from 'antd/es/table';
import { FilterValue, SorterResult } from 'antd/es/table/interface';
import { ColumnsType } from 'antd/lib/table';
import { FilterConfirmProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import getAll from 'api/errors/getAll';
import getErrorCounts from 'api/errors/getErrorCounts';
import { ResizeTable } from 'components/ResizeTable';
@@ -24,8 +23,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
@@ -412,26 +410,6 @@ function AllErrors(): JSX.Element {
[pathname],
);
const logEventCalledRef = useRef(false);
useEffect(() => {
if (
!logEventCalledRef.current &&
!isUndefined(errorCountResponse.data?.payload)
) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === 'resource_deployment_environment',
)?.tagValue;
logEvent('Exception: List page visited', {
numberOfExceptions: errorCountResponse?.data?.payload,
selectedEnvironments,
resourceAttributeUsed: !!queries?.length,
});
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [errorCountResponse.data?.payload]);
return (
<ResizeTable
columns={columns}

View File

@@ -5,6 +5,7 @@
.app-content {
width: calc(100% - 64px);
overflow: auto;
z-index: 0;
.content-container {

View File

@@ -3,13 +3,12 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes';
import SideNav from 'container/SideNav';
@@ -28,6 +27,7 @@ import {
useRef,
useState,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
@@ -38,6 +38,7 @@ import { sideBarCollapse } from 'store/actions';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_LATEST_VERSION,
@@ -65,7 +66,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
const [
getUserVersionResponse,
getUserLatestVersionResponse,
getDynamicConfigsResponse,
] = useQueries([
{
queryFn: getUserVersion,
queryKey: ['getUserVersion', user?.accessJwt],
@@ -76,6 +81,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getDynamicConfigs,
queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]);
useEffect(() => {
@@ -86,7 +95,15 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
getUserVersionResponse.refetch();
}
}, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]);
if (getDynamicConfigsResponse.status === 'idle') {
getDynamicConfigsResponse.refetch();
}
}, [
getUserLatestVersionResponse,
getUserVersionResponse,
isLoggedIn,
getDynamicConfigsResponse,
]);
const { children } = props;
@@ -94,6 +111,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0);
const latestConfigCounter = useRef(0);
const { notifications } = useNotifications();
@@ -171,6 +189,23 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
},
});
}
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
getDynamicConfigsResponse.data &&
getDynamicConfigsResponse.data.payload &&
latestConfigCounter.current === 0
) {
latestConfigCounter.current = 1;
dispatch({
type: UPDATE_CONFIGS,
payload: {
configs: getDynamicConfigsResponse.data.payload,
},
});
}
}, [
dispatch,
isLoggedIn,
@@ -185,6 +220,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserLatestVersionResponse.isFetched,
getUserVersionResponse.isFetched,
getUserLatestVersionResponse.isSuccess,
getDynamicConfigsResponse.data,
getDynamicConfigsResponse.isFetched,
getDynamicConfigsResponse.isSuccess,
notifications,
]);
@@ -198,8 +236,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
pathname === ROUTES.GET_STARTED_AWS_MONITORING ||
pathname === ROUTES.GET_STARTED_AZURE_MONITORING;
pathname === ROUTES.GET_STARTED_AWS_MONITORING;
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
@@ -304,31 +341,26 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
collapsed={collapsed}
/>
)}
<div
className={cx('app-content', collapsed ? 'collapsed' : '')}
data-overlayscrollbars-initialize
>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent data-overlayscrollbars-initialize>
<OverlayScrollbar>
<ChildrenContainer
style={{
margin:
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView()
? 0
: '0 1rem',
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
</OverlayScrollbar>
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<LayoutContent>
<ChildrenContainer
style={{
margin:
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView()
? 0
: '0 1rem',
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
</LayoutContent>
</Sentry.ErrorBoundary>
</ErrorBoundary>
</div>
</Flex>
</Layout>

View File

@@ -13,6 +13,7 @@ export const Layout = styled(LayoutComponent)`
`;
export const LayoutContent = styled(LayoutComponent.Content)`
overflow-y: auto;
height: 100%;
&::-webkit-scrollbar {
width: 0.1rem;

View File

@@ -19,10 +19,10 @@ import { ColumnsType } from 'antd/es/table';
import updateCreditCardApi from 'api/billing/checkout';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import manageCreditCardApi from 'api/billing/manage';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useAnalytics from 'hooks/analytics/useAnalytics';
import useAxiosError from 'hooks/useAxiosError';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
@@ -137,6 +137,8 @@ export default function BillingContainer(): JSX.Element {
Partial<UsageResponsePayloadProps>
>({});
const { trackEvent } = useAnalytics();
const { isFetching, data: licensesData, error: licenseError } = useLicense();
const { user, org } = useSelector<AppState, AppReducer>((state) => state.app);
@@ -314,7 +316,7 @@ export default function BillingContainer(): JSX.Element {
const handleBilling = useCallback(async () => {
if (isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription) {
logEvent('Billing : Upgrade Plan', {
trackEvent('Billing : Upgrade Plan', {
user: pick(user, ['email', 'userId', 'name']),
org,
});
@@ -325,7 +327,7 @@ export default function BillingContainer(): JSX.Element {
cancelURL: window.location.href,
});
} else {
logEvent('Billing : Manage Billing', {
trackEvent('Billing : Manage Billing', {
user: pick(user, ['email', 'userId', 'name']),
org,
});

View File

@@ -11,12 +11,11 @@ import testOpsGenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -44,10 +43,6 @@ function CreateAlertChannels({
const [formInstance] = Form.useForm();
useEffect(() => {
logEvent('Alert Channel: Create channel page visited', {});
}, []);
const [selectedConfig, setSelectedConfig] = useState<
Partial<
SlackChannel &
@@ -144,25 +139,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareSlackRequest, t, notifications]);
const prepareWebhookRequest = useCallback(() => {
@@ -211,25 +200,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareWebhookRequest, t, notifications]);
const preparePagerRequest = useCallback(() => {
@@ -262,8 +245,8 @@ function CreateAlertChannels({
setSavingState(true);
const request = preparePagerRequest();
try {
if (request) {
if (request) {
try {
const response = await createPagerApi(request);
if (response.statusCode === 200) {
@@ -272,31 +255,20 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
} catch (e) {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
description: t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
}
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [t, notifications, preparePagerRequest]);
const prepareOpsgenieRequest = useCallback(
@@ -323,25 +295,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareOpsgenieRequest, t, notifications]);
const prepareEmailRequest = useCallback(
@@ -366,25 +332,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareEmailRequest, t, notifications]);
const prepareMsTeamsRequest = useCallback(
@@ -410,25 +370,19 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_creation_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareMsTeamsRequest, t, notifications]);
const onSaveHandler = useCallback(
@@ -446,15 +400,7 @@ function CreateAlertChannels({
const functionToCall = functionMapper[value as keyof typeof functionMapper];
if (functionToCall) {
const result = await functionToCall();
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status: result?.status,
statusMessage: result?.statusMessage,
});
functionToCall();
} else {
notifications.error({
message: 'Error',
@@ -463,7 +409,6 @@ function CreateAlertChannels({
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
onSlackHandler,
onWebhookHandler,
@@ -527,25 +472,14 @@ function CreateAlertChannels({
description: t('channel_test_failed'),
});
}
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_test_unexpected'),
});
}
setTestingState(false);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
prepareWebhookRequest,
t,

View File

@@ -1,6 +1,4 @@
import { Row, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -36,13 +34,6 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
default:
break;
}
logEvent('Alert: Sample alert link clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[option],
link: url,
page: 'New alert data source selection page',
});
window.open(url, '_blank');
}
const renderOptions = useMemo(

View File

@@ -1,5 +1,4 @@
import { Form, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query';
import FormAlertRules from 'container/FormAlertRules';
@@ -69,8 +68,6 @@ function CreateRules(): JSX.Element {
useEffect(() => {
if (alertType) {
onSelectType(alertType);
} else {
logEvent('Alert: New alert data source selection page visited', {});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alertType]);

View File

@@ -11,7 +11,6 @@ import testOpsgenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import {
ChannelType,
@@ -90,7 +89,7 @@ function EditAlertChannels({
description: t('webhook_url_required'),
});
setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') };
return;
}
const response = await editSlackApi(prepareSlackRequest());
@@ -102,17 +101,13 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareSlackRequest, t, notifications, selectedConfig]);
const prepareWebhookRequest = useCallback(() => {
@@ -141,13 +136,13 @@ function EditAlertChannels({
if (selectedConfig?.api_url === '') {
showError(t('webhook_url_required'));
setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') };
return;
}
if (username && (!password || password === '')) {
showError(t('username_no_password'));
setSavingState(false);
return { status: 'failed', statusMessage: t('username_no_password') };
return;
}
const response = await editWebhookApi(prepareWebhookRequest());
@@ -159,15 +154,10 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
showError(response.error || t('channel_edit_failed'));
}
showError(response.error || t('channel_edit_failed'));
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
const prepareEmailRequest = useCallback(
@@ -191,18 +181,13 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareEmailRequest, t, notifications]);
const preparePagerRequest = useCallback(
@@ -233,7 +218,7 @@ function EditAlertChannels({
description: validationError,
});
setSavingState(false);
return { status: 'failed', statusMessage: validationError };
return;
}
const response = await editPagerApi(preparePagerRequest());
@@ -244,18 +229,13 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback(
@@ -279,7 +259,7 @@ function EditAlertChannels({
description: t('api_key_required'),
});
setSavingState(false);
return { status: 'failed', statusMessage: t('api_key_required') };
return;
}
const response = await editOpsgenie(prepareOpsgenieRequest());
@@ -291,18 +271,13 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback(
@@ -326,7 +301,7 @@ function EditAlertChannels({
description: t('webhook_url_required'),
});
setSavingState(false);
return { status: 'failed', statusMessage: t('webhook_url_required') };
return;
}
const response = await editMsTeamsApi(prepareMsTeamsRequest());
@@ -338,46 +313,31 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
return { status: 'success', statusMessage: t('channel_edit_done') };
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback(
async (value: ChannelType) => {
let result;
(value: ChannelType) => {
if (value === ChannelType.Slack) {
result = await onSlackEditHandler();
onSlackEditHandler();
} else if (value === ChannelType.Webhook) {
result = await onWebhookEditHandler();
onWebhookEditHandler();
} else if (value === ChannelType.Pagerduty) {
result = await onPagerEditHandler();
onPagerEditHandler();
} else if (value === ChannelType.MsTeams) {
result = await onMsTeamsEditHandler();
onMsTeamsEditHandler();
} else if (value === ChannelType.Opsgenie) {
result = await onOpsgenieEditHandler();
onOpsgenieEditHandler();
} else if (value === ChannelType.Email) {
result = await onEmailEditHandler();
onEmailEditHandler();
}
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status: result?.status,
statusMessage: result?.statusMessage,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
onSlackEditHandler,
onWebhookEditHandler,
@@ -439,14 +399,6 @@ function EditAlertChannels({
description: t('channel_test_failed'),
});
}
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) {
notifications.error({
message: 'Error',
@@ -455,7 +407,6 @@ function EditAlertChannels({
}
setTestingState(false);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
t,
prepareWebhookRequest,

View File

@@ -1,34 +1,8 @@
import './EmptyLogsSearch.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { useEffect, useRef } from 'react';
import { DataSource, PanelTypeKeys } from 'types/common/queryBuilder';
export default function EmptyLogsSearch({
dataSource,
panelType,
}: {
dataSource: DataSource;
panelType: PanelTypeKeys;
}): JSX.Element {
const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current) {
if (dataSource === DataSource.TRACES) {
logEvent('Traces Explorer: No results', {
panelType,
});
} else if (dataSource === DataSource.LOGS) {
logEvent('Logs Explorer: No results', {
panelType,
});
}
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
export default function EmptyLogsSearch(): JSX.Element {
return (
<div className="empty-logs-search-container">
<div className="empty-logs-search-container-content">

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