Compare commits
2 Commits
feat/gener
...
error-resp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a58d70079b | ||
|
|
2ae341cfa9 |
1
.github/workflows/push.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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:**
|
||||
|
||||
1
Makefile
@@ -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/...
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -237,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
[
|
||||
|
||||
@@ -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:
|
||||
[
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,7 @@ type ServerOptions struct {
|
||||
// alert specific params
|
||||
DisableRules bool
|
||||
RuleRepoURL string
|
||||
PreferDelta bool
|
||||
PreferSpanMetrics bool
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
@@ -255,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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -17,19 +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")
|
||||
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)
|
||||
@@ -61,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{}
|
||||
@@ -85,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",
|
||||
@@ -95,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
|
||||
@@ -110,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"))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -120,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}
|
||||
@@ -144,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}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,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
|
||||
@@ -186,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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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"
|
||||
}
|
||||
@@ -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 bit’s enough ⎯ we’re getting your {{dataSource}}!"
|
||||
"logged_in": "Logged In"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -17,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';
|
||||
@@ -39,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,
|
||||
@@ -93,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 =
|
||||
@@ -113,7 +112,9 @@ function App(): JSX.Element {
|
||||
};
|
||||
|
||||
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
|
||||
|
||||
const domain = extractDomain(email);
|
||||
|
||||
const hostNameParts = hostname.split('.');
|
||||
|
||||
const groupTraits = {
|
||||
@@ -126,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(() => {
|
||||
@@ -163,6 +144,10 @@ function App(): JSX.Element {
|
||||
!isIdentifiedUser
|
||||
) {
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
|
||||
|
||||
if (isCloudUserVal) {
|
||||
enableAnalytics(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -210,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]);
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ function CustomTimePicker({
|
||||
)
|
||||
}
|
||||
arrow={false}
|
||||
trigger="click"
|
||||
trigger="hover"
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
style={{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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} />
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -40,5 +40,4 @@ export const getComponentForPanelType = (
|
||||
export const AVAILABLE_EXPORT_PANEL_TYPES = [
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
PANEL_TYPES.TABLE,
|
||||
PANEL_TYPES.LIST,
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
/* 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';
|
||||
@@ -27,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';
|
||||
@@ -37,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,
|
||||
@@ -64,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],
|
||||
@@ -75,6 +81,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
queryKey: ['getUserLatestVersion', user?.accessJwt],
|
||||
enabled: isLoggedIn,
|
||||
},
|
||||
{
|
||||
queryFn: getDynamicConfigs,
|
||||
queryKey: ['getDynamicConfigs', user?.accessJwt],
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -85,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;
|
||||
|
||||
@@ -93,6 +111,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
const latestConfigCounter = useRef(0);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
@@ -170,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,
|
||||
@@ -184,6 +220,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
getUserLatestVersionResponse.isFetched,
|
||||
getUserVersionResponse.isFetched,
|
||||
getUserLatestVersionResponse.isSuccess,
|
||||
getDynamicConfigsResponse.data,
|
||||
getDynamicConfigsResponse.isFetched,
|
||||
getDynamicConfigsResponse.isSuccess,
|
||||
notifications,
|
||||
]);
|
||||
|
||||
@@ -197,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,7 +342,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
/>
|
||||
)}
|
||||
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<LayoutContent>
|
||||
<ChildrenContainer
|
||||
style={{
|
||||
@@ -322,7 +360,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
{children}
|
||||
</ChildrenContainer>
|
||||
</LayoutContent>
|
||||
</Sentry.ErrorBoundary>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Flex>
|
||||
</Layout>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -91,7 +91,8 @@
|
||||
box-shadow: none !important;
|
||||
|
||||
&.ant-btn-round {
|
||||
padding: 8px 12px 8px 10px;
|
||||
padding-inline-start: 10px;
|
||||
padding-inline-end: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import createDashboard from 'api/dashboard/create';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
@@ -71,7 +70,6 @@ function ExportPanelContainer({
|
||||
ns: 'dashboard',
|
||||
}),
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
}, [t, createNewDashboard]);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import './FormAlertRules.styles.scss';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Select, Switch, Tooltip } from 'antd';
|
||||
import getChannels from 'api/channels/getAll';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
@@ -12,7 +10,6 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
@@ -76,24 +73,9 @@ function BasicInfo({
|
||||
|
||||
const noChannels = channels.payload?.length === 0;
|
||||
const handleCreateNewChannels = useCallback(() => {
|
||||
logEvent('Alert: Create notification channel button clicked', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
ruleId: isNewRule ? 0 : alertDef?.id,
|
||||
});
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!channels.loading && isNewRule) {
|
||||
logEvent('Alert: New alert creation page visited', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
numberOfChannels: channels.payload?.length,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [channels.payload, channels.loading]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step3')} </StepHeading>
|
||||
|
||||
@@ -246,19 +246,17 @@ function ChartPreview({
|
||||
return (
|
||||
<ChartContainer>
|
||||
{headline}
|
||||
|
||||
<div ref={graphRef} style={{ height: '100%' }}>
|
||||
{queryResponse.isLoading && (
|
||||
<Spinner size="large" tip="Loading..." height="100%" />
|
||||
)}
|
||||
{(queryResponse?.isError || queryResponse?.error) && (
|
||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||
<InfoCircleOutlined />{' '}
|
||||
{queryResponse.error.message || t('preview_chart_unexpected_error')}
|
||||
</FailedMessageContainer>
|
||||
)}
|
||||
|
||||
{chartData && !queryResponse.isError && (
|
||||
{(queryResponse?.isError || queryResponse?.error) && (
|
||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||
<InfoCircleOutlined />{' '}
|
||||
{queryResponse.error.message || t('preview_chart_unexpected_error')}
|
||||
</FailedMessageContainer>
|
||||
)}
|
||||
{chartData && !queryResponse.isError && (
|
||||
<div ref={graphRef} style={{ height: '100%' }}>
|
||||
{queryResponse.isLoading && (
|
||||
<Spinner size="large" tip="Loading..." height="100%" />
|
||||
)}
|
||||
<GridPanelSwitch
|
||||
options={options}
|
||||
panelType={graphType}
|
||||
@@ -270,8 +268,8 @@ function ChartPreview({
|
||||
query={query || initialQueriesMap.metrics}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ChartContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import './QuerySection.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tabs, Tooltip } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
@@ -32,7 +31,6 @@ function QuerySection({
|
||||
runQuery,
|
||||
alertDef,
|
||||
panelType,
|
||||
ruleId,
|
||||
}: QuerySectionProps): JSX.Element {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('alerts');
|
||||
@@ -160,15 +158,7 @@ function QuerySection({
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={(): void => {
|
||||
runQuery();
|
||||
logEvent('Alert: Stage and run query', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: queryCategory,
|
||||
});
|
||||
}}
|
||||
onClick={runQuery}
|
||||
className="stage-run-query"
|
||||
icon={<Play size={14} />}
|
||||
>
|
||||
@@ -238,7 +228,6 @@ interface QuerySectionProps {
|
||||
runQuery: VoidFunction;
|
||||
alertDef: AlertDef;
|
||||
panelType: PANEL_TYPES;
|
||||
ruleId: number;
|
||||
}
|
||||
|
||||
export default QuerySection;
|
||||
|
||||
@@ -12,10 +12,8 @@ import {
|
||||
} from 'antd';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import testAlertApi from 'api/alerts/testAlert';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
||||
import { alertHelpMessage } from 'components/facingIssueBtn/util';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -79,8 +77,7 @@ function FormAlertRules({
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
// In case of alert the panel types should always be "Graph" only
|
||||
const panelType = PANEL_TYPES.TIME_SERIES;
|
||||
const panelType = urlQuery.get(QueryParams.panelTypes) as PANEL_TYPES | null;
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
@@ -340,13 +337,8 @@ function FormAlertRules({
|
||||
return;
|
||||
}
|
||||
const postableAlert = memoizedPreparePostData();
|
||||
|
||||
setLoading(true);
|
||||
|
||||
let logData = {
|
||||
status: 'error',
|
||||
statusMessage: t('unexpected_error'),
|
||||
};
|
||||
|
||||
try {
|
||||
const apiReq =
|
||||
ruleId && ruleId > 0
|
||||
@@ -356,15 +348,10 @@ function FormAlertRules({
|
||||
const response = await saveAlertApi(apiReq);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
logData = {
|
||||
status: 'success',
|
||||
statusMessage:
|
||||
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
||||
};
|
||||
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: logData.statusMessage,
|
||||
description:
|
||||
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
|
||||
});
|
||||
|
||||
// invalidate rule in cache
|
||||
@@ -379,42 +366,18 @@ function FormAlertRules({
|
||||
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
|
||||
}, 2000);
|
||||
} else {
|
||||
logData = {
|
||||
status: 'error',
|
||||
statusMessage: response.error || t('unexpected_error'),
|
||||
};
|
||||
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: logData.statusMessage,
|
||||
description: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logData = {
|
||||
status: 'error',
|
||||
statusMessage: t('unexpected_error'),
|
||||
};
|
||||
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: logData.statusMessage,
|
||||
description: t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
logEvent('Alert: Save alert', {
|
||||
...logData,
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[postableAlert?.alertType as AlertTypes],
|
||||
channelNames: postableAlert?.preferredChannels,
|
||||
broadcastToAll: postableAlert?.broadcastToAll,
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: currentQuery.queryType,
|
||||
alertId: postableAlert?.id,
|
||||
alertName: postableAlert?.alert,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
isFormValid,
|
||||
memoizedPreparePostData,
|
||||
@@ -450,7 +413,6 @@ function FormAlertRules({
|
||||
}
|
||||
const postableAlert = memoizedPreparePostData();
|
||||
|
||||
let statusResponse = { status: 'failed', message: '' };
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await testAlertApi({ data: postableAlert });
|
||||
@@ -462,43 +424,25 @@ function FormAlertRules({
|
||||
message: 'Error',
|
||||
description: t('no_alerts_found'),
|
||||
});
|
||||
statusResponse = { status: 'failed', message: t('no_alerts_found') };
|
||||
} else {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('rule_test_fired'),
|
||||
});
|
||||
statusResponse = { status: 'success', message: t('rule_test_fired') };
|
||||
}
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('unexpected_error'),
|
||||
});
|
||||
statusResponse = {
|
||||
status: 'failed',
|
||||
message: response.error || t('unexpected_error'),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('unexpected_error'),
|
||||
});
|
||||
statusResponse = { status: 'failed', message: t('unexpected_error') };
|
||||
}
|
||||
setLoading(false);
|
||||
logEvent('Alert: Test notification', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
channelNames: postableAlert?.preferredChannels,
|
||||
broadcastToAll: postableAlert?.broadcastToAll,
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: currentQuery.queryType,
|
||||
status: statusResponse.status,
|
||||
statusMessage: statusResponse.message,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
||||
|
||||
const renderBasicInfo = (): JSX.Element => (
|
||||
@@ -568,16 +512,6 @@ function FormAlertRules({
|
||||
|
||||
const isRuleCreated = !ruleId || ruleId === 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRuleCreated) {
|
||||
logEvent('Alert: Edit page visited', {
|
||||
ruleId,
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertType as AlertTypes],
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
function handleRedirection(option: AlertTypes): void {
|
||||
let url = '';
|
||||
switch (option) {
|
||||
@@ -600,13 +534,6 @@ function FormAlertRules({
|
||||
default:
|
||||
break;
|
||||
}
|
||||
logEvent('Alert: Check example alert clicked', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
@@ -644,7 +571,6 @@ function FormAlertRules({
|
||||
alertDef={alertDef}
|
||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||
key={currentQuery.queryType}
|
||||
ruleId={ruleId}
|
||||
/>
|
||||
|
||||
<RuleOptions
|
||||
|
||||
@@ -524,13 +524,7 @@ function GeneralSettings({
|
||||
) {
|
||||
return (
|
||||
<Fragment key={category.name}>
|
||||
<Col
|
||||
xs={22}
|
||||
xl={11}
|
||||
key={category.name}
|
||||
style={{ margin: '0.5rem' }}
|
||||
data-testid={`${category.name.toLowerCase()}-card`}
|
||||
>
|
||||
<Col xs={22} xl={11} key={category.name} style={{ margin: '0.5rem' }}>
|
||||
<Card style={{ height: '100%' }}>
|
||||
<Typography.Title style={{ margin: 0 }} level={3}>
|
||||
{category.name}
|
||||
@@ -560,7 +554,6 @@ function GeneralSettings({
|
||||
type="primary"
|
||||
onClick={category.save.modalOpen}
|
||||
disabled={category.save.isDisabled}
|
||||
data-testid="retention-submit-button"
|
||||
>
|
||||
{category.save.saveButtonText}
|
||||
</Button>
|
||||
@@ -581,7 +574,6 @@ function GeneralSettings({
|
||||
centered
|
||||
open={category.save.modal}
|
||||
confirmLoading={category.save.apiLoading}
|
||||
data-testid={`${category.name.toLowerCase()}-modal`}
|
||||
>
|
||||
<Typography>
|
||||
{t('retention_confirmation_description', {
|
||||
@@ -605,16 +597,14 @@ function GeneralSettings({
|
||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||
<ErrorTextContainer>
|
||||
{!isCloudUserVal && (
|
||||
<div data-testid="help-icon">
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to set retention period`,
|
||||
url: 'https://signoz.io/docs/userguide/retention-period/',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TextToolTip
|
||||
{...{
|
||||
text: `More details on how to set retention period`,
|
||||
url: 'https://signoz.io/docs/userguide/retention-period/',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{errorText && <ErrorText data-testid="error-text">{errorText}</ErrorText>}
|
||||
{errorText && <ErrorText>{errorText}</ErrorText>}
|
||||
</ErrorTextContainer>
|
||||
|
||||
<Row justify="start">{renderConfig}</Row>
|
||||
@@ -625,7 +615,7 @@ function GeneralSettings({
|
||||
);
|
||||
}
|
||||
|
||||
export interface GeneralSettingsProps {
|
||||
interface GeneralSettingsProps {
|
||||
getAvailableDiskPayload: GetDisksPayload;
|
||||
metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload;
|
||||
tracesTtlValuesPayload: GetRetentionPeriodTracesPayload;
|
||||
|
||||
@@ -95,11 +95,9 @@ function Retention({
|
||||
|
||||
return (
|
||||
<RetentionContainer>
|
||||
<Row justify="space-between" aria-label={text}>
|
||||
<Row justify="space-between">
|
||||
<Col span={12} style={{ display: 'flex' }}>
|
||||
<RetentionFieldLabel data-testid="retention-field-label">
|
||||
{text}
|
||||
</RetentionFieldLabel>
|
||||
<RetentionFieldLabel>{text}</RetentionFieldLabel>
|
||||
</Col>
|
||||
<Row justify="end">
|
||||
<RetentionFieldInputContainer>
|
||||
@@ -108,14 +106,12 @@ function Retention({
|
||||
disabled={isCloudUserVal}
|
||||
onChange={(e): void => onChangeHandler(e, setSelectedValue)}
|
||||
style={{ width: 75 }}
|
||||
data-testid="retention-field-input"
|
||||
/>
|
||||
<Select
|
||||
value={selectedTimeUnit}
|
||||
onChange={currentSelectedOption}
|
||||
disabled={isCloudUserVal}
|
||||
style={{ width: 100 }}
|
||||
data-testid="retention-field-dropdown"
|
||||
>
|
||||
{menuItems}
|
||||
</Select>
|
||||
|
||||
@@ -60,7 +60,6 @@ function StatusMessage({
|
||||
style={{
|
||||
color: messageColor,
|
||||
}}
|
||||
data-testid="status-message"
|
||||
>
|
||||
{statusMessage}
|
||||
</Col>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Typography } from 'antd';
|
||||
import getDisks from 'api/disks/getDisks';
|
||||
import getRetentionPeriodApi from 'api/settings/getRetention';
|
||||
import Spinner from 'components/Spinner';
|
||||
import GeneralSettingsContainer from 'container/GeneralSettings/GeneralSettings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
@@ -12,6 +11,8 @@ import { TTTLType } from 'types/api/settings/common';
|
||||
import { PayloadProps as GetRetentionPeriodAPIPayloadProps } from 'types/api/settings/getRetention';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import GeneralSettingsContainer from './GeneralSettings';
|
||||
|
||||
type TRetentionAPIReturn<T extends TTTLType> = Promise<
|
||||
SuccessResponse<GetRetentionPeriodAPIPayloadProps<T>> | ErrorResponse
|
||||
>;
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function GeneralSettingsCloud(): JSX.Element {
|
||||
return (
|
||||
<Card className="general-settings-container">
|
||||
<Info size={16} />
|
||||
<Typography.Text data-testid="cloud-user-info-card">
|
||||
<Typography.Text>
|
||||
Please <a href="mailto:cloud-support@signoz.io"> email us </a> or connect
|
||||
with us via intercom support to change the retention period.
|
||||
</Typography.Text>
|
||||
|
||||
@@ -80,8 +80,6 @@ function FullView({
|
||||
query: updatedQuery,
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
fillGaps: widget.fillSpans,
|
||||
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
|
||||
};
|
||||
}
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
|
||||
@@ -108,8 +108,6 @@ function GridCardGraph({
|
||||
query: updatedQuery,
|
||||
globalSelectedInterval,
|
||||
variables: getDashboardVariables(variables),
|
||||
fillGaps: widget.fillSpans,
|
||||
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
|
||||
};
|
||||
}
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
@@ -124,7 +122,6 @@ function GridCardGraph({
|
||||
limit: updatedQuery.builder.queryData[0].limit || 0,
|
||||
},
|
||||
},
|
||||
fillGaps: widget.fillSpans,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -155,7 +152,6 @@ function GridCardGraph({
|
||||
widget?.query,
|
||||
widget?.panelTypes,
|
||||
widget.timePreferance,
|
||||
widget.fillSpans,
|
||||
requestData,
|
||||
],
|
||||
retry(failureCount, error): boolean {
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
export const tableDataMultipleQueriesSuccessResponse = {
|
||||
columns: [
|
||||
{
|
||||
name: 'service_name',
|
||||
queryName: '',
|
||||
isValueColumn: false,
|
||||
},
|
||||
{
|
||||
name: 'A',
|
||||
queryName: 'A',
|
||||
isValueColumn: true,
|
||||
},
|
||||
{
|
||||
name: 'B',
|
||||
queryName: 'B',
|
||||
isValueColumn: true,
|
||||
},
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
data: {
|
||||
A: 4196.71,
|
||||
B: 'n/a',
|
||||
service_name: 'demo-app',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 500.83,
|
||||
B: 'n/a',
|
||||
service_name: 'customer',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 499.5,
|
||||
B: 'n/a',
|
||||
service_name: 'mysql',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 293.22,
|
||||
B: 'n/a',
|
||||
service_name: 'frontend',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 230.03,
|
||||
B: 'n/a',
|
||||
service_name: 'driver',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 67.09,
|
||||
B: 'n/a',
|
||||
service_name: 'route',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 30.96,
|
||||
B: 'n/a',
|
||||
service_name: 'redis',
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
A: 'n/a',
|
||||
B: 112.27,
|
||||
service_name: 'n/a',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const widgetQueryWithLegend = {
|
||||
clickhouse_sql: [
|
||||
{
|
||||
name: 'A',
|
||||
legend: '',
|
||||
disabled: false,
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
promql: [
|
||||
{
|
||||
name: 'A',
|
||||
query: '',
|
||||
legend: '',
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
dataSource: 'metrics',
|
||||
queryName: 'A',
|
||||
aggregateOperator: 'count',
|
||||
aggregateAttribute: {
|
||||
dataType: 'float64',
|
||||
id: 'signoz_latency--float64--ExponentialHistogram--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'signoz_latency',
|
||||
type: 'ExponentialHistogram',
|
||||
},
|
||||
timeAggregation: '',
|
||||
spaceAggregation: 'p90',
|
||||
functions: [],
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
stepInterval: 60,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
groupBy: [
|
||||
{
|
||||
dataType: 'string',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'service_name',
|
||||
type: 'tag',
|
||||
id: 'service_name--string--tag--false',
|
||||
},
|
||||
],
|
||||
legend: 'p99',
|
||||
reduceTo: 'avg',
|
||||
},
|
||||
{
|
||||
dataSource: 'metrics',
|
||||
queryName: 'B',
|
||||
aggregateOperator: 'rate',
|
||||
aggregateAttribute: {
|
||||
dataType: 'float64',
|
||||
id: 'system_disk_operations--float64--Sum--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'system_disk_operations',
|
||||
type: 'Sum',
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
functions: [],
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
expression: 'B',
|
||||
disabled: false,
|
||||
stepInterval: 60,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
groupBy: [],
|
||||
legend: '',
|
||||
reduceTo: 'avg',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
id: '48ad5a67-9a3c-49d4-a886-d7a34f8b875d',
|
||||
queryType: 'builder',
|
||||
};
|
||||
|
||||
export const expectedOutputWithLegends = {
|
||||
dataSource: [
|
||||
{
|
||||
A: 4196.71,
|
||||
B: 'n/a',
|
||||
service_name: 'demo-app',
|
||||
},
|
||||
{
|
||||
A: 500.83,
|
||||
B: 'n/a',
|
||||
service_name: 'customer',
|
||||
},
|
||||
{
|
||||
A: 499.5,
|
||||
B: 'n/a',
|
||||
service_name: 'mysql',
|
||||
},
|
||||
{
|
||||
A: 293.22,
|
||||
B: 'n/a',
|
||||
service_name: 'frontend',
|
||||
},
|
||||
{
|
||||
A: 230.03,
|
||||
B: 'n/a',
|
||||
service_name: 'driver',
|
||||
},
|
||||
{
|
||||
A: 67.09,
|
||||
B: 'n/a',
|
||||
service_name: 'route',
|
||||
},
|
||||
{
|
||||
A: 30.96,
|
||||
B: 'n/a',
|
||||
service_name: 'redis',
|
||||
},
|
||||
{
|
||||
A: 'n/a',
|
||||
B: 112.27,
|
||||
service_name: 'n/a',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
createColumnsAndDataSource,
|
||||
getQueryLegend,
|
||||
sortFunction,
|
||||
} from '../utils';
|
||||
import {
|
||||
expectedOutputWithLegends,
|
||||
tableDataMultipleQueriesSuccessResponse,
|
||||
widgetQueryWithLegend,
|
||||
} from './response';
|
||||
|
||||
describe('Table Panel utils', () => {
|
||||
it('createColumnsAndDataSource function', () => {
|
||||
const data = tableDataMultipleQueriesSuccessResponse;
|
||||
const query = widgetQueryWithLegend as Query;
|
||||
|
||||
const { columns, dataSource } = createColumnsAndDataSource(data, query);
|
||||
|
||||
expect(dataSource).toStrictEqual(expectedOutputWithLegends.dataSource);
|
||||
|
||||
// this makes sure that the columns are rendered in the same order as response
|
||||
expect(columns[0].title).toBe('service_name');
|
||||
// the next specifically makes sure that the legends are properly applied in multiple queries
|
||||
expect(columns[1].title).toBe('p99');
|
||||
// this makes sure that the query without a legend takes the title from the query response
|
||||
expect(columns[2].title).toBe('B');
|
||||
|
||||
// this is to ensure that the rows properly map to the column data indexes as the dataIndex should be equal to name of the columns
|
||||
// returned in the response as the rows will be mapped with them
|
||||
expect((columns[0] as any).dataIndex).toBe('service_name');
|
||||
expect((columns[1] as any).dataIndex).toBe('A');
|
||||
expect((columns[2] as any).dataIndex).toBe('B');
|
||||
});
|
||||
|
||||
it('getQueryLegend function', () => {
|
||||
const query = widgetQueryWithLegend as Query;
|
||||
|
||||
// query A has a legend of p99
|
||||
expect(getQueryLegend(query, 'A')).toBe('p99');
|
||||
|
||||
// should return undefined when legend not present
|
||||
expect(getQueryLegend(query, 'B')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('sorter function for table sorting', () => {
|
||||
let rowA: {
|
||||
A: string | number;
|
||||
timestamp: number;
|
||||
key: string;
|
||||
} = {
|
||||
A: 22.4,
|
||||
timestamp: 111111,
|
||||
key: '1111',
|
||||
};
|
||||
let rowB: {
|
||||
A: string | number;
|
||||
timestamp: number;
|
||||
key: string;
|
||||
} = {
|
||||
A: 'n/a',
|
||||
timestamp: 111112,
|
||||
key: '1112',
|
||||
};
|
||||
const item = {
|
||||
isValueColumn: true,
|
||||
name: 'A',
|
||||
queryName: 'A',
|
||||
};
|
||||
// A has value and value is considered bigger than n/a hence 1
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(1);
|
||||
|
||||
rowA = {
|
||||
A: 'n/a',
|
||||
timestamp: 111111,
|
||||
key: '1111',
|
||||
};
|
||||
rowB = {
|
||||
A: 22.4,
|
||||
timestamp: 111112,
|
||||
key: '1112',
|
||||
};
|
||||
|
||||
// B has value and value is considered bigger than n/a hence -1
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(-1);
|
||||
|
||||
rowA = {
|
||||
A: 11,
|
||||
timestamp: 111111,
|
||||
key: '1111',
|
||||
};
|
||||
rowB = {
|
||||
A: 22,
|
||||
timestamp: 111112,
|
||||
key: '1112',
|
||||
};
|
||||
|
||||
// A and B has value , since B > A hence A-B
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(-11);
|
||||
|
||||
rowA = {
|
||||
A: 'read',
|
||||
timestamp: 111111,
|
||||
key: '1111',
|
||||
};
|
||||
rowB = {
|
||||
A: 'write',
|
||||
timestamp: 111112,
|
||||
key: '1112',
|
||||
};
|
||||
|
||||
// A and B are strings so A is smaller than B because r comes before w hence -1
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(-1);
|
||||
|
||||
rowA = {
|
||||
A: 'n/a',
|
||||
timestamp: 111111,
|
||||
key: '1111',
|
||||
};
|
||||
rowB = {
|
||||
A: 'n/a',
|
||||
timestamp: 111112,
|
||||
key: '1112',
|
||||
};
|
||||
|
||||
// A and B are strings n/a , since both of them are same hence 0
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,10 @@ import { Space, Tooltip } from 'antd';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { Events } from 'constants/events';
|
||||
import { QueryTable } from 'container/QueryTable';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import {
|
||||
createTableColumnsFromQuery,
|
||||
RowData,
|
||||
} from 'lib/query/createTableColumnsFromQuery';
|
||||
import { cloneDeep, get, isEmpty, set } from 'lodash-es';
|
||||
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -11,11 +14,7 @@ import { eventEmitter } from 'utils/getEventEmitter';
|
||||
|
||||
import { WrapperStyled } from './styles';
|
||||
import { GridTableComponentProps } from './types';
|
||||
import {
|
||||
createColumnsAndDataSource,
|
||||
findMatchingThreshold,
|
||||
TableData,
|
||||
} from './utils';
|
||||
import { findMatchingThreshold } from './utils';
|
||||
|
||||
function GridTableComponent({
|
||||
data,
|
||||
@@ -26,26 +25,28 @@ function GridTableComponent({
|
||||
...props
|
||||
}: GridTableComponentProps): JSX.Element {
|
||||
const { t } = useTranslation(['valueGraph']);
|
||||
|
||||
// create columns and dataSource in the ui friendly structure
|
||||
// use the query from the widget here to extract the legend information
|
||||
const { columns, dataSource: originalDataSource } = useMemo(
|
||||
() => createColumnsAndDataSource((data as unknown) as TableData, query),
|
||||
[query, data],
|
||||
() =>
|
||||
createTableColumnsFromQuery({
|
||||
query,
|
||||
queryTableData: data,
|
||||
}),
|
||||
[data, query],
|
||||
);
|
||||
|
||||
const createDataInCorrectFormat = useCallback(
|
||||
(dataSource: RowData[]): RowData[] =>
|
||||
dataSource.map((d) => {
|
||||
const finalObject = {};
|
||||
|
||||
// we use the order of the columns here to have similar download as the user view
|
||||
columns.forEach((k) => {
|
||||
set(
|
||||
finalObject,
|
||||
get(k, 'title', '') as string,
|
||||
get(d, get(k, 'dataIndex', ''), 'n/a'),
|
||||
const keys = Object.keys(d);
|
||||
keys.forEach((k) => {
|
||||
const label = get(
|
||||
columns.find((c) => get(c, 'dataIndex', '') === k) || {},
|
||||
'title',
|
||||
'',
|
||||
);
|
||||
if (label) {
|
||||
set(finalObject, label as string, d[k]);
|
||||
}
|
||||
});
|
||||
return finalObject as RowData;
|
||||
}),
|
||||
@@ -61,15 +62,10 @@ function GridTableComponent({
|
||||
|
||||
mutateDataSource = mutateDataSource.map(
|
||||
(val): RowData => {
|
||||
const newValue = { ...val };
|
||||
const newValue = val;
|
||||
Object.keys(val).forEach((k) => {
|
||||
if (columnUnits[k]) {
|
||||
// the check below takes care of not adding units for rows that have n/a values
|
||||
newValue[k] =
|
||||
val[k] !== 'n/a'
|
||||
? getYAxisFormattedValue(String(val[k]), columnUnits[k])
|
||||
: val[k];
|
||||
newValue[`${k}_without_unit`] = val[k];
|
||||
newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
|
||||
}
|
||||
});
|
||||
return newValue;
|
||||
@@ -85,6 +81,7 @@ function GridTableComponent({
|
||||
applyColumnUnits,
|
||||
originalDataSource,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableProcessedDataRef) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
|
||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { isEmpty, isNaN } from 'lodash-es';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
// Helper function to evaluate the condition based on the operator
|
||||
function evaluateCondition(
|
||||
@@ -64,107 +56,3 @@ export function findMatchingThreshold(
|
||||
hasMultipleMatches,
|
||||
};
|
||||
}
|
||||
|
||||
export interface TableData {
|
||||
columns: { name: string; queryName: string; isValueColumn: boolean }[];
|
||||
rows: { data: any }[];
|
||||
}
|
||||
|
||||
export function getQueryLegend(
|
||||
currentQuery: Query,
|
||||
queryName: string,
|
||||
): string | undefined {
|
||||
let legend: string | undefined;
|
||||
switch (currentQuery.queryType) {
|
||||
case EQueryType.QUERY_BUILDER:
|
||||
// check if the value is present in the queries
|
||||
legend = currentQuery.builder.queryData.find(
|
||||
(query) => query.queryName === queryName,
|
||||
)?.legend;
|
||||
|
||||
if (!legend) {
|
||||
// check if the value is present in the formula
|
||||
legend = currentQuery.builder.queryFormulas.find(
|
||||
(query) => query.queryName === queryName,
|
||||
)?.legend;
|
||||
}
|
||||
break;
|
||||
case EQueryType.CLICKHOUSE:
|
||||
legend = currentQuery.clickhouse_sql.find(
|
||||
(query) => query.name === queryName,
|
||||
)?.legend;
|
||||
break;
|
||||
case EQueryType.PROM:
|
||||
legend = currentQuery.promql.find((query) => query.name === queryName)
|
||||
?.legend;
|
||||
break;
|
||||
default:
|
||||
legend = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
return legend;
|
||||
}
|
||||
|
||||
export function sortFunction(
|
||||
a: RowData,
|
||||
b: RowData,
|
||||
item: {
|
||||
name: string;
|
||||
queryName: string;
|
||||
isValueColumn: boolean;
|
||||
},
|
||||
): number {
|
||||
// assumption :- number values is bigger than 'n/a'
|
||||
const valueA = Number(a[`${item.name}_without_unit`] ?? a[item.name]);
|
||||
const valueB = Number(b[`${item.name}_without_unit`] ?? b[item.name]);
|
||||
|
||||
// if both the values are numbers then return the difference here
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) {
|
||||
return valueA - valueB;
|
||||
}
|
||||
|
||||
// if valueB is a number then make it bigger value
|
||||
if (isNaN(valueA) && !isNaN(valueB)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// if valueA is number make it the bigger value
|
||||
if (!isNaN(valueA) && isNaN(valueB)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if both of them are strings do the localecompare
|
||||
return ((a[item.name] as string) || '').localeCompare(
|
||||
(b[item.name] as string) || '',
|
||||
);
|
||||
}
|
||||
export function createColumnsAndDataSource(
|
||||
data: TableData,
|
||||
currentQuery: Query,
|
||||
renderColumnCell?: QueryTableProps['renderColumnCell'],
|
||||
): { columns: ColumnsType<RowData>; dataSource: RowData[] } {
|
||||
const columns: ColumnsType<RowData> =
|
||||
data.columns?.reduce<ColumnsType<RowData>>((acc, item) => {
|
||||
// is the column is the value column then we need to check for the available legend
|
||||
const legend = item.isValueColumn
|
||||
? getQueryLegend(currentQuery, item.queryName)
|
||||
: undefined;
|
||||
|
||||
const column: ColumnType<RowData> = {
|
||||
dataIndex: item.name,
|
||||
// if no legend present then rely on the column name value
|
||||
title: !isEmpty(legend) ? legend : item.name,
|
||||
width: QUERY_TABLE_CONFIG.width,
|
||||
render: renderColumnCell && renderColumnCell[item.name],
|
||||
sorter: (a: RowData, b: RowData): number => sortFunction(a, b, item),
|
||||
};
|
||||
|
||||
return [...acc, column];
|
||||
}, []) || [];
|
||||
|
||||
// the rows returned have data encapsulation hence removing the same here
|
||||
const dataSource = data.rows?.map((d) => d.data) || [];
|
||||
|
||||
return { columns, dataSource };
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
import { useSelector } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { License } from 'types/api/licenses/def';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
||||
|
||||
@@ -110,13 +109,9 @@ function HeaderContainer(): JSX.Element {
|
||||
|
||||
const { data: licenseData, isFetching, status: licenseStatus } = useLicense();
|
||||
|
||||
const licensesStatus: string =
|
||||
licenseData?.payload?.licenses?.find((e: License) => e.isCurrent)?.status ||
|
||||
'';
|
||||
|
||||
const isLicenseActive =
|
||||
licensesStatus?.toLocaleLowerCase() ===
|
||||
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase();
|
||||
licenseData?.payload?.licenses?.find((e) => e.isCurrent)?.status ===
|
||||
LICENSE_PLAN_STATUS.VALID;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
||||
@@ -7,20 +7,17 @@ interface AlertInfoCardProps {
|
||||
header: string;
|
||||
subheader: string;
|
||||
link: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function AlertInfoCard({
|
||||
header,
|
||||
subheader,
|
||||
link,
|
||||
onClick,
|
||||
}: AlertInfoCardProps): JSX.Element {
|
||||
return (
|
||||
<div
|
||||
className="alert-info-card"
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,6 @@ import './AlertsEmptyState.styles.scss';
|
||||
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -11,26 +10,12 @@ import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import AlertInfoCard from './AlertInfoCard';
|
||||
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
|
||||
import InfoLinkText from './InfoLinkText';
|
||||
|
||||
const alertLogEvents = (
|
||||
title: string,
|
||||
link: string,
|
||||
dataSource?: DataSource,
|
||||
): void => {
|
||||
const attributes = {
|
||||
link,
|
||||
page: 'Alert empty state page',
|
||||
};
|
||||
|
||||
logEvent(title, dataSource ? { ...attributes, dataSource } : attributes);
|
||||
};
|
||||
|
||||
export function AlertsEmptyState(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
||||
@@ -106,33 +91,18 @@ export function AlertsEmptyState(): JSX.Element {
|
||||
link="https://youtu.be/xjxNIqiv4_M"
|
||||
leftIconVisible
|
||||
rightIconVisible
|
||||
onClick={(): void =>
|
||||
alertLogEvents(
|
||||
'Alert: Video tutorial link clicked',
|
||||
'https://youtu.be/xjxNIqiv4_M',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ALERT_INFO_LINKS.map((info) => {
|
||||
const logEventTriggered = (): void =>
|
||||
alertLogEvents(
|
||||
'Alert: Tutorial doc link clicked',
|
||||
info.link,
|
||||
info.dataSource,
|
||||
);
|
||||
return (
|
||||
<InfoLinkText
|
||||
key={info.link}
|
||||
infoText={info.infoText}
|
||||
link={info.link}
|
||||
leftIconVisible={info.leftIconVisible}
|
||||
rightIconVisible={info.rightIconVisible}
|
||||
onClick={logEventTriggered}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{ALERT_INFO_LINKS.map((info) => (
|
||||
<InfoLinkText
|
||||
key={info.link}
|
||||
infoText={info.infoText}
|
||||
link={info.link}
|
||||
leftIconVisible={info.leftIconVisible}
|
||||
rightIconVisible={info.rightIconVisible}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<div className="get-started-text">
|
||||
@@ -143,23 +113,14 @@ export function AlertsEmptyState(): JSX.Element {
|
||||
</Divider>
|
||||
</div>
|
||||
|
||||
{ALERT_CARDS.map((card) => {
|
||||
const logEventTriggered = (): void =>
|
||||
alertLogEvents(
|
||||
'Alert: Sample alert link clicked',
|
||||
card.link,
|
||||
card.dataSource,
|
||||
);
|
||||
return (
|
||||
<AlertInfoCard
|
||||
key={card.link}
|
||||
header={card.header}
|
||||
subheader={card.subheader}
|
||||
link={card.link}
|
||||
onClick={logEventTriggered}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{ALERT_CARDS.map((card) => (
|
||||
<AlertInfoCard
|
||||
key={card.link}
|
||||
header={card.header}
|
||||
subheader={card.subheader}
|
||||
link={card.link}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ interface InfoLinkTextProps {
|
||||
link: string;
|
||||
leftIconVisible: boolean;
|
||||
rightIconVisible: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
function InfoLinkText({
|
||||
@@ -14,12 +13,10 @@ function InfoLinkText({
|
||||
link,
|
||||
leftIconVisible,
|
||||
rightIconVisible,
|
||||
onClick,
|
||||
}: InfoLinkTextProps): JSX.Element {
|
||||
return (
|
||||
<Flex
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
}}
|
||||
className="info-link-container"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const ALERT_INFO_LINKS = [
|
||||
{
|
||||
infoText: 'How to create Metrics-based alerts',
|
||||
@@ -7,7 +5,6 @@ export const ALERT_INFO_LINKS = [
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||
leftIconVisible: false,
|
||||
rightIconVisible: true,
|
||||
dataSource: DataSource.METRICS,
|
||||
},
|
||||
{
|
||||
infoText: 'How to create Log-based alerts',
|
||||
@@ -15,7 +12,6 @@ export const ALERT_INFO_LINKS = [
|
||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||
leftIconVisible: false,
|
||||
rightIconVisible: true,
|
||||
dataSource: DataSource.LOGS,
|
||||
},
|
||||
{
|
||||
infoText: 'How to create Trace-based alerts',
|
||||
@@ -23,7 +19,6 @@ export const ALERT_INFO_LINKS = [
|
||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
|
||||
leftIconVisible: false,
|
||||
rightIconVisible: true,
|
||||
dataSource: DataSource.TRACES,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -31,28 +26,24 @@ export const ALERT_CARDS = [
|
||||
{
|
||||
header: 'Alert on high memory usage',
|
||||
subheader: "Monitor your host's memory usage",
|
||||
dataSource: DataSource.METRICS,
|
||||
link:
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory',
|
||||
},
|
||||
{
|
||||
header: 'Alert on slow external API calls',
|
||||
subheader: 'Monitor your external API calls',
|
||||
dataSource: DataSource.TRACES,
|
||||
link:
|
||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins',
|
||||
},
|
||||
{
|
||||
header: 'Alert on high percentage of timeout errors in logs',
|
||||
subheader: 'Monitor your logs for errors',
|
||||
dataSource: DataSource.LOGS,
|
||||
link:
|
||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins',
|
||||
},
|
||||
{
|
||||
header: 'Alert on high error percentage of an endpoint',
|
||||
subheader: 'Monitor your API endpoint',
|
||||
dataSource: DataSource.METRICS,
|
||||
link:
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5',
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Input, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import DropDown from 'components/DropDown/DropDown';
|
||||
import { listAlertMessage } from 'components/facingIssueBtn/util';
|
||||
import {
|
||||
@@ -42,7 +41,7 @@ import {
|
||||
} from './styles';
|
||||
import Status from './TableComponents/Status';
|
||||
import ToggleAlertState from './ToggleAlertState';
|
||||
import { alertActionLogEvent, filterAlerts } from './utils';
|
||||
import { filterAlerts } from './utils';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
@@ -56,9 +55,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
role,
|
||||
);
|
||||
|
||||
const [editLoader, setEditLoader] = useState<boolean>(false);
|
||||
const [cloneLoader, setCloneLoader] = useState<boolean>(false);
|
||||
|
||||
const params = useUrlQuery();
|
||||
const orderColumnParam = params.get('columnKey');
|
||||
const orderQueryParam = params.get('order');
|
||||
@@ -108,20 +104,15 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}, [notificationsApi, t]);
|
||||
|
||||
const onClickNewAlertHandler = useCallback(() => {
|
||||
logEvent('Alert: New alert button clicked', {
|
||||
number: allAlertRules?.length,
|
||||
});
|
||||
featureResponse
|
||||
.refetch()
|
||||
.then(() => {
|
||||
history.push(ROUTES.ALERTS_NEW);
|
||||
})
|
||||
.catch(handleError);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [featureResponse, handleError]);
|
||||
|
||||
const onEditHandler = (record: GettableAlert) => (): void => {
|
||||
setEditLoader(true);
|
||||
featureResponse
|
||||
.refetch()
|
||||
.then(() => {
|
||||
@@ -138,11 +129,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
|
||||
params.set(QueryParams.ruleId, record.id.toString());
|
||||
|
||||
setEditLoader(false);
|
||||
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
|
||||
})
|
||||
.catch(handleError)
|
||||
.finally(() => setEditLoader(false));
|
||||
.catch(handleError);
|
||||
};
|
||||
|
||||
const onCloneHandler = (
|
||||
@@ -154,41 +143,33 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
};
|
||||
const apiReq = { data: copyAlert };
|
||||
|
||||
try {
|
||||
setCloneLoader(true);
|
||||
const response = await saveAlertApi(apiReq);
|
||||
const response = await saveAlertApi(apiReq);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notificationsApi.success({
|
||||
message: 'Success',
|
||||
description: 'Alert cloned successfully',
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
notificationsApi.success({
|
||||
message: 'Success',
|
||||
description: 'Alert cloned successfully',
|
||||
});
|
||||
|
||||
const { data: refetchData, status } = await refetch();
|
||||
if (status === 'success' && refetchData.payload) {
|
||||
setData(refetchData.payload || []);
|
||||
setTimeout(() => {
|
||||
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
|
||||
params.set(QueryParams.ruleId, String(clonedAlert.id));
|
||||
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
|
||||
}, 2000);
|
||||
}
|
||||
if (status === 'error') {
|
||||
notificationsApi.error({
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const { data: refetchData, status } = await refetch();
|
||||
if (status === 'success' && refetchData.payload) {
|
||||
setData(refetchData.payload || []);
|
||||
setTimeout(() => {
|
||||
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
|
||||
params.set(QueryParams.ruleId, String(clonedAlert.id));
|
||||
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
|
||||
}, 2000);
|
||||
}
|
||||
if (status === 'error') {
|
||||
notificationsApi.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('something_went_wrong'),
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
handleError();
|
||||
console.error(error);
|
||||
} finally {
|
||||
setCloneLoader(false);
|
||||
} else {
|
||||
notificationsApi.error({
|
||||
message: 'Error',
|
||||
description: response.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -326,7 +307,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
width: 10,
|
||||
render: (id: GettableAlert['id'], record): JSX.Element => (
|
||||
<DropDown
|
||||
onDropDownItemClick={(item): void => alertActionLogEvent(item.key, record)}
|
||||
element={[
|
||||
<ToggleAlertState
|
||||
key="1"
|
||||
@@ -334,20 +314,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
setData={setData}
|
||||
id={id}
|
||||
/>,
|
||||
<ColumnButton
|
||||
key="2"
|
||||
onClick={onEditHandler(record)}
|
||||
type="link"
|
||||
loading={editLoader}
|
||||
>
|
||||
<ColumnButton key="2" onClick={onEditHandler(record)} type="link">
|
||||
Edit
|
||||
</ColumnButton>,
|
||||
<ColumnButton
|
||||
key="3"
|
||||
onClick={onCloneHandler(record)}
|
||||
type="link"
|
||||
loading={cloneLoader}
|
||||
>
|
||||
<ColumnButton key="3" onClick={onCloneHandler(record)} type="link">
|
||||
Clone
|
||||
</ColumnButton>,
|
||||
<DeleteAlert
|
||||
@@ -362,9 +332,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
});
|
||||
}
|
||||
|
||||
const paginationConfig = {
|
||||
defaultCurrent: Number(paginationParam) || 1,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<SearchContainer>
|
||||
@@ -394,10 +361,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
shouldSendAlertsLogEvent
|
||||
dynamicColumns={dynamicColumns}
|
||||
onChange={handleChange}
|
||||
pagination={paginationConfig}
|
||||
pagination={{
|
||||
defaultCurrent: Number(paginationParam) || 1,
|
||||
}}
|
||||
facingIssueBtn={{
|
||||
attributes: {
|
||||
screen: 'Alert list page',
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Space } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ReleaseNote from 'components/ReleaseNote';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -21,19 +19,8 @@ function ListAlertRules(): JSX.Element {
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
const logEventCalledRef = useRef(false);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
||||
logEvent('Alert: List page visited', {
|
||||
number: data?.payload?.length,
|
||||
});
|
||||
logEventCalledRef.current = true;
|
||||
}
|
||||
}, [data?.payload]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||
notifications.error({
|
||||
|
||||
@@ -27,8 +27,5 @@ export const ColumnButton = styled(ButtonComponent)`
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-right: 1.5em;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
export const filterAlerts = (
|
||||
@@ -26,32 +23,3 @@ export const filterAlerts = (
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const alertActionLogEvent = (
|
||||
action: string,
|
||||
record: GettableAlert,
|
||||
): void => {
|
||||
let actionValue = '';
|
||||
switch (action) {
|
||||
case '0':
|
||||
actionValue = 'Enable/Disable';
|
||||
break;
|
||||
case '1':
|
||||
actionValue = 'Edit';
|
||||
break;
|
||||
case '2':
|
||||
actionValue = 'Clone';
|
||||
break;
|
||||
case '3':
|
||||
actionValue = 'Delete';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
logEvent('Alert: Action', {
|
||||
ruleId: record.id,
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[record.alertType as AlertTypes],
|
||||
name: record.alert,
|
||||
action: actionValue,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -609,16 +609,6 @@ function DashboardsList(): JSX.Element {
|
||||
</>
|
||||
);
|
||||
|
||||
const paginationConfig = data.length > 20 && {
|
||||
pageSize: 20,
|
||||
showTotal: showPaginationItem,
|
||||
showSizeChanger: false,
|
||||
onChange: (page: any): void => handlePageSizeUpdate(page),
|
||||
current: Number(sortOrder.pagination),
|
||||
defaultCurrent: Number(sortOrder.pagination) || 1,
|
||||
hideOnSinglePage: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dashboards-list-container">
|
||||
<div className="dashboards-list-view-content">
|
||||
@@ -709,16 +699,7 @@ function DashboardsList(): JSX.Element {
|
||||
New Dashboard
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
type="text"
|
||||
className="learn-more"
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
'https://signoz.io/docs/userguide/manage-dashboards?utm_source=product&utm_medium=dashboard-list-empty-state',
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Button type="text" className="learn-more">
|
||||
Learn more
|
||||
</Button>
|
||||
<ArrowUpRight size={16} className="learn-more-arrow" />
|
||||
@@ -832,7 +813,16 @@ function DashboardsList(): JSX.Element {
|
||||
showSorterTooltip
|
||||
loading={isDashboardListLoading || isFilteringDashboards}
|
||||
showHeader={false}
|
||||
pagination={paginationConfig}
|
||||
pagination={
|
||||
data.length > 20 && {
|
||||
pageSize: 20,
|
||||
showTotal: showPaginationItem,
|
||||
showSizeChanger: false,
|
||||
onChange: (page): void => handlePageSizeUpdate(page),
|
||||
current: Number(sortOrder.pagination),
|
||||
defaultCurrent: Number(sortOrder.pagination) || 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -141,6 +141,11 @@ function ImportJSON({
|
||||
colors: {
|
||||
'editor.background': Color.BG_INK_300,
|
||||
},
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 20,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 18,
|
||||
letterSpacing: -0.06,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,11 +233,6 @@ function ImportJSON({
|
||||
fontFamily: 'Space Mono',
|
||||
}}
|
||||
theme={isDarkMode ? 'my-theme' : 'light'}
|
||||
onMount={(_, monaco): void => {
|
||||
document.fonts.ready.then(() => {
|
||||
monaco.editor.remeasureFonts();
|
||||
});
|
||||
}}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
beforeMount={setEditorTheme}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
.label {
|
||||
color: var(--text-robin-400);
|
||||
font-family: SF Mono;
|
||||
font-family: 'Geist Mono';
|
||||
font-family: 'Space Mono', monospace;
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 18px;
|
||||
|
||||
@@ -28,7 +28,7 @@ function JSONView({ logData }: JSONViewProps): JSX.Element {
|
||||
},
|
||||
fontWeight: 400,
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Geist Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
colorDecorators: true,
|
||||
|
||||
@@ -53,7 +53,8 @@ function Overview({
|
||||
enabled: false,
|
||||
},
|
||||
fontWeight: 400,
|
||||
fontFamily: 'Geist Mono',
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 13,
|
||||
lineHeight: '18px',
|
||||
colorDecorators: true,
|
||||
@@ -79,6 +80,12 @@ function Overview({
|
||||
colors: {
|
||||
'editor.background': Color.BG_INK_400,
|
||||
},
|
||||
// fontFamily: 'SF Mono',
|
||||
fontFamily: 'Space Mono',
|
||||
fontSize: 12,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 18,
|
||||
letterSpacing: -0.06,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,11 +124,6 @@ function Overview({
|
||||
onChange={(): void => {}}
|
||||
height="20vh"
|
||||
theme={isDarkMode ? 'my-theme' : 'light'}
|
||||
onMount={(_, monaco): void => {
|
||||
document.fonts.ready.then(() => {
|
||||
monaco.editor.remeasureFonts();
|
||||
});
|
||||
}}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
beforeMount={setEditorTheme}
|
||||
/>
|
||||
|
||||
@@ -57,8 +57,6 @@
|
||||
background: rgba(22, 25, 34, 0.4);
|
||||
|
||||
.value-field {
|
||||
font-family: 'Geist Mono';
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
@@ -289,13 +289,7 @@ function TableView({
|
||||
return (
|
||||
<div className="value-field">
|
||||
<CopyClipboardHOC textToCopy={textToCopy}>
|
||||
<span
|
||||
style={{
|
||||
color: Color.BG_SIENNA_400,
|
||||
whiteSpace: 'pre-wrap',
|
||||
tabSize: 4,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: Color.BG_SIENNA_400 }}>
|
||||
{removeEscapeCharacters(fieldData.value)}
|
||||
</span>
|
||||
</CopyClipboardHOC>
|
||||
|
||||
@@ -9,6 +9,6 @@ export const CardStyled = styled(Card)`
|
||||
height: 200px;
|
||||
min-height: 200px;
|
||||
padding: 0 16px 16px 16px;
|
||||
font-family: 'Geist Mono';
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -37,7 +37,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData';
|
||||
import { cloneDeep, defaultTo, isEmpty, omit, set } from 'lodash-es';
|
||||
import { defaultTo, isEmpty, omit } from 'lodash-es';
|
||||
import { Sliders } from 'lucide-react';
|
||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -63,10 +63,10 @@ import { v4 } from 'uuid';
|
||||
|
||||
function LogsExplorerViews({
|
||||
selectedView,
|
||||
showFrequencyChart,
|
||||
showHistogram,
|
||||
}: {
|
||||
selectedView: SELECTED_VIEWS;
|
||||
showFrequencyChart: boolean;
|
||||
showHistogram: boolean;
|
||||
}): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const history = useHistory();
|
||||
@@ -117,12 +117,6 @@ function LogsExplorerViews({
|
||||
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
||||
}, [stagedQuery]);
|
||||
|
||||
const { options, config } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: initialDataSource || DataSource.LOGS,
|
||||
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||
});
|
||||
|
||||
const orderByTimestamp: OrderByPayload | null = useMemo(() => {
|
||||
const timestampOrderBy = listQuery?.orderBy.find(
|
||||
(item) => item.columnName === 'timestamp',
|
||||
@@ -180,10 +174,10 @@ function LogsExplorerViews({
|
||||
() =>
|
||||
updateAllQueriesOperators(
|
||||
currentQuery || initialQueriesMap.logs,
|
||||
selectedPanelType,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
DataSource.LOGS,
|
||||
),
|
||||
[currentQuery, selectedPanelType, updateAllQueriesOperators],
|
||||
[currentQuery, updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
const handleModeChange = (panelType: PANEL_TYPES): void => {
|
||||
@@ -315,14 +309,6 @@ function LogsExplorerViews({
|
||||
isLoading: isUpdateDashboardLoading,
|
||||
} = useUpdateDashboard();
|
||||
|
||||
const getUpdatedQueryForExport = useCallback((): Query => {
|
||||
const updatedQuery = cloneDeep(currentQuery);
|
||||
|
||||
set(updatedQuery, 'builder.queryData[0].pageSize', 10);
|
||||
|
||||
return updatedQuery;
|
||||
}, [currentQuery]);
|
||||
|
||||
const handleExport = useCallback(
|
||||
(dashboard: Dashboard | null): void => {
|
||||
if (!dashboard || !panelType) return;
|
||||
@@ -333,17 +319,11 @@ function LogsExplorerViews({
|
||||
|
||||
const widgetId = v4();
|
||||
|
||||
const query =
|
||||
panelType === PANEL_TYPES.LIST
|
||||
? getUpdatedQueryForExport()
|
||||
: exportDefaultQuery;
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
query,
|
||||
exportDefaultQuery,
|
||||
widgetId,
|
||||
panelTypeParam,
|
||||
options.selectColumns,
|
||||
);
|
||||
|
||||
updateDashboard(updatedDashboard, {
|
||||
@@ -373,7 +353,7 @@ function LogsExplorerViews({
|
||||
}
|
||||
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query,
|
||||
query: exportDefaultQuery,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
@@ -385,9 +365,7 @@ function LogsExplorerViews({
|
||||
});
|
||||
},
|
||||
[
|
||||
getUpdatedQueryForExport,
|
||||
exportDefaultQuery,
|
||||
options.selectColumns,
|
||||
history,
|
||||
notifications,
|
||||
panelType,
|
||||
@@ -482,6 +460,12 @@ function LogsExplorerViews({
|
||||
selectedView,
|
||||
]);
|
||||
|
||||
const { options, config } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: initialDataSource || DataSource.METRICS,
|
||||
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||
});
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
if (!stagedQuery) return [];
|
||||
|
||||
@@ -561,7 +545,7 @@ function LogsExplorerViews({
|
||||
|
||||
return (
|
||||
<div className="logs-explorer-views-container">
|
||||
{showFrequencyChart && (
|
||||
{showHistogram && (
|
||||
<LogsExplorerChart
|
||||
className="logs-histogram"
|
||||
isLoading={isFetchingListChartData || isLoadingListChartData}
|
||||
|
||||
@@ -76,10 +76,7 @@ const renderer = (): RenderResult =>
|
||||
<VirtuosoMockContext.Provider
|
||||
value={{ viewportHeight: 300, itemHeight: 100 }}
|
||||
>
|
||||
<LogsExplorerViews
|
||||
selectedView={SELECTED_VIEWS.SEARCH}
|
||||
showFrequencyChart
|
||||
/>
|
||||
<LogsExplorerViews selectedView={SELECTED_VIEWS.SEARCH} showHistogram />
|
||||
</VirtuosoMockContext.Provider>
|
||||
</QueryBuilderProvider>
|
||||
</MockQueryClientProvider>
|
||||
@@ -123,7 +120,11 @@ describe('LogsExplorerViews -', () => {
|
||||
|
||||
// switch to table view
|
||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
||||
expect(queryByText('pending_data_placeholder')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'Just a bit of patience, just a little bit’s enough ⎯ we’re getting your logs!',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('check error state', async () => {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import './LogsLoading.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export function LogsLoading(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
return (
|
||||
<div className="loading-logs">
|
||||
<div className="loading-logs-content">
|
||||
@@ -16,7 +13,8 @@ export function LogsLoading(): JSX.Element {
|
||||
/>
|
||||
|
||||
<Typography>
|
||||
{t('pending_data_placeholder', { dataSource: DataSource.LOGS })}
|
||||
Just a bit of patience, just a little bit’s enough ⎯ we’re getting your
|
||||
logs!
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
height: 100%;
|
||||
|
||||
.resize-table {
|
||||
height: calc(100% - 70px);
|
||||
height: calc(100% - 40px);
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -94,26 +93,6 @@ function External(): JSX.Element {
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const errorApmToTraceQuery = useGetAPMToTracesQueries({
|
||||
servicename,
|
||||
isExternalCall: true,
|
||||
filters: [
|
||||
{
|
||||
id: uuid().slice(0, 8),
|
||||
key: {
|
||||
key: 'hasError',
|
||||
dataType: DataTypes.bool,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'hasError--bool--tag--true',
|
||||
},
|
||||
op: 'in',
|
||||
value: ['true'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const externalCallRPSWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -177,7 +156,7 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
selectedTraceTags,
|
||||
timestamp: selectedTimeStamp,
|
||||
apmToTraceQuery: errorApmToTraceQuery,
|
||||
apmToTraceQuery,
|
||||
})}
|
||||
>
|
||||
View Traces
|
||||
|
||||