Compare commits
1 Commits
v0.56.0-tr
...
variables-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782e5303c3 |
13
Makefile
13
Makefile
@@ -79,7 +79,7 @@ build-query-service-static:
|
||||
@if [ $(DEV_BUILD) != "" ]; then \
|
||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
||||
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
||||
else \
|
||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||
@@ -188,4 +188,13 @@ check-no-ee-references:
|
||||
fi
|
||||
|
||||
test:
|
||||
go test ./pkg/query-service/...
|
||||
go test ./pkg/query-service/app/metrics/...
|
||||
go test ./pkg/query-service/cache/...
|
||||
go test ./pkg/query-service/app/...
|
||||
go test ./pkg/query-service/app/querier/...
|
||||
go test ./pkg/query-service/converter/...
|
||||
go test ./pkg/query-service/formatter/...
|
||||
go test ./pkg/query-service/tests/integration/...
|
||||
go test ./pkg/query-service/rules/...
|
||||
go test ./pkg/query-service/collectorsimulator/...
|
||||
go test ./pkg/query-service/postprocess/...
|
||||
|
||||
@@ -133,7 +133,7 @@ services:
|
||||
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.23.7
|
||||
image: signoz/alertmanager:0.23.5
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
@@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.56.0
|
||||
image: signoz/query-service:0.55.0
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.56.0
|
||||
image: signoz/frontend:0.55.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.102.12
|
||||
image: signoz/signoz-otel-collector:0.102.10
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
|
||||
@@ -131,8 +131,8 @@ processors:
|
||||
exporters:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/signoz_traces
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
resource_to_telemetry_conversion:
|
||||
@@ -142,7 +142,7 @@ exporters:
|
||||
# logging: {}
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||
timeout: 10s
|
||||
use_new_schema: true
|
||||
extensions:
|
||||
|
||||
@@ -57,7 +57,7 @@ services:
|
||||
|
||||
alertmanager:
|
||||
container_name: signoz-alertmanager
|
||||
image: signoz/alertmanager:0.23.7
|
||||
image: signoz/alertmanager:0.23.5
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.102.12
|
||||
image: signoz/signoz-otel-collector:0.102.10
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
|
||||
@@ -147,7 +147,7 @@ services:
|
||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.7}
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.5}
|
||||
container_name: signoz-alertmanager
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
@@ -162,7 +162,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.55.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -201,7 +201,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.55.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -227,7 +227,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.12}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -152,7 +152,7 @@ services:
|
||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.7}
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.5}
|
||||
container_name: signoz-alertmanager
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
@@ -167,7 +167,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.55.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -207,7 +207,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.55.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -233,7 +233,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.12}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -142,8 +142,8 @@ extensions:
|
||||
exporters:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/signoz_traces
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
resource_to_telemetry_conversion:
|
||||
@@ -152,7 +152,7 @@ exporters:
|
||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://clickhouse:9000/signoz_logs
|
||||
docker_multi_node_cluster: ${env:DOCKER_MULTI_NODE_CLUSTER}
|
||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||
timeout: 10s
|
||||
use_new_schema: true
|
||||
# logging: {}
|
||||
|
||||
@@ -38,9 +38,8 @@ type APIHandlerOptions struct {
|
||||
Cache cache.Cache
|
||||
Gateway *httputil.ReverseProxy
|
||||
// Querier Influx Interval
|
||||
FluxInterval time.Duration
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
FluxInterval time.Duration
|
||||
UseLogsNewSchema bool
|
||||
}
|
||||
|
||||
type APIHandler struct {
|
||||
@@ -66,7 +65,6 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
Cache: opts.Cache,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
UseLogsNewSchema: opts.UseLogsNewSchema,
|
||||
UseTraceNewSchema: opts.UseTraceNewSchema,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -9,15 +9,7 @@ import (
|
||||
|
||||
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
validPath := false
|
||||
for _, allowedPrefix := range gateway.AllowedPrefix {
|
||||
if strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+allowedPrefix) {
|
||||
validPath = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !validPath {
|
||||
if !strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+gateway.AllowedPrefix) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,11 +53,7 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
if anomalyQueryExists {
|
||||
// ensure all queries have metric data source, and there should be only one anomaly query
|
||||
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
||||
// What is query.QueryName == query.Expression doing here?
|
||||
// In the current implementation, the way to recognize if a query is a formula is by
|
||||
// checking if the expression is the same as the query name. if the expression is different
|
||||
// then it is a formula. otherwise, it is simple builder query.
|
||||
if query.DataSource != v3.DataSourceMetrics && query.QueryName == query.Expression {
|
||||
if query.DataSource != v3.DataSourceMetrics {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("all queries must have metric data source")}, nil)
|
||||
return
|
||||
}
|
||||
@@ -104,13 +100,6 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
default:
|
||||
provider = anomaly.NewDailyProvider(
|
||||
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
||||
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
||||
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
||||
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
||||
)
|
||||
}
|
||||
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})
|
||||
if err != nil {
|
||||
|
||||
@@ -2,31 +2,32 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/app/db"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ah.APIHandler.SearchTraces(w, r)
|
||||
return
|
||||
if !ah.CheckFeature(basemodel.SmartTraceDetail) {
|
||||
zap.L().Info("SmartTraceDetail feature is not enabled in this plan")
|
||||
ah.APIHandler.SearchTraces(w, r)
|
||||
return
|
||||
}
|
||||
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
|
||||
if err != nil {
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
|
||||
return
|
||||
}
|
||||
|
||||
// This is commented since this will be taken care by new trace API
|
||||
result, err := ah.opts.DataConnector.SearchTraces(r.Context(), searchTracesParams, db.SmartTraceAlgorithm)
|
||||
if ah.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
// if !ah.CheckFeature(basemodel.SmartTraceDetail) {
|
||||
// zap.L().Info("SmartTraceDetail feature is not enabled in this plan")
|
||||
// ah.APIHandler.SearchTraces(w, r)
|
||||
// return
|
||||
// }
|
||||
// searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
|
||||
// if err != nil {
|
||||
// RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
|
||||
// return
|
||||
// }
|
||||
|
||||
// result, err := ah.opts.DataConnector.SearchTraces(r.Context(), searchTracesParams, db.SmartTraceAlgorithm)
|
||||
// if ah.HandleError(w, err, http.StatusBadRequest) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// ah.WriteJSON(w, r, result)
|
||||
ah.WriteJSON(w, r, result)
|
||||
|
||||
}
|
||||
|
||||
@@ -26,9 +26,8 @@ func NewDataConnector(
|
||||
dialTimeout time.Duration,
|
||||
cluster string,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool,
|
||||
) *ClickhouseReader {
|
||||
ch := basechr.NewReader(localDB, promConfigPath, lm, maxIdleConns, maxOpenConns, dialTimeout, cluster, useLogsNewSchema, useTraceNewSchema)
|
||||
ch := basechr.NewReader(localDB, promConfigPath, lm, maxIdleConns, maxOpenConns, dialTimeout, cluster, useLogsNewSchema)
|
||||
return &ClickhouseReader{
|
||||
conn: ch.GetConn(),
|
||||
appdb: localDB,
|
||||
|
||||
@@ -78,7 +78,6 @@ type ServerOptions struct {
|
||||
Cluster string
|
||||
GatewayUrl string
|
||||
UseLogsNewSchema bool
|
||||
UseTraceNewSchema bool
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -157,7 +156,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
serverOptions.DialTimeout,
|
||||
serverOptions.Cluster,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
)
|
||||
go qb.Start(readerReady)
|
||||
reader = qb
|
||||
@@ -191,7 +189,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
serverOptions.DisableRules,
|
||||
lm,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -273,7 +270,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
FluxInterval: fluxInterval,
|
||||
Gateway: gatewayProxy,
|
||||
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
||||
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
@@ -740,8 +736,7 @@ func makeRulesManager(
|
||||
cache cache.Cache,
|
||||
disableRules bool,
|
||||
fm baseint.FeatureLookup,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool) (*baserules.Manager, error) {
|
||||
useLogsNewSchema bool) (*baserules.Manager, error) {
|
||||
|
||||
// create engine
|
||||
pqle, err := pqle.FromConfigPath(promConfigPath)
|
||||
@@ -770,9 +765,8 @@ func makeRulesManager(
|
||||
Cache: cache,
|
||||
EvalDelay: baseconst.GetEvalDelay(),
|
||||
|
||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
}
|
||||
|
||||
// create Manager
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
const (
|
||||
RoutePrefix string = "/api/gateway"
|
||||
AllowedPrefix []string = []string{"/v1/workspaces/me", "/v2/profiles/me"}
|
||||
AllowedPrefix string = "/v1/workspaces/me"
|
||||
)
|
||||
|
||||
type proxy struct {
|
||||
|
||||
@@ -94,7 +94,6 @@ func main() {
|
||||
var cluster string
|
||||
|
||||
var useLogsNewSchema bool
|
||||
var useTraceNewSchema bool
|
||||
var cacheConfigPath, fluxInterval string
|
||||
var enableQueryServiceLogOTLPExport bool
|
||||
var preferSpanMetrics bool
|
||||
@@ -105,7 +104,6 @@ func main() {
|
||||
var gatewayUrl string
|
||||
|
||||
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
||||
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
|
||||
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
|
||||
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
|
||||
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
|
||||
@@ -145,7 +143,6 @@ func main() {
|
||||
Cluster: cluster,
|
||||
GatewayUrl: gatewayUrl,
|
||||
UseLogsNewSchema: useLogsNewSchema,
|
||||
UseTraceNewSchema: useTraceNewSchema,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -373,7 +373,7 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AnomalyDetection,
|
||||
Active: false,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
|
||||
@@ -21,7 +21,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.FF,
|
||||
opts.Reader,
|
||||
opts.UseLogsNewSchema,
|
||||
opts.UseTraceNewSchema,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
)
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"option_last": "last",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_above_below": "above/below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
@@ -111,8 +110,6 @@
|
||||
"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.",
|
||||
"anomaly_based_alert": "Anomaly based Alert",
|
||||
"anomaly_based_alert_desc": "Send a notification when a condition occurs in the metric data.",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"option_last": "last",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_above_below": "above/below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
|
||||
@@ -13,12 +13,9 @@
|
||||
"button_no": "No",
|
||||
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
|
||||
"remove_label_success": "Labels cleared",
|
||||
"alert_form_step1": "Choose a detection method",
|
||||
"alert_form_step2": "Define the metric",
|
||||
"alert_form_step3": "Define Alert Conditions",
|
||||
"alert_form_step4": "Alert Configuration",
|
||||
"threshold_alert_desc": "An alert is triggered whenever a metric deviates from an expected threshold.",
|
||||
"anomaly_detection_alert_desc": "An alert is triggered whenever a metric deviates from an expected pattern.",
|
||||
"alert_form_step1": "Step 1 - Define the metric",
|
||||
"alert_form_step2": "Step 2 - Define Alert Conditions",
|
||||
"alert_form_step3": "Step 3 - Alert Configuration",
|
||||
"metric_query_max_limit": "Can not create query. You can create maximum of 5 queries",
|
||||
"confirm_save_title": "Save Changes",
|
||||
"confirm_save_content_part1": "Your alert built with",
|
||||
@@ -38,7 +35,6 @@
|
||||
"button_cancelchanges": "Cancel",
|
||||
"button_discard": "Discard",
|
||||
"text_condition1": "Send a notification when",
|
||||
"text_condition1_anomaly": "Send notification when the observed value for",
|
||||
"text_condition2": "the threshold",
|
||||
"text_condition3": "during the last",
|
||||
"option_1min": "1 min",
|
||||
@@ -60,7 +56,6 @@
|
||||
"option_last": "last",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_above_below": "above/below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
@@ -114,9 +109,7 @@
|
||||
"user_tooltip_more_help": "More details on how to create alerts",
|
||||
"choose_alert_type": "Choose a type for the alert",
|
||||
"metric_based_alert": "Metric based Alert",
|
||||
"anomaly_based_alert": "Anomaly based Alert",
|
||||
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data.",
|
||||
"anomaly_based_alert_desc": "Send a notification when a condition occurs in the metric data.",
|
||||
"log_based_alert": "Log-based Alert",
|
||||
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
|
||||
"traces_based_alert": "Trace-based Alert",
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"option_last": "last",
|
||||
"option_above": "above",
|
||||
"option_below": "below",
|
||||
"option_above_below": "above/below",
|
||||
"option_equal": "is equal to",
|
||||
"option_notequal": "not equal to",
|
||||
"button_query": "Query",
|
||||
|
||||
@@ -7,7 +7,6 @@ const create = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.post('/rules', {
|
||||
...props.data,
|
||||
version: 'v4',
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -129,7 +129,6 @@ function LogDetail({
|
||||
return (
|
||||
<Drawer
|
||||
width="60%"
|
||||
maskStyle={{ background: 'none' }}
|
||||
title={
|
||||
<>
|
||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||
|
||||
@@ -195,20 +195,21 @@ function ListLogView({
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
$isActiveLog={
|
||||
isHighlighted ||
|
||||
activeLog?.id === logData.id ||
|
||||
activeContextLog?.id === logData.id
|
||||
}
|
||||
$isActiveLog={isHighlighted}
|
||||
$isDarkMode={isDarkMode}
|
||||
$logType={logType}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={handleDetailedView}
|
||||
fontSize={fontSize}
|
||||
>
|
||||
<div className="log-line">
|
||||
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||
<LogStateIndicator
|
||||
type={logType}
|
||||
isActive={
|
||||
activeLog?.id === logData.id || activeContextLog?.id === logData.id
|
||||
}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
<div>
|
||||
<LogContainer fontSize={fontSize}>
|
||||
<LogGeneralField
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Card, Typography } from 'antd';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import styled from 'styled-components';
|
||||
import { getActiveLogBackground } from 'utils/logs';
|
||||
|
||||
interface LogTextProps {
|
||||
linesPerRow?: number;
|
||||
@@ -15,7 +15,6 @@ interface LogContainerProps {
|
||||
export const Container = styled(Card)<{
|
||||
$isActiveLog: boolean;
|
||||
$isDarkMode: boolean;
|
||||
$logType: string;
|
||||
fontSize: FontSize;
|
||||
}>`
|
||||
width: 100% !important;
|
||||
@@ -42,8 +41,13 @@ export const Container = styled(Card)<{
|
||||
? `padding:0.3rem 0.6rem;`
|
||||
: ``}
|
||||
|
||||
${({ $isActiveLog, $isDarkMode, $logType }): string =>
|
||||
getActiveLogBackground($isActiveLog, $isDarkMode, $logType)}
|
||||
${({ $isActiveLog, $isDarkMode }): string =>
|
||||
$isActiveLog
|
||||
? `background-color: ${
|
||||
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
||||
} !important`
|
||||
: ''}
|
||||
}
|
||||
`;
|
||||
|
||||
export const Text = styled(Typography.Text)`
|
||||
|
||||
@@ -41,4 +41,10 @@
|
||||
background-color: var(--bg-sakura-500);
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
.line {
|
||||
background-color: var(--bg-robin-400, #7190f9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ describe('LogStateIndicator', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders correctly when isActive is true', () => {
|
||||
const { container } = render(
|
||||
<LogStateIndicator type="INFO" isActive fontSize={FontSize.MEDIUM} />,
|
||||
);
|
||||
const indicator = container.firstChild as HTMLElement;
|
||||
expect(indicator.classList.contains('isActive')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders correctly with different types', () => {
|
||||
const { container: containerInfo } = render(
|
||||
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
|
||||
|
||||
@@ -44,16 +44,22 @@ export const LogType = {
|
||||
|
||||
function LogStateIndicator({
|
||||
type,
|
||||
isActive,
|
||||
fontSize,
|
||||
}: {
|
||||
type: string;
|
||||
fontSize: FontSize;
|
||||
isActive?: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="log-state-indicator">
|
||||
<div className={cx('log-state-indicator', isActive ? 'isActive' : '')}>
|
||||
<div className={cx('line', type, fontSize)}> </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LogStateIndicator.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
export default LogStateIndicator;
|
||||
|
||||
@@ -162,15 +162,20 @@ function RawLogView({
|
||||
$isDarkMode={isDarkMode}
|
||||
$isReadOnly={isReadOnly}
|
||||
$isHightlightedLog={isHighlighted}
|
||||
$isActiveLog={
|
||||
activeLog?.id === data.id || activeContextLog?.id === data.id || isActiveLog
|
||||
}
|
||||
$logType={logType}
|
||||
$isActiveLog={isActiveLog}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
fontSize={fontSize}
|
||||
>
|
||||
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||
<LogStateIndicator
|
||||
type={logType}
|
||||
isActive={
|
||||
activeLog?.id === data.id ||
|
||||
activeContextLog?.id === data.id ||
|
||||
isActiveLog
|
||||
}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
|
||||
<RawLogContent
|
||||
$isReadOnly={isReadOnly}
|
||||
|
||||
@@ -13,7 +13,6 @@ export const RawLogViewContainer = styled(Row)<{
|
||||
$isReadOnly?: boolean;
|
||||
$isActiveLog?: boolean;
|
||||
$isHightlightedLog: boolean;
|
||||
$logType: string;
|
||||
fontSize: FontSize;
|
||||
}>`
|
||||
position: relative;
|
||||
@@ -35,12 +34,11 @@ export const RawLogViewContainer = styled(Row)<{
|
||||
: `margin: 2px 0;`}
|
||||
}
|
||||
|
||||
${({ $isActiveLog, $logType }): string =>
|
||||
getActiveLogBackground($isActiveLog, true, $logType)}
|
||||
${({ $isActiveLog }): string => getActiveLogBackground($isActiveLog)}
|
||||
|
||||
${({ $isReadOnly, $isActiveLog, $isDarkMode, $logType }): string =>
|
||||
${({ $isReadOnly, $isActiveLog, $isDarkMode }): string =>
|
||||
$isActiveLog
|
||||
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
|
||||
? getActiveLogBackground($isActiveLog, $isDarkMode)
|
||||
: getDefaultLogBackground($isReadOnly, $isDarkMode)}
|
||||
|
||||
${({ $isHightlightedLog, $isDarkMode }): string =>
|
||||
|
||||
@@ -35,6 +35,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
linesPerRow,
|
||||
fontSize,
|
||||
appendTo = 'center',
|
||||
activeContextLog,
|
||||
activeLog,
|
||||
isListViewPanel,
|
||||
} = props;
|
||||
|
||||
@@ -88,6 +90,9 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
<div className="table-timestamp">
|
||||
<LogStateIndicator
|
||||
type={getLogIndicatorTypeForTable(item)}
|
||||
isActive={
|
||||
activeLog?.id === item.id || activeContextLog?.id === item.id
|
||||
}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||
@@ -125,7 +130,16 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
},
|
||||
...(appendTo === 'end' ? fieldColumns : []),
|
||||
];
|
||||
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
|
||||
}, [
|
||||
fields,
|
||||
isListViewPanel,
|
||||
appendTo,
|
||||
isDarkMode,
|
||||
linesPerRow,
|
||||
activeLog?.id,
|
||||
activeContextLog?.id,
|
||||
fontSize,
|
||||
]);
|
||||
|
||||
return { columns, dataSource: flattenLogData };
|
||||
};
|
||||
|
||||
@@ -107,7 +107,6 @@ function DynamicColumnTable({
|
||||
className="dynamicColumnTable-button filter-btn"
|
||||
size="middle"
|
||||
icon={<SlidersHorizontal size={14} />}
|
||||
data-testid="additional-filters-button"
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const ALERTS_DATA_SOURCE_MAP: Record<AlertTypes, DataSource> = {
|
||||
[AlertTypes.ANOMALY_BASED_ALERT]: DataSource.METRICS,
|
||||
[AlertTypes.METRICS_BASED_ALERT]: DataSource.METRICS,
|
||||
[AlertTypes.LOGS_BASED_ALERT]: DataSource.LOGS,
|
||||
[AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES,
|
||||
|
||||
@@ -22,5 +22,4 @@ export enum FeatureKeys {
|
||||
GATEWAY = 'GATEWAY',
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
}
|
||||
|
||||
@@ -36,5 +36,4 @@ export enum QueryParams {
|
||||
topic = 'topic',
|
||||
partition = 'partition',
|
||||
selectedTimelineQuery = 'selectedTimelineQuery',
|
||||
ruleType = 'ruleType',
|
||||
}
|
||||
|
||||
@@ -3,10 +3,6 @@ import { QueryFunctionsTypes } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
|
||||
export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
{
|
||||
value: QueryFunctionsTypes.ANOMALY,
|
||||
label: 'Anomaly',
|
||||
},
|
||||
{
|
||||
value: QueryFunctionsTypes.CUTOFF_MIN,
|
||||
label: 'Cut Off Min',
|
||||
@@ -71,10 +67,6 @@ export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
value: QueryFunctionsTypes.TIME_SHIFT,
|
||||
label: 'Time Shift',
|
||||
},
|
||||
{
|
||||
value: QueryFunctionsTypes.TIME_SHIFT,
|
||||
label: 'Time Shift',
|
||||
},
|
||||
];
|
||||
|
||||
export const logsQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
@@ -88,15 +80,10 @@ interface QueryFunctionConfigType {
|
||||
showInput: boolean;
|
||||
inputType?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const queryFunctionsTypesConfig: QueryFunctionConfigType = {
|
||||
anomaly: {
|
||||
showInput: false,
|
||||
disabled: true,
|
||||
},
|
||||
cutOffMin: {
|
||||
showInput: true,
|
||||
inputType: 'text',
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
.anomaly-alert-evaluation-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.anomaly-alert-evaluation-view-chart-section {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&.has-multi-series-data {
|
||||
width: calc(100% - 240px);
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-no-data-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-series-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 240px;
|
||||
padding: 0px 8px;
|
||||
height: 100%;
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list-search {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list-title {
|
||||
margin-top: 12px;
|
||||
font-size: 13px !important;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.anomaly-alert-evaluation-view-series-list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
}
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgb(136, 136, 136);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uplot {
|
||||
.u-title {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-legend {
|
||||
display: flex;
|
||||
margin-top: 16px;
|
||||
|
||||
tbody {
|
||||
width: 100%;
|
||||
|
||||
.u-series {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
import 'uplot/dist/uPlot.min.css';
|
||||
import './AnomalyAlertEvaluationView.styles.scss';
|
||||
|
||||
import { Checkbox, Typography } from 'antd';
|
||||
import Search from 'antd/es/input/Search';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import getAxes from 'lib/uPlotLib/utils/getAxes';
|
||||
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale';
|
||||
import { LineChart } from 'lucide-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
function UplotChart({
|
||||
data,
|
||||
options,
|
||||
chartRef,
|
||||
}: {
|
||||
data: any;
|
||||
options: any;
|
||||
chartRef: any;
|
||||
}): JSX.Element {
|
||||
const plotInstance = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (plotInstance.current) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
plotInstance.current.destroy();
|
||||
}
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line new-cap
|
||||
plotInstance.current = new uPlot(options, data, chartRef.current);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
if (plotInstance.current) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
plotInstance.current.destroy();
|
||||
}
|
||||
};
|
||||
}, [data, options, chartRef]);
|
||||
|
||||
return <div ref={chartRef} />;
|
||||
}
|
||||
|
||||
function AnomalyAlertEvaluationView({
|
||||
data,
|
||||
yAxisUnit,
|
||||
}: {
|
||||
data: any;
|
||||
yAxisUnit: string;
|
||||
}): JSX.Element {
|
||||
const { spline } = uPlot.paths;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const _spline = spline ? spline() : undefined;
|
||||
const chartRef = useRef<HTMLDivElement>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [seriesData, setSeriesData] = useState<any>({});
|
||||
const [selectedSeries, setSelectedSeries] = useState<string | null>(null);
|
||||
|
||||
const [filteredSeriesKeys, setFilteredSeriesKeys] = useState<string[]>([]);
|
||||
const [allSeries, setAllSeries] = useState<string[]>([]);
|
||||
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const dimensions = useResizeObserver(graphRef);
|
||||
|
||||
useEffect(() => {
|
||||
const chartData = getUplotChartDataForAnomalyDetection(data);
|
||||
setSeriesData(chartData);
|
||||
|
||||
setAllSeries(Object.keys(chartData));
|
||||
|
||||
setFilteredSeriesKeys(Object.keys(chartData));
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const seriesKeys = Object.keys(seriesData);
|
||||
if (seriesKeys.length === 1) {
|
||||
setSelectedSeries(seriesKeys[0]); // Automatically select if only one series
|
||||
} else {
|
||||
setSelectedSeries(null); // Default to "Show All" if multiple series
|
||||
}
|
||||
}, [seriesData]);
|
||||
|
||||
const handleSeriesChange = (series: string | null): void => {
|
||||
setSelectedSeries(series);
|
||||
};
|
||||
|
||||
const bandsPlugin = {
|
||||
hooks: {
|
||||
draw: [
|
||||
(u: any): void => {
|
||||
if (!selectedSeries) return;
|
||||
|
||||
const { ctx } = u;
|
||||
const upperBandIdx = 3;
|
||||
const lowerBandIdx = 4;
|
||||
|
||||
const xData = u.data[0];
|
||||
const yUpperData = u.data[upperBandIdx];
|
||||
const yLowerData = u.data[lowerBandIdx];
|
||||
|
||||
const strokeStyle =
|
||||
u.series[1]?.stroke || seriesData[selectedSeries].color;
|
||||
const fillStyle =
|
||||
typeof strokeStyle === 'string'
|
||||
? strokeStyle.replace(')', ', 0.1)')
|
||||
: 'rgba(255, 255, 255, 0.1)';
|
||||
|
||||
ctx.beginPath();
|
||||
const firstX = u.valToPos(xData[0], 'x', true);
|
||||
const firstUpperY = u.valToPos(yUpperData[0], 'y', true);
|
||||
ctx.moveTo(firstX, firstUpperY);
|
||||
|
||||
for (let i = 0; i < xData.length; i++) {
|
||||
const x = u.valToPos(xData[i], 'x', true);
|
||||
const y = u.valToPos(yUpperData[i], 'y', true);
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
for (let i = xData.length - 1; i >= 0; i--) {
|
||||
const x = u.valToPos(xData[i], 'x', true);
|
||||
const y = u.valToPos(yLowerData[i], 'y', true);
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const initialData = allSeries.length
|
||||
? [
|
||||
seriesData[allSeries[0]].data[0], // Shared X-axis
|
||||
...allSeries.map((key) => seriesData[key].data[1]), // Map through Y-axis data for all series
|
||||
]
|
||||
: [];
|
||||
|
||||
const options = {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height - 36,
|
||||
plugins: [bandsPlugin],
|
||||
focus: {
|
||||
alpha: 0.3,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
label: 'Time',
|
||||
},
|
||||
...(selectedSeries
|
||||
? [
|
||||
{
|
||||
label: `Main Series`,
|
||||
stroke: seriesData[selectedSeries].color,
|
||||
width: 2,
|
||||
show: true,
|
||||
paths: _spline,
|
||||
},
|
||||
{
|
||||
label: `Predicted Value`,
|
||||
stroke: seriesData[selectedSeries].color,
|
||||
width: 1,
|
||||
dash: [2, 2],
|
||||
show: true,
|
||||
paths: _spline,
|
||||
},
|
||||
{
|
||||
label: `Upper Band`,
|
||||
stroke: 'transparent',
|
||||
show: false,
|
||||
paths: _spline,
|
||||
},
|
||||
{
|
||||
label: `Lower Band`,
|
||||
stroke: 'transparent',
|
||||
show: false,
|
||||
paths: _spline,
|
||||
},
|
||||
]
|
||||
: allSeries.map((seriesKey) => ({
|
||||
label: seriesKey,
|
||||
stroke: seriesData[seriesKey].color,
|
||||
width: 2,
|
||||
show: true,
|
||||
paths: _spline,
|
||||
}))),
|
||||
],
|
||||
scales: {
|
||||
x: {
|
||||
time: true,
|
||||
},
|
||||
y: {
|
||||
...getYAxisScaleForAnomalyDetection({
|
||||
seriesData,
|
||||
selectedSeries,
|
||||
initialData,
|
||||
yAxisUnit,
|
||||
}),
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
},
|
||||
axes: getAxes(isDarkMode, yAxisUnit),
|
||||
};
|
||||
|
||||
const handleSearch = (searchText: string): void => {
|
||||
if (!searchText || searchText.length === 0) {
|
||||
setFilteredSeriesKeys(allSeries);
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredSeries = allSeries.filter((series) =>
|
||||
series.toLowerCase().includes(searchText.toLowerCase()),
|
||||
);
|
||||
|
||||
setFilteredSeriesKeys(filteredSeries);
|
||||
};
|
||||
|
||||
const handleSearchValueChange = useDebouncedFn((event): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const value = event?.target?.value || '';
|
||||
|
||||
handleSearch(value);
|
||||
}, 300);
|
||||
|
||||
return (
|
||||
<div className="anomaly-alert-evaluation-view">
|
||||
<div
|
||||
className={`anomaly-alert-evaluation-view-chart-section ${
|
||||
allSeries.length > 1 ? 'has-multi-series-data' : ''
|
||||
}`}
|
||||
ref={graphRef}
|
||||
>
|
||||
{allSeries.length > 0 ? (
|
||||
<UplotChart
|
||||
data={selectedSeries ? seriesData[selectedSeries].data : initialData}
|
||||
options={options}
|
||||
chartRef={chartRef}
|
||||
/>
|
||||
) : (
|
||||
<div className="anomaly-alert-evaluation-view-no-data-container">
|
||||
<LineChart size={48} strokeWidth={0.5} />
|
||||
|
||||
<Typography>No Data</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{allSeries.length > 1 && (
|
||||
<div className="anomaly-alert-evaluation-view-series-selection">
|
||||
{allSeries.length > 1 && (
|
||||
<div className="anomaly-alert-evaluation-view-series-list">
|
||||
<Search
|
||||
className="anomaly-alert-evaluation-view-series-list-search"
|
||||
placeholder="Search a series"
|
||||
allowClear
|
||||
onChange={handleSearchValueChange}
|
||||
/>
|
||||
|
||||
<div className="anomaly-alert-evaluation-view-series-list-items">
|
||||
{filteredSeriesKeys.length > 0 && (
|
||||
<Checkbox
|
||||
className="anomaly-alert-evaluation-view-series-list-item"
|
||||
type="checkbox"
|
||||
name="series"
|
||||
value="all"
|
||||
checked={selectedSeries === null}
|
||||
onChange={(): void => handleSeriesChange(null)}
|
||||
>
|
||||
Show All
|
||||
</Checkbox>
|
||||
)}
|
||||
|
||||
{filteredSeriesKeys.map((seriesKey) => (
|
||||
<Checkbox
|
||||
className="anomaly-alert-evaluation-view-series-list-item"
|
||||
key={seriesKey}
|
||||
type="checkbox"
|
||||
name="series"
|
||||
value={seriesKey}
|
||||
checked={selectedSeries === seriesKey}
|
||||
onChange={(): void => handleSeriesChange(seriesKey)}
|
||||
>
|
||||
{seriesKey}
|
||||
</Checkbox>
|
||||
))}
|
||||
|
||||
{filteredSeriesKeys.length === 0 && (
|
||||
<Typography>No series found</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AnomalyAlertEvaluationView;
|
||||
@@ -1,3 +0,0 @@
|
||||
import AnomalyAlertEvaluationView from './AnomalyAlertEvaluationView';
|
||||
|
||||
export default AnomalyAlertEvaluationView;
|
||||
@@ -211,13 +211,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}
|
||||
}, [licenseData, isFetching]);
|
||||
|
||||
useEffect(() => {
|
||||
// after logging out hide the trial expiry banner
|
||||
if (!isLoggedIn) {
|
||||
setShowTrialExpiryBanner(false);
|
||||
}
|
||||
}, [isLoggedIn]);
|
||||
|
||||
const handleUpgrade = (): void => {
|
||||
if (role === 'ADMIN') {
|
||||
history.push(ROUTES.BILLING);
|
||||
|
||||
@@ -3,41 +3,25 @@ import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
import { OptionType } from './types';
|
||||
|
||||
export const getOptionList = (
|
||||
t: TFunction,
|
||||
isAnomalyDetectionEnabled: boolean,
|
||||
): OptionType[] => {
|
||||
const optionList: OptionType[] = [
|
||||
{
|
||||
title: t('metric_based_alert'),
|
||||
selection: AlertTypes.METRICS_BASED_ALERT,
|
||||
description: t('metric_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('log_based_alert'),
|
||||
selection: AlertTypes.LOGS_BASED_ALERT,
|
||||
description: t('log_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('traces_based_alert'),
|
||||
selection: AlertTypes.TRACES_BASED_ALERT,
|
||||
description: t('traces_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('exceptions_based_alert'),
|
||||
selection: AlertTypes.EXCEPTIONS_BASED_ALERT,
|
||||
description: t('exceptions_based_alert_desc'),
|
||||
},
|
||||
];
|
||||
|
||||
if (isAnomalyDetectionEnabled) {
|
||||
optionList.unshift({
|
||||
title: t('anomaly_based_alert'),
|
||||
selection: AlertTypes.ANOMALY_BASED_ALERT,
|
||||
description: t('anomaly_based_alert_desc'),
|
||||
isBeta: true,
|
||||
});
|
||||
}
|
||||
|
||||
return optionList;
|
||||
};
|
||||
export const getOptionList = (t: TFunction): OptionType[] => [
|
||||
{
|
||||
title: t('metric_based_alert'),
|
||||
selection: AlertTypes.METRICS_BASED_ALERT,
|
||||
description: t('metric_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('log_based_alert'),
|
||||
selection: AlertTypes.LOGS_BASED_ALERT,
|
||||
description: t('log_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('traces_based_alert'),
|
||||
selection: AlertTypes.TRACES_BASED_ALERT,
|
||||
description: t('traces_based_alert_desc'),
|
||||
},
|
||||
{
|
||||
title: t('exceptions_based_alert'),
|
||||
selection: AlertTypes.EXCEPTIONS_BASED_ALERT,
|
||||
description: t('exceptions_based_alert_desc'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Row, Tag, Typography } from 'antd';
|
||||
import { Row, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
@@ -14,18 +12,11 @@ import { OptionType } from './types';
|
||||
function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
||||
const { t } = useTranslation(['alerts']);
|
||||
|
||||
const isAnomalyDetectionEnabled =
|
||||
useFeatureFlags(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
||||
|
||||
const optionList = getOptionList(t, isAnomalyDetectionEnabled);
|
||||
const optionList = getOptionList(t);
|
||||
|
||||
function handleRedirection(option: AlertTypes): void {
|
||||
let url = '';
|
||||
switch (option) {
|
||||
case AlertTypes.ANOMALY_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
|
||||
break;
|
||||
case AlertTypes.METRICS_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
|
||||
@@ -61,13 +52,6 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
||||
<AlertTypeCard
|
||||
key={option.selection}
|
||||
title={option.title}
|
||||
extra={
|
||||
option.isBeta ? (
|
||||
<Tag bordered={false} color="geekblue">
|
||||
Beta
|
||||
</Tag>
|
||||
) : undefined
|
||||
}
|
||||
onClick={(): void => {
|
||||
onSelect(option.selection);
|
||||
}}
|
||||
|
||||
@@ -4,5 +4,4 @@ export interface OptionType {
|
||||
title: string;
|
||||
selection: AlertTypes;
|
||||
description: string;
|
||||
isBeta?: boolean;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,9 @@ import {
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import {
|
||||
AlertDef,
|
||||
defaultAlgorithm,
|
||||
defaultCompareOp,
|
||||
defaultEvalWindow,
|
||||
defaultMatchType,
|
||||
defaultSeasonality,
|
||||
} from 'types/api/alerts/def';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
|
||||
@@ -48,8 +46,6 @@ export const alertDefaults: AlertDef = {
|
||||
},
|
||||
op: defaultCompareOp,
|
||||
matchType: defaultMatchType,
|
||||
algorithm: defaultAlgorithm,
|
||||
seasonality: defaultSeasonality,
|
||||
},
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
@@ -149,7 +145,6 @@ export const exceptionAlertDefaults: AlertDef = {
|
||||
};
|
||||
|
||||
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
|
||||
[AlertTypes.ANOMALY_BASED_ALERT]: alertDefaults,
|
||||
[AlertTypes.METRICS_BASED_ALERT]: alertDefaults,
|
||||
[AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults,
|
||||
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Form, Row } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import FormAlertRules, { AlertDetectionTypes } from 'container/FormAlertRules';
|
||||
import FormAlertRules from 'container/FormAlertRules';
|
||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||
import history from 'lib/history';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -45,7 +45,6 @@ function CreateRules(): JSX.Element {
|
||||
|
||||
const onSelectType = (typ: AlertTypes): void => {
|
||||
setAlertType(typ);
|
||||
|
||||
switch (typ) {
|
||||
case AlertTypes.LOGS_BASED_ALERT:
|
||||
setInitValues(logAlertDefaults);
|
||||
@@ -56,37 +55,13 @@ function CreateRules(): JSX.Element {
|
||||
case AlertTypes.EXCEPTIONS_BASED_ALERT:
|
||||
setInitValues(exceptionAlertDefaults);
|
||||
break;
|
||||
case AlertTypes.ANOMALY_BASED_ALERT:
|
||||
setInitValues({
|
||||
...alertDefaults,
|
||||
version: version || ENTITY_VERSION_V4,
|
||||
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
setInitValues({
|
||||
...alertDefaults,
|
||||
version: version || ENTITY_VERSION_V4,
|
||||
ruleType: AlertDetectionTypes.THRESHOLD_ALERT,
|
||||
});
|
||||
}
|
||||
|
||||
queryParams.set(
|
||||
QueryParams.alertType,
|
||||
typ === AlertTypes.ANOMALY_BASED_ALERT
|
||||
? AlertTypes.METRICS_BASED_ALERT
|
||||
: typ,
|
||||
);
|
||||
|
||||
if (typ === AlertTypes.ANOMALY_BASED_ALERT) {
|
||||
queryParams.set(
|
||||
QueryParams.ruleType,
|
||||
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
);
|
||||
} else {
|
||||
queryParams.set(QueryParams.ruleType, AlertDetectionTypes.THRESHOLD_ALERT);
|
||||
}
|
||||
|
||||
queryParams.set(QueryParams.alertType, typ);
|
||||
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
};
|
||||
|
||||
@@ -7,16 +7,18 @@ function EditRules({ initialValue, ruleId }: EditRulesProps): JSX.Element {
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
return (
|
||||
<FormAlertRules
|
||||
alertType={
|
||||
initialValue.alertType
|
||||
? (initialValue.alertType as AlertTypes)
|
||||
: AlertTypes.METRICS_BASED_ALERT
|
||||
}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
ruleId={ruleId}
|
||||
/>
|
||||
<div style={{ marginTop: '1rem' }}>
|
||||
<FormAlertRules
|
||||
alertType={
|
||||
initialValue.alertType
|
||||
? (initialValue.alertType as AlertTypes)
|
||||
: AlertTypes.METRICS_BASED_ALERT
|
||||
}
|
||||
formInstance={formInstance}
|
||||
initialValue={initialValue}
|
||||
ruleId={ruleId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: rgba(22, 24, 29, 0.6);
|
||||
@@ -33,7 +33,6 @@
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-slate-500);
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
PanelBottomClose,
|
||||
Plus,
|
||||
X,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
CSSProperties,
|
||||
@@ -514,11 +515,7 @@ function ExplorerOptions({
|
||||
|
||||
return (
|
||||
<div className="explorer-options-container">
|
||||
{
|
||||
// if a viewName is selected and the explorer options are not hidden then
|
||||
// always show the clear option
|
||||
}
|
||||
{!isExplorerOptionHidden && viewName && (
|
||||
{isQueryUpdated && !isExplorerOptionHidden && (
|
||||
<div
|
||||
className={cx(
|
||||
isEditDeleteSupported ? '' : 'hide-update',
|
||||
@@ -532,25 +529,18 @@ function ExplorerOptions({
|
||||
icon={<X size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
{
|
||||
// only show the update view option when the query is updated
|
||||
}
|
||||
{isQueryUpdated && (
|
||||
<>
|
||||
<Divider
|
||||
type="vertical"
|
||||
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||
/>
|
||||
<Tooltip title="Update this view" placement="top">
|
||||
<Button
|
||||
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
|
||||
disabled={isViewUpdating}
|
||||
onClick={onUpdateQueryHandler}
|
||||
icon={<Disc3 size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Divider
|
||||
type="vertical"
|
||||
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||
/>
|
||||
<Tooltip title="Update this view" placement="top">
|
||||
<Button
|
||||
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
|
||||
disabled={isViewUpdating}
|
||||
onClick={onUpdateQueryHandler}
|
||||
icon={<Disc3 size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{!isExplorerOptionHidden && (
|
||||
@@ -574,7 +564,10 @@ function ExplorerOptions({
|
||||
}}
|
||||
dropdownStyle={dropdownStyle}
|
||||
className="views-dropdown"
|
||||
allowClear={false}
|
||||
allowClear={{
|
||||
clearIcon: <XCircle size={16} style={{ marginTop: '-3px' }} />,
|
||||
}}
|
||||
onClear={handleClearSelect}
|
||||
ref={ref}
|
||||
>
|
||||
{viewsData?.data?.data?.map((view) => {
|
||||
@@ -669,8 +662,8 @@ function ExplorerOptions({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ExplorerOptionsHideArea
|
||||
viewName={viewName}
|
||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||
sourcepage={sourcepage}
|
||||
@@ -679,6 +672,7 @@ function ExplorerOptions({
|
||||
onUpdateQueryHandler={onUpdateQueryHandler}
|
||||
isEditDeleteSupported={isEditDeleteSupported}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
className="save-view-modal"
|
||||
title={<span className="title">Save this view</span>}
|
||||
@@ -711,6 +705,7 @@ function ExplorerOptions({
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
footer={null}
|
||||
onOk={onCancel(false)}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { setExplorerToolBarVisibility } from './utils';
|
||||
|
||||
interface DroppableAreaProps {
|
||||
viewName: string;
|
||||
isQueryUpdated: boolean;
|
||||
isExplorerOptionHidden?: boolean;
|
||||
sourcepage: DataSource;
|
||||
@@ -21,7 +20,6 @@ interface DroppableAreaProps {
|
||||
}
|
||||
|
||||
function ExplorerOptionsHideArea({
|
||||
viewName,
|
||||
isQueryUpdated,
|
||||
isExplorerOptionHidden,
|
||||
sourcepage,
|
||||
@@ -41,7 +39,7 @@ function ExplorerOptionsHideArea({
|
||||
<div className="explorer-option-droppable-container">
|
||||
{isExplorerOptionHidden && (
|
||||
<>
|
||||
{viewName && (
|
||||
{isQueryUpdated && (
|
||||
<div className="explorer-actions-btn">
|
||||
<Tooltip title="Clear this view">
|
||||
<Button
|
||||
@@ -51,7 +49,7 @@ function ExplorerOptionsHideArea({
|
||||
icon={<X size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isEditDeleteSupported && isQueryUpdated && (
|
||||
{isEditDeleteSupported && (
|
||||
<Tooltip title="Update this View">
|
||||
<Button
|
||||
onClick={onUpdateQueryHandler}
|
||||
|
||||
@@ -96,7 +96,7 @@ function BasicInfo({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step4')} </StepHeading>
|
||||
<StepHeading> {t('alert_form_step3')} </StepHeading>
|
||||
<FormContainer>
|
||||
<Form.Item
|
||||
label={t('field_severity')}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.alert-chart-container {
|
||||
height: 57vh;
|
||||
width: 100%;
|
||||
|
||||
.threshold-alert-uplot-chart-container {
|
||||
height: calc(100% - 24px);
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-loading-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.anomaly-alert-evaluation-view-error-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import './ChartPreview.styles.scss';
|
||||
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import AnomalyAlertEvaluationView from 'container/AnomalyAlertEvaluationView';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
@@ -18,7 +14,6 @@ import {
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
@@ -39,7 +34,6 @@ import { getGraphType } from 'utils/getGraphType';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { AlertDetectionTypes } from '..';
|
||||
import { ChartContainer, FailedMessageContainer } from './styles';
|
||||
import { getThresholdLabel } from './utils';
|
||||
|
||||
@@ -147,7 +141,6 @@ function ChartPreview({
|
||||
selectedInterval,
|
||||
minTime,
|
||||
maxTime,
|
||||
alertDef?.ruleType,
|
||||
],
|
||||
retry: false,
|
||||
enabled: canQuery,
|
||||
@@ -170,6 +163,8 @@ function ChartPreview({
|
||||
queryResponse.data.payload.data.result = sortedSeriesData;
|
||||
}
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload);
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -207,10 +202,7 @@ function ChartPreview({
|
||||
id: 'alert_legend_widget',
|
||||
yAxisUnit,
|
||||
apiResponse: queryResponse?.data?.payload,
|
||||
dimensions: {
|
||||
height: containerDimensions?.height ? containerDimensions.height - 48 : 0,
|
||||
width: containerDimensions?.width,
|
||||
},
|
||||
dimensions: containerDimensions,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
isDarkMode,
|
||||
@@ -253,59 +245,36 @@ function ChartPreview({
|
||||
],
|
||||
);
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload);
|
||||
|
||||
const isAnomalyDetectionAlert =
|
||||
alertDef?.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT;
|
||||
|
||||
const chartDataAvailable =
|
||||
chartData && !queryResponse.isError && !queryResponse.isLoading;
|
||||
|
||||
const isAnomalyDetectionEnabled =
|
||||
useFeatureFlags(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
||||
|
||||
return (
|
||||
<div className="alert-chart-container" ref={graphRef}>
|
||||
<ChartContainer>
|
||||
{headline}
|
||||
<ChartContainer>
|
||||
{headline}
|
||||
|
||||
<div className="threshold-alert-uplot-chart-container">
|
||||
{queryResponse.isLoading && (
|
||||
<Spinner size="large" tip="Loading..." height="100%" />
|
||||
)}
|
||||
{(queryResponse?.isError || queryResponse?.error) && (
|
||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||
<InfoCircleOutlined />
|
||||
{queryResponse.error.message || t('preview_chart_unexpected_error')}
|
||||
</FailedMessageContainer>
|
||||
)}
|
||||
<div ref={graphRef} style={{ height: '100%' }}>
|
||||
{queryResponse.isLoading && (
|
||||
<Spinner size="large" tip="Loading..." height="100%" />
|
||||
)}
|
||||
{(queryResponse?.isError || queryResponse?.error) && (
|
||||
<FailedMessageContainer color="red" title="Failed to refresh the chart">
|
||||
<InfoCircleOutlined />{' '}
|
||||
{queryResponse.error.message || t('preview_chart_unexpected_error')}
|
||||
</FailedMessageContainer>
|
||||
)}
|
||||
|
||||
{chartDataAvailable && !isAnomalyDetectionAlert && (
|
||||
<GridPanelSwitch
|
||||
options={options}
|
||||
panelType={graphType}
|
||||
data={chartData}
|
||||
name={name || 'Chart Preview'}
|
||||
panelData={
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result || []
|
||||
}
|
||||
query={query || initialQueriesMap.metrics}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{chartDataAvailable &&
|
||||
isAnomalyDetectionAlert &&
|
||||
isAnomalyDetectionEnabled &&
|
||||
queryResponse?.data?.payload?.data?.resultType === 'anomaly' && (
|
||||
<AnomalyAlertEvaluationView
|
||||
data={queryResponse?.data?.payload}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
{chartData && !queryResponse.isError && !queryResponse.isLoading && (
|
||||
<GridPanelSwitch
|
||||
options={options}
|
||||
panelType={graphType}
|
||||
data={chartData}
|
||||
name={name || 'Chart Preview'}
|
||||
panelData={
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result || []
|
||||
}
|
||||
query={query || initialQueriesMap.metrics}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ChartContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,70 +21,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.steps-container {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.qb-chart-preview-container {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.alert-type-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.alert-type-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-preview-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
.ant-card {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.detection-method-container {
|
||||
margin: 24px 0;
|
||||
|
||||
.ant-tabs-nav {
|
||||
margin-bottom: 0;
|
||||
|
||||
.ant-tabs-tab {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detection-method-description {
|
||||
padding: 8px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-help-btns {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
||||
@@ -222,7 +222,7 @@ function QuerySection({
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<StepHeading> {t('alert_form_step2')}</StepHeading>
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
<FormContainer>
|
||||
<div>{renderTabs(alertType)}</div>
|
||||
{renderQuerySection(currentTab)}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
.rule-definition {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import './RuleOptions.styles.scss';
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
Collapse,
|
||||
@@ -20,17 +18,14 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AlertDef,
|
||||
defaultAlgorithm,
|
||||
defaultCompareOp,
|
||||
defaultEvalWindow,
|
||||
defaultFrequency,
|
||||
defaultMatchType,
|
||||
defaultSeasonality,
|
||||
} from 'types/api/alerts/def';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { AlertDetectionTypes } from '.';
|
||||
import {
|
||||
FormContainer,
|
||||
InlineSelect,
|
||||
@@ -48,8 +43,6 @@ function RuleOptions({
|
||||
const { t } = useTranslation('alerts');
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const { ruleType } = alertDef;
|
||||
|
||||
const handleMatchOptChange = (value: string | unknown): void => {
|
||||
const m = (value as string) || alertDef.condition?.matchType;
|
||||
setAlertDef({
|
||||
@@ -93,19 +86,8 @@ function RuleOptions({
|
||||
>
|
||||
<Select.Option value="1">{t('option_above')}</Select.Option>
|
||||
<Select.Option value="2">{t('option_below')}</Select.Option>
|
||||
|
||||
{/* hide equal and not eqaul in case of analmoy based alert */}
|
||||
|
||||
{ruleType !== 'anomaly_rule' && (
|
||||
<>
|
||||
<Select.Option value="3">{t('option_equal')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_notequal')}</Select.Option>
|
||||
</>
|
||||
)}
|
||||
|
||||
{ruleType === 'anomaly_rule' && (
|
||||
<Select.Option value="5">{t('option_above_below')}</Select.Option>
|
||||
)}
|
||||
<Select.Option value="3">{t('option_equal')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_notequal')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@@ -119,14 +101,9 @@ function RuleOptions({
|
||||
>
|
||||
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
|
||||
<Select.Option value="2">{t('option_allthetimes')}</Select.Option>
|
||||
|
||||
{ruleType !== 'anomaly_rule' && (
|
||||
<>
|
||||
<Select.Option value="3">{t('option_onaverage')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_intotal')}</Select.Option>
|
||||
<Select.Option value="5">{t('option_last')}</Select.Option>
|
||||
</>
|
||||
)}
|
||||
<Select.Option value="3">{t('option_onaverage')}</Select.Option>
|
||||
<Select.Option value="4">{t('option_intotal')}</Select.Option>
|
||||
<Select.Option value="5">{t('option_last')}</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
@@ -138,28 +115,6 @@ function RuleOptions({
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeAlgorithm = (value: string | unknown): void => {
|
||||
const alg = (value as string) || alertDef.condition.algorithm;
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
algorithm: alg,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeSeasonality = (value: string | unknown): void => {
|
||||
const seasonality = (value as string) || alertDef.condition.seasonality;
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
seasonality,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderEvalWindows = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
@@ -191,32 +146,6 @@ function RuleOptions({
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderAlgorithms = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultAlgorithm}
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.condition.algorithm}
|
||||
onChange={onChangeAlgorithm}
|
||||
>
|
||||
<Select.Option value="standard">Standard</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderSeasonality = (): JSX.Element => (
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
defaultValue={defaultSeasonality}
|
||||
style={{ minWidth: '120px' }}
|
||||
value={alertDef.condition.seasonality}
|
||||
onChange={onChangeSeasonality}
|
||||
>
|
||||
<Select.Option value="hourly">Hourly</Select.Option>
|
||||
<Select.Option value="daily">Daily</Select.Option>
|
||||
<Select.Option value="weekly">Weekly</Select.Option>
|
||||
</InlineSelect>
|
||||
);
|
||||
|
||||
const renderThresholdRuleOpts = (): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text>
|
||||
@@ -237,39 +166,6 @@ function RuleOptions({
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const renderAnomalyRuleOpts = (
|
||||
onChange: InputNumberProps['onChange'],
|
||||
): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text className="rule-definition">
|
||||
{t('text_condition1_anomaly')}
|
||||
<InlineSelect
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={queryOptions}
|
||||
placeholder={t('selected_query_placeholder')}
|
||||
value={alertDef.condition.selectedQueryName}
|
||||
onChange={onChangeSelectedQueryName}
|
||||
/>
|
||||
{t('text_condition3')} {renderEvalWindows()}
|
||||
<Typography.Text>is</Typography.Text>
|
||||
<InputNumber
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
<Typography.Text>deviations</Typography.Text>
|
||||
{renderCompareOps()}
|
||||
<Typography.Text>the predicted data</Typography.Text>
|
||||
{renderMatchOpts()}
|
||||
using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '}
|
||||
seasonality
|
||||
</Typography.Text>
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const renderPromRuleOptions = (): JSX.Element => (
|
||||
<Form.Item>
|
||||
<Typography.Text>
|
||||
@@ -349,46 +245,36 @@ function RuleOptions({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StepHeading>{t('alert_form_step3')}</StepHeading>
|
||||
<StepHeading>{t('alert_form_step2')}</StepHeading>
|
||||
<FormContainer>
|
||||
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||
<>{renderAnomalyRuleOpts(onChange)}</>
|
||||
)}
|
||||
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
ruleType === AlertDetectionTypes.THRESHOLD_ALERT &&
|
||||
renderThresholdRuleOpts()}
|
||||
{queryCategory === EQueryType.PROM
|
||||
? renderPromRuleOptions()
|
||||
: renderThresholdRuleOpts()}
|
||||
|
||||
<Space direction="vertical" size="large">
|
||||
{queryCategory !== EQueryType.PROM &&
|
||||
ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
placeholder={t('field_unit')}
|
||||
value={alertDef.condition.targetUnit}
|
||||
onChange={onChangeAlertUnit}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
)}
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle name={['condition', 'target']}>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
allowClear
|
||||
showSearch
|
||||
options={categorySelectOptions}
|
||||
placeholder={t('field_unit')}
|
||||
value={alertDef.condition.targetUnit}
|
||||
onChange={onChangeAlertUnit}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Collapse>
|
||||
<Collapse.Panel header={t('More options')} key="1">
|
||||
<Space direction="vertical" size="large">
|
||||
|
||||
@@ -3,6 +3,7 @@ import './FormAlertRules.styles.scss';
|
||||
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
FormInstance,
|
||||
Modal,
|
||||
SelectProps,
|
||||
@@ -12,6 +13,8 @@ import {
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import testAlertApi from 'api/alerts/testAlert';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||
import { alertHelpMessage } from 'components/LaunchChatSupport/util';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -23,18 +26,13 @@ import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import useFeatureFlag, {
|
||||
MESSAGE,
|
||||
useIsFeatureDisabled,
|
||||
} from 'hooks/useFeatureFlag';
|
||||
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { BellDot, ExternalLink } from 'lucide-react';
|
||||
import Tabs2 from 'periscope/components/Tabs2';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueryClient } from 'react-query';
|
||||
@@ -46,11 +44,7 @@ import {
|
||||
defaultEvalWindow,
|
||||
defaultMatchType,
|
||||
} from 'types/api/alerts/def';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
QueryFunctionProps,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -62,16 +56,13 @@ import {
|
||||
ActionButton,
|
||||
ButtonContainer,
|
||||
MainFormContainer,
|
||||
PanelContainer,
|
||||
StepContainer,
|
||||
StepHeading,
|
||||
StyledLeftContainer,
|
||||
} from './styles';
|
||||
import UserGuide from './UserGuide';
|
||||
import { getSelectedQueryOptions } from './utils';
|
||||
|
||||
export enum AlertDetectionTypes {
|
||||
THRESHOLD_ALERT = 'threshold_rule',
|
||||
ANOMALY_DETECTION_ALERT = 'anomaly_rule',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function FormAlertRules({
|
||||
alertType,
|
||||
@@ -95,7 +86,6 @@ function FormAlertRules({
|
||||
const {
|
||||
currentQuery,
|
||||
stagedQuery,
|
||||
handleSetQueryData,
|
||||
handleRunQuery,
|
||||
handleSetConfig,
|
||||
initialDataSource,
|
||||
@@ -118,12 +108,6 @@ function FormAlertRules({
|
||||
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
|
||||
const [yAxisUnit, setYAxisUnit] = useState<string>(currentQuery.unit || '');
|
||||
|
||||
const alertTypeFromURL = urlQuery.get(QueryParams.ruleType);
|
||||
|
||||
const [detectionMethod, setDetectionMethod] = useState<string>(
|
||||
AlertDetectionTypes.THRESHOLD_ALERT,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(currentQuery.unit, yAxisUnit)) {
|
||||
setYAxisUnit(currentQuery.unit || '');
|
||||
@@ -154,45 +138,6 @@ function FormAlertRules({
|
||||
|
||||
useShareBuilderUrl(sq);
|
||||
|
||||
const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => {
|
||||
const anomalyFunction = {
|
||||
name: 'anomaly',
|
||||
args: [],
|
||||
namedArgs: { z_score_threshold: 9 },
|
||||
};
|
||||
const functions = data.functions || [];
|
||||
|
||||
if (alertDef.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) {
|
||||
// Add anomaly if not already present
|
||||
if (!functions.some((func) => func.name === 'anomaly')) {
|
||||
functions.push(anomalyFunction);
|
||||
}
|
||||
} else {
|
||||
// Remove anomaly if present
|
||||
const index = functions.findIndex((func) => func.name === 'anomaly');
|
||||
if (index !== -1) {
|
||||
functions.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return functions;
|
||||
};
|
||||
|
||||
const updateFunctionsBasedOnAlertType = (): void => {
|
||||
for (let index = 0; index < currentQuery.builder.queryData.length; index++) {
|
||||
const queryData = currentQuery.builder.queryData[index];
|
||||
|
||||
const updatedFunctions = updateFunctions(queryData);
|
||||
queryData.functions = updatedFunctions;
|
||||
handleSetQueryData(index, queryData);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateFunctionsBasedOnAlertType();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [detectionMethod, alertDef, currentQuery.builder.queryData.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const broadcastToSpecificChannels =
|
||||
(initialValue &&
|
||||
@@ -200,22 +145,11 @@ function FormAlertRules({
|
||||
initialValue.preferredChannels.length > 0) ||
|
||||
isNewRule;
|
||||
|
||||
let ruleType = AlertDetectionTypes.THRESHOLD_ALERT;
|
||||
|
||||
if (initialValue.ruleType) {
|
||||
ruleType = initialValue.ruleType as AlertDetectionTypes;
|
||||
} else if (alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) {
|
||||
ruleType = AlertDetectionTypes.ANOMALY_DETECTION_ALERT;
|
||||
}
|
||||
|
||||
setAlertDef({
|
||||
...initialValue,
|
||||
broadcastToAll: !broadcastToSpecificChannels,
|
||||
ruleType,
|
||||
});
|
||||
|
||||
setDetectionMethod(ruleType);
|
||||
}, [initialValue, isNewRule, alertTypeFromURL]);
|
||||
}, [initialValue, isNewRule]);
|
||||
|
||||
useEffect(() => {
|
||||
// Set selectedQueryName based on the length of queryOptions
|
||||
@@ -366,15 +300,12 @@ function FormAlertRules({
|
||||
const postableAlert: AlertDef = {
|
||||
...alertDef,
|
||||
preferredChannels: alertDef.broadcastToAll ? [] : alertDef.preferredChannels,
|
||||
alertType:
|
||||
alertType === AlertTypes.ANOMALY_BASED_ALERT
|
||||
? AlertTypes.METRICS_BASED_ALERT
|
||||
: alertType,
|
||||
alertType,
|
||||
source: window?.location.toString(),
|
||||
ruleType:
|
||||
currentQuery.queryType === EQueryType.PROM
|
||||
? 'promql_rule'
|
||||
: alertDef.ruleType,
|
||||
: 'threshold_rule',
|
||||
condition: {
|
||||
...alertDef.condition,
|
||||
compositeQuery: {
|
||||
@@ -391,12 +322,6 @@ function FormAlertRules({
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (alertDef.ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT) {
|
||||
postableAlert.condition.algorithm = alertDef.condition.algorithm;
|
||||
postableAlert.condition.seasonality = alertDef.condition.seasonality;
|
||||
}
|
||||
|
||||
return postableAlert;
|
||||
};
|
||||
|
||||
@@ -660,102 +585,63 @@ function FormAlertRules({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
value: AlertDetectionTypes.THRESHOLD_ALERT,
|
||||
label: 'Threshold Alert',
|
||||
},
|
||||
{
|
||||
value: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||
label: 'Anomaly Detection Alert',
|
||||
isBeta: true,
|
||||
},
|
||||
];
|
||||
|
||||
const handleDetectionMethodChange = (value: any): void => {
|
||||
setAlertDef((def) => ({
|
||||
...def,
|
||||
ruleType: value,
|
||||
}));
|
||||
|
||||
setDetectionMethod(value);
|
||||
};
|
||||
|
||||
const isAnomalyDetectionEnabled =
|
||||
useFeatureFlag(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
||||
function handleRedirection(option: AlertTypes): void {
|
||||
let url = '';
|
||||
switch (option) {
|
||||
case AlertTypes.METRICS_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
|
||||
break;
|
||||
case AlertTypes.LOGS_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
|
||||
break;
|
||||
case AlertTypes.TRACES_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
|
||||
break;
|
||||
case AlertTypes.EXCEPTIONS_BASED_ALERT:
|
||||
url =
|
||||
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
logEvent('Alert: Check example alert clicked', {
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
isNewRule: !ruleId || ruleId === 0,
|
||||
ruleId,
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
|
||||
<div id="top">
|
||||
<div className="overview-header">
|
||||
<div className="alert-type-container">
|
||||
{isNewRule && (
|
||||
<Typography.Title level={5} className="alert-type-title">
|
||||
<BellDot size={14} />
|
||||
|
||||
{alertDef.alertType === AlertTypes.ANOMALY_BASED_ALERT &&
|
||||
'Anomaly Detection Alert'}
|
||||
{alertDef.alertType === AlertTypes.METRICS_BASED_ALERT &&
|
||||
'Metrics Based Alert'}
|
||||
{alertDef.alertType === AlertTypes.LOGS_BASED_ALERT &&
|
||||
'Logs Based Alert'}
|
||||
{alertDef.alertType === AlertTypes.TRACES_BASED_ALERT &&
|
||||
'Traces Based Alert'}
|
||||
{alertDef.alertType === AlertTypes.EXCEPTIONS_BASED_ALERT &&
|
||||
'Exceptions Based Alert'}
|
||||
</Typography.Title>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button className="periscope-btn" icon={<ExternalLink size={14} />}>
|
||||
Alert Setup Guide
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<MainFormContainer
|
||||
initialValues={initialValue}
|
||||
layout="vertical"
|
||||
form={formInstance}
|
||||
className="main-container"
|
||||
>
|
||||
<div className="chart-preview-container">
|
||||
<PanelContainer id="top">
|
||||
<StyledLeftContainer flex="5 1 600px" md={18}>
|
||||
<MainFormContainer
|
||||
initialValues={initialValue}
|
||||
layout="vertical"
|
||||
form={formInstance}
|
||||
className="main-container"
|
||||
>
|
||||
{currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
renderQBChartPreview()}
|
||||
{currentQuery.queryType === EQueryType.PROM &&
|
||||
renderPromAndChQueryChartPreview()}
|
||||
{currentQuery.queryType === EQueryType.CLICKHOUSE &&
|
||||
renderPromAndChQueryChartPreview()}
|
||||
</div>
|
||||
|
||||
<StepContainer>
|
||||
<BuilderUnitsFilter
|
||||
onChange={onUnitChangeHandler}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
</StepContainer>
|
||||
|
||||
<div className="steps-container">
|
||||
{alertDef.alertType === AlertTypes.METRICS_BASED_ALERT &&
|
||||
isAnomalyDetectionEnabled && (
|
||||
<div className="detection-method-container">
|
||||
<StepHeading> {t('alert_form_step1')}</StepHeading>
|
||||
|
||||
<Tabs2
|
||||
key={detectionMethod}
|
||||
tabs={tabs}
|
||||
initialSelectedTab={detectionMethod}
|
||||
onSelectTab={handleDetectionMethodChange}
|
||||
/>
|
||||
|
||||
<div className="detection-method-description">
|
||||
{detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||
? t('anomaly_detection_alert_desc')
|
||||
: t('threshold_alert_desc')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<StepContainer>
|
||||
<BuilderUnitsFilter
|
||||
onChange={onUnitChangeHandler}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
</StepContainer>
|
||||
|
||||
<QuerySection
|
||||
queryCategory={currentQuery.queryType}
|
||||
@@ -776,49 +662,79 @@ function FormAlertRules({
|
||||
/>
|
||||
|
||||
{renderBasicInfo()}
|
||||
</div>
|
||||
<ButtonContainer>
|
||||
<Tooltip title={isAlertAvailableToSave ? MESSAGE.ALERT : ''}>
|
||||
<ButtonContainer>
|
||||
<Tooltip title={isAlertAvailableToSave ? MESSAGE.ALERT : ''}>
|
||||
<ActionButton
|
||||
loading={loading || false}
|
||||
type="primary"
|
||||
onClick={onSaveHandler}
|
||||
icon={<SaveOutlined />}
|
||||
disabled={
|
||||
isAlertNameMissing ||
|
||||
isAlertAvailableToSave ||
|
||||
!isChannelConfigurationValid ||
|
||||
queryStatus === 'error'
|
||||
}
|
||||
>
|
||||
{isNewRule ? t('button_createrule') : t('button_savechanges')}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
|
||||
<ActionButton
|
||||
loading={loading || false}
|
||||
type="primary"
|
||||
onClick={onSaveHandler}
|
||||
icon={<SaveOutlined />}
|
||||
disabled={
|
||||
isAlertNameMissing ||
|
||||
isAlertAvailableToSave ||
|
||||
!isChannelConfigurationValid ||
|
||||
queryStatus === 'error'
|
||||
}
|
||||
type="default"
|
||||
onClick={onTestRuleHandler}
|
||||
>
|
||||
{isNewRule ? t('button_createrule') : t('button_savechanges')}
|
||||
{' '}
|
||||
{t('button_testrule')}
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
|
||||
<ActionButton
|
||||
loading={loading || false}
|
||||
disabled={
|
||||
isAlertNameMissing ||
|
||||
!isChannelConfigurationValid ||
|
||||
queryStatus === 'error'
|
||||
<ActionButton
|
||||
disabled={loading || false}
|
||||
type="default"
|
||||
onClick={onCancelHandler}
|
||||
>
|
||||
{ruleId === 0 && t('button_cancelchanges')}
|
||||
{ruleId > 0 && t('button_discard')}
|
||||
</ActionButton>
|
||||
</ButtonContainer>
|
||||
</MainFormContainer>
|
||||
</StyledLeftContainer>
|
||||
<Col flex="1 1 300px">
|
||||
<UserGuide queryType={currentQuery.queryType} />
|
||||
<div className="info-help-btns">
|
||||
<Button
|
||||
style={{ height: 32 }}
|
||||
onClick={(): void =>
|
||||
handleRedirection(alertDef?.alertType as AlertTypes)
|
||||
}
|
||||
type="default"
|
||||
onClick={onTestRuleHandler}
|
||||
className="doc-redirection-btn"
|
||||
>
|
||||
{' '}
|
||||
{t('button_testrule')}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
disabled={loading || false}
|
||||
type="default"
|
||||
onClick={onCancelHandler}
|
||||
>
|
||||
{ruleId === 0 && t('button_cancelchanges')}
|
||||
{ruleId > 0 && t('button_discard')}
|
||||
</ActionButton>
|
||||
</ButtonContainer>
|
||||
</MainFormContainer>
|
||||
</div>
|
||||
Check an example alert
|
||||
</Button>
|
||||
<LaunchChatSupport
|
||||
attributes={{
|
||||
alert: alertDef?.alert,
|
||||
alertType: alertDef?.alertType,
|
||||
id: ruleId,
|
||||
ruleType: alertDef?.ruleType,
|
||||
state: (alertDef as any)?.state,
|
||||
panelType,
|
||||
screen: isRuleCreated ? 'Edit Alert' : 'New Alert',
|
||||
}}
|
||||
className="facing-issue-btn"
|
||||
eventName="Alert: Facing Issues in alert"
|
||||
buttonText="Need help with this alert?"
|
||||
message={alertHelpMessage(alertDef, ruleId)}
|
||||
onHoverText="Click here to get help with this alert"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</PanelContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Button, Card, Col, Form, Input, Select, Typography } from 'antd';
|
||||
import { Button, Card, Col, Form, Input, Row, Select, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Item } = Form;
|
||||
|
||||
export const PanelContainer = styled(Row)`
|
||||
flex-wrap: nowrap;
|
||||
`;
|
||||
|
||||
export const StyledLeftContainer = styled(Col)`
|
||||
&&& {
|
||||
margin-right: 1rem;
|
||||
|
||||
@@ -97,19 +97,13 @@ function GridTableComponent({
|
||||
|
||||
const newColumnData = columns.map((e) => ({
|
||||
...e,
|
||||
render: (text: string, ...rest: any): ReactNode => {
|
||||
let textForThreshold = text;
|
||||
if (columnUnits && columnUnits?.[e.title as string]) {
|
||||
textForThreshold = rest[0][`${e.title}_without_unit`];
|
||||
}
|
||||
const isNumber = !Number.isNaN(Number(textForThreshold));
|
||||
|
||||
render: (text: string): ReactNode => {
|
||||
const isNumber = !Number.isNaN(Number(text));
|
||||
if (thresholds && isNumber) {
|
||||
const { hasMultipleMatches, threshold } = findMatchingThreshold(
|
||||
thresholds,
|
||||
e.title as string,
|
||||
Number(textForThreshold),
|
||||
columnUnits?.[e.title as string],
|
||||
Number(text),
|
||||
);
|
||||
|
||||
const idx = thresholds.findIndex(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||
import { convertUnit } from 'container/NewWidget/RightContainer/dataFormatCategories';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
|
||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||
@@ -31,39 +30,10 @@ function evaluateCondition(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates whether a given value meets a specified threshold condition.
|
||||
* It first converts the value to the appropriate unit if a threshold unit is provided,
|
||||
* and then checks the condition using the specified operator.
|
||||
*
|
||||
* @param value - The value to be evaluated.
|
||||
* @param thresholdValue - The threshold value to compare against.
|
||||
* @param thresholdOperator - The operator used for comparison (e.g., '>', '<', '==').
|
||||
* @param thresholdUnit - The unit to which the value should be converted.
|
||||
* @param columnUnit - The current unit of the value.
|
||||
* @returns A boolean indicating whether the value meets the threshold condition.
|
||||
*/
|
||||
function evaluateThresholdWithConvertedValue(
|
||||
value: number,
|
||||
thresholdValue: number,
|
||||
thresholdOperator?: string,
|
||||
thresholdUnit?: string,
|
||||
columnUnit?: string,
|
||||
): boolean {
|
||||
const convertedValue = convertUnit(value, columnUnit, thresholdUnit);
|
||||
|
||||
if (convertedValue) {
|
||||
return evaluateCondition(thresholdOperator, convertedValue, thresholdValue);
|
||||
}
|
||||
|
||||
return evaluateCondition(thresholdOperator, value, thresholdValue);
|
||||
}
|
||||
|
||||
export function findMatchingThreshold(
|
||||
thresholds: ThresholdProps[],
|
||||
label: string,
|
||||
value: number,
|
||||
columnUnit?: string,
|
||||
): {
|
||||
threshold: ThresholdProps;
|
||||
hasMultipleMatches: boolean;
|
||||
@@ -75,12 +45,10 @@ export function findMatchingThreshold(
|
||||
if (
|
||||
threshold.thresholdValue !== undefined &&
|
||||
threshold.thresholdTableOptions === label &&
|
||||
evaluateThresholdWithConvertedValue(
|
||||
value,
|
||||
threshold?.thresholdValue,
|
||||
evaluateCondition(
|
||||
threshold.thresholdOperator,
|
||||
threshold.thresholdUnit,
|
||||
columnUnit,
|
||||
value,
|
||||
threshold.thresholdValue,
|
||||
)
|
||||
) {
|
||||
matchingThresholds.push(threshold);
|
||||
|
||||
@@ -82,12 +82,6 @@ function ImportJSON({
|
||||
|
||||
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
||||
|
||||
// Add validation for uuid
|
||||
if (dashboardData.uuid !== undefined && dashboardData.uuid.trim() === '') {
|
||||
// silently remove uuid if it is empty
|
||||
delete dashboardData.uuid;
|
||||
}
|
||||
|
||||
if (dashboardData?.layout) {
|
||||
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
||||
} else {
|
||||
@@ -129,14 +123,11 @@ function ImportJSON({
|
||||
});
|
||||
}
|
||||
setDashboardCreating(false);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setDashboardCreating(false);
|
||||
setIsFeatureAlert(false);
|
||||
|
||||
setIsCreateDashboardError(true);
|
||||
notifications.error({
|
||||
message: error instanceof Error ? error.message : t('error_loading_json'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,25 +2,3 @@
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-row-backdrop {
|
||||
&.INFO {
|
||||
background-color: var(--bg-robin-500) 10;
|
||||
}
|
||||
&.WARNING,
|
||||
&.WARN {
|
||||
background-color: var(--bg-amber-500) 10;
|
||||
}
|
||||
&.ERROR {
|
||||
background-color: var(--bg-cherry-500) 10;
|
||||
}
|
||||
&.TRACE {
|
||||
background-color: var(--bg-forest-400) 10;
|
||||
}
|
||||
&.DEBUG {
|
||||
background-color: var(--bg-aqua-500) 10;
|
||||
}
|
||||
&.FATAL {
|
||||
background-color: var(--bg-sakura-500) 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { getLogIndicatorType } from 'components/Logs/LogStateIndicator/utils';
|
||||
import { useTableView } from 'components/Logs/TableView/useTableView';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
@@ -22,11 +21,6 @@ import { TableHeaderCellStyled, TableRowStyled } from './styles';
|
||||
import TableRow from './TableRow';
|
||||
import { InfinityTableProps } from './types';
|
||||
|
||||
interface CustomTableRowProps {
|
||||
activeContextLogId: string;
|
||||
activeLogId: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const CustomTableRow: TableComponents<ILog>['TableRow'] = ({
|
||||
children,
|
||||
@@ -37,17 +31,10 @@ const CustomTableRow: TableComponents<ILog>['TableRow'] = ({
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const logType = getLogIndicatorType(props.item);
|
||||
|
||||
return (
|
||||
<TableRowStyled
|
||||
$isDarkMode={isDarkMode}
|
||||
$isActiveLog={
|
||||
isHighlighted ||
|
||||
(context as CustomTableRowProps).activeContextLogId === props.item.id ||
|
||||
(context as CustomTableRowProps).activeLogId === props.item.id
|
||||
}
|
||||
$logType={logType}
|
||||
$isActiveLog={isHighlighted}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
>
|
||||
@@ -79,6 +66,8 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
...tableViewProps,
|
||||
onClickExpand: onSetActiveLog,
|
||||
onOpenLogsContext: handleSetActiveContextLog,
|
||||
activeLog,
|
||||
activeContextLog,
|
||||
});
|
||||
|
||||
const { draggedColumns, onDragColumns } = useDragColumns<
|
||||
@@ -164,14 +153,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
// TODO: fix it in the future
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
TableRow: (props): any =>
|
||||
CustomTableRow({
|
||||
...props,
|
||||
context: {
|
||||
activeContextLogId: activeContextLog?.id,
|
||||
activeLogId: activeLog?.id,
|
||||
},
|
||||
} as any),
|
||||
TableRow: CustomTableRow,
|
||||
}}
|
||||
itemContent={itemContent}
|
||||
fixedHeaderContent={tableHeader}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import styled from 'styled-components';
|
||||
@@ -36,12 +37,13 @@ export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
||||
export const TableRowStyled = styled.tr<{
|
||||
$isActiveLog: boolean;
|
||||
$isDarkMode: boolean;
|
||||
$logType: string;
|
||||
}>`
|
||||
td {
|
||||
${({ $isActiveLog, $isDarkMode, $logType }): string =>
|
||||
${({ $isActiveLog, $isDarkMode }): string =>
|
||||
$isActiveLog
|
||||
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
|
||||
? `background-color: ${
|
||||
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
||||
} !important`
|
||||
: ''};
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
Dashboard,
|
||||
DashboardData,
|
||||
IDashboardVariable,
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
@@ -67,30 +63,6 @@ interface DashboardDescriptionProps {
|
||||
handle: FullScreenHandle;
|
||||
}
|
||||
|
||||
function sanitizeDashboardData(
|
||||
selectedData: DashboardData,
|
||||
): Omit<DashboardData, 'uuid'> {
|
||||
if (!selectedData?.variables) {
|
||||
const { uuid, ...rest } = selectedData;
|
||||
return rest;
|
||||
}
|
||||
|
||||
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||
(acc, [key, value]) => {
|
||||
const { selectedValue, ...rest } = value;
|
||||
acc[key] = rest;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, IDashboardVariable>,
|
||||
);
|
||||
|
||||
const { uuid, ...restData } = selectedData;
|
||||
return {
|
||||
...restData,
|
||||
variables: updatedVariables,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const { handle } = props;
|
||||
@@ -435,10 +407,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
type="text"
|
||||
icon={<FileJson size={14} />}
|
||||
onClick={(): void => {
|
||||
downloadObjectAsJson(
|
||||
sanitizeDashboardData(selectedData),
|
||||
selectedData.title,
|
||||
);
|
||||
downloadObjectAsJson(selectedData, selectedData.title);
|
||||
setIsDashbordSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
@@ -448,9 +417,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
type="text"
|
||||
icon={<ClipboardCopy size={14} />}
|
||||
onClick={(): void => {
|
||||
setCopy(
|
||||
JSON.stringify(sanitizeDashboardData(selectedData), null, 2),
|
||||
);
|
||||
setCopy(JSON.stringify(selectedData, null, 2));
|
||||
setIsDashbordSettingsOpen(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -24,7 +24,7 @@ import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParse
|
||||
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
|
||||
import { debounce, isArray, isString } from 'lodash-es';
|
||||
import map from 'lodash-es/map';
|
||||
import { ChangeEvent, memo, useEffect, useMemo, useState } from 'react';
|
||||
import { ChangeEvent, memo, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -257,7 +257,8 @@ function VariableItem({
|
||||
if (variableData.name) {
|
||||
if (
|
||||
value === ALL_SELECT_VALUE ||
|
||||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE))
|
||||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
) {
|
||||
onValueUpdate(variableData.name, variableData.id, optionsData, true);
|
||||
} else {
|
||||
@@ -277,6 +278,25 @@ function VariableItem({
|
||||
|
||||
const enableSelectAll = variableData.multiSelect && variableData.showALLOption;
|
||||
|
||||
const [variableDropdownOpen, setVariableDropdownOpen] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent): void => {
|
||||
if (
|
||||
wrapperRef.current &&
|
||||
!wrapperRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setVariableDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('click', handleClickOutside);
|
||||
return (): void => {
|
||||
window.removeEventListener('click', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectValue =
|
||||
variableData.allSelected && enableSelectAll
|
||||
? 'ALL'
|
||||
@@ -323,6 +343,10 @@ function VariableItem({
|
||||
Array.isArray(selectedValueStringified) &&
|
||||
selectedValueStringified.includes(option.toString())
|
||||
) {
|
||||
if (newSelectedValue.length === 0) {
|
||||
handleChange(ALL_SELECT_VALUE);
|
||||
return;
|
||||
}
|
||||
if (newSelectedValue.length === 1) {
|
||||
handleChange(newSelectedValue[0].toString());
|
||||
return;
|
||||
@@ -407,7 +431,7 @@ function VariableItem({
|
||||
<Typography.Text className="variable-name" ellipsis>
|
||||
${variableData.name}
|
||||
</Typography.Text>
|
||||
<div className="variable-value">
|
||||
<div className="variable-value" ref={wrapperRef}>
|
||||
{variableData.type === 'TEXTBOX' ? (
|
||||
<Input
|
||||
placeholder="Enter value"
|
||||
@@ -440,6 +464,10 @@ function VariableItem({
|
||||
style={SelectItemStyle}
|
||||
loading={isLoading}
|
||||
showSearch
|
||||
open={variableDropdownOpen}
|
||||
onDropdownVisibleChange={(visible): void =>
|
||||
setVariableDropdownOpen(visible)
|
||||
}
|
||||
data-testid="variable-select"
|
||||
className="variable-select"
|
||||
popupClassName="dropdown-styles"
|
||||
|
||||
@@ -297,16 +297,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-unit {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Giest Mono';
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.48px;
|
||||
}
|
||||
}
|
||||
|
||||
.threshold-card-container:hover {
|
||||
|
||||
@@ -3,18 +3,17 @@ import './Threshold.styles.scss';
|
||||
|
||||
import { Button, Input, InputNumber, Select, Space, Typography } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { unitOptions } from 'container/NewWidget/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { Check, Pencil, Trash2, X } from 'lucide-react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
|
||||
import {
|
||||
operatorOptions,
|
||||
panelTypeVsDragAndDrop,
|
||||
showAsOptions,
|
||||
unitOptions,
|
||||
} from '../constants';
|
||||
import { convertUnit } from '../dataFormatCategories';
|
||||
import ColorSelector from './ColorSelector';
|
||||
import CustomColor from './CustomColor';
|
||||
import ShowCaseValue from './ShowCaseValue';
|
||||
@@ -41,7 +40,6 @@ function Threshold({
|
||||
thresholdLabel = '',
|
||||
tableOptions,
|
||||
thresholdTableOptions = '',
|
||||
columnUnits,
|
||||
}: ThresholdProps): JSX.Element {
|
||||
const [isEditMode, setIsEditMode] = useState<boolean>(isEditEnabled);
|
||||
const [operator, setOperator] = useState<string | number>(
|
||||
@@ -194,13 +192,6 @@ function Threshold({
|
||||
|
||||
const allowDragAndDrop = panelTypeVsDragAndDrop[selectedGraph];
|
||||
|
||||
const isInvalidUnitComparison = useMemo(
|
||||
() =>
|
||||
unit !== 'none' &&
|
||||
convertUnit(value, unit, columnUnits?.[tableSelectedOption]) === null,
|
||||
[unit, value, columnUnits, tableSelectedOption],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={allowDragAndDrop ? ref : null}
|
||||
@@ -312,7 +303,7 @@ function Threshold({
|
||||
{isEditMode ? (
|
||||
<Select
|
||||
defaultValue={unit}
|
||||
options={unitOptions(columnUnits?.[tableSelectedOption] || '')}
|
||||
options={unitOptions}
|
||||
onChange={handleUnitChange}
|
||||
showSearch
|
||||
className="unit-selection"
|
||||
@@ -348,12 +339,6 @@ function Threshold({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isInvalidUnitComparison && (
|
||||
<Typography.Text className="invalid-unit">
|
||||
Threshold unit ({unit}) is not valid in comparison with the column unit (
|
||||
{columnUnits?.[tableSelectedOption] || 'none'})
|
||||
</Typography.Text>
|
||||
)}
|
||||
{isEditMode && (
|
||||
<div className="threshold-action-button">
|
||||
<Button
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import './ThresholdSelector.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { Events } from 'constants/events';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { Antenna, Plus } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import Threshold from './Threshold';
|
||||
@@ -19,23 +21,22 @@ function ThresholdSelector({
|
||||
setThresholds,
|
||||
yAxisUnit,
|
||||
selectedGraph,
|
||||
columnUnits,
|
||||
}: ThresholdSelectorProps): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
function getAggregateColumnsNamesAndLabels(): string[] {
|
||||
if (currentQuery.queryType === EQueryType.QUERY_BUILDER) {
|
||||
const queries = currentQuery.builder.queryData.map((q) => q.queryName);
|
||||
const formulas = currentQuery.builder.queryFormulas.map((q) => q.queryName);
|
||||
return [...queries, ...formulas];
|
||||
}
|
||||
if (currentQuery.queryType === EQueryType.CLICKHOUSE) {
|
||||
return currentQuery.clickhouse_sql.map((q) => q.name);
|
||||
}
|
||||
return currentQuery.promql.map((q) => q.name);
|
||||
}
|
||||
|
||||
const aggregationQueries = getAggregateColumnsNamesAndLabels();
|
||||
const [tableOptions, setTableOptions] = useState<
|
||||
Array<{ value: string; label: string }>
|
||||
>([]);
|
||||
useEffect(() => {
|
||||
eventEmitter.on(
|
||||
Events.TABLE_COLUMNS_DATA,
|
||||
(data: { columns: ColumnsType<RowData>; dataSource: RowData[] }) => {
|
||||
const newTableOptions = data.columns.map((e) => ({
|
||||
value: e.title as string,
|
||||
label: e.title as string,
|
||||
}));
|
||||
setTableOptions([...newTableOptions]);
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
||||
const moveThreshold = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
@@ -65,7 +66,7 @@ function ThresholdSelector({
|
||||
moveThreshold,
|
||||
keyIndex: thresholds.length,
|
||||
selectedGraph,
|
||||
thresholdTableOptions: aggregationQueries[0] || '',
|
||||
thresholdTableOptions: tableOptions[0]?.value || '',
|
||||
},
|
||||
...thresholds,
|
||||
]);
|
||||
@@ -104,12 +105,8 @@ function ThresholdSelector({
|
||||
moveThreshold={moveThreshold}
|
||||
selectedGraph={selectedGraph}
|
||||
thresholdLabel={threshold.thresholdLabel}
|
||||
tableOptions={aggregationQueries.map((query) => ({
|
||||
value: query,
|
||||
label: query,
|
||||
}))}
|
||||
tableOptions={tableOptions}
|
||||
thresholdTableOptions={threshold.thresholdTableOptions}
|
||||
columnUnits={columnUnits}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
||||
|
||||
export type ThresholdOperators = '>' | '<' | '>=' | '<=' | '=';
|
||||
|
||||
@@ -20,7 +19,6 @@ export type ThresholdProps = {
|
||||
moveThreshold: (dragIndex: number, hoverIndex: number) => void;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
tableOptions?: Array<{ value: string; label: string }>;
|
||||
columnUnits?: ColumnUnit;
|
||||
};
|
||||
|
||||
export type ShowCaseValueProps = {
|
||||
@@ -38,5 +36,4 @@ export type ThresholdSelectorProps = {
|
||||
thresholds: ThresholdProps[];
|
||||
setThresholds: Dispatch<SetStateAction<ThresholdProps[]>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
columnUnits: ColumnUnit;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||
|
||||
import { getCategorySelectOptionByName } from './alertFomatCategories';
|
||||
|
||||
export const operatorOptions: DefaultOptionType[] = [
|
||||
{ value: '>', label: '>' },
|
||||
@@ -8,6 +11,11 @@ export const operatorOptions: DefaultOptionType[] = [
|
||||
{ value: '<=', label: '<=' },
|
||||
];
|
||||
|
||||
export const unitOptions = categoryToSupport.map((category) => ({
|
||||
label: category,
|
||||
options: getCategorySelectOptionByName(category),
|
||||
}));
|
||||
|
||||
export const showAsOptions: DefaultOptionType[] = [
|
||||
{ value: 'Text', label: 'Text' },
|
||||
{ value: 'Background', label: 'Background' },
|
||||
|
||||
@@ -438,168 +438,3 @@ export const dataTypeCategories: DataTypeCategories = [
|
||||
export const flattenedCategories = flattenDeep(
|
||||
dataTypeCategories.map((category) => category.formats),
|
||||
);
|
||||
|
||||
type ConversionFactors = {
|
||||
[key: string]: {
|
||||
[key: string]: number | null;
|
||||
};
|
||||
};
|
||||
|
||||
// Object containing conversion factors for various categories and formats
|
||||
const conversionFactors: ConversionFactors = {
|
||||
[CategoryNames.Time]: {
|
||||
[TimeFormats.Hertz]: 1,
|
||||
[TimeFormats.Nanoseconds]: 1e-9,
|
||||
[TimeFormats.Microseconds]: 1e-6,
|
||||
[TimeFormats.Milliseconds]: 1e-3,
|
||||
[TimeFormats.Seconds]: 1,
|
||||
[TimeFormats.Minutes]: 60,
|
||||
[TimeFormats.Hours]: 3600,
|
||||
[TimeFormats.Days]: 86400,
|
||||
[TimeFormats.DurationMs]: 1e-3,
|
||||
[TimeFormats.DurationS]: 1,
|
||||
[TimeFormats.DurationHms]: null, // Requires special handling
|
||||
[TimeFormats.DurationDhms]: null, // Requires special handling
|
||||
[TimeFormats.Timeticks]: null, // Requires special handling
|
||||
[TimeFormats.ClockMs]: 1e-3,
|
||||
[TimeFormats.ClockS]: 1,
|
||||
},
|
||||
[CategoryNames.Throughput]: {
|
||||
[ThroughputFormats.CountsPerSec]: 1,
|
||||
[ThroughputFormats.OpsPerSec]: 1,
|
||||
[ThroughputFormats.RequestsPerSec]: 1,
|
||||
[ThroughputFormats.ReadsPerSec]: 1,
|
||||
[ThroughputFormats.WritesPerSec]: 1,
|
||||
[ThroughputFormats.IOOpsPerSec]: 1,
|
||||
[ThroughputFormats.CountsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.OpsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.ReadsPerMin]: 1 / 60,
|
||||
[ThroughputFormats.WritesPerMin]: 1 / 60,
|
||||
},
|
||||
[CategoryNames.Data]: {
|
||||
[DataFormats.BytesIEC]: 1,
|
||||
[DataFormats.BytesSI]: 1,
|
||||
[DataFormats.BitsIEC]: 0.125,
|
||||
[DataFormats.BitsSI]: 0.125,
|
||||
[DataFormats.KibiBytes]: 1024,
|
||||
[DataFormats.KiloBytes]: 1000,
|
||||
[DataFormats.MebiBytes]: 1048576,
|
||||
[DataFormats.MegaBytes]: 1000000,
|
||||
[DataFormats.GibiBytes]: 1073741824,
|
||||
[DataFormats.GigaBytes]: 1000000000,
|
||||
[DataFormats.TebiBytes]: 1099511627776,
|
||||
[DataFormats.TeraBytes]: 1000000000000,
|
||||
[DataFormats.PebiBytes]: 1125899906842624,
|
||||
[DataFormats.PetaBytes]: 1000000000000000,
|
||||
},
|
||||
[CategoryNames.DataRate]: {
|
||||
[DataRateFormats.PacketsPerSec]: null, // Cannot convert directly to other data rates
|
||||
[DataRateFormats.BytesPerSecIEC]: 1,
|
||||
[DataRateFormats.BytesPerSecSI]: 1,
|
||||
[DataRateFormats.BitsPerSecIEC]: 0.125,
|
||||
[DataRateFormats.BitsPerSecSI]: 0.125,
|
||||
[DataRateFormats.KibiBytesPerSec]: 1024,
|
||||
[DataRateFormats.KibiBitsPerSec]: 128,
|
||||
[DataRateFormats.KiloBytesPerSec]: 1000,
|
||||
[DataRateFormats.KiloBitsPerSec]: 125,
|
||||
[DataRateFormats.MebiBytesPerSec]: 1048576,
|
||||
[DataRateFormats.MebiBitsPerSec]: 131072,
|
||||
[DataRateFormats.MegaBytesPerSec]: 1000000,
|
||||
[DataRateFormats.MegaBitsPerSec]: 125000,
|
||||
[DataRateFormats.GibiBytesPerSec]: 1073741824,
|
||||
[DataRateFormats.GibiBitsPerSec]: 134217728,
|
||||
[DataRateFormats.GigaBytesPerSec]: 1000000000,
|
||||
[DataRateFormats.GigaBitsPerSec]: 125000000,
|
||||
[DataRateFormats.TebiBytesPerSec]: 1099511627776,
|
||||
[DataRateFormats.TebiBitsPerSec]: 137438953472,
|
||||
[DataRateFormats.TeraBytesPerSec]: 1000000000000,
|
||||
[DataRateFormats.TeraBitsPerSec]: 125000000000,
|
||||
[DataRateFormats.PebiBytesPerSec]: 1125899906842624,
|
||||
[DataRateFormats.PebiBitsPerSec]: 140737488355328,
|
||||
[DataRateFormats.PetaBytesPerSec]: 1000000000000000,
|
||||
[DataRateFormats.PetaBitsPerSec]: 125000000000000,
|
||||
},
|
||||
[CategoryNames.Miscellaneous]: {
|
||||
[MiscellaneousFormats.None]: null,
|
||||
[MiscellaneousFormats.String]: null,
|
||||
[MiscellaneousFormats.Short]: null,
|
||||
[MiscellaneousFormats.Percent]: 1,
|
||||
[MiscellaneousFormats.PercentUnit]: 100,
|
||||
[MiscellaneousFormats.Humidity]: 1,
|
||||
[MiscellaneousFormats.Decibel]: null,
|
||||
[MiscellaneousFormats.Hexadecimal0x]: null,
|
||||
[MiscellaneousFormats.Hexadecimal]: null,
|
||||
[MiscellaneousFormats.ScientificNotation]: null,
|
||||
[MiscellaneousFormats.LocaleFormat]: null,
|
||||
[MiscellaneousFormats.Pixels]: null,
|
||||
},
|
||||
[CategoryNames.Boolean]: {
|
||||
[BooleanFormats.TRUE_FALSE]: null, // Not convertible
|
||||
[BooleanFormats.YES_NO]: null, // Not convertible
|
||||
[BooleanFormats.ON_OFF]: null, // Not convertible
|
||||
},
|
||||
};
|
||||
|
||||
// Function to get the conversion factor between two units in a specific category
|
||||
function getConversionFactor(
|
||||
fromUnit: string,
|
||||
toUnit: string,
|
||||
category: CategoryNames,
|
||||
): number | null {
|
||||
// Retrieves the conversion factors for the specified category
|
||||
const categoryFactors = conversionFactors[category];
|
||||
if (!categoryFactors) {
|
||||
return null; // Returns null if the category does not exist
|
||||
}
|
||||
const fromFactor = categoryFactors[fromUnit];
|
||||
const toFactor = categoryFactors[toUnit];
|
||||
if (
|
||||
fromFactor === undefined ||
|
||||
toFactor === undefined ||
|
||||
fromFactor === null ||
|
||||
toFactor === null
|
||||
) {
|
||||
return null; // Returns null if either unit does not exist or is not convertible
|
||||
}
|
||||
return fromFactor / toFactor; // Returns the conversion factor ratio
|
||||
}
|
||||
|
||||
// Function to convert a value from one unit to another
|
||||
export function convertUnit(
|
||||
value: number,
|
||||
fromUnitId?: string,
|
||||
toUnitId?: string,
|
||||
): number | null {
|
||||
let fromUnit: string | undefined;
|
||||
let toUnit: string | undefined;
|
||||
|
||||
// Finds the category that contains the specified units and extracts fromUnit and toUnit using array methods
|
||||
const category = dataTypeCategories.find((category) =>
|
||||
category.formats.some((format) => {
|
||||
if (format.id === fromUnitId) fromUnit = format.id;
|
||||
if (format.id === toUnitId) toUnit = format.id;
|
||||
return fromUnit && toUnit; // Break out early if both units are found
|
||||
}),
|
||||
);
|
||||
|
||||
if (!category || !fromUnit || !toUnit) return null; // Return null if category or units are not found
|
||||
|
||||
// Gets the conversion factor for the specified units
|
||||
const conversionFactor = getConversionFactor(
|
||||
fromUnit,
|
||||
toUnit,
|
||||
category.name as any,
|
||||
);
|
||||
if (conversionFactor === null) return null; // Return null if conversion is not possible
|
||||
|
||||
return value * conversionFactor;
|
||||
}
|
||||
|
||||
// Function to get the category name for a given unit ID
|
||||
export const getCategoryName = (unitId: string): CategoryNames | null => {
|
||||
// Finds the category that contains the specified unit ID
|
||||
const foundCategory = dataTypeCategories.find((category) =>
|
||||
category.formats.some((format) => format.id === unitId),
|
||||
);
|
||||
return foundCategory ? (foundCategory.name as CategoryNames) : null;
|
||||
};
|
||||
|
||||
@@ -311,7 +311,6 @@ function RightContainer({
|
||||
setThresholds={setThresholds}
|
||||
yAxisUnit={yAxisUnit}
|
||||
selectedGraph={selectedGraph}
|
||||
columnUnits={columnUnits}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
@@ -9,19 +8,12 @@ import {
|
||||
listViewInitialTraceQuery,
|
||||
PANEL_TYPES_INITIAL_QUERY,
|
||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||
import { cloneDeep, isEmpty, isEqual, set, unset } from 'lodash-es';
|
||||
import { cloneDeep, isEqual, set, unset } from 'lodash-es';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
dataTypeCategories,
|
||||
getCategoryName,
|
||||
} from './RightContainer/dataFormatCategories';
|
||||
import { CategoryNames } from './RightContainer/types';
|
||||
|
||||
export const getIsQueryModified = (
|
||||
currentQuery: Query,
|
||||
stagedQuery: Query | null,
|
||||
@@ -537,41 +529,3 @@ export const PANEL_TYPE_TO_QUERY_TYPES: Record<PANEL_TYPES, EQueryType[]> = {
|
||||
EQueryType.PROM,
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a list of category select options based on the provided category name.
|
||||
* If the category is found, it maps the formats to an array of objects containing
|
||||
* the label and value for each format.
|
||||
*/
|
||||
export const getCategorySelectOptionByName = (
|
||||
name?: CategoryNames | string,
|
||||
): DefaultOptionType[] =>
|
||||
dataTypeCategories
|
||||
.find((category) => category.name === name)
|
||||
?.formats.map((format) => ({
|
||||
label: format.name,
|
||||
value: format.id,
|
||||
})) || [];
|
||||
|
||||
/**
|
||||
* Generates unit options based on the provided column unit.
|
||||
* It first retrieves the category name associated with the column unit.
|
||||
* If the category is empty, it maps all supported categories to their respective
|
||||
* select options. If a valid category is found, it filters the supported categories
|
||||
* to return only the options for the matched category.
|
||||
*/
|
||||
export const unitOptions = (columnUnit: string): DefaultOptionType[] => {
|
||||
const category = getCategoryName(columnUnit);
|
||||
if (isEmpty(category)) {
|
||||
return categoryToSupport.map((category) => ({
|
||||
label: category,
|
||||
options: getCategorySelectOptionByName(category),
|
||||
}));
|
||||
}
|
||||
return categoryToSupport
|
||||
.filter((supportedCategory) => supportedCategory === category)
|
||||
.map((filteredCategory) => ({
|
||||
label: filteredCategory,
|
||||
options: getCategorySelectOptionByName(filteredCategory),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ This is a **sample cURL request** which can be used as a template:
|
||||
|
||||
|
||||
```bash
|
||||
curl --location 'https://ingest.{{REGION}}.signoz.cloud:443/logs/json' \
|
||||
curl --location 'https://ingest.{{REGION}}.signoz.cloud:443/logs/json/' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'signoz-access-token: {{SIGNOZ_INGESTION_KEY}}' \
|
||||
--data '[
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function Function({
|
||||
handleDeleteFunction,
|
||||
}: FunctionProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { showInput, disabled } = queryFunctionsTypesConfig[funcData.name];
|
||||
const { showInput } = queryFunctionsTypesConfig[funcData.name];
|
||||
|
||||
let functionValue;
|
||||
|
||||
@@ -62,7 +62,6 @@ export default function Function({
|
||||
<Select
|
||||
className={cx('query-function-name-selector', showInput ? 'showInput' : '')}
|
||||
value={funcData.name}
|
||||
disabled={disabled}
|
||||
style={{ minWidth: '100px' }}
|
||||
onChange={(value): void => {
|
||||
handleUpdateFunctionName(funcData, index, value);
|
||||
|
||||
@@ -59,10 +59,9 @@ export const useOrderByFilter = ({
|
||||
];
|
||||
}, [searchText]);
|
||||
|
||||
const selectedValue = useMemo(
|
||||
() => transformToOrderByStringValues(query, entityVersion),
|
||||
[query, entityVersion],
|
||||
);
|
||||
const selectedValue = useMemo(() => transformToOrderByStringValues(query), [
|
||||
query,
|
||||
]);
|
||||
|
||||
const generateOptions = useCallback(
|
||||
(options: IOption[]): IOption[] => {
|
||||
|
||||
@@ -13,14 +13,11 @@ export const orderByValueDelimiter = '|';
|
||||
|
||||
export const transformToOrderByStringValues = (
|
||||
query: IBuilderQuery,
|
||||
entityVersion?: string,
|
||||
): IOption[] => {
|
||||
const prepareSelectedValue: IOption[] = query.orderBy.map((item) => {
|
||||
if (item.columnName === SIGNOZ_VALUE) {
|
||||
return {
|
||||
label: `${
|
||||
entityVersion === 'v4' ? query.spaceAggregation : query.aggregateOperator
|
||||
}(${query.aggregateAttribute.key}) ${item.order}`,
|
||||
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${item.order}`,
|
||||
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import './QueryBuilderSearchV2.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import {
|
||||
ArrowDown,
|
||||
|
||||
@@ -319,7 +319,7 @@ function QueryBuilderSearchV2(
|
||||
value: '',
|
||||
}));
|
||||
setCurrentState(DropdownState.OPERATOR);
|
||||
setSearchValue(`${(parsedValue as BaseAutocompleteData)?.key} `);
|
||||
setSearchValue((parsedValue as BaseAutocompleteData)?.key);
|
||||
}
|
||||
} else if (currentState === DropdownState.OPERATOR) {
|
||||
if (isEmpty(value) && currentFilterItem?.key?.key) {
|
||||
@@ -360,7 +360,7 @@ function QueryBuilderSearchV2(
|
||||
value: '',
|
||||
}));
|
||||
setCurrentState(DropdownState.ATTRIBUTE_VALUE);
|
||||
setSearchValue(`${currentFilterItem?.key?.key} ${value} `);
|
||||
setSearchValue(`${currentFilterItem?.key?.key} ${value}`);
|
||||
}
|
||||
} else if (currentState === DropdownState.ATTRIBUTE_VALUE) {
|
||||
const operatorType =
|
||||
@@ -512,6 +512,11 @@ function QueryBuilderSearchV2(
|
||||
|
||||
// this useEffect takes care of tokenisation based on the search state
|
||||
useEffect(() => {
|
||||
// if we are still fetching the suggestions then return as we won't know the type / data-type etc for the attribute key
|
||||
if (isFetchingSuggestions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is no search value reset to the default state
|
||||
if (!searchValue) {
|
||||
setCurrentFilterItem(undefined);
|
||||
@@ -761,7 +766,6 @@ function QueryBuilderSearchV2(
|
||||
suggestionsData?.payload?.attributes,
|
||||
]);
|
||||
|
||||
// keep the query in sync with the selected tags in logs explorer page
|
||||
useEffect(() => {
|
||||
const filterTags: IBuilderQuery['filters'] = {
|
||||
op: 'AND',
|
||||
@@ -784,14 +788,16 @@ function QueryBuilderSearchV2(
|
||||
|
||||
if (!isEqual(query.filters, filterTags)) {
|
||||
onChange(filterTags);
|
||||
setTags(
|
||||
filterTags.items.map((tag) => ({
|
||||
...tag,
|
||||
op: getOperatorFromValue(tag.op),
|
||||
})) as ITag[],
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tags]);
|
||||
|
||||
// keep the use effects pure!
|
||||
// if the tags lacks the ID then the above use effect will add it to query
|
||||
// and then the below use effect will take care of adding it to the tags.
|
||||
// keep the tags in sycn with current query.
|
||||
useEffect(() => {
|
||||
// convert the query and tags to same format before comparison
|
||||
if (!isEqual(getInitTags(query), tags)) {
|
||||
|
||||
@@ -98,7 +98,7 @@ function NoFilterTable({
|
||||
return (
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
rowKey={(record): string => `${record.startsAt}-${record.fingerprint}`}
|
||||
rowKey="startsAt"
|
||||
dataSource={filteredAlerts}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -32,6 +32,7 @@ export async function GetMetricQueryRange(
|
||||
signal,
|
||||
headers,
|
||||
);
|
||||
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
let error = `API responded with ${response.statusCode} - ${response.error} status: ${response.message}`;
|
||||
@@ -70,19 +71,6 @@ export async function GetMetricQueryRange(
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (response.payload?.data?.newResult?.data?.resultType === 'anomaly') {
|
||||
response.payload.data.newResult.data.result = response.payload.data.newResult.data.result.map(
|
||||
(queryData) => {
|
||||
if (legendMap[queryData.queryName]) {
|
||||
queryData.legend = legendMap[queryData.queryName];
|
||||
}
|
||||
|
||||
return queryData;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable sonarjs/no-identical-functions */
|
||||
import {
|
||||
MetricRangePayloadProps,
|
||||
MetricRangePayloadV3,
|
||||
@@ -13,8 +12,8 @@ export const convertNewDataToOld = (
|
||||
|
||||
result.forEach((item) => {
|
||||
if (item.series) {
|
||||
item.series.forEach((series) => {
|
||||
const values: QueryData['values'] = series.values.reduce<
|
||||
item.series.forEach((serie) => {
|
||||
const values: QueryData['values'] = serie.values.reduce<
|
||||
QueryData['values']
|
||||
>((acc, currentInfo) => {
|
||||
const renderValues: [number, string] = [
|
||||
@@ -24,87 +23,16 @@ export const convertNewDataToOld = (
|
||||
|
||||
return [...acc, renderValues];
|
||||
}, []);
|
||||
|
||||
const result: QueryData = {
|
||||
metric: series.labels,
|
||||
metric: serie.labels,
|
||||
values,
|
||||
queryName: `${item.queryName}`,
|
||||
};
|
||||
|
||||
oldResult.push(result);
|
||||
});
|
||||
}
|
||||
|
||||
if (item.predictedSeries) {
|
||||
item.predictedSeries.forEach((series) => {
|
||||
const values: QueryData['values'] = series.values.reduce<
|
||||
QueryData['values']
|
||||
>((acc, currentInfo) => {
|
||||
const renderValues: [number, string] = [
|
||||
currentInfo.timestamp / 1000,
|
||||
currentInfo.value,
|
||||
];
|
||||
|
||||
return [...acc, renderValues];
|
||||
}, []);
|
||||
|
||||
const result: QueryData = {
|
||||
metric: series.labels,
|
||||
values,
|
||||
queryName: `${item.queryName}`,
|
||||
};
|
||||
|
||||
oldResult.push(result);
|
||||
});
|
||||
}
|
||||
|
||||
if (item.upperBoundSeries) {
|
||||
item.upperBoundSeries.forEach((series) => {
|
||||
const values: QueryData['values'] = series.values.reduce<
|
||||
QueryData['values']
|
||||
>((acc, currentInfo) => {
|
||||
const renderValues: [number, string] = [
|
||||
currentInfo.timestamp / 1000,
|
||||
currentInfo.value,
|
||||
];
|
||||
|
||||
return [...acc, renderValues];
|
||||
}, []);
|
||||
|
||||
const result: QueryData = {
|
||||
metric: series.labels,
|
||||
values,
|
||||
queryName: `${item.queryName}`,
|
||||
};
|
||||
|
||||
oldResult.push(result);
|
||||
});
|
||||
}
|
||||
|
||||
if (item.lowerBoundSeries) {
|
||||
item.lowerBoundSeries.forEach((series) => {
|
||||
const values: QueryData['values'] = series.values.reduce<
|
||||
QueryData['values']
|
||||
>((acc, currentInfo) => {
|
||||
const renderValues: [number, string] = [
|
||||
currentInfo.timestamp / 1000,
|
||||
currentInfo.value,
|
||||
];
|
||||
|
||||
return [...acc, renderValues];
|
||||
}, []);
|
||||
|
||||
const result: QueryData = {
|
||||
metric: series.labels,
|
||||
values,
|
||||
queryName: `${item.queryName}`,
|
||||
queryName: item.queryName,
|
||||
};
|
||||
|
||||
oldResult.push(result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const oldResultType = resultType;
|
||||
|
||||
// TODO: fix it later for using only v3 version of api
|
||||
|
||||
@@ -163,8 +163,6 @@ export const getUPlotChartOptions = ({
|
||||
|
||||
const stackBarChart = stackChart && isUndefined(hiddenGraph);
|
||||
|
||||
const isAnomalyRule = apiResponse?.data?.newResult?.data?.result[0].isAnomaly;
|
||||
|
||||
const series = getStackedSeries(apiResponse?.data?.result || []);
|
||||
|
||||
const bands = stackBarChart ? getBands(series) : null;
|
||||
@@ -253,14 +251,11 @@ export const getUPlotChartOptions = ({
|
||||
hooks: {
|
||||
draw: [
|
||||
(u): void => {
|
||||
if (isAnomalyRule) {
|
||||
return;
|
||||
}
|
||||
|
||||
thresholds?.forEach((threshold) => {
|
||||
if (threshold.thresholdValue !== undefined) {
|
||||
const { ctx } = u;
|
||||
ctx.save();
|
||||
|
||||
const yPos = u.valToPos(
|
||||
convertValue(
|
||||
threshold.thresholdValue,
|
||||
@@ -270,22 +265,30 @@ export const getUPlotChartOptions = ({
|
||||
'y',
|
||||
true,
|
||||
);
|
||||
|
||||
ctx.strokeStyle = threshold.thresholdColor || 'red';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([10, 5]);
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
const plotLeft = u.bbox.left; // left edge of the plot area
|
||||
const plotRight = plotLeft + u.bbox.width; // right edge of the plot area
|
||||
|
||||
ctx.moveTo(plotLeft, yPos);
|
||||
ctx.lineTo(plotRight, yPos);
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
// Text configuration
|
||||
if (threshold.thresholdLabel) {
|
||||
const text = threshold.thresholdLabel;
|
||||
const textX = plotRight - ctx.measureText(text).width - 20;
|
||||
|
||||
const canvasHeight = ctx.canvas.height;
|
||||
const yposHeight = canvasHeight - yPos;
|
||||
const isHeightGreaterThan90Percent = canvasHeight * 0.9 < yposHeight;
|
||||
|
||||
// Adjust textY based on the condition
|
||||
let textY;
|
||||
if (isHeightGreaterThan90Percent) {
|
||||
@@ -296,6 +299,7 @@ export const getUPlotChartOptions = ({
|
||||
ctx.fillStyle = threshold.thresholdColor || 'red';
|
||||
ctx.fillText(text, textX, textY);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import { cloneDeep, isUndefined } from 'lodash-es';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
@@ -22,7 +20,7 @@ function getXAxisTimestamps(seriesList: QueryData[]): number[] {
|
||||
function fillMissingXAxisTimestamps(timestampArr: number[], data: any[]): any {
|
||||
// Generate a set of all timestamps in the range
|
||||
const allTimestampsSet = new Set(timestampArr);
|
||||
const processedData = cloneDeep(data);
|
||||
const processedData = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Fill missing timestamps with null values
|
||||
processedData.forEach((entry: { values: (number | null)[][] }) => {
|
||||
@@ -92,70 +90,3 @@ export const getUPlotChartData = (
|
||||
: yAxisValuesArr),
|
||||
];
|
||||
};
|
||||
|
||||
const processAnomalyDetectionData = (
|
||||
anomalyDetectionData: any,
|
||||
): Record<string, { data: number[][]; color: string }> => {
|
||||
if (!anomalyDetectionData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const processedData: Record<
|
||||
string,
|
||||
{ data: number[][]; color: string; legendLabel: string }
|
||||
> = {};
|
||||
|
||||
for (
|
||||
let queryIndex = 0;
|
||||
queryIndex < anomalyDetectionData.length;
|
||||
queryIndex++
|
||||
) {
|
||||
const {
|
||||
series,
|
||||
predictedSeries,
|
||||
upperBoundSeries,
|
||||
lowerBoundSeries,
|
||||
queryName,
|
||||
legend,
|
||||
} = anomalyDetectionData[queryIndex];
|
||||
|
||||
for (let index = 0; index < series?.length; index++) {
|
||||
const label = getLabelName(
|
||||
series[index].labels,
|
||||
queryName || '', // query
|
||||
legend || '',
|
||||
);
|
||||
|
||||
const objKey = `${queryName}-${label}`;
|
||||
|
||||
processedData[objKey] = {
|
||||
data: [
|
||||
series[index].values.map((v: { timestamp: number }) => v.timestamp / 1000),
|
||||
series[index].values.map((v: { value: number }) => v.value),
|
||||
predictedSeries[index].values.map((v: { value: number }) => v.value),
|
||||
upperBoundSeries[index].values.map((v: { value: number }) => v.value),
|
||||
lowerBoundSeries[index].values.map((v: { value: number }) => v.value),
|
||||
],
|
||||
color: colors[index],
|
||||
legendLabel: label,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
export const getUplotChartDataForAnomalyDetection = (
|
||||
apiResponse?: MetricRangePayloadProps,
|
||||
): Record<
|
||||
string,
|
||||
{
|
||||
[x: string]: any;
|
||||
data: number[][];
|
||||
color: string;
|
||||
}
|
||||
> => {
|
||||
const anomalyDetectionData = apiResponse?.data?.newResult?.data?.result;
|
||||
|
||||
return processAnomalyDetectionData(anomalyDetectionData);
|
||||
};
|
||||
|
||||
@@ -233,43 +233,6 @@ GetYAxisScale): { auto?: boolean; range?: uPlot.Scale.Range } => {
|
||||
return { auto: false, range: [min, max] };
|
||||
};
|
||||
|
||||
function getMinMax(data: any): { minValue: number; maxValue: number } {
|
||||
// Exclude the first array
|
||||
const arrays = data.slice(1);
|
||||
|
||||
// Flatten the array and convert all elements to float
|
||||
const flattened = arrays.flat().map(Number);
|
||||
|
||||
// Get min and max, with fallback of 0 for min
|
||||
const minValue = flattened.length ? Math.min(...flattened) : 0;
|
||||
const maxValue = Math.max(...flattened);
|
||||
|
||||
return { minValue, maxValue };
|
||||
}
|
||||
|
||||
export const getYAxisScaleForAnomalyDetection = ({
|
||||
seriesData,
|
||||
selectedSeries,
|
||||
initialData,
|
||||
}: {
|
||||
seriesData: any;
|
||||
selectedSeries: string | null;
|
||||
initialData: any;
|
||||
yAxisUnit?: string;
|
||||
}): { auto?: boolean; range?: uPlot.Scale.Range } => {
|
||||
if (!selectedSeries && !initialData) {
|
||||
return { auto: true };
|
||||
}
|
||||
|
||||
const selectedSeriesData = selectedSeries
|
||||
? seriesData[selectedSeries]?.data
|
||||
: initialData;
|
||||
|
||||
const { minValue, maxValue } = getMinMax(selectedSeriesData);
|
||||
|
||||
return { auto: false, range: [minValue, maxValue] };
|
||||
};
|
||||
|
||||
export type GetYAxisScale = {
|
||||
thresholds?: ThresholdProps[];
|
||||
series?: QueryDataV3[];
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
.edit-rules-card {
|
||||
width: 20rem;
|
||||
padding: 1rem;
|
||||
|
||||
.content {
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const KAFKA_SETUP_DOC_LINK =
|
||||
'https://signoz.io/docs/messaging-queues/kafka?utm_source=product&utm_medium=kafka-get-started';
|
||||
'https://github.com/shivanshuraj1333/kafka-opentelemetry-instrumentation/tree/master';
|
||||
|
||||
export function convertToTitleCase(text: string): string {
|
||||
return text
|
||||
|
||||
@@ -303,6 +303,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
return (
|
||||
loading ||
|
||||
!values.email ||
|
||||
!values.organizationName ||
|
||||
(!precheck.sso && (!values.password || !values.confirmPassword)) ||
|
||||
(!isDetailsDisable && !values.firstName) ||
|
||||
confirmPasswordError ||
|
||||
@@ -353,6 +354,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
<FormContainer.Item noStyle name="organizationName">
|
||||
<Input
|
||||
placeholder={t('placeholder_orgname')}
|
||||
required
|
||||
id="organizationName"
|
||||
disabled={isDetailsDisable}
|
||||
/>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
gap: 10px;
|
||||
color: var(--text-vanilla-400);
|
||||
background: var(--bg-ink-400);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
padding: 6px 24px;
|
||||
border-color: var(--bg-slate-400);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './Tabs2.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tag } from 'antd';
|
||||
import { Button } from 'antd';
|
||||
import { TimelineFilter } from 'container/AlertHistory/types';
|
||||
import { Undo } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
@@ -11,7 +11,6 @@ interface Tab {
|
||||
label: string | JSX.Element;
|
||||
disabled?: boolean;
|
||||
icon?: string | JSX.Element;
|
||||
isBeta?: boolean;
|
||||
}
|
||||
|
||||
interface TimelineTabsProps {
|
||||
@@ -64,12 +63,6 @@ function Tabs2({
|
||||
style={{ minWidth: buttonMinWidth }}
|
||||
>
|
||||
{tab.label}
|
||||
|
||||
{tab.isBeta && (
|
||||
<Tag bordered={false} color="geekblue">
|
||||
Beta
|
||||
</Tag>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
</Button.Group>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// this list must exactly match with the backend
|
||||
export enum AlertTypes {
|
||||
ANOMALY_BASED_ALERT = 'ANOMALY_BASED_ALERT',
|
||||
METRICS_BASED_ALERT = 'METRIC_BASED_ALERT',
|
||||
LOGS_BASED_ALERT = 'LOGS_BASED_ALERT',
|
||||
TRACES_BASED_ALERT = 'TRACES_BASED_ALERT',
|
||||
|
||||
@@ -12,10 +12,6 @@ export const defaultFrequency = '1m0s';
|
||||
// default compare op: above
|
||||
export const defaultCompareOp = '1';
|
||||
|
||||
export const defaultAlgorithm = 'standard';
|
||||
|
||||
export const defaultSeasonality = 'hourly';
|
||||
|
||||
export interface AlertDef {
|
||||
id?: number;
|
||||
alertType?: string;
|
||||
@@ -44,8 +40,6 @@ export interface RuleCondition {
|
||||
absentFor?: number | undefined;
|
||||
requireMinPoints?: boolean | undefined;
|
||||
requiredNumPoints?: number | undefined;
|
||||
algorithm?: string;
|
||||
seasonality?: string;
|
||||
}
|
||||
export interface Labels {
|
||||
[key: string]: string;
|
||||
|
||||
@@ -8,10 +8,6 @@ export interface PayloadProps {
|
||||
export type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
|
||||
|
||||
export interface QueryData {
|
||||
lowerBoundSeries?: [number, string][];
|
||||
upperBoundSeries?: [number, string][];
|
||||
predictedSeries?: [number, string][];
|
||||
anomalyScores?: [number, string][];
|
||||
metric: {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -38,11 +34,6 @@ export interface QueryDataV3 {
|
||||
quantity?: number;
|
||||
unitPrice?: number;
|
||||
unit?: string;
|
||||
lowerBoundSeries?: SeriesItem[] | null;
|
||||
upperBoundSeries?: SeriesItem[] | null;
|
||||
predictedSeries?: SeriesItem[] | null;
|
||||
anomalyScores?: SeriesItem[] | null;
|
||||
isAnomaly?: boolean;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
|
||||
@@ -153,7 +153,6 @@ export enum LogsAggregatorOperator {
|
||||
}
|
||||
|
||||
export enum QueryFunctionsTypes {
|
||||
ANOMALY = 'anomaly',
|
||||
CUTOFF_MIN = 'cutOffMin',
|
||||
CUTOFF_MAX = 'cutOffMax',
|
||||
CLAMP_MIN = 'clampMin',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { orange } from '@ant-design/colors';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
|
||||
export const getDefaultLogBackground = (
|
||||
isReadOnly?: boolean,
|
||||
@@ -18,28 +17,10 @@ export const getDefaultLogBackground = (
|
||||
export const getActiveLogBackground = (
|
||||
isActiveLog = true,
|
||||
isDarkMode = true,
|
||||
logType?: string,
|
||||
): string => {
|
||||
if (!isActiveLog) return ``;
|
||||
if (isDarkMode) {
|
||||
switch (logType) {
|
||||
case LogType.INFO:
|
||||
return `background-color: ${Color.BG_ROBIN_500}10 !important;`;
|
||||
case LogType.WARN:
|
||||
return `background-color: ${Color.BG_AMBER_500}10 !important;`;
|
||||
case LogType.ERROR:
|
||||
return `background-color: ${Color.BG_CHERRY_500}10 !important;`;
|
||||
case LogType.TRACE:
|
||||
return `background-color: ${Color.BG_FOREST_400}10 !important;`;
|
||||
case LogType.DEBUG:
|
||||
return `background-color: ${Color.BG_AQUA_500}10 !important;`;
|
||||
case LogType.FATAL:
|
||||
return `background-color: ${Color.BG_SAKURA_500}10 !important;`;
|
||||
default:
|
||||
return `background-color: ${Color.BG_SLATE_200} !important;`;
|
||||
}
|
||||
}
|
||||
return `background-color: ${Color.BG_VANILLA_300}!important; color: ${Color.TEXT_SLATE_400} !important;`;
|
||||
if (isDarkMode) return `background-color: ${Color.BG_SLATE_200};`;
|
||||
return `background-color: ${Color.BG_VANILLA_300}; color: ${Color.TEXT_SLATE_400}`;
|
||||
};
|
||||
|
||||
export const getHightLightedLogBackground = (
|
||||
|
||||
2
go.mod
2
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.25.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.12
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.10
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
|
||||
4
go.sum
4
go.sum
@@ -70,8 +70,8 @@ github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkb
|
||||
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
|
||||
github.com/SigNoz/prometheus v1.12.0 h1:+BXeIHyMOOWWa+xjhJ+x80JFva7r1WzWIfIhQ5PUmIE=
|
||||
github.com/SigNoz/prometheus v1.12.0/go.mod h1:EqNM27OwmPfqMUk+E+XG1L9rfDFcyXnzzDrg0EPOfxA=
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.12 h1:5yY0IBtNz6SHMzKzwHmKfIx99Ij8mr72nDI2Xi08pDQ=
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.12/go.mod h1:tcNyU+NSn7ZkzZcLa+k+dJIPOPV+CjHn3+z1SICAfdA=
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.10 h1:1zjU31OcRZL6fS0IIag8LA8bdhP4S28dzovDwuOg7Lg=
|
||||
github.com/SigNoz/signoz-otel-collector v0.102.10/go.mod h1:APoBVD4aRu9vIny1vdzZSi2wPY3elyjHA/I/rh1hKfs=
|
||||
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
|
||||
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
|
||||
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=
|
||||
|
||||
@@ -13,7 +13,7 @@ https://github.com/SigNoz/signoz/blob/main/CONTRIBUTING.md#to-run-clickhouse-set
|
||||
- Change the alertmanager section in `signoz/deploy/docker/clickhouse-setup/docker-compose.yaml` as follows:
|
||||
```console
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.23.7
|
||||
image: signoz/alertmanager:0.23.5
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
expose:
|
||||
|
||||
@@ -45,9 +45,6 @@ const (
|
||||
defaultLogsTableV2 string = "distributed_logs_v2"
|
||||
defaultLogsResourceLocalTableV2 string = "logs_v2_resource"
|
||||
defaultLogsResourceTableV2 string = "distributed_logs_v2_resource"
|
||||
|
||||
defaultTraceIndexTableV3 string = "distributed_signoz_index_v3"
|
||||
defaultTraceResourceTableV3 string = "distributed_traces_v3_resource"
|
||||
)
|
||||
|
||||
// NamespaceConfig is Clickhouse's internal configuration data
|
||||
@@ -85,9 +82,6 @@ type namespaceConfig struct {
|
||||
LogsTableV2 string
|
||||
LogsResourceLocalTableV2 string
|
||||
LogsResourceTableV2 string
|
||||
|
||||
TraceIndexTableV3 string
|
||||
TraceResourceTableV3 string
|
||||
}
|
||||
|
||||
// Connecto defines how to connect to the database
|
||||
@@ -180,9 +174,6 @@ func NewOptions(
|
||||
LogsLocalTableV2: defaultLogsLocalTableV2,
|
||||
LogsResourceTableV2: defaultLogsResourceTableV2,
|
||||
LogsResourceLocalTableV2: defaultLogsResourceLocalTableV2,
|
||||
|
||||
TraceIndexTableV3: defaultTraceIndexTableV3,
|
||||
TraceResourceTableV3: defaultTraceResourceTableV3,
|
||||
},
|
||||
others: make(map[string]*namespaceConfig, len(otherNamespaces)),
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user