Compare commits

..

4 Commits

Author SHA1 Message Date
Srikanth Chekuri
ec21d1bca1 fix: tests 2024-01-04 07:53:11 +05:30
Srikanth Chekuri
52e98bcddf chore: add ee query limits 2024-01-04 00:55:58 +05:30
Srikanth Chekuri
a8b7e9582a Merge branch 'develop' of github.com:SigNoz/signoz into query-limits 2024-01-02 20:53:51 +05:30
Srikanth Chekuri
4a042fa413 feat: add time-series-limit config option 2023-11-14 02:58:59 +05:30
231 changed files with 2438 additions and 7825 deletions

View File

@@ -146,11 +146,11 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.38.0
image: signoz/query-service:0.36.2
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--prefer-delta=true"
]
# ports:
# - "6060:6060" # pprof port
@@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.38.0
image: signoz/frontend:0.36.2
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.88.9
image: signoz/signoz-otel-collector:0.88.6
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -237,7 +237,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.88.9
image: signoz/signoz-schema-migrator:0.88.6
deploy:
restart_policy:
condition: on-failure
@@ -249,6 +249,25 @@ services:
# - clickhouse-2
# - clickhouse-3
otel-collector-metrics:
image: signoz/signoz-otel-collector:0.88.6
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",
"--feature-gates=-pkg.translator.prometheus.NormalizeName"
]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
# - "1777:1777" # pprof extension
# - "8888:8888" # OtelCollector internal metrics
# - "13133:13133" # Health check extension
# - "55679:55679" # zPages extension
deploy:
restart_policy:
condition: on-failure
<<: *db-depend
logspout:
image: "gliderlabs/logspout:v3.2.14"
volumes:

View File

@@ -15,9 +15,13 @@ receivers:
# please remove names from below if you want to collect logs from them
- type: filter
id: signoz_logs_filter
expr: 'attributes.container_name matches "^signoz_(logspout|frontend|alertmanager|query-service|otel-collector|clickhouse|zookeeper)"'
expr: 'attributes.container_name matches "^signoz_(logspout|frontend|alertmanager|query-service|otel-collector|otel-collector-metrics|clickhouse|zookeeper)"'
opencensus:
endpoint: 0.0.0.0:55678
otlp/spanmetrics:
protocols:
grpc:
endpoint: localhost:12345
otlp:
protocols:
grpc:
@@ -65,8 +69,8 @@ processors:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
signozspanmetrics/cumulative:
metrics_exporter: clickhousemetricswrite
signozspanmetrics/prometheus:
metrics_exporter: prometheus
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
dimensions:
@@ -93,20 +97,6 @@ processors:
# num_workers: 4
# queue_size: 100
# retry_on_failure: true
signozspanmetrics/delta:
metrics_exporter: clickhousemetricswrite
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA
dimensions:
- name: service.namespace
default: default
- name: deployment.environment
default: default
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
exporters:
clickhousetraces:
@@ -119,6 +109,8 @@ exporters:
enabled: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
prometheus:
endpoint: 0.0.0.0:8889
# logging: {}
clickhouselogsexporter:
dsn: tcp://clickhouse:9000/
@@ -148,7 +140,7 @@ service:
pipelines:
traces:
receivers: [jaeger, otlp]
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
processors: [signozspanmetrics/prometheus, batch]
exporters: [clickhousetraces]
metrics:
receivers: [otlp]
@@ -162,6 +154,9 @@ service:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite/prometheus]
metrics/spanmetrics:
receivers: [otlp/spanmetrics]
exporters: [prometheus]
logs:
receivers: [otlp, tcplog/docker]
processors: [batch]

View File

@@ -0,0 +1,64 @@
receivers:
prometheus:
config:
scrape_configs:
# otel-collector-metrics internal metrics
- job_name: otel-collector-metrics
scrape_interval: 60s
static_configs:
- targets:
- localhost:8888
labels:
job_name: otel-collector-metrics
# SigNoz span metrics
- job_name: signozspanmetrics-collector
scrape_interval: 60s
dns_sd_configs:
- names:
- tasks.otel-collector
type: A
port: 8889
processors:
batch:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
# # 25% of limit up to 2G
# spike_limit_mib: 512
# check_interval: 5s
#
# # 50% of the maximum memory
# limit_percentage: 50
# # 20% of max memory usage spike expected
# spike_limit_percentage: 20
# queued_retry:
# num_workers: 4
# queue_size: 100
# retry_on_failure: true
exporters:
clickhousemetricswrite:
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
extensions:
health_check:
endpoint: 0.0.0.0:13133
zpages:
endpoint: 0.0.0.0:55679
pprof:
endpoint: 0.0.0.0:1777
service:
telemetry:
metrics:
address: 0.0.0.0:8888
extensions: [health_check, zpages, pprof]
pipelines:
metrics:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite]

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.9}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6}
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.88.9
image: signoz/signoz-otel-collector:0.88.6
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -116,6 +116,28 @@ services:
query-service:
condition: service_healthy
otel-collector-metrics:
container_name: signoz-otel-collector-metrics
image: signoz/signoz-otel-collector:0.88.6
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",
"--feature-gates=-pkg.translator.prometheus.NormalizeName"
]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
# - "1777:1777" # pprof extension
# - "8888:8888" # OtelCollector internal metrics
# - "13133:13133" # Health check extension
# - "55679:55679" # zPages extension
restart: on-failure
depends_on:
clickhouse:
condition: service_healthy
otel-collector-migrator:
condition: service_completed_successfully
logspout:
image: "gliderlabs/logspout:v3.2.14"
container_name: signoz-logspout

View File

@@ -25,7 +25,7 @@ services:
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--prefer-delta=true"
]
ports:
- "6060:6060"

View File

@@ -164,12 +164,12 @@ 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.38.0}
image: signoz/query-service:${DOCKER_TAG:-0.36.2}
container_name: signoz-query-service
command:
[
"-config=/root/config/prometheus.yml",
# "--prefer-delta=true"
"--prefer-delta=true"
]
# ports:
# - "6060:6060" # pprof port
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.38.0}
image: signoz/frontend:${DOCKER_TAG:-0.36.2}
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.88.9}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.9}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6}
container_name: signoz-otel-collector
command:
[
@@ -268,6 +268,24 @@ services:
query-service:
condition: service_healthy
otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6}
container_name: signoz-otel-collector-metrics
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",
"--feature-gates=-pkg.translator.prometheus.NormalizeName"
]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
# - "1777:1777" # pprof extension
# - "8888:8888" # OtelCollector internal metrics
# - "13133:13133" # Health check extension
# - "55679:55679" # zPages extension
restart: on-failure
<<: *db-depend
logspout:
image: "gliderlabs/logspout:v3.2.14"
container_name: signoz-logspout

View File

@@ -15,9 +15,13 @@ receivers:
# please remove names from below if you want to collect logs from them
- type: filter
id: signoz_logs_filter
expr: 'attributes.container_name matches "^signoz-(logspout|frontend|alertmanager|query-service|otel-collector|clickhouse|zookeeper)"'
expr: 'attributes.container_name matches "^signoz-(logspout|frontend|alertmanager|query-service|otel-collector|otel-collector-metrics|clickhouse|zookeeper)"'
opencensus:
endpoint: 0.0.0.0:55678
otlp/spanmetrics:
protocols:
grpc:
endpoint: localhost:12345
otlp:
protocols:
grpc:
@@ -62,9 +66,8 @@ processors:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
signozspanmetrics/cumulative:
metrics_exporter: clickhousemetricswrite
metrics_flush_interval: 60s
signozspanmetrics/prometheus:
metrics_exporter: prometheus
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
dimensions:
@@ -95,21 +98,6 @@ processors:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
signozspanmetrics/delta:
metrics_exporter: clickhousemetricswrite
metrics_flush_interval: 60s
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA
dimensions:
- name: service.namespace
default: default
- name: deployment.environment
default: default
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
extensions:
health_check:
@@ -130,6 +118,8 @@ exporters:
enabled: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
prometheus:
endpoint: 0.0.0.0:8889
# logging: {}
clickhouselogsexporter:
@@ -155,7 +145,7 @@ service:
pipelines:
traces:
receivers: [jaeger, otlp]
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
processors: [signozspanmetrics/prometheus, batch]
exporters: [clickhousetraces]
metrics:
receivers: [otlp]
@@ -169,6 +159,9 @@ service:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite/prometheus]
metrics/spanmetrics:
receivers: [otlp/spanmetrics]
exporters: [prometheus]
logs:
receivers: [otlp, tcplog/docker]
processors: [batch]

View File

@@ -0,0 +1,69 @@
receivers:
otlp:
protocols:
grpc:
http:
prometheus:
config:
scrape_configs:
# otel-collector-metrics internal metrics
- job_name: otel-collector-metrics
scrape_interval: 60s
static_configs:
- targets:
- localhost:8888
labels:
job_name: otel-collector-metrics
# SigNoz span metrics
- job_name: signozspanmetrics-collector
scrape_interval: 60s
static_configs:
- targets:
- otel-collector:8889
processors:
batch:
send_batch_size: 10000
send_batch_max_size: 11000
timeout: 10s
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
# # 25% of limit up to 2G
# spike_limit_mib: 512
# check_interval: 5s
#
# # 50% of the maximum memory
# limit_percentage: 50
# # 20% of max memory usage spike expected
# spike_limit_percentage: 20
# queued_retry:
# num_workers: 4
# queue_size: 100
# retry_on_failure: true
extensions:
health_check:
endpoint: 0.0.0.0:13133
zpages:
endpoint: 0.0.0.0:55679
pprof:
endpoint: 0.0.0.0:1777
exporters:
clickhousemetricswrite:
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
service:
telemetry:
metrics:
address: 0.0.0.0:8888
extensions:
- health_check
- zpages
- pprof
pipelines:
metrics:
receivers: [prometheus]
processors: [batch]
exporters: [clickhousemetricswrite]

View File

@@ -150,11 +150,12 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost)
router.HandleFunc("/api/v3/query_range", am.ViewAccess(ah.QueryRangeV3)).Methods(http.MethodPost)
// PAT APIs
router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/pat/{id}", am.AdminAccess(ah.deletePAT)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.createPAT)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
@@ -163,6 +164,10 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.getQueryLimits)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.addQueryLimits)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/limits", am.AdminAccess(ah.updateQueryLimits)).Methods(http.MethodPut)
router.HandleFunc("/api/v2/licenses",
am.ViewAccess(ah.listLicensesV2)).
Methods(http.MethodGet)

View File

@@ -40,7 +40,6 @@ type billingDetails struct {
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
Details details `json:"details"`
Discount float64 `json:"discount"`
SubscriptionStatus string `json:"subscriptionStatus"`
} `json:"data"`
}

View File

@@ -0,0 +1,51 @@
package api
import (
"encoding/json"
"net/http"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
func (aH *APIHandler) getQueryLimits(w http.ResponseWriter, r *http.Request) {
queryLimits, err := aH.AppDao().GetQueryLimits(r.Context())
if err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
return
}
aH.Respond(w, queryLimits)
}
func (aH *APIHandler) addQueryLimits(w http.ResponseWriter, r *http.Request) {
var queryLimits []*model.QueryLimit
if err := json.NewDecoder(r.Body).Decode(&queryLimits); err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
return
}
err := aH.AppDao().AddQueryLimits(r.Context(), queryLimits)
if err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
return
}
aH.Respond(w, nil)
}
func (aH *APIHandler) updateQueryLimits(w http.ResponseWriter, r *http.Request) {
var queryLimits []*model.QueryLimit
if err := json.NewDecoder(r.Body).Decode(&queryLimits); err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
return
}
err := aH.AppDao().UpdateQueryLimits(r.Context(), queryLimits)
if err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}, nil)
return
}
aH.Respond(w, nil)
}

View File

@@ -4,14 +4,17 @@ import (
"bytes"
"fmt"
"net/http"
"strings"
"sync"
"text/template"
"time"
"go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/metrics"
"go.signoz.io/signoz/pkg/query-service/app/parser"
"go.signoz.io/signoz/pkg/query-service/constants"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
"go.uber.org/zap"
)
@@ -234,3 +237,52 @@ func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request
resp := ResponseFormat{ResultType: "matrix", Result: seriesList, TableName: tableName}
ah.Respond(w, resp)
}
func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
queryRangeParams, apiErrorObj := app.ParseQueryRangeParams(r)
if apiErrorObj != nil {
zap.S().Errorf(apiErrorObj.Err.Error())
RespondError(w, apiErrorObj, nil)
return
}
limits, err := aH.AppDao().GetQueryLimits(r.Context())
if err != nil {
zap.S().Errorf("Error while getting query limits: %v", err)
// We don't want to fail the request if we can't get the limits
// iterate over the nil slice will not execute the loop
// and the query will be executed without any limits
// this shouldn't happen ideally but we don't want to fail the request
}
var timeSeriesLimt int
for _, limit := range limits {
if limit.Name == "time_series_limit" {
timeSeriesLimt = limit.UsageLimit
}
}
if len(queryRangeParams.CompositeQuery.BuilderQueries) > 0 {
for idx := range queryRangeParams.CompositeQuery.BuilderQueries {
queryRangeParams.CompositeQuery.BuilderQueries[idx].QueryLimits = v3.QueryLimits{
MaxTimeSeries: timeSeriesLimt,
}
// TODO(srikanthccv): should apply to explore page also
if !strings.Contains(queryRangeParams.SourcePage, "dashboard") {
queryRangeParams.CompositeQuery.BuilderQueries[idx].QueryLimits.MaxTimeSeries = 0
}
}
}
// add temporality for each metric
temporalityErr := aH.APIHandler.AddTemporality(r.Context(), queryRangeParams)
if temporalityErr != nil {
zap.S().Errorf("Error while adding temporality for metrics: %v", temporalityErr)
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: temporalityErr}, nil)
return
}
aH.APIHandler.ExecQueryRangeV3(r.Context(), queryRangeParams, w, r)
}

View File

@@ -42,7 +42,6 @@ import (
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
pqle "go.signoz.io/signoz/pkg/query-service/pqlEngine"
rules "go.signoz.io/signoz/pkg/query-service/rules"
@@ -87,7 +86,7 @@ type Server struct {
privateHTTP *http.Server
// feature flags
featureLookup baseint.FeatureLookup
featureLookup baseInterface.FeatureLookup
// Usage manager
usageManager *usage.Manager
@@ -193,7 +192,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// start the usagemanager
usageManager, err := usage.New("sqlite", modelDao, lm.GetRepo(), reader.GetConn())
usageManager, err := usage.New("sqlite", localDB, lm.GetRepo(), reader.GetConn())
if err != nil {
return nil, err
}
@@ -331,7 +330,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
apiHandler.RegisterMetricsRoutes(r, am)
apiHandler.RegisterLogsRoutes(r, am)
apiHandler.RegisterQueryRangeV3Routes(r, am)
apiHandler.RegisterQueryRangeV4Routes(r, am)
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
@@ -642,7 +640,7 @@ func makeRulesManager(
alertManagerURL string,
ruleRepoURL string,
db *sqlx.DB,
ch baseint.Reader,
ch baseInterface.Reader,
disableRules bool,
fm baseInterface.FeatureLookup) (*rules.Manager, error) {

View File

@@ -39,4 +39,8 @@ type ModelDao interface {
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError)
DeletePAT(ctx context.Context, id string) basemodel.BaseApiError
GetQueryLimits(ctx context.Context) ([]*model.QueryLimit, error)
AddQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error
UpdateQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error
}

View File

@@ -0,0 +1,54 @@
package sqlite
import (
"context"
"go.signoz.io/signoz/ee/query-service/model"
)
// GetQueryLimits returns the query limits
func (m *modelDao) GetQueryLimits(ctx context.Context) ([]*model.QueryLimit, error) {
query := `SELECT name, title, usage_limit FROM resource_usage_limits`
var queryLimits []*model.QueryLimit
err := m.DB().Select(&queryLimits, query)
if err != nil {
return nil, err
}
return queryLimits, nil
}
// AddQueryLimits adds the query limits
func (m *modelDao) AddQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error {
tx := m.DB().MustBegin()
for _, queryLimit := range queryLimits {
query := `INSERT INTO resource_usage_limits (name, title, usage_limit) VALUES (?, ?, ?)`
_, err := tx.Exec(query, queryLimit.Name, queryLimit.Title, queryLimit.UsageLimit)
if err != nil {
tx.Rollback()
return err
}
}
err := tx.Commit()
if err != nil {
return err
}
return nil
}
// UpdateQueryLimits updates the query limits
func (m *modelDao) UpdateQueryLimits(ctx context.Context, queryLimits []*model.QueryLimit) error {
tx := m.DB().MustBegin()
for _, queryLimit := range queryLimits {
query := `UPDATE resource_usage_limits SET usage_limit = ? WHERE name = ?`
_, err := tx.Exec(query, queryLimit.UsageLimit, queryLimit.Name)
if err != nil {
tx.Rollback()
return err
}
}
err := tx.Commit()
if err != nil {
return err
}
return nil
}

View File

@@ -38,7 +38,7 @@ func InitDB(dataSourceName string) (*modelDao, error) {
basedao.SetDB(dao)
m := &modelDao{ModelDaoSqlite: dao}
table_schema := `
tableSchema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS org_domains(
id TEXT PRIMARY KEY,
@@ -60,11 +60,23 @@ func InitDB(dataSourceName string) (*modelDao, error) {
);
`
_, err = m.DB().Exec(table_schema)
_, err = m.DB().Exec(tableSchema)
if err != nil {
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
}
// create resource_usage_limits table
tableSchema = `CREATE TABLE IF NOT EXISTS resource_usage_limits (
name TEXT PRIMARY KEY,
title TEXT,
usage_limit INTEGER DEFAULT 0
);`
_, err = m.DB().Exec(tableSchema)
if err != nil {
return nil, fmt.Errorf("Error in creating resource_usage_limits table: %s", err.Error())
}
return m, nil
}

View File

@@ -9,8 +9,8 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"
saml2 "github.com/russellhaering/gosaml2"
"go.signoz.io/signoz/ee/query-service/sso/saml"
"go.signoz.io/signoz/ee/query-service/sso"
"go.signoz.io/signoz/ee/query-service/sso/saml"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -24,16 +24,16 @@ const (
// OrgDomain identify org owned web domains for auth and other purposes
type OrgDomain struct {
Id uuid.UUID `json:"id"`
Name string `json:"name"`
OrgId string `json:"orgId"`
SsoEnabled bool `json:"ssoEnabled"`
SsoType SSOType `json:"ssoType"`
Id uuid.UUID `json:"id"`
Name string `json:"name"`
OrgId string `json:"orgId"`
SsoEnabled bool `json:"ssoEnabled"`
SsoType SSOType `json:"ssoType"`
SamlConfig *SamlConfig `json:"samlConfig"`
SamlConfig *SamlConfig `json:"samlConfig"`
GoogleAuthConfig *GoogleOAuthConfig `json:"googleAuthConfig"`
Org *basemodel.Organization
Org *basemodel.Organization
}
func (od *OrgDomain) String() string {
@@ -100,8 +100,8 @@ func (od *OrgDomain) GetSAMLCert() string {
return ""
}
// PrepareGoogleOAuthProvider creates GoogleProvider that is used in
// requesting OAuth and also used in processing response from google
// PrepareGoogleOAuthProvider creates GoogleProvider that is used in
// requesting OAuth and also used in processing response from google
func (od *OrgDomain) PrepareGoogleOAuthProvider(siteUrl *url.URL) (sso.OAuthCallbackProvider, error) {
if od.GoogleAuthConfig == nil {
return nil, fmt.Errorf("Google auth is not setup correctly for this domain")
@@ -137,38 +137,36 @@ func (od *OrgDomain) PrepareSamlRequest(siteUrl *url.URL) (*saml2.SAMLServicePro
}
func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) {
fmtDomainId := strings.Replace(od.Id.String(), "-", ":", -1)
// build redirect url from window.location sent by frontend
redirectURL := fmt.Sprintf("%s://%s%s", siteUrl.Scheme, siteUrl.Host, siteUrl.Path)
// prepare state that gets relayed back when the auth provider
// calls back our url. here we pass the app url (where signoz runs)
// and the domain Id. The domain Id helps in identifying sso config
// when the call back occurs and the app url is useful in redirecting user
// back to the right path.
// when the call back occurs and the app url is useful in redirecting user
// back to the right path.
// why do we need to pass app url? the callback typically is handled by backend
// and sometimes backend might right at a different port or is unaware of frontend
// endpoint (unless SITE_URL param is set). hence, we receive this build sso request
// along with frontend window.location and use it to relay the information through
// auth provider to the backend (HandleCallback or HandleSSO method).
// along with frontend window.location and use it to relay the information through
// auth provider to the backend (HandleCallback or HandleSSO method).
relayState := fmt.Sprintf("%s?domainId=%s", redirectURL, fmtDomainId)
switch (od.SsoType) {
switch od.SsoType {
case SAML:
sp, err := od.PrepareSamlRequest(siteUrl)
if err != nil {
return "", err
}
return sp.BuildAuthURL(relayState)
case GoogleAuth:
googleProvider, err := od.PrepareGoogleOAuthProvider(siteUrl)
if err != nil {
return "", err
@@ -177,8 +175,7 @@ func (od *OrgDomain) BuildSsoUrl(siteUrl *url.URL) (ssoUrl string, err error) {
default:
zap.S().Errorf("found unsupported SSO config for the org domain", zap.String("orgDomain", od.Name))
return "", fmt.Errorf("unsupported SSO config for the domain")
return "", fmt.Errorf("unsupported SSO config for the domain")
}
}

View File

@@ -0,0 +1,12 @@
package model
type QueryLimit struct {
// Name of the query limit
Name string `db:"name" json:"name"`
// Title of the query limit
Title string `db:"title" json:"title"`
// UsageLimit indicates the usage limit of the query
// If the usage limit is 0, then there is no limit
UsageLimit int `db:"usage_limit" json:"usage_limit"`
}

View File

@@ -20,8 +20,6 @@ type Usage struct {
TimeStamp time.Time `json:"timestamp"`
Count int64 `json:"count"`
Size int64 `json:"size"`
OrgName string `json:"orgName"`
TenantId string `json:"tenantId"`
}
type UsageDB struct {

View File

@@ -4,8 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"sync/atomic"
"time"
@@ -13,10 +11,10 @@ import (
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/go-co-op/gocron"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
"go.signoz.io/signoz/ee/query-service/dao"
licenseserver "go.signoz.io/signoz/ee/query-service/integrations/signozio"
"go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/model"
@@ -40,29 +38,15 @@ type Manager struct {
licenseRepo *license.Repo
scheduler *gocron.Scheduler
modelDao dao.ModelDao
tenantID string
}
func New(dbType string, modelDao dao.ModelDao, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn) (*Manager, error) {
hostNameRegex := regexp.MustCompile(`tcp://(?P<hostname>.*):`)
hostNameRegexMatches := hostNameRegex.FindStringSubmatch(os.Getenv("ClickHouseUrl"))
tenantID := ""
if len(hostNameRegexMatches) == 2 {
tenantID = hostNameRegexMatches[1]
tenantID = strings.TrimRight(tenantID, "-clickhouse")
}
func New(dbType string, db *sqlx.DB, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn) (*Manager, error) {
m := &Manager{
// repository: repo,
clickhouseConn: clickhouseConn,
licenseRepo: licenseRepo,
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
modelDao: modelDao,
tenantID: tenantID,
}
return m, nil
}
@@ -139,19 +123,6 @@ func (lm *Manager) UploadUsage() {
zap.S().Info("uploading usage data")
// Try to get the org name
orgName := ""
orgNames, err := lm.modelDao.GetOrgs(ctx)
if err != nil {
zap.S().Errorf("failed to get org data: %v", zap.Error(err))
} else {
if len(orgNames) != 1 {
zap.S().Errorf("expected one org but got %d orgs", len(orgNames))
} else {
orgName = orgNames[0].Name
}
}
usagesPayload := []model.Usage{}
for _, usage := range usages {
usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data))
@@ -171,8 +142,6 @@ func (lm *Manager) UploadUsage() {
usageData.ExporterID = usage.ExporterID
usageData.Type = usage.Type
usageData.Tenant = usage.Tenant
usageData.OrgName = orgName
usageData.TenantId = lm.tenantID
usagesPayload = append(usagesPayload, usageData)
}

View File

@@ -36,7 +36,6 @@
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
"@monaco-editor/react": "^4.3.1",
"@signozhq/design-tokens": "0.0.6",
"@uiw/react-md-editor": "3.23.5",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",

View File

@@ -1,11 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2048_2251)">
<path opacity="0.9" d="M8.02226 15.9866C3.56539 15.9866 -6.10352e-05 12.4896 -6.10352e-05 8.11832C-6.10352e-05 3.79075 3.56539 0.25 8.02226 0.25H13.0584C14.7075 0.25 15.9999 1.56139 15.9999 3.13506V8.11832C15.9999 12.4896 12.4345 15.9866 8.02226 15.9866Z" fill="#F25733"/>
<path d="M7.95919 4.71207C4.63025 4.71207 2.75514 7.46868 2.67693 7.58603C2.48413 7.87508 2.48413 8.24888 2.67707 8.53816C2.75514 8.65528 4.63025 11.4119 7.95919 11.4119C11.2881 11.4119 13.1633 8.65528 13.2414 8.53792C13.4342 8.24888 13.4342 7.87508 13.2413 7.58582C13.1632 7.46868 11.2881 4.71207 7.95919 4.71207ZM3.13771 8.23088C3.06925 8.12832 3.06925 7.99571 3.13771 7.89307C3.20059 7.79867 4.53564 5.83764 6.92256 5.36723C5.84092 5.78476 5.07127 6.83485 5.07127 8.062C5.07127 9.28912 5.84092 10.3392 6.92256 10.7567C4.53564 10.2863 3.20059 8.32528 3.13771 8.23088ZM6.62838 8.062C6.62838 8.21488 6.50443 8.3388 6.35151 8.3388C6.19859 8.3388 6.07465 8.21488 6.07465 8.062C6.07465 7.02287 6.92003 6.17748 7.95916 6.17748C8.11207 6.17748 8.23599 6.30141 8.23599 6.45434C8.23599 6.60727 8.11207 6.73119 7.95916 6.73119C7.22535 6.73119 6.62838 7.32815 6.62838 8.062ZM7.95919 8.73504C7.58803 8.73504 7.2861 8.43312 7.2861 8.062C7.2861 7.69085 7.58803 7.3889 7.95919 7.3889C8.33039 7.3889 8.63231 7.69083 8.63231 8.062C8.63231 8.43312 8.33039 8.73504 7.95919 8.73504ZM12.7806 8.23088C12.7178 8.32528 11.3827 10.2863 8.99583 10.7567C10.0775 10.3392 10.8471 9.28912 10.8471 8.062C10.8471 6.83487 10.0775 5.78477 8.99583 5.36724C11.3827 5.83768 12.7178 7.7987 12.7806 7.89307C12.8491 7.99571 12.8491 8.12832 12.7806 8.23088Z" fill="#F9F2F9"/>
</g>
<defs>
<clipPath id="clip0_2048_2251">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -62,7 +62,6 @@
"button_cancel": "No",
"field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name",
"field_notification_channel": "Notification Channel",
"field_alert_desc": "Alert Description",
"field_labels": "Labels",
"field_severity": "Severity",
@@ -101,7 +100,7 @@
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
"user_tooltip_more_help": "More details on how to create alerts",
"choose_alert_type": "Choose a type for the alert",
"choose_alert_type": "Choose a type for the alert:",
"metric_based_alert": "Metric based Alert",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
"log_based_alert": "Log-based Alert",

View File

@@ -54,7 +54,6 @@
"field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name",
"field_alert_desc": "Alert Description",
"field_notification_channel": "Notification Channel",
"field_labels": "Labels",
"field_severity": "Severity",
"option_critical": "Critical",

View File

@@ -63,7 +63,6 @@
"field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name",
"field_alert_desc": "Alert Description",
"field_notification_channel": "Notification Channel",
"field_labels": "Labels",
"field_severity": "Severity",
"option_critical": "Critical",
@@ -101,7 +100,7 @@
"user_guide_ch_step3a": "Set alert severity, name and descriptions",
"user_guide_ch_step3b": "Add tags to the alert in the Label field if needed",
"user_tooltip_more_help": "More details on how to create alerts",
"choose_alert_type": "Choose a type for the alert",
"choose_alert_type": "Choose a type for the alert:",
"metric_based_alert": "Metric based Alert",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
"log_based_alert": "Log-based Alert",

View File

@@ -54,7 +54,6 @@
"field_promql_expr": "PromQL Expression",
"field_alert_name": "Alert Name",
"field_alert_desc": "Alert Description",
"field_notification_channel": "Notification Channel",
"field_labels": "Labels",
"field_severity": "Severity",
"option_critical": "Critical",

View File

@@ -31,7 +31,6 @@
"NOT_FOUND": "SigNoz | Page Not Found",
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"OLD_LOGS_EXPLORER": "SigNoz | Old Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"LOGS_PIPELINES": "SigNoz | Logs Pipelines",
"HOME_PAGE": "Open source Observability Platform | SigNoz",

View File

@@ -28,11 +28,7 @@ import AppReducer, { User } from 'types/reducer/app';
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
import PrivateRoute from './Private';
import defaultRoutes, {
AppRoutes,
LIST_LICENSES,
SUPPORT_ROUTE,
} from './routes';
import defaultRoutes, { AppRoutes, SUPPORT_ROUTE } from './routes';
function App(): JSX.Element {
const themeConfig = useThemeConfig();
@@ -154,10 +150,6 @@ function App(): JSX.Element {
if (isCloudUserVal || isEECloudUser()) {
const newRoutes = [...routes, SUPPORT_ROUTE];
setRoutes(newRoutes);
} else {
const newRoutes = [...routes, LIST_LICENSES];
setRoutes(newRoutes);
}

View File

@@ -112,25 +112,17 @@ export const MySettings = Loadable(
);
export const Logs = Loadable(
() => import(/* webpackChunkName: "Logs" */ 'pages/LogsModulePage'),
() => import(/* webpackChunkName: "Logs" */ 'pages/Logs'),
);
export const LogsExplorer = Loadable(
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsModulePage'),
);
export const OldLogsExplorer = Loadable(
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/Logs'),
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'),
);
export const LiveLogs = Loadable(
() => import(/* webpackChunkName: "Live Logs" */ 'pages/LiveLogs'),
);
export const PipelinePage = Loadable(
() => import(/* webpackChunkName: "Pipelines" */ 'pages/LogsModulePage'),
);
export const Login = Loadable(
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
);
@@ -159,6 +151,10 @@ export const LogsIndexToFields = Loadable(
import(/* webpackChunkName: "LogsIndexToFields Page" */ 'pages/LogsSettings'),
);
export const PipelinePage = Loadable(
() => import(/* webpackChunkName: "Pipelines" */ 'pages/Pipelines'),
);
export const BillingPage = Loadable(
() => import(/* webpackChunkName: "BillingPage" */ 'pages/Billing'),
);

View File

@@ -23,7 +23,6 @@ import {
LogsIndexToFields,
MySettings,
NewDashboardPage,
OldLogsExplorer,
Onboarding,
OrganizationSettings,
PasswordReset,
@@ -191,6 +190,13 @@ const routes: AppRoutes[] = [
component: AllErrors,
key: 'ALL_ERROR',
},
{
path: ROUTES.LIST_LICENSES,
exact: true,
component: LicensePage,
isPrivate: true,
key: 'LIST_LICENSES',
},
{
path: ROUTES.ERROR_DETAIL,
exact: true,
@@ -240,13 +246,6 @@ const routes: AppRoutes[] = [
key: 'LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.OLD_LOGS_EXPLORER,
exact: true,
component: OldLogsExplorer,
key: 'OLD_LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.LIVE_LOGS,
exact: true,
@@ -313,14 +312,6 @@ export const SUPPORT_ROUTE: AppRoutes = {
isPrivate: true,
};
export const LIST_LICENSES: AppRoutes = {
path: ROUTES.LIST_LICENSES,
exact: true,
component: LicensePage,
isPrivate: true,
key: 'LIST_LICENSES',
};
export interface AppRoutes {
component: RouteProps['component'];
path: RouteProps['path'];

View File

@@ -4,6 +4,5 @@ import { MetricMetaProps } from 'types/api/metrics/getApDex';
export const getMetricMeta = (
metricName: string,
servicename: string,
): Promise<AxiosResponse<MetricMetaProps>> =>
axios.get(`/metric_meta?metricName=${metricName}&serviceName=${servicename}`);
axios.get(`/metric_meta?metricName=${metricName}`);

View File

@@ -66,11 +66,7 @@ export const Logout = (): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (window && window.Intercom) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Intercom('shutdown');
}
window.Intercom('shutdown');
history.push(ROUTES.LOGIN);
};

View File

@@ -1,96 +0,0 @@
.custom-time-picker {
display: flex;
flex-direction: column;
}
.time-options-container {
.time-options-item {
margin: 2px 0;
padding: 8px;
border-radius: 2px;
&.active {
background-color: rgba($color: #000000, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #000000, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #000000, $alpha: 0.3);
}
}
}
.time-selection-dropdown-content {
min-width: 172px;
width: 100%;
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
&.custom-time {
input:not(:focus) {
min-width: 240px;
}
}
input::placeholder {
color: white;
}
input:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
}
}
.valid-format-error {
margin-top: 4px;
color: var(--bg-cherry-400) !important;
font-size: 13px !important;
font-weight: 400 !important;
}
.lightMode {
.time-options-container {
.time-options-item {
&.active {
background-color: rgba($color: #ffffff, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
input::placeholder {
color: var(---bg-ink-300);
}
input:focus::placeholder {
color: rgba($color: #000000, $alpha: 0.4);
}
}
}

View File

@@ -1,238 +0,0 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import './CustomTimePicker.styles.scss';
import { Input, Popover, Tooltip, Typography } from 'antd';
import cx from 'classnames';
import { Options } from 'container/TopNav/DateTimeSelection/config';
import dayjs from 'dayjs';
import debounce from 'lodash-es/debounce';
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
import { ChangeEvent, useEffect, useState } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
const maxAllowedMinTimeInMonths = 6;
interface CustomTimePickerProps {
onSelect: (value: string) => void;
onError: (value: boolean) => void;
items: any[];
selectedValue: string;
selectedTime: string;
onValidCustomDateChange: ([t1, t2]: any[]) => void;
}
function CustomTimePicker({
onSelect,
onError,
items,
selectedValue,
selectedTime,
onValidCustomDateChange,
}: CustomTimePickerProps): JSX.Element {
const [open, setOpen] = useState(false);
const [
selectedTimePlaceholderValue,
setSelectedTimePlaceholderValue,
] = useState('Select / Enter Time Range');
const [inputValue, setInputValue] = useState('');
const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>('');
const [inputErrorMessage, setInputErrorMessage] = useState<string | null>(
null,
);
const [isInputFocused, setIsInputFocused] = useState(false);
const getSelectedTimeRangeLabel = (
selectedTime: string,
selectedTimeValue: string,
): string => {
if (selectedTime === 'custom') {
return selectedTimeValue;
}
for (let index = 0; index < Options.length; index++) {
if (Options[index].value === selectedTime) {
return Options[index].label;
}
}
return '';
};
useEffect(() => {
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setSelectedTimePlaceholderValue(value);
}, [selectedTime, selectedValue]);
const hide = (): void => {
setOpen(false);
};
const handleOpenChange = (newOpen: boolean): void => {
setOpen(newOpen);
};
const debouncedHandleInputChange = debounce((inputValue): void => {
const isValidFormat = /^(\d+)([mhdw])$/.test(inputValue);
if (isValidFormat) {
setInputStatus('success');
onError(false);
setInputErrorMessage(null);
const match = inputValue.match(/^(\d+)([mhdw])$/);
const value = parseInt(match[1], 10);
const unit = match[2];
const currentTime = dayjs();
const maxAllowedMinTime = currentTime.subtract(
maxAllowedMinTimeInMonths,
'month',
);
let minTime = null;
switch (unit) {
case 'm':
minTime = currentTime.subtract(value, 'minute');
break;
case 'h':
minTime = currentTime.subtract(value, 'hour');
break;
case 'd':
minTime = currentTime.subtract(value, 'day');
break;
case 'w':
minTime = currentTime.subtract(value, 'week');
break;
default:
break;
}
if (minTime && minTime < maxAllowedMinTime) {
setInputStatus('error');
onError(true);
setInputErrorMessage('Please enter time less than 6 months');
} else {
onValidCustomDateChange([minTime, currentTime]);
}
} else {
setInputStatus('error');
onError(true);
setInputErrorMessage(null);
}
}, 300);
const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
const inputValue = event.target.value;
if (inputValue.length > 0) {
setOpen(false);
} else {
setOpen(true);
}
setInputValue(inputValue);
// Call the debounced function with the input value
debouncedHandleInputChange(inputValue);
};
const content = (
<div className="time-selection-dropdown-content">
<div className="time-options-container">
{items.map(({ value, label }) => (
<div
onClick={(): void => {
onSelect(value);
setSelectedTimePlaceholderValue(label);
setInputStatus('');
onError(false);
setInputErrorMessage(null);
setInputValue('');
hide();
}}
key={value}
className={cx(
'time-options-item',
selectedValue === value ? 'active' : '',
)}
>
{label}
</div>
))}
</div>
</div>
);
const handleFocus = (): void => {
setIsInputFocused(true);
};
const handleBlur = (): void => {
setIsInputFocused(false);
};
return (
<div className="custom-time-picker">
<Popover
className={cx(
'timeSelection-input-container',
selectedTime === 'custom' && inputValue === '' ? 'custom-time' : '',
)}
placement="bottomRight"
getPopupContainer={popupContainer}
content={content}
arrow={false}
open={open}
onOpenChange={handleOpenChange}
trigger={['click']}
style={{
padding: 0,
}}
>
<Input
className="timeSelection-input"
type="text"
status={inputValue && inputStatus === 'error' ? 'error' : ''}
placeholder={
isInputFocused
? 'Time Format (1m or 2h or 3d or 4w)'
: selectedTimePlaceholderValue
}
value={inputValue}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={handleInputChange}
prefix={
inputValue && inputStatus === 'success' ? (
<CheckCircle size={14} color="#51E7A8" />
) : (
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
<Clock size={14} />
</Tooltip>
)
}
suffix={
<ChevronDown
size={14}
onClick={(): void => {
setOpen(!open);
}}
/>
}
/>
</Popover>
{inputStatus === 'error' && inputErrorMessage && (
<Typography.Title level={5} className="valid-format-error">
{inputErrorMessage}
</Typography.Title>
)}
</div>
);
}
export default CustomTimePicker;

View File

@@ -13,8 +13,6 @@ import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
// hooks
import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData';
import { isEmpty, isUndefined } from 'lodash-es';
import {
KeyboardEvent,
MouseEvent,
@@ -41,13 +39,10 @@ function RawLogView({
data,
linesPerRow,
isTextOverflowEllipsisDisabled,
selectedFields = [],
}: RawLogViewProps): JSX.Element {
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
data.id,
);
const flattenLogData = useMemo(() => FlatLogData(data), [data]);
const {
activeLog: activeContextLog,
onSetActiveLog: handleSetActiveContextLog,
@@ -67,31 +62,12 @@ function RawLogView({
const severityText = data.severity_text ? `${data.severity_text} |` : '';
const updatedSelecedFields = useMemo(
() => selectedFields.filter((e) => e.name !== 'id'),
[selectedFields],
);
const attributesValues = updatedSelecedFields
.map((field) => flattenLogData[field.name])
.filter((attribute) => !isUndefined(attribute) && !isEmpty(attribute));
let attributesText = attributesValues.join(' | ');
if (attributesText.length > 0) {
attributesText += ' | ';
}
const text = useMemo(
() =>
typeof data.timestamp === 'string'
? `${dayjs(data.timestamp).format()} | ${attributesText} ${severityText} ${
data.body
}`
: `${dayjs(
data.timestamp / 1e6,
).format()} | ${attributesText} ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText, attributesText],
? `${dayjs(data.timestamp).format()} | ${severityText} ${data.body}`
: `${dayjs(data.timestamp / 1e6).format()} | ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText],
);
const handleClickExpand = useCallback(() => {

View File

@@ -48,9 +48,8 @@ export const RawLogContent = styled.div<RawLogContentProps>`
line-clamp: ${linesPerRow};
-webkit-box-orient: vertical;`};
font-size: 12px;
line-height: 24px;
padding: 4px;
font-size: 1rem;
line-height: 2rem;
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};

View File

@@ -1,4 +1,3 @@
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
export interface RawLogViewProps {
@@ -7,7 +6,6 @@ export interface RawLogViewProps {
isTextOverflowEllipsisDisabled?: boolean;
data: ILog;
linesPerRow: number;
selectedFields?: IField[];
}
export interface RawLogContentProps {

View File

@@ -43,7 +43,7 @@ function DynamicColumnTable({
: undefined,
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, dynamicColumns]);
}, [columns]);
const onToggleHandler = (index: number) => (
checked: boolean,

View File

@@ -73,19 +73,12 @@ function ResizeTable({
}
}, [columns]);
const paginationConfig = {
hideOnSinglePage: true,
showTotal: (total: number, range: number[]): string =>
`${range[0]}-${range[1]} of ${total} items`,
...tableParams.pagination,
};
return onDragColumn ? (
<ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}>
<Table {...tableParams} pagination={paginationConfig} />
<Table {...tableParams} />
</ReactDragListView.DragColumn>
) : (
<Table {...tableParams} pagination={paginationConfig} />
<Table {...tableParams} />
);
}

View File

@@ -13,7 +13,6 @@ export const Container = styled.div`
&&& {
display: flex;
justify-content: center;
gap: 16px;
align-items: center;
min-height: 100vh;

View File

@@ -1,4 +1,2 @@
const MAX_RPS_LIMIT = 100;
export { MAX_RPS_LIMIT };
export const LEGEND = 'legend';

View File

@@ -6,6 +6,7 @@ const ROUTES = {
TRACE: '/trace',
TRACE_DETAIL: '/trace/:id',
TRACES_EXPLORER: '/traces-explorer',
SETTINGS: '/settings',
GET_STARTED: '/get-started',
USAGE_EXPLORER: '/usage-explorer',
APPLICATION: '/services',
@@ -22,18 +23,15 @@ const ROUTES = {
ERROR_DETAIL: '/error-detail',
VERSION: '/status',
MY_SETTINGS: '/my-settings',
SETTINGS: '/settings',
ORG_SETTINGS: '/settings/org-settings',
INGESTION_SETTINGS: '/settings/ingestion-settings',
SOMETHING_WENT_WRONG: '/something-went-wrong',
UN_AUTHORIZED: '/un-authorized',
NOT_FOUND: '/not-found',
LOGS_BASE: '/logs',
LOGS: '/logs/logs-explorer',
OLD_LOGS_EXPLORER: '/logs/old-logs-explorer',
LOGS_EXPLORER: '/logs/logs-explorer',
LIVE_LOGS: '/logs/logs-explorer/live',
LOGS_PIPELINES: '/logs/pipelines',
LOGS: '/logs',
LOGS_EXPLORER: '/logs-explorer',
LIVE_LOGS: '/logs-explorer/live',
LOGS_PIPELINES: '/pipelines',
HOME_PAGE: '/',
PASSWORD_RESET: '/password-reset',
LIST_LICENSES: '/licenses',

View File

@@ -1,6 +1,5 @@
const themeColors = {
chartcolors: {
robin: '#3F5ECC',
dodgerBlue: '#2F80ED',
mediumOrchid: '#BB6BD9',
seaBuckthorn: '#F2994A',

View File

@@ -15,7 +15,6 @@ export const ButtonContainer = styled.div`
align-items: center;
margin-top: 1rem;
margin-bottom: 1rem;
padding-right: 1rem;
}
`;

View File

@@ -48,15 +48,6 @@ import {
urlKey,
} from './utils';
type QueryParams = {
order: string;
offset: number;
orderParam: string;
pageSize: number;
exceptionType?: string;
serviceName?: string;
};
function AllErrors(): JSX.Element {
const { maxTime, minTime, loading } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
@@ -171,23 +162,16 @@ function AllErrors(): JSX.Element {
filterKey,
filterValue || '',
);
const queryParams: QueryParams = {
order: updatedOrder,
offset: getUpdatedOffset,
orderParam: getUpdatedParams,
pageSize: getUpdatedPageSize,
};
if (exceptionFilterValue && exceptionFilterValue !== 'undefined') {
queryParams.exceptionType = exceptionFilterValue;
}
if (serviceFilterValue && serviceFilterValue !== 'undefined') {
queryParams.serviceName = serviceFilterValue;
}
history.replace(`${pathname}?${createQueryParams(queryParams)}`);
history.replace(
`${pathname}?${createQueryParams({
order: updatedOrder,
offset: getUpdatedOffset,
orderParam: getUpdatedParams,
pageSize: getUpdatedPageSize,
exceptionType: exceptionFilterValue,
serviceName: serviceFilterValue,
})}`,
);
confirm();
},
[
@@ -214,10 +198,8 @@ function AllErrors(): JSX.Element {
<Input
placeholder={placeholder}
value={selectedKeys[0]}
onChange={
(e): void => setSelectedKeys(e.target.value ? [e.target.value] : [])
// Need to fix this logic, when the value in empty, it's setting undefined string as value
onChange={(e): void =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
allowClear
defaultValue={getDefaultFilterValue(

View File

@@ -1,53 +0,0 @@
@import '@signozhq/design-tokens';
.app-layout {
height: 100%;
width: 100%;
.app-content {
width: 100%;
overflow: auto;
}
}
.isDarkMode {
.app-layout {
.app-content {
background: #0b0c0e;
}
}
}
.isLightMode {
.app-layout {
.app-content {
background: #ffffff;
}
}
}
.trial-expiry-banner {
padding: 8px;
background-color: #f25733;
color: white;
text-align: center;
}
.upgrade-link {
padding: 0px;
padding-right: 4px;
display: inline !important;
color: white;
text-decoration: underline;
text-decoration-color: white;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
&:hover {
color: white;
text-decoration: underline;
text-decoration-color: white;
text-decoration-thickness: 2px;
text-underline-offset: 2px;
}
}

View File

@@ -1,22 +1,13 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/anchor-is-valid */
import './AppLayout.styles.scss';
import { Flex } from 'antd';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
import ROUTES from 'constants/routes';
import Header from 'container/Header';
import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { ReactNode, useEffect, useMemo, useRef } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
@@ -34,20 +25,15 @@ import {
UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app';
import AppReducer from 'types/reducer/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import { ChildrenContainer, Layout, LayoutContent } from './styles';
import { getRouteKey } from './utils';
function AppLayout(props: AppLayoutProps): JSX.Element {
const { isLoggedIn, user, role } = useSelector<AppState, AppReducer>(
const { isLoggedIn, user } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const isDarkMode = useIsDarkMode();
const { data: licenseData, isFetching } = useLicense();
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
@@ -210,68 +196,25 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const renderFullScreen =
pathname === ROUTES.GET_STARTED || pathname === ROUTES.WORKSPACE_LOCKED;
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
useEffect(() => {
if (
!isFetching &&
licenseData?.payload?.onTrial &&
!licenseData?.payload?.trialConvertedToSubscription &&
!licenseData?.payload?.workSpaceBlock &&
getRemainingDays(licenseData?.payload.trialEnd) < 7
) {
setShowTrialExpiryBanner(true);
}
}, [licenseData, isFetching]);
const handleUpgrade = (): void => {
if (role === 'ADMIN') {
history.push(ROUTES.BILLING);
}
};
return (
<Layout className={isDarkMode ? 'darkMode' : 'lightMode'}>
<Layout>
<Helmet>
<title>{pageTitle}</title>
</Helmet>
{showTrialExpiryBanner && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
<span>
{getFormattedDate(licenseData?.payload?.trialEnd || Date.now())}.
</span>
{role === 'ADMIN' ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleUpgrade}>
upgrade
</a>
to continue using SigNoz features.
</span>
) : (
'Please contact your administrator for upgrading to a paid plan.'
)}
</div>
)}
{isToDisplayLayout && <Header />}
<Layout>
{isToDisplayLayout && !renderFullScreen && <SideNav />}
<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
{isToDisplayLayout && !renderFullScreen && (
<SideNav licenseData={licenseData} isFetching={isFetching} />
)}
<div className="app-content">
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<LayoutContent>
<ChildrenContainer>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
</LayoutContent>
</ErrorBoundary>
</div>
</Flex>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<LayoutContent>
<ChildrenContainer>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
</LayoutContent>
</ErrorBoundary>
</Layout>
</Layout>
);
}

View File

@@ -8,13 +8,11 @@ export const Layout = styled(LayoutComponent)`
min-height: calc(100vh - 8rem);
overflow: hidden;
height: 100%;
flex-direction: column !important;
}
`;
export const LayoutContent = styled(LayoutComponent.Content)`
overflow-y: auto;
height: 100%;
`;
export const ChildrenContainer = styled.div`

View File

@@ -1,4 +1,4 @@
import { Row, Typography } from 'antd';
import { Row } from 'antd';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -33,14 +33,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
return (
<SelectTypeContainer>
<Typography.Title
level={4}
style={{
padding: '0 8px',
}}
>
{t('choose_alert_type')}
</Typography.Title>
<h3> {t('choose_alert_type')} </h3>
<Row>{renderOptions}</Row>
</SelectTypeContainer>
);

View File

@@ -1,5 +1,4 @@
import { Form, Select, Switch } from 'antd';
import { useEffect, useState } from 'react';
import { Form, Select } from 'antd';
import { useTranslation } from 'react-i18next';
import { AlertDef, Labels } from 'types/api/alerts/def';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
@@ -8,6 +7,7 @@ import { popupContainer } from 'utils/selectPopupContainer';
import ChannelSelect from './ChannelSelect';
import LabelSelect from './labels';
import {
ChannelSelectTip,
FormContainer,
FormItemMedium,
InputSmall,
@@ -19,41 +19,14 @@ import {
const { Option } = Select;
interface BasicInfoProps {
isNewRule: boolean;
alertDef: AlertDef;
setAlertDef: (a: AlertDef) => void;
}
function BasicInfo({
isNewRule,
alertDef,
setAlertDef,
}: BasicInfoProps): JSX.Element {
function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element {
// init namespace for translations
const { t } = useTranslation('alerts');
const [
shouldBroadCastToAllChannels,
setShouldBroadCastToAllChannels,
] = useState(false);
useEffect(() => {
const hasPreferredChannels =
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0) ||
isNewRule;
setShouldBroadCastToAllChannels(!hasPreferredChannels);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleBroadcastToAllChannels = (shouldBroadcast: boolean): void => {
setShouldBroadCastToAllChannels(shouldBroadcast);
setAlertDef({
...alertDef,
broadcastToAll: shouldBroadcast,
});
};
return (
<>
<StepHeading> {t('alert_form_step3')} </StepHeading>
@@ -132,38 +105,18 @@ function BasicInfo({
initialValues={alertDef.labels}
/>
</FormItemMedium>
<FormItemMedium
name="alert_all_configured_channels"
label="Alert all the configured channels"
>
<Switch
checked={shouldBroadCastToAllChannels}
onChange={handleBroadcastToAllChannels}
<FormItemMedium label="Notification Channels">
<ChannelSelect
currentValue={alertDef.preferredChannels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
...alertDef,
preferredChannels,
});
}}
/>
<ChannelSelectTip> {t('channel_select_tooltip')}</ChannelSelectTip>
</FormItemMedium>
{!shouldBroadCastToAllChannels && (
<FormItemMedium
label="Notification Channels"
name="notification_channels"
required
rules={[
{ required: true, message: requireErrorMessage(t('field_alert_name')) },
]}
>
<ChannelSelect
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
...alertDef,
preferredChannels,
});
}}
/>
</FormItemMedium>
)}
</FormContainer>
</>
);

View File

@@ -8,13 +8,11 @@ import { useTranslation } from 'react-i18next';
import { StyledSelect } from './styles';
export interface ChannelSelectProps {
disabled?: boolean;
currentValue?: string[];
onSelectChannels: (s: string[]) => void;
}
function ChannelSelect({
disabled,
currentValue,
onSelectChannels,
}: ChannelSelectProps): JSX.Element | null {
@@ -54,7 +52,6 @@ function ChannelSelect({
};
return (
<StyledSelect
disabled={disabled}
status={error ? 'error' : ''}
mode="multiple"
style={{ width: '100%' }}
@@ -71,7 +68,6 @@ function ChannelSelect({
}
ChannelSelect.defaultProps = {
disabled: false,
currentValue: [],
};
export default ChannelSelect;

View File

@@ -150,8 +150,6 @@ function ChartPreview({
thresholdUnit: alertDef?.condition.targetUnit,
},
],
softMax: null,
softMin: null,
}),
[
yAxisUnit,

View File

@@ -53,7 +53,6 @@ import {
import UserGuide from './UserGuide';
import { getSelectedQueryOptions } from './utils';
// eslint-disable-next-line sonarjs/cognitive-complexity
function FormAlertRules({
alertType,
formInstance,
@@ -79,8 +78,6 @@ function FormAlertRules({
// use query client
const ruleCache = useQueryClient();
const isNewRule = ruleId === 0;
const [loading, setLoading] = useState(false);
// alertDef holds the form values to be posted
@@ -111,17 +108,8 @@ function FormAlertRules({
useShareBuilderUrl(sq);
useEffect(() => {
const broadcastToSpecificChannels =
(initialValue &&
initialValue.preferredChannels &&
initialValue.preferredChannels.length > 0) ||
isNewRule;
setAlertDef({
...initialValue,
broadcastToAll: !broadcastToSpecificChannels,
});
}, [initialValue, isNewRule]);
setAlertDef(initialValue);
}, [initialValue]);
useEffect(() => {
// Set selectedQueryName based on the length of queryOptions
@@ -255,7 +243,6 @@ function FormAlertRules({
const preparePostData = (): AlertDef => {
const postableAlert: AlertDef = {
...alertDef,
preferredChannels: alertDef.broadcastToAll ? [] : alertDef.preferredChannels,
alertType,
source: window?.location.toString(),
ruleType:
@@ -399,11 +386,7 @@ function FormAlertRules({
}, [t, isFormValid, memoizedPreparePostData, notifications]);
const renderBasicInfo = (): JSX.Element => (
<BasicInfo
alertDef={alertDef}
setAlertDef={setAlertDef}
isNewRule={isNewRule}
/>
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
);
const renderQBChartPreview = (): JSX.Element => (
@@ -438,6 +421,8 @@ function FormAlertRules({
/>
);
const isNewRule = ruleId === 0;
const isAlertNameMissing = !formInstance.getFieldValue('alert');
const isAlertAvialableToSave =
@@ -457,10 +442,6 @@ function FormAlertRules({
}));
};
const isChannelConfigurationValid =
alertDef?.broadcastToAll ||
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0);
return (
<>
{Element}
@@ -508,11 +489,7 @@ function FormAlertRules({
type="primary"
onClick={onSaveHandler}
icon={<SaveOutlined />}
disabled={
isAlertNameMissing ||
isAlertAvialableToSave ||
!isChannelConfigurationValid
}
disabled={isAlertNameMissing || isAlertAvialableToSave}
>
{isNewRule ? t('button_createrule') : t('button_savechanges')}
</ActionButton>
@@ -520,7 +497,6 @@ function FormAlertRules({
<ActionButton
loading={loading || false}
disabled={isAlertNameMissing || !isChannelConfigurationValid}
type="default"
onClick={onTestRuleHandler}
>

View File

@@ -1,37 +0,0 @@
.full-screen-header-container {
display: flex;
justify-content: center;
align-items: center;
padding: 24px 0;
.brand-logo {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
cursor: pointer;
img {
height: 32px;
width: 32px;
}
.brand-logo-name {
font-family: 'Work Sans', sans-serif;
font-size: 24px;
font-style: normal;
font-weight: 500;
line-height: 18px;
color: #fff;
}
}
}
.lightMode {
.brand-logo {
.brand-logo-name {
color: black;
}
}
}

View File

@@ -1,28 +0,0 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import './FullScreenHeader.styles.scss';
import history from 'lib/history';
export default function FullScreenHeader({
overrideRoute,
}: {
overrideRoute?: string;
}): React.ReactElement {
const handleLogoClick = (): void => {
history.push(overrideRoute || '/');
};
return (
<div className="full-screen-header-container">
<div className="brand-logo" onClick={handleLogoClick}>
<img src="/Logos/signoz-brand-logo.svg" alt="SigNoz" />
<div className="brand-logo-name">SigNoz</div>
</div>
</div>
);
}
FullScreenHeader.defaultProps = {
overrideRoute: '/',
};

View File

@@ -18,14 +18,13 @@ import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariab
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange';
import { getGraphVisibilityStateOnDataChange } from '../utils';
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
import GraphManager from './GraphManager';
// import GraphManager from './GraphManager';
@@ -37,14 +36,13 @@ function FullView({
fullViewOptions = true,
onClickHandler,
name,
originalName,
yAxisUnit,
options,
onDragSelect,
isDependedDataLoaded = false,
graphsVisibilityStates,
onToggleModelHandler,
parentChartRef,
parentGraphVisibilityState,
setGraphsVisibilityStates,
}: FullViewProps): JSX.Element {
const { selectedTime: globalSelectedTime } = useSelector<
AppState,
@@ -57,20 +55,6 @@ function FullView({
const { selectedDashboard, isDashboardLocked } = useDashboard();
const { graphVisibilityStates: localStoredVisibilityStates } = useMemo(
() =>
getGraphVisibilityStateOnDataChange({
options,
isExpandedName: false,
name: originalName,
}),
[options, originalName],
);
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState(
localStoredVisibilityStates,
);
const getSelectedTime = useCallback(
() =>
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
@@ -105,7 +89,7 @@ function FullView({
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
});
const chartData = getUPlotChartData(response?.data?.payload, widget.fillSpans);
const chartData = getUPlotChartData(response?.data?.payload);
const isDarkMode = useIsDarkMode();
@@ -148,8 +132,6 @@ function FullView({
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
softMax: widget.softMax === undefined ? null : widget.softMax,
softMin: widget.softMin === undefined ? null : widget.softMin,
});
setChartOptions(newChartOptions);
@@ -160,9 +142,9 @@ function FullView({
useEffect(() => {
graphsVisibilityStates?.forEach((e, i) => {
fullViewChartRef?.current?.toggleGraph(i, e);
parentChartRef?.current?.toggleGraph(i, e);
});
parentGraphVisibilityState(graphsVisibilityStates);
}, [graphsVisibilityStates, parentGraphVisibilityState]);
}, [graphsVisibilityStates, parentChartRef]);
if (response.isFetching) {
return <Spinner height="100%" size="large" tip="Loading..." />;
@@ -222,7 +204,7 @@ function FullView({
{canModifyChart && chartOptions && !isDashboardLocked && (
<GraphManager
data={chartData}
name={originalName}
name={name}
options={chartOptions}
yAxisUnit={yAxisUnit}
onToggleModelHandler={onToggleModelHandler}

View File

@@ -50,14 +50,13 @@ export interface FullViewProps {
fullViewOptions?: boolean;
onClickHandler?: OnClickPluginOpts['onClick'];
name: string;
originalName: string;
options: uPlot.Options;
yAxisUnit?: string;
onDragSelect: (start: number, end: number) => void;
isDependedDataLoaded?: boolean;
graphsVisibilityStates?: boolean[];
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
setGraphsVisibilityStates: Dispatch<SetStateAction<boolean[]>>;
parentChartRef: GraphManagerProps['lineChartRef'];
parentGraphVisibilityState: Dispatch<SetStateAction<boolean[]>>;
}
export interface GraphManagerProps extends UplotProps {
@@ -65,8 +64,8 @@ export interface GraphManagerProps extends UplotProps {
yAxisUnit?: string;
onToggleModelHandler?: () => void;
options: uPlot.Options;
graphsVisibilityStates?: boolean[];
setGraphsVisibilityStates: Dispatch<SetStateAction<boolean[]>>;
setGraphsVisibilityStates: FullViewProps['setGraphsVisibilityStates'];
graphsVisibilityStates: FullViewProps['graphsVisibilityStates'];
lineChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
parentChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
}

View File

@@ -17,6 +17,7 @@ import {
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
@@ -38,15 +39,13 @@ function WidgetGraphComponent({
queryResponse,
errorMessage,
name,
onClickHandler,
threshold,
headerMenuList,
isWarning,
data,
options,
graphVisibiltyState,
onClickHandler,
onDragSelect,
setGraphVisibility,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false);
@@ -60,28 +59,29 @@ function WidgetGraphComponent({
const lineChartRef = useRef<ToggleGraphProps>();
const graphRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (queryResponse.isSuccess) {
const {
graphVisibilityStates: localStoredVisibilityState,
} = getGraphVisibilityStateOnDataChange({
const { graphVisibilityStates: localStoredVisibilityStates } = useMemo(
() =>
getGraphVisibilityStateOnDataChange({
options,
isExpandedName: false,
isExpandedName: true,
name,
});
setGraphVisibility(localStoredVisibilityState);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryResponse.isSuccess]);
}),
[options, name],
);
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<
boolean[]
>(localStoredVisibilityStates);
useEffect(() => {
setGraphsVisibilityStates(localStoredVisibilityStates);
if (!lineChartRef.current) return;
graphVisibiltyState.forEach((state, index) => {
localStoredVisibilityStates.forEach((state, index) => {
lineChartRef.current?.toggleGraph(index, state);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
setGraphsVisibilityStates(localStoredVisibilityStates);
}, [localStoredVisibilityStates]);
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
@@ -124,7 +124,6 @@ function WidgetGraphComponent({
if (setSelectedDashboard && updatedDashboard.payload) {
setSelectedDashboard(updatedDashboard.payload);
}
setDeleteModal(false);
featureResponse.refetch();
},
onError: () => {
@@ -256,7 +255,6 @@ function WidgetGraphComponent({
destroyOnClose
onCancel={onDeleteModelHandler}
open={deleteModal}
confirmLoading={updateDashboardMutation.isLoading}
title="Delete"
height="10vh"
onOk={onDeleteHandler}
@@ -276,14 +274,13 @@ function WidgetGraphComponent({
>
<FullView
name={`${name}expanded`}
originalName={name}
widget={widget}
yAxisUnit={widget.yAxisUnit}
onToggleModelHandler={onToggleModelHandler}
parentChartRef={lineChartRef}
parentGraphVisibilityState={setGraphVisibility}
onDragSelect={onDragSelect}
options={options}
setGraphsVisibilityStates={setGraphsVisibilityStates}
graphsVisibilityStates={graphsVisibilityStates}
/>
</Modal>

View File

@@ -1,15 +1,10 @@
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import isEmpty from 'lodash-es/isEmpty';
@@ -17,7 +12,6 @@ import _noop from 'lodash-es/noop';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -43,12 +37,6 @@ function GridCardGraph({
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const urlQuery = useUrlQuery();
const location = useLocation();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const onDragSelect = useCallback(
(start: number, end: number): void => {
@@ -58,44 +46,10 @@ function GridCardGraph({
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
const { maxTime, minTime } = GetMinMax('custom', [
startTimestamp,
endTimestamp,
]);
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.push(generatedUrl);
},
[dispatch, location.pathname, urlQuery],
[dispatch],
);
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
const startTime = searchParams.get(QueryParams.startTime);
const endTime = searchParams.get(QueryParams.endTime);
if (startTime && endTime && startTime !== endTime) {
dispatch(
UpdateTimeInterval('custom', [
parseInt(getTimeString(startTime), 10),
parseInt(getTimeString(endTime), 10),
]),
);
}
};
useEffect(() => {
window.addEventListener('popstate', handleBackNavigation);
return (): void => {
window.removeEventListener('popstate', handleBackNavigation);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const graphRef = useRef<HTMLDivElement>(null);
const isVisible = useIntersectionObserver(graphRef, undefined, true);
@@ -116,6 +70,11 @@ function GridCardGraph({
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const queryResponse = useGetQueryRange(
{
selectedTime: widget?.timePreferance,
@@ -163,10 +122,6 @@ function GridCardGraph({
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
: headerMenuList;
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
Array(queryResponse.data?.payload?.data.result.length || 0).fill(true),
);
const options = useMemo(
() =>
getUPlotChartOptions({
@@ -180,17 +135,11 @@ function GridCardGraph({
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
softMax: widget.softMax === undefined ? null : widget.softMax,
softMin: widget.softMin === undefined ? null : widget.softMin,
graphsVisibilityStates: graphVisibility,
setGraphsVisibilityStates: setGraphVisibility,
}),
[
widget?.id,
widget?.yAxisUnit,
widget.thresholds,
widget.softMax,
widget.softMin,
queryResponse.data?.payload,
containerDimensions,
isDarkMode,
@@ -198,8 +147,6 @@ function GridCardGraph({
onClickHandler,
minTimeScale,
maxTimeScale,
graphVisibility,
setGraphVisibility,
],
);
@@ -220,8 +167,6 @@ function GridCardGraph({
threshold={threshold}
headerMenuList={menuList}
onClickHandler={onClickHandler}
graphVisibiltyState={graphVisibility}
setGraphVisibility={setGraphVisibility}
/>
)}
</div>

View File

@@ -1,7 +1,7 @@
import { ToggleGraphProps } from 'components/Graph/types';
import { UplotProps } from 'components/Uplot/Uplot';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
import { MutableRefObject, ReactNode } from 'react';
import { UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
@@ -28,8 +28,6 @@ export interface WidgetGraphComponentProps extends UplotProps {
threshold?: ReactNode;
headerMenuList: MenuItemKeys[];
isWarning: boolean;
graphVisibiltyState: boolean[];
setGraphVisibility: Dispatch<SetStateAction<boolean[]>>;
}
export interface GridCardGraphProps {

View File

@@ -1,10 +1,8 @@
.fullscreen-grid-container {
overflow: auto;
margin-top: 1rem;
.react-grid-layout {
border: none !important;
margin-top: 0;
}
}
@@ -15,9 +13,3 @@
height: calc(100% - 30px);
}
}
.lightMode {
.fullscreen-grid-container {
background-color: rgb(250, 250, 250);
}
}

View File

@@ -1,6 +1,6 @@
import './GridCardLayout.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme';
@@ -8,12 +8,9 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import isEqual from 'lodash-es/isEqual';
import { FullscreenIcon } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useEffect, useState } from 'react';
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -32,7 +29,6 @@ import {
ReactGridLayout,
} from './styles';
import { GraphLayoutProps } from './types';
import { removeUndefinedValuesFromLayout } from './utils';
function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
const {
@@ -55,8 +51,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
const updateDashboardMutation = useUpdateDashboard();
const { notifications } = useNotifications();
@@ -77,10 +71,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
userRole,
);
useEffect(() => {
setDashboardLayout(layouts);
}, [layouts]);
const onSaveHandler = (): void => {
if (!selectedDashboard) return;
@@ -88,7 +78,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
...selectedDashboard,
data: {
...selectedDashboard.data,
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
layout: layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
},
uuid: selectedDashboard.uuid,
};
@@ -100,6 +90,9 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
setLayouts(updatedDashboard.payload.data.layout);
setSelectedDashboard(updatedDashboard.payload);
}
notifications.success({
message: t('dashboard:layout_saved_successfully'),
});
featureResponse.refetch();
},
@@ -115,32 +108,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
? [...ViewMenuAction, ...EditMenuAction]
: [...ViewMenuAction];
const handleLayoutChange = (layout: Layout[]): void => {
const filterLayout = removeUndefinedValuesFromLayout(layout);
const filterDashboardLayout = removeUndefinedValuesFromLayout(
dashboardLayout,
);
if (!isEqual(filterLayout, filterDashboardLayout)) {
setDashboardLayout(layout);
}
};
useEffect(() => {
if (
dashboardLayout &&
Array.isArray(dashboardLayout) &&
dashboardLayout.length > 0 &&
!isEqual(layouts, dashboardLayout) &&
!isDashboardLocked &&
saveLayoutPermission &&
!updateDashboardMutation.isLoading
) {
onSaveHandler();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dashboardLayout]);
return (
<>
<ButtonContainer>
@@ -153,6 +120,17 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
{t('dashboard:full_view')}
</Button>
{!isDashboardLocked && saveLayoutPermission && (
<Button
loading={updateDashboardMutation.isLoading}
onClick={onSaveHandler}
icon={<SaveFilled />}
disabled={updateDashboardMutation.isLoading}
>
{t('dashboard:save_layout')}
</Button>
)}
{!isDashboardLocked && addPanelPermission && (
<Button
onClick={onAddPanelHandler}
@@ -175,12 +153,12 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
isDroppable={!isDashboardLocked && addPanelPermission}
isResizable={!isDashboardLocked && addPanelPermission}
allowOverlap={false}
onLayoutChange={handleLayoutChange}
onLayoutChange={setLayouts}
draggableHandle=".drag-handle"
layout={dashboardLayout}
layout={layouts}
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
>
{dashboardLayout.map((layout) => {
{layouts.map((layout) => {
const { i: id } = layout;
const currentWidget = (widgets || [])?.find((e) => e.id === id);
@@ -200,7 +178,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
name={currentWidget?.id || ''}
headerMenuList={widgetActions}
variables={variables}
fillSpans={currentWidget?.fillSpans}
/>
</Card>
</CardContainer>

View File

@@ -8,8 +8,6 @@
box-sizing: border-box;
font-size: 14px;
font-weight: 600;
cursor: move;
}
.widget-header-title {

View File

@@ -1,14 +1,21 @@
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback } from 'react';
import { Layout } from 'react-grid-layout';
import { EMPTY_WIDGET_LAYOUT } from './config';
import GraphLayoutContainer from './GridCardLayout';
function GridGraph(): JSX.Element {
const { handleToggleDashboardSlider } = useDashboard();
const { handleToggleDashboardSlider, setLayouts } = useDashboard();
const onEmptyWidgetHandler = useCallback(() => {
handleToggleDashboardSlider(true);
}, [handleToggleDashboardSlider]);
setLayouts((preLayout: Layout[]) => [
EMPTY_WIDGET_LAYOUT,
...(preLayout || []),
]);
}, [handleToggleDashboardSlider, setLayouts]);
return <GraphLayoutContainer onAddPanelHandler={onEmptyWidgetHandler} />;
}

View File

@@ -1,8 +0,0 @@
import { Layout } from 'react-grid-layout';
export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
layout.map((obj) =>
Object.fromEntries(
Object.entries(obj).filter(([, value]) => value !== undefined),
),
) as Layout[];

View File

@@ -24,14 +24,10 @@ import { Dashboard } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import DateComponent from '../../components/ResizeTable/TableComponent/DateComponent';
import useSortableTable from '../../hooks/ResizeTable/useSortableTable';
import useUrlQuery from '../../hooks/useUrlQuery';
import { GettableAlert } from '../../types/api/alerts/get';
import ImportJSON from './ImportJSON';
import { ButtonContainer, NewDashboardButton, TableContainer } from './styles';
import DeleteButton from './TableComponents/DeleteButton';
import Name from './TableComponents/Name';
import { filterDashboard } from './utils';
const { Search } = Input;
@@ -59,26 +55,8 @@ function DashboardsList(): JSX.Element {
const [uploadedGrafana, setUploadedGrafana] = useState<boolean>(false);
const [isFilteringDashboards, setIsFilteringDashboards] = useState(false);
const params = useUrlQuery();
const orderColumnParam = params.get('columnKey');
const orderQueryParam = params.get('order');
const paginationParam = params.get('page');
const searchParams = params.get('search');
const [searchString, setSearchString] = useState<string>(searchParams || '');
const [dashboards, setDashboards] = useState<Dashboard[]>();
const sortingOrder: 'ascend' | 'descend' | null =
orderQueryParam === 'ascend' || orderQueryParam === 'descend'
? orderQueryParam
: null;
const { sortedInfo, handleChange } = useSortableTable<GettableAlert>(
sortingOrder,
orderColumnParam || '',
searchString,
);
const sortDashboardsByCreatedAt = (dashboards: Dashboard[]): void => {
const sortedDashboards = dashboards.sort(
(a, b) =>
@@ -89,12 +67,7 @@ function DashboardsList(): JSX.Element {
useEffect(() => {
sortDashboardsByCreatedAt(dashboardListResponse);
const filteredDashboards = filterDashboard(
searchString,
dashboardListResponse,
);
setDashboards(filteredDashboards || []);
}, [dashboardListResponse, searchString]);
}, [dashboardListResponse]);
const [newDashboardState, setNewDashboardState] = useState({
loading: false,
@@ -116,10 +89,6 @@ function DashboardsList(): JSX.Element {
return prev - next;
},
render: DateComponent,
sortOrder:
sortedInfo.columnKey === DynamicColumnsKey.CreatedAt
? sortedInfo.order
: null,
},
{
title: 'Created By',
@@ -139,10 +108,6 @@ function DashboardsList(): JSX.Element {
return prev - next;
},
render: DateComponent,
sortOrder:
sortedInfo.columnKey === DynamicColumnsKey.UpdatedAt
? sortedInfo.order
: null,
},
{
title: 'Last Updated By',
@@ -284,13 +249,28 @@ function DashboardsList(): JSX.Element {
return menuItems;
}, [createNewDashboard, isDashboardListLoading, onNewDashboardHandler, t]);
const searchArrayOfObjects = (searchValue: string): any[] => {
// Convert the searchValue to lowercase for case-insensitive search
const searchValueLowerCase = searchValue.toLowerCase();
// Use the filter method to find matching objects
return dashboardListResponse.filter((item: any) => {
// Convert each property value to lowercase for case-insensitive search
const itemValues = Object.values(item?.data).map((value: any) =>
value.toString().toLowerCase(),
);
// Check if any property value contains the searchValue
return itemValues.some((value) => value.includes(searchValueLowerCase));
});
};
const handleSearch = useDebouncedFn((event: unknown): void => {
setIsFilteringDashboards(true);
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
const filteredDashboards = filterDashboard(searchText, dashboardListResponse);
const filteredDashboards = searchArrayOfObjects(searchText);
setDashboards(filteredDashboards);
setIsFilteringDashboards(false);
setSearchString(searchText);
}, 500);
const GetHeader = useMemo(
@@ -303,7 +283,6 @@ function DashboardsList(): JSX.Element {
onChange={handleSearch}
loading={isFilteringDashboards}
style={{ marginBottom: 16, marginTop: 16 }}
defaultValue={searchString}
/>
</Col>
@@ -349,12 +328,11 @@ function DashboardsList(): JSX.Element {
newDashboardState.loading,
newDashboardState.error,
getText,
searchString,
],
);
return (
<Card style={{ margin: '16px 0' }}>
<Card>
{GetHeader}
<TableContainer>
@@ -371,14 +349,12 @@ function DashboardsList(): JSX.Element {
pageSize: 10,
defaultPageSize: 10,
total: data?.length || 0,
defaultCurrent: Number(paginationParam) || 1,
}}
showHeader
bordered
sticky
loading={isDashboardListLoading}
dataSource={data}
onChange={handleChange}
showSorterTooltip
/>
</TableContainer>

View File

@@ -1,5 +0,0 @@
.delete-modal {
.ant-modal-confirm-body {
align-items: center;
}
}

View File

@@ -1,5 +1,3 @@
import './DeleteButton.styles.scss';
import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Modal, Tooltip, Typography } from 'antd';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@@ -66,7 +64,6 @@ function DeleteButton({
okText: 'Delete',
okButtonProps: { danger: true },
centered: true,
className: 'delete-modal',
});
}, [modal, name, deleteDashboardMutation, notifications, t, queryClient]);

View File

@@ -1,7 +1,8 @@
import { blue } from '@ant-design/colors';
import { Typography } from 'antd';
import styled from 'styled-components';
export const TableLinkText = styled(Typography.Text)`
color: #4e74f8 !important;
color: ${blue.primary} !important;
cursor: pointer;
`;

View File

@@ -1,27 +0,0 @@
import { Dashboard } from 'types/api/dashboard/getAll';
export const filterDashboard = (
searchValue: string,
dashboardList: Dashboard[],
): Dashboard[] => {
const searchValueLowerCase = searchValue?.toLowerCase();
// Filter by title, description, tags
return dashboardList.filter((item: Dashboard) => {
const { title, description, tags } = item.data;
const itemValuesNew = [title, description];
if (tags && tags.length > 0) {
itemValuesNew.push(...tags);
}
// Check if any property value contains the searchValue
return itemValuesNew.some((value) => {
if (value) {
return value.toLowerCase().includes(searchValueLowerCase);
}
return false;
});
});
};

View File

@@ -70,12 +70,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
(_: number, log: ILog): JSX.Element => {
if (options.format === 'raw') {
return (
<RawLogView
key={log.id}
data={log}
linesPerRow={options.maxLines}
selectedFields={selectedFields}
/>
<RawLogView key={log.id} data={log} linesPerRow={options.maxLines} />
);
}

View File

@@ -1,18 +1,19 @@
import { Button, ButtonProps } from 'antd';
import { themeColors } from 'constants/theme';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
export const LiveButtonStyled = styled(Button)<ButtonProps>`
background-color: #1eb475;
background-color: rgba(${themeColors.buttonSuccessRgb}, 0.9);
${({ danger }): FlattenSimpleInterpolation =>
!danger
? css`
&:hover {
background-color: #1eb475 !important;
background-color: rgba(${themeColors.buttonSuccessRgb}, 1) !important;
}
&:active {
background-color: #1eb475 !important;
background-color: rgba(${themeColors.buttonSuccessRgb}, 0.7) !important;
}
`
: css``}

View File

@@ -1,9 +1,7 @@
import { Col, Row, Space, Typography } from 'antd';
import ROUTES from 'constants/routes';
import { Col, Row, Space } from 'antd';
import NewExplorerCTA from 'container/NewExplorerCTA';
import { FileText } from 'lucide-react';
import { useLocation } from 'react-use';
import ShowBreadcrumbs from '../TopNav/Breadcrumbs';
import DateTimeSelector from '../TopNav/DateTimeSelection';
import { Container } from './styles';
import { LocalTopNavProps } from './types';
@@ -12,25 +10,13 @@ function LocalTopNav({
actions,
renderPermissions,
}: LocalTopNavProps): JSX.Element | null {
const { pathname } = useLocation();
const isLiveLogsPage = pathname === ROUTES.LIVE_LOGS;
return (
<Container>
{isLiveLogsPage && (
<Col span={16}>
<Space>
<FileText color="#fff" size={16} />
<Col span={16}>
<ShowBreadcrumbs />
</Col>
<Typography.Title level={4} style={{ marginTop: 0, marginBottom: 0 }}>
Live Logs
</Typography.Title>
</Space>
</Col>
)}
<Col span={isLiveLogsPage ? 8 : 24}>
<Col span={8}>
<Row justify="end">
<Space align="start" size={30} direction="horizontal">
<NewExplorerCTA />

View File

@@ -3,7 +3,7 @@ import styled from 'styled-components';
export const Container = styled(Row)`
&&& {
margin-top: 1rem;
margin-top: 2rem;
min-height: 8vh;
}
`;

View File

@@ -63,7 +63,7 @@ function LogDetailedView({
queryString,
);
history.replace(`${ROUTES.OLD_LOGS_EXPLORER}?q=${updatedQueryString}`);
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
},
[history, queryString],
);

View File

@@ -80,12 +80,7 @@ function LogsExplorerList({
(_: number, log: ILog): JSX.Element => {
if (options.format === 'raw') {
return (
<RawLogView
key={log.id}
data={log}
linesPerRow={options.maxLines}
selectedFields={selectedFields}
/>
<RawLogView key={log.id} data={log} linesPerRow={options.maxLines} />
);
}

View File

@@ -72,14 +72,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
const log = logs[index];
if (viewMode === 'raw') {
return (
<RawLogView
key={log.id}
data={log}
linesPerRow={linesPerRow}
selectedFields={selected}
/>
);
return <RawLogView key={log.id} data={log} linesPerRow={linesPerRow} />;
}
return (

View File

@@ -74,7 +74,6 @@ function LogsTopNav(): JSX.Element {
icon={<PlayCircleFilled />}
onClick={handleGoLive}
type="primary"
size="small"
>
Go Live
</LiveButtonStyled>

View File

@@ -1,18 +1,19 @@
import { Button, ButtonProps } from 'antd';
import { themeColors } from 'constants/theme';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components';
export const LiveButtonStyled = styled(Button)<ButtonProps>`
background-color: #1eb475;
background-color: rgba(${themeColors.buttonSuccessRgb}, 0.9);
${({ danger }): FlattenSimpleInterpolation =>
!danger
? css`
&:hover {
background-color: #1eb475 !important;
background-color: rgba(${themeColors.buttonSuccessRgb}, 1) !important;
}
&:active {
background-color: #1eb475 !important;
background-color: rgba(${themeColors.buttonSuccessRgb}, 0.7) !important;
}
`
: css``}

View File

@@ -20,6 +20,4 @@ export const getWidgetQueryBuilder = ({
timePreferance: 'GLOBAL_TIME',
title,
yAxisUnit,
softMax: null,
softMin: null,
});

View File

@@ -13,10 +13,8 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { defaultTo } from 'lodash-es';
import { useCallback, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
@@ -54,10 +52,8 @@ function Application(): JSX.Element {
);
const { servicename } = useParams<IServiceName>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { search, pathname } = useLocation();
const { search } = useLocation();
const { queries } = useResourceAttribute();
const urlQuery = useUrlQuery();
const selectedTags = useMemo(
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
[queries],
@@ -108,10 +104,7 @@ function Application(): JSX.Element {
);
const topLevelOperationsRoute = useMemo(
() =>
topLevelOperations
? defaultTo(topLevelOperations[servicename || ''], [])
: [],
() => (topLevelOperations ? topLevelOperations[servicename || ''] : []),
[servicename, topLevelOperations],
);
@@ -164,16 +157,11 @@ function Application(): JSX.Element {
const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end);
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
},
[dispatch, pathname, urlQuery],
[dispatch],
);
const onErrorTrackHandler = (timestamp: number): (() => void) => (): void => {

View File

@@ -1,9 +1,7 @@
import Spinner from 'components/Spinner';
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
import useErrorNotification from 'hooks/useErrorNotification';
import { useParams } from 'react-router-dom';
import { IServiceName } from '../../types';
import ApDexMetrics from './ApDexMetrics';
import { metricMeta } from './constants';
import { ApDexDataSwitcherProps } from './types';
@@ -15,8 +13,7 @@ function ApDexMetricsApplication({
thresholdValue,
topLevelOperationsRoute,
}: ApDexDataSwitcherProps): JSX.Element {
const { servicename } = useParams<IServiceName>();
const { data, isLoading, error } = useGetMetricMeta(metricMeta, servicename);
const { data, isLoading, error } = useGetMetricMeta(metricMeta);
useErrorNotification(error);
if (isLoading) {

View File

@@ -1,5 +0,0 @@
.flexBtn {
display: flex;
align-items: center;
gap: 8px;
}

View File

@@ -1,7 +1,6 @@
import { Button, Card, Space, Typography } from 'antd';
import { Button, Space, Typography } from 'antd';
import changeMyPassword from 'api/user/changeMyPassword';
import { useNotifications } from 'hooks/useNotifications';
import { Save } from 'lucide-react';
import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -21,7 +20,9 @@ function PasswordContainer(): JSX.Element {
false,
);
const defaultPlaceHolder = '*************';
const defaultPlaceHolder = t('input_password', {
ns: 'settings',
});
const { notifications } = useNotifications();
@@ -88,69 +89,66 @@ function PasswordContainer(): JSX.Element {
currentPassword === updatePassword;
return (
<Card>
<Space direction="vertical" size="small">
<Typography.Title level={4} style={{ marginTop: 0 }}>
{t('change_password', {
<Space direction="vertical" size="large">
<Typography.Title level={3}>
{t('change_password', {
ns: 'settings',
})}
</Typography.Title>
<Space direction="vertical">
<Typography>
{t('current_password', {
ns: 'settings',
})}
</Typography.Title>
<Space direction="vertical">
<Typography>
{t('current_password', {
ns: 'settings',
})}
</Typography>
<Password
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
setCurrentPassword(event.target.value);
}}
value={currentPassword}
/>
</Space>
<Space direction="vertical">
<Typography>
{t('new_password', {
ns: 'settings',
})}
</Typography>
<Password
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
const updatedValue = event.target.value;
setUpdatePassword(updatedValue);
}}
value={updatePassword}
/>
</Space>
<Space>
{isPasswordPolicyError && (
<Typography.Paragraph
style={{
color: '#D89614',
marginTop: '0.50rem',
}}
>
{isPasswordNotValidMessage}
</Typography.Paragraph>
)}
</Space>
<Button
disabled={isDisabled}
loading={isLoading}
onClick={onChangePasswordClickHandler}
type="primary"
>
<Save size={12} style={{ marginRight: '8px' }} />{' '}
{t('change_password', {
ns: 'settings',
})}
</Button>
</Typography>
<Password
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
setCurrentPassword(event.target.value);
}}
value={currentPassword}
/>
</Space>
</Card>
<Space direction="vertical">
<Typography>
{t('new_password', {
ns: 'settings',
})}
</Typography>
<Password
disabled={isLoading}
placeholder={defaultPlaceHolder}
onChange={(event): void => {
const updatedValue = event.target.value;
setUpdatePassword(updatedValue);
}}
value={updatePassword}
/>
</Space>
<Space>
{isPasswordPolicyError && (
<Typography.Paragraph
style={{
color: '#D89614',
marginTop: '0.50rem',
}}
>
{isPasswordNotValidMessage}
</Typography.Paragraph>
)}
</Space>
<Button
disabled={isDisabled}
loading={isLoading}
onClick={onChangePasswordClickHandler}
type="primary"
>
{t('change_password', {
ns: 'settings',
})}
</Button>
</Space>
);
}

View File

@@ -1,10 +1,6 @@
import '../MySettings.styles.scss';
import './UserInfo.styles.scss';
import { Button, Card, Flex, Input, Space, Typography } from 'antd';
import { Button, Space, Typography } from 'antd';
import editUser from 'api/user/editUser';
import { useNotifications } from 'hooks/useNotifications';
import { PencilIcon, UserSquare } from 'lucide-react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -16,7 +12,7 @@ import AppReducer from 'types/reducer/app';
import { NameInput } from '../styles';
function UserInfo(): JSX.Element {
function UpdateName(): JSX.Element {
const { user, role, org, userFlags } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
@@ -76,51 +72,28 @@ function UserInfo(): JSX.Element {
};
return (
<Card>
<div>
<Space direction="vertical" size="middle">
<Flex gap={8}>
<UserSquare />{' '}
<Typography.Title level={4} style={{ marginTop: 0 }}>
User Details
</Typography.Title>
</Flex>
<Flex gap={16}>
<Space>
<Typography className="userInfo-label">Name</Typography>
<NameInput
placeholder="Your Name"
onChange={(event): void => {
setChangedName(event.target.value);
}}
value={changedName}
disabled={loading}
/>
</Space>
<Button
className="flexBtn"
loading={loading}
disabled={loading}
onClick={onClickUpdateHandler}
type="primary"
>
<PencilIcon size={12} /> Update
</Button>
</Flex>
<Space>
<Typography className="userInfo-label"> Email </Typography>
<Input className="userInfo-value" value={user.email} disabled />
</Space>
<Space>
<Typography className="userInfo-label"> Role </Typography>
<Input className="userInfo-value" value={role || ''} disabled />
</Space>
<Typography>Name</Typography>
<NameInput
placeholder="Your Name"
onChange={(event): void => {
setChangedName(event.target.value);
}}
value={changedName}
disabled={loading}
/>
<Button
loading={loading}
disabled={loading}
onClick={onClickUpdateHandler}
type="primary"
>
Update Name
</Button>
</Space>
</Card>
</div>
);
}
export default UserInfo;
export default UpdateName;

View File

@@ -1,7 +0,0 @@
.userInfo-label {
min-width: 150px;
}
.userInfo-value {
min-width: 20rem;
}

View File

@@ -1,28 +1,16 @@
import './MySettings.styles.scss';
import { Button, Space } from 'antd';
import { Logout } from 'api/utils';
import { LogOut } from 'lucide-react';
import { Space, Typography } from 'antd';
import { useTranslation } from 'react-i18next';
import Password from './Password';
import UserInfo from './UserInfo';
import UpdateName from './UpdateName';
function MySettings(): JSX.Element {
const { t } = useTranslation(['routes']);
return (
<Space
direction="vertical"
size="large"
style={{
margin: '16px 0',
}}
>
<UserInfo />
<Space direction="vertical" size="large">
<Typography.Title level={2}>{t('my_settings')}</Typography.Title>
<UpdateName />
<Password />
<Button className="flexBtn" onClick={(): void => Logout()} type="primary">
<LogOut size={12} /> Logout
</Button>
</Space>
);
}

View File

@@ -63,8 +63,6 @@ function DashboardGraphSlider(): JSX.Element {
panelTypes: name,
query: initialQueriesMap.metrics,
timePreferance: 'GLOBAL_TIME',
softMax: null,
softMin: null,
},
],
},

View File

@@ -6,11 +6,3 @@
text-overflow: ellipsis;
color: gray;
}
.variable-item {
.variable-select {
.ant-select-dropdown {
max-width: 300px;
}
}
}

View File

@@ -5,16 +5,15 @@ import { WarningOutlined } from '@ant-design/icons';
import { Input, Popover, Select, Tooltip, Typography } from 'antd';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useDebounce from 'hooks/useDebounce';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { debounce } from 'lodash-es';
import map from 'lodash-es/map';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { VariableResponseProps } from 'types/api/dashboard/variables/query';
import { popupContainer } from 'utils/selectPopupContainer';
import { variablePropsToPayloadVariables } from '../utils';
import { SelectItemStyle, VariableContainer, VariableValue } from './styles';
@@ -45,7 +44,6 @@ const getSelectValue = (
return selectedValue?.toString() || '';
};
// eslint-disable-next-line sonarjs/cognitive-complexity
function VariableItem({
variableData,
existingVariables,
@@ -57,8 +55,24 @@ function VariableItem({
[],
);
const [variableValue, setVaribleValue] = useState(
variableData?.selectedValue?.toString() || '',
);
const debouncedVariableValue = useDebounce(variableValue, 500);
const [errorMessage, setErrorMessage] = useState<null | string>(null);
useEffect(() => {
const { selectedValue } = variableData;
if (selectedValue) {
setVaribleValue(selectedValue?.toString());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variableData]);
const getDependentVariables = (queryValue: string): string[] => {
const matches = queryValue.match(variableRegexPattern);
@@ -78,12 +92,7 @@ function VariableItem({
const variableName = variableData.name || '';
dependentVariables?.forEach((element) => {
const [, variable] =
Object.entries(existingVariables).find(
([, value]) => value.name === element,
) || [];
dependentVariablesStr += `${element}${variable?.selectedValue}`;
dependentVariablesStr += `${element}${existingVariables[element]?.selectedValue}`;
});
const variableKey = dependentVariablesStr.replace(/\s/g, '');
@@ -195,9 +204,6 @@ function VariableItem({
}
};
// do not debounce the above function as we do not need debounce in select variables
const debouncedHandleChange = debounce(handleChange, 500);
const { selectedValue } = variableData;
const selectedValueStringified = useMemo(() => getSelectValue(selectedValue), [
selectedValue,
@@ -213,6 +219,14 @@ function VariableItem({
: undefined;
const enableSelectAll = variableData.multiSelect && variableData.showALLOption;
useEffect(() => {
if (debouncedVariableValue !== variableData?.selectedValue?.toString()) {
handleChange(debouncedVariableValue);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedVariableValue]);
useEffect(() => {
// Fetch options for CUSTOM Type
if (variableData.type === 'CUSTOM') {
@@ -226,7 +240,7 @@ function VariableItem({
placement="top"
title={isDashboardLocked ? 'Dashboard is locked' : ''}
>
<VariableContainer className="variable-item">
<VariableContainer>
<Typography.Text className="variable-name" ellipsis>
${variableData.name}
</Typography.Text>
@@ -236,10 +250,9 @@ function VariableItem({
placeholder="Enter value"
disabled={isDashboardLocked}
bordered={false}
key={variableData.selectedValue?.toString()}
defaultValue={variableData.selectedValue?.toString()}
value={variableValue}
onChange={(e): void => {
debouncedHandleChange(e.target.value || '');
setVaribleValue(e.target.value || '');
}}
style={{
width:
@@ -250,25 +263,18 @@ function VariableItem({
!errorMessage &&
optionsData && (
<Select
key={
selectValue && Array.isArray(selectValue)
? selectValue.join(' ')
: selectValue || variableData.id
}
defaultValue={selectValue}
value={selectValue}
onChange={handleChange}
bordered={false}
placeholder="Select value"
placement="bottomRight"
mode={mode}
dropdownMatchSelectWidth={false}
style={SelectItemStyle}
loading={isLoading}
showArrow
showSearch
data-testid="variable-select"
className="variable-select"
disabled={isDashboardLocked}
getPopupContainer={popupContainer}
>
{enableSelectAll && (
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>

View File

@@ -7,5 +7,5 @@ export const RIBBON_STYLES = {
export const buttonText = {
[ROUTES.LOGS_EXPLORER]: 'Switch to Old Logs Explorer',
[ROUTES.TRACE]: 'Try new Traces Explorer',
[ROUTES.OLD_LOGS_EXPLORER]: 'Switch to New Logs Explorer',
[ROUTES.LOGS]: 'Switch to New Logs Explorer',
};

View File

@@ -14,16 +14,16 @@ function NewExplorerCTA(): JSX.Element | null {
() =>
location.pathname === ROUTES.LOGS_EXPLORER ||
location.pathname === ROUTES.TRACE ||
location.pathname === ROUTES.OLD_LOGS_EXPLORER,
location.pathname === ROUTES.LOGS,
[location.pathname],
);
const onClickHandler = useCallback((): void => {
if (location.pathname === ROUTES.LOGS_EXPLORER) {
history.push(ROUTES.OLD_LOGS_EXPLORER);
history.push(ROUTES.LOGS);
} else if (location.pathname === ROUTES.TRACE) {
history.push(ROUTES.TRACES_EXPLORER);
} else if (location.pathname === ROUTES.OLD_LOGS_EXPLORER) {
} else if (location.pathname === ROUTES.LOGS) {
history.push(ROUTES.LOGS_EXPLORER);
}
}, [location.pathname]);
@@ -36,7 +36,6 @@ function NewExplorerCTA(): JSX.Element | null {
danger
data-testid="newExplorerCTA"
type="primary"
size="small"
>
{buttonText[location.pathname]}
</Button>

View File

@@ -30,10 +30,11 @@ function QueryHeader({
const [collapse, setCollapse] = useState(false);
return (
<QueryWrapper>
<Row style={{ justifyContent: 'space-between', marginBottom: '1rem' }}>
<Row style={{ justifyContent: 'space-between' }}>
<Row>
<Button
type="default"
ghost
icon={disabled ? <EyeInvisibleFilled /> : <EyeFilled />}
onClick={onDisable}
>
@@ -41,6 +42,7 @@ function QueryHeader({
</Button>
<Button
type="default"
ghost
icon={collapse ? <RightOutlined /> : <DownOutlined />}
onClick={(): void => setCollapse(!collapse)}
/>
@@ -49,6 +51,7 @@ function QueryHeader({
{deletable && (
<Button
type="default"
ghost
danger
icon={<DeleteOutlined />}
onClick={onDelete}

View File

@@ -1,11 +1,9 @@
import { Input } from 'antd';
import MonacoEditor from 'components/Editor';
import { LEGEND } from 'constants/global';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ChangeEvent, useCallback } from 'react';
import { IClickHouseQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { getFormatedLegend } from 'utils/getFormatedLegend';
import QueryHeader from '../QueryHeader';
@@ -59,11 +57,7 @@ function ClickHouseQueryBuilder({
const handleUpdateInput = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
let { value } = e.target;
if (name === LEGEND) {
value = getFormatedLegend(value);
}
const { name, value } = e.target;
handleUpdateQuery(name as keyof IClickHouseQuery, value);
},
[handleUpdateQuery],

View File

@@ -1,10 +1,8 @@
import { Input } from 'antd';
import { LEGEND } from 'constants/global';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ChangeEvent, useCallback } from 'react';
import { IPromQLQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { getFormatedLegend } from 'utils/getFormatedLegend';
import QueryHeader from '../QueryHeader';
@@ -30,11 +28,7 @@ function PromQLQueryBuilder({
const handleUpdateQuery = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const { name } = e.target;
let { value } = e.target;
if (name === LEGEND) {
value = getFormatedLegend(value);
}
const { name, value } = e.target;
const newQuery: IPromQLQuery = { ...queryData, [name]: value };
handleSetQueryItemData(queryIndex, EQueryType.PROM, newQuery);

View File

@@ -14,8 +14,6 @@ function WidgetGraphContainer({
selectedTime,
thresholds,
fillSpans = false,
softMax,
softMin,
}: WidgetGraphProps): JSX.Element {
const { selectedDashboard } = useDashboard();
@@ -61,8 +59,6 @@ function WidgetGraphContainer({
selectedWidget={selectedWidget}
thresholds={thresholds}
fillSpans={fillSpans}
softMax={softMax}
softMin={softMin}
/>
);
}

View File

@@ -1,19 +1,14 @@
import { QueryParams } from 'constants/query';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
@@ -28,8 +23,6 @@ function WidgetGraph({
yAxisUnit,
thresholds,
fillSpans,
softMax,
softMin,
}: WidgetGraphProps): JSX.Element {
const { stagedQuery } = useQueryBuilder();
@@ -40,7 +33,6 @@ function WidgetGraph({
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const location = useLocation();
useEffect((): void => {
const { startTime, endTime } = getTimeRange(getWidgetQueryRange);
@@ -70,47 +62,14 @@ function WidgetGraph({
(start: number, end: number): void => {
const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
const { maxTime, minTime } = GetMinMax('custom', [
startTimestamp,
endTimestamp,
]);
params.set(QueryParams.startTime, minTime.toString());
params.set(QueryParams.endTime, maxTime.toString());
const generatedUrl = `${location.pathname}?${params.toString()}`;
history.push(generatedUrl);
},
[dispatch, location.pathname, params],
[dispatch],
);
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
const startTime = searchParams.get(QueryParams.startTime);
const endTime = searchParams.get(QueryParams.endTime);
if (startTime && endTime && startTime !== endTime) {
dispatch(
UpdateTimeInterval('custom', [
parseInt(getTimeString(startTime), 10),
parseInt(getTimeString(endTime), 10),
]),
);
}
};
useEffect(() => {
window.addEventListener('popstate', handleBackNavigation);
return (): void => {
window.removeEventListener('popstate', handleBackNavigation);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const options = useMemo(
() =>
getUPlotChartOptions({
@@ -124,8 +83,6 @@ function WidgetGraph({
fillSpans,
minTimeScale,
maxTimeScale,
softMax,
softMin,
}),
[
widgetId,
@@ -138,8 +95,6 @@ function WidgetGraph({
fillSpans,
minTimeScale,
maxTimeScale,
softMax,
softMin,
],
);
@@ -170,8 +125,6 @@ interface WidgetGraphProps {
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
softMax: number | null;
softMin: number | null;
}
export default WidgetGraph;

View File

@@ -17,8 +17,6 @@ function WidgetGraph({
selectedTime,
thresholds,
fillSpans,
softMax,
softMin,
}: WidgetGraphProps): JSX.Element {
const { currentQuery } = useQueryBuilder();
const { selectedDashboard } = useDashboard();
@@ -55,8 +53,6 @@ function WidgetGraph({
selectedGraph={selectedGraph}
yAxisUnit={yAxisUnit}
fillSpans={fillSpans}
softMax={softMax}
softMin={softMin}
/>
</Container>
);

View File

@@ -11,8 +11,6 @@ function LeftContainer({
selectedTime,
thresholds,
fillSpans,
softMax,
softMin,
}: WidgetGraphProps): JSX.Element {
return (
<>
@@ -22,8 +20,6 @@ function LeftContainer({
selectedGraph={selectedGraph}
yAxisUnit={yAxisUnit}
fillSpans={fillSpans}
softMax={softMax}
softMin={softMin}
/>
<QueryContainer>
<QuerySection selectedTime={selectedTime} selectedGraph={selectedGraph} />

View File

@@ -30,15 +30,6 @@ export const panelTypeVsThreshold: { [key in PANEL_TYPES]: boolean } = {
[PANEL_TYPES.EMPTY_WIDGET]: false,
} as const;
export const panelTypeVsSoftMinMax: { [key in PANEL_TYPES]: boolean } = {
[PANEL_TYPES.TIME_SERIES]: true,
[PANEL_TYPES.VALUE]: false,
[PANEL_TYPES.TABLE]: false,
[PANEL_TYPES.LIST]: false,
[PANEL_TYPES.TRACE]: false,
[PANEL_TYPES.EMPTY_WIDGET]: false,
} as const;
export const panelTypeVsDragAndDrop: { [key in PANEL_TYPES]: boolean } = {
[PANEL_TYPES.TIME_SERIES]: false,
[PANEL_TYPES.VALUE]: true,

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