Compare commits

..

86 Commits

Author SHA1 Message Date
ahrefabhi
070decb102 fix(query): added fix for multi value context and in-place replacement 2025-07-01 01:51:22 +05:30
ahrefabhi
0fbe60d68f feat: enhance IQueryPair to support multi-value operators and update query context handling 2025-06-29 17:26:43 +05:30
SagarRajput-7
7e877790d0 feat: new qb selectedfields changes for logs and traces 2025-06-27 18:36:09 +05:30
ahrefabhi
3b2d3c3849 fix: simplify condition for wrapping string values in quotes and comment out query context display 2025-06-27 18:29:28 +05:30
ahrefabhi
815d3b8bc0 feat: enhance query processing to support negation context and improve space handling 2025-06-27 18:29:28 +05:30
ahrefabhi
e529ad8d57 chore: added grammer parity for frontend grammer with main grammer 2025-06-27 18:29:28 +05:30
ahrefabhi
c8f36de6fa feat: add support for negation context in query processing 2025-06-27 18:29:28 +05:30
ahrefabhi
85caefa945 feat: enhance query context to support detection of values wrapped in quotes 2025-06-27 18:29:28 +05:30
ahrefabhi
62d8dd929a chore: removed IS_NULL and IS_NOT_NULL and added NOT_EXISTS 2025-06-27 18:29:28 +05:30
ahrefabhi
afceff33d6 feat: added IS_NULL and IS_NOT_NULL operators and fixed support for not value operators 2025-06-27 18:29:28 +05:30
ahrefabhi
ed77c6abd0 chore: updated grammer for value, added parsetree for finding current context 2025-06-27 18:29:27 +05:30
SagarRajput-7
5575334893 fix: implemented the filter retention across view switch in explorer pages 2025-06-27 18:29:27 +05:30
SagarRajput-7
137a3f6d27 fix: added metricName to the metric where clause keys api 2025-06-27 18:29:27 +05:30
SagarRajput-7
c332a3c48a fix: removed hadrcoded values suggestion for Having Filter 2025-06-27 18:29:27 +05:30
SagarRajput-7
84ff35100a fix: fixed infinite loop of states around operator 2025-06-27 18:29:27 +05:30
SagarRajput-7
67ce050f53 fix: handled qb - order, group and having's vertical expansion 2025-06-27 18:29:27 +05:30
SagarRajput-7
ceefe50d82 fix: removed noop from suggestions and default values 2025-06-27 18:29:26 +05:30
SagarRajput-7
04c9e852e6 fix: fixed metric having clause and traces order sorting 2025-06-27 18:29:26 +05:30
SagarRajput-7
97cd377fa6 feat: new query builder misc fixes (#8359)
* feat: qb fixes

* feat: fixed handlerunquery props

* feat: fixes logs list order by

* feat: fix logs order by issue

* feat: safety check and order by correction

* feat: updated version in new create dashboards

* feat: added new formatOptions for table and fixed the pie chart plotting

* feat: keyboard shortcut overriding issue and pie ch correction in dashboard views

* feat: fixed dashboard data state management across datasource * paneltypes

* feat: fixed explorer pages data management issues

* feat: integrated new backend payload/request diff, to the UI types

* feat: fixed the collapse behaviour of QB - queries

* feat: fix order by and default aggregation to count()
2025-06-27 18:29:26 +05:30
SagarRajput-7
157213defc feat: resolved conflicts 2025-06-27 18:29:25 +05:30
SagarRajput-7
8092df8961 Query builder misc - fixes (#8295)
* feat: trace and logs explorer fixes

* fix: ui fixes

* fix: handle multi arg aggregation

* feat: explorer pages fixes

* feat: added fixes for order by for datasource

* feat: metric order by issue

* feat: support for paneltype selectedview tab switch

* feat: qb v2 compatiblity with url's composite query

* feat: conversion fixes

* feat: where clause and aggregation fix

---------

Co-authored-by: Yunus M <myounis.ar@live.com>
2025-06-27 18:29:25 +05:30
Yunus M
3c895981d9 feat: fetch more keys is complete list not already fetched 2025-06-27 18:29:25 +05:30
SagarRajput-7
a058dac45b feat: query_range migration from v3/v4 -> v5 (#8192)
* feat: query_range migration from v3/v4 -> v5

* feat: cleanup files

* feat: cleanup code

* feat: metric payload improvements

* feat: metric payload improvements

* feat: data retention and qb v2 for dashboard cleanup

* feat: corrected datasource change daata updatation in qb v2

* feat: fix value panel plotting with new query v5

* feat: alert migration

* feat: fixed aggregation css

* feat: explorer pages migration

* feat: trace and logs explorer fixes
2025-06-27 18:29:25 +05:30
Yunus M
a18106f5d8 fix: responsiveness issues 2025-06-27 18:29:24 +05:30
Yunus M
ea88177936 feat: where clause key updates 2025-06-27 18:29:24 +05:30
Yunus M
84a17dd376 feat: update styles for light mode 2025-06-27 18:29:24 +05:30
Yunus M
f699773aec feat: show errors 2025-06-27 18:29:24 +05:30
Yunus M
d46d1a0f24 feat: update context and show suggestions on select 2025-06-27 18:29:24 +05:30
Yunus M
a3b66935d8 feat: add a space after selecting a value from suggestion 2025-06-27 18:29:24 +05:30
Yunus M
1f8c97cd5b feat: improve suggestion ux in query search 2025-06-27 18:29:24 +05:30
Yunus M
2659e03564 feat: ui improvements 2025-06-27 18:29:24 +05:30
Yunus M
121696c1d7 feat: handle close on blur 2025-06-27 18:29:24 +05:30
Yunus M
20be9dd600 feat: query search component clean up 2025-06-27 18:29:24 +05:30
Yunus M
45e4c65c9f feat: handle having option autocomplete ux 2025-06-27 18:29:24 +05:30
Yunus M
b7490fcf68 feat: disable clicking on placeholder items in suggestions 2025-06-27 18:29:24 +05:30
Yunus M
4bfd4e536c feat: improve having suggestions 2025-06-27 18:29:24 +05:30
Yunus M
f7d5a26403 feat: handle add ons 2025-06-27 18:29:24 +05:30
Yunus M
f87594243e feat: handle list panel type options 2025-06-27 18:29:24 +05:30
Yunus M
dacc3d6d9e feat: pass index to query addons 2025-06-27 18:29:24 +05:30
Yunus M
6b28ec2f7f feat: update qb elements based on panel type 2025-06-27 18:29:24 +05:30
Yunus M
9b757af028 feat: hide extra qb elements 2025-06-27 18:29:24 +05:30
Yunus M
ce87bcae71 feat: use qb-v2 in explorers and alerts 2025-06-27 18:29:24 +05:30
Yunus M
25fb8b6561 feat: update explorer views 2025-06-27 18:29:24 +05:30
Yunus M
428a16326a feat: update logs, metrics and traces qb 2025-06-27 18:29:24 +05:30
Yunus M
78fec2188d feat: query builder layout updates 2025-06-27 18:29:24 +05:30
Yunus M
c5650cc131 fix: minor fixes 2025-06-27 18:29:24 +05:30
Yunus M
f67213096c feat: create separate containers for traces, logs and metrics qbs 2025-06-27 18:29:24 +05:30
Yunus M
d71f85a8ec feat: metrics qb 2025-06-27 18:29:24 +05:30
Yunus M
9335261314 fix: update dropdown css 2025-06-27 18:29:24 +05:30
Yunus M
0f5c54cabb feat: remove () from suggestions 2025-06-27 18:29:24 +05:30
Yunus M
0204337396 feat: handle parenthesis and conjunction operators 2025-06-27 18:29:24 +05:30
Yunus M
8101fef874 feat: support multiple having key value pairs 2025-06-27 18:29:24 +05:30
Yunus M
2d223fe9e8 feat: move state to context 2025-06-27 18:29:24 +05:30
Yunus M
de464e6042 feat: handle having options creation 2025-06-27 18:29:24 +05:30
Yunus M
ea42e4db6b feat: hide already used variables 2025-06-27 18:29:24 +05:30
Yunus M
2b5d2f0061 fix: show operator suggestions only on manual trigger or valid key 2025-06-27 18:29:24 +05:30
Yunus M
a013cc0fd3 fix: handle autocomplete 2025-06-27 18:29:24 +05:30
Yunus M
e68d860adf fix: update styles 2025-06-27 18:29:23 +05:30
Yunus M
9ed93ae5ac fix: update css 2025-06-27 18:29:23 +05:30
Yunus M
9989af10d6 feat: handle multie select functions 2025-06-27 18:29:23 +05:30
Yunus M
1bc89c9d1a feat: handle field suggestions for aggregate operators 2025-06-27 18:29:23 +05:30
Yunus M
3fbe111bc0 feat: support aggregation function with values 2025-06-27 18:29:23 +05:30
Yunus M
c449d1da8e feat: add groupBy, having, order by, limit and legend format 2025-06-27 18:29:23 +05:30
Yunus M
4635da0ee8 feat: handle multie select values better 2025-06-27 18:29:23 +05:30
Yunus M
67453e27f7 feat: improve suggestions 2025-06-27 18:29:23 +05:30
Yunus M
fdcc6a6c92 feat: console log context based on cursor position 2025-06-27 18:29:23 +05:30
Yunus M
62c71e6306 fix: handle . notation keywords better 2025-06-27 18:29:23 +05:30
Yunus M
e2e535eaca feat: remove card container above where clause 2025-06-27 18:29:23 +05:30
Yunus M
2520718afb feat: use new qb in logs explorer 2025-06-27 18:29:23 +05:30
Yunus M
0ffa666903 feat: handle parenthesis 2025-06-27 18:29:23 +05:30
Yunus M
c653e83461 feat: handle value selection 2025-06-27 18:29:23 +05:30
Yunus M
b80cf96faf feat: styling updates 2025-06-27 18:29:23 +05:30
Yunus M
a2126ad22c feat: handle string and number values correctly 2025-06-27 18:29:23 +05:30
Yunus M
5a75df30e2 feat: handle async value fetching 2025-06-27 18:29:23 +05:30
Yunus M
aeca98b6aa feat: update the context with additonal properties 2025-06-27 18:29:23 +05:30
Yunus M
53b31ae516 feat: styling updates 2025-06-27 18:29:23 +05:30
Yunus M
209828de01 feat: update theme and syntax highlighting 2025-06-27 18:29:23 +05:30
Yunus M
491a0140e3 feat: handle context switch 2025-06-27 18:29:23 +05:30
Yunus M
b9494a3375 feat: handle multiple spaces 2025-06-27 18:29:23 +05:30
Yunus M
d4b379ccc0 feat: integrate the apis 2025-06-27 18:29:23 +05:30
Yunus M
a7ff27d30c feat: update context logic and return auto-suggestions based on context 2025-06-27 18:29:23 +05:30
Yunus M
6008e8df72 feat: add apis and hooks 2025-06-27 18:29:23 +05:30
Yunus M
27d5e16d18 feat: update context to recognise conjunction operator 2025-06-27 18:29:23 +05:30
Yunus M
24d6b48ad4 feat: add codemirror 2025-06-27 18:29:23 +05:30
Yunus M
78af24b4df feat: add types, base components 2025-06-27 18:29:23 +05:30
Yunus M
45fcf746b0 feat: add antlr4, parser files and grammar 2025-06-27 18:29:23 +05:30
343 changed files with 3973 additions and 13626 deletions

View File

@@ -40,7 +40,7 @@ services:
timeout: 5s
retries: 3
schema-migrator-sync:
image: signoz/signoz-schema-migrator:v0.128.0
image: signoz/signoz-schema-migrator:v0.111.42
container_name: schema-migrator-sync
command:
- sync
@@ -53,7 +53,7 @@ services:
condition: service_healthy
restart: on-failure
schema-migrator-async:
image: signoz/signoz-schema-migrator:v0.128.0
image: signoz/signoz-schema-migrator:v0.111.42
container_name: schema-migrator-async
command:
- async

View File

@@ -22,7 +22,7 @@ jobs:
- 24.1.2-alpine
- 24.12-alpine
schema-migrator-version:
- v0.128.0
- v0.111.38
postgres-version:
- 15
if: |

View File

@@ -174,7 +174,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.89.0
image: signoz/signoz:v0.87.0
command:
- --config=/root/config/prometheus.yml
ports:
@@ -194,7 +194,6 @@ services:
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-swarm
- SIGNOZ_JWT_SECRET=secret
- DOT_METRICS_ENABLED=true
healthcheck:
test:
- CMD
@@ -207,7 +206,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.128.0
image: signoz/signoz-otel-collector:v0.111.42
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -231,7 +230,7 @@ services:
- signoz
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:v0.128.0
image: signoz/signoz-schema-migrator:v0.111.42
deploy:
restart_policy:
condition: on-failure

View File

@@ -100,7 +100,7 @@ services:
# - "9000:9000"
# - "8123:8123"
# - "9181:9181"
configs:
- source: clickhouse-config
target: /etc/clickhouse-server/config.xml
@@ -110,12 +110,13 @@ services:
target: /etc/clickhouse-server/custom-function.xml
- source: clickhouse-cluster
target: /etc/clickhouse-server/config.d/cluster.xml
volumes:
- clickhouse:/var/lib/clickhouse/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.89.0
image: signoz/signoz:v0.87.0
command:
- --config=/root/config/prometheus.yml
ports:
@@ -135,7 +136,6 @@ services:
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-swarm
- DOT_METRICS_ENABLED=true
healthcheck:
test:
- CMD
@@ -148,7 +148,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.128.0
image: signoz/signoz-otel-collector:v0.111.42
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -174,7 +174,7 @@ services:
- signoz
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:v0.128.0
image: signoz/signoz-schema-migrator:v0.111.42
deploy:
restart_policy:
condition: on-failure
@@ -195,6 +195,7 @@ volumes:
name: signoz-sqlite
zookeeper-1:
name: signoz-zookeeper-1
configs:
clickhouse-config:
file: ../common/clickhouse/config.xml
@@ -204,6 +205,7 @@ configs:
file: ../common/clickhouse/custom-function.xml
clickhouse-cluster:
file: ../common/clickhouse/cluster.xml
signoz-prometheus-config:
file: ../common/signoz/prometheus.yml
# If you have multiple dashboard files, you can list them individually:

View File

@@ -26,7 +26,7 @@ processors:
detectors: [env, system]
timeout: 2s
signozspanmetrics/delta:
metrics_exporter: signozclickhousemetrics
metrics_exporter: clickhousemetricswrite, signozclickhousemetrics
metrics_flush_interval: 60s
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
@@ -60,16 +60,27 @@ exporters:
datasource: tcp://clickhouse:9000/signoz_traces
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
use_new_schema: true
clickhousemetricswrite:
endpoint: tcp://clickhouse:9000/signoz_metrics
resource_to_telemetry_conversion:
enabled: true
disable_v2: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/signoz_metrics
disable_v2: true
signozclickhousemetrics:
dsn: tcp://clickhouse:9000/signoz_metrics
clickhouselogsexporter:
dsn: tcp://clickhouse:9000/signoz_logs
timeout: 10s
use_new_schema: true
# debug: {}
service:
telemetry:
logs:
encoding: json
metrics:
address: 0.0.0.0:8888
extensions:
- health_check
- pprof
@@ -81,11 +92,11 @@ service:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [signozclickhousemetrics]
exporters: [clickhousemetricswrite, signozclickhousemetrics]
metrics/prometheus:
receivers: [prometheus]
processors: [batch]
exporters: [signozclickhousemetrics]
exporters: [clickhousemetricswrite/prometheus, signozclickhousemetrics]
logs:
receivers: [otlp]
processors: [batch]

View File

@@ -177,7 +177,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.89.0}
image: signoz/signoz:${VERSION:-v0.87.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml
@@ -197,7 +197,6 @@ services:
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-standalone-amd
- DOT_METRICS_ENABLED=true
healthcheck:
test:
- CMD
@@ -211,7 +210,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.42}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -237,7 +236,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
container_name: schema-migrator-sync
command:
- sync
@@ -248,7 +247,7 @@ services:
condition: service_healthy
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
container_name: schema-migrator-async
command:
- async

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.89.0}
image: signoz/signoz:${VERSION:-v0.87.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml
@@ -130,7 +130,6 @@ services:
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-standalone-amd
- DOT_METRICS_ENABLED=true
healthcheck:
test:
- CMD
@@ -143,7 +142,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.42}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -165,7 +164,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
container_name: schema-migrator-sync
command:
- sync
@@ -177,7 +176,7 @@ services:
restart: on-failure
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.42}
container_name: schema-migrator-async
command:
- async

View File

@@ -26,7 +26,7 @@ processors:
detectors: [env, system]
timeout: 2s
signozspanmetrics/delta:
metrics_exporter: signozclickhousemetrics
metrics_exporter: clickhousemetricswrite, signozclickhousemetrics
metrics_flush_interval: 60s
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
dimensions_cache_size: 100000
@@ -60,16 +60,27 @@ exporters:
datasource: tcp://clickhouse:9000/signoz_traces
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
use_new_schema: true
clickhousemetricswrite:
endpoint: tcp://clickhouse:9000/signoz_metrics
disable_v2: true
resource_to_telemetry_conversion:
enabled: true
clickhousemetricswrite/prometheus:
endpoint: tcp://clickhouse:9000/signoz_metrics
disable_v2: true
signozclickhousemetrics:
dsn: tcp://clickhouse:9000/signoz_metrics
clickhouselogsexporter:
dsn: tcp://clickhouse:9000/signoz_logs
timeout: 10s
use_new_schema: true
# debug: {}
service:
telemetry:
logs:
encoding: json
metrics:
address: 0.0.0.0:8888
extensions:
- health_check
- pprof
@@ -81,11 +92,11 @@ service:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [signozclickhousemetrics]
exporters: [clickhousemetricswrite, signozclickhousemetrics]
metrics/prometheus:
receivers: [prometheus]
processors: [batch]
exporters: [signozclickhousemetrics]
exporters: [clickhousemetricswrite/prometheus, signozclickhousemetrics]
logs:
receivers: [otlp]
processors: [batch]

View File

@@ -16,7 +16,7 @@ __Table of Contents__
- [Prerequisites](#prerequisites-1)
- [Install Helm Repo and Charts](#install-helm-repo-and-charts)
- [Start the OpenTelemetry Demo App](#start-the-opentelemetry-demo-app-1)
- [Monitor with SigNoz (Kubernetes)](#monitor-with-signoz-kubernetes)
- [Moniitor with SigNoz (Kubernetes)](#monitor-with-signoz-kubernetes)
- [What's next](#whats-next)

View File

@@ -203,6 +203,17 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz, jwt *authtypes.JWT)
&opAmpModel.AllAgents, agentConfMgr, signoz.Instrumentation,
)
orgs, err := apiHandler.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
if err != nil {
return nil, err
}
for _, org := range orgs {
errorList := reader.PreloadMetricsMetadata(context.Background(), org.ID)
for _, er := range errorList {
zap.L().Error("failed to preload metrics metadata", zap.Error(er))
}
}
return s, nil
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/query-service/app"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
"github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
@@ -22,7 +21,6 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -147,14 +145,6 @@ func main() {
signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(),
signoz.NewWebProviderFactories(),
func(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
existingFactories := signoz.NewSQLSchemaProviderFactories(sqlstore)
if err := existingFactories.Add(postgressqlschema.NewFactory(sqlstore)); err != nil {
zap.L().Fatal("Failed to add postgressqlschema factory", zap.Error(err))
}
return existingFactories
},
sqlStoreFactories,
signoz.NewTelemetryStoreProviderFactories(),
)

View File

@@ -1,36 +0,0 @@
package postgressqlschema
import (
"strings"
"github.com/SigNoz/signoz/pkg/sqlschema"
)
type Formatter struct {
sqlschema.Formatter
}
func (formatter Formatter) SQLDataTypeOf(dataType sqlschema.DataType) string {
if dataType == sqlschema.DataTypeTimestamp {
return "TIMESTAMPTZ"
}
return strings.ToUpper(dataType.String())
}
func (formatter Formatter) DataTypeOf(dataType string) sqlschema.DataType {
switch strings.ToUpper(dataType) {
case "TIMESTAMPTZ", "TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP WITH TIME ZONE":
return sqlschema.DataTypeTimestamp
case "INT8":
return sqlschema.DataTypeBigInt
case "INT2", "INT4", "SMALLINT", "INTEGER":
return sqlschema.DataTypeInteger
case "BOOL", "BOOLEAN":
return sqlschema.DataTypeBoolean
case "VARCHAR", "CHARACTER VARYING", "CHARACTER":
return sqlschema.DataTypeText
}
return formatter.Formatter.DataTypeOf(dataType)
}

View File

@@ -1,285 +0,0 @@
package postgressqlschema
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
)
type provider struct {
settings factory.ScopedProviderSettings
fmter sqlschema.SQLFormatter
sqlstore sqlstore.SQLStore
operator sqlschema.SQLOperator
}
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config] {
return factory.NewProviderFactory(factory.MustNewName("postgres"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlschema.Config) (sqlschema.SQLSchema, error) {
return New(ctx, providerSettings, config, sqlstore)
})
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlschema.Config, sqlstore sqlstore.SQLStore) (sqlschema.SQLSchema, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/sqlschema/postgressqlschema")
fmter := Formatter{Formatter: sqlschema.NewFormatter(sqlstore.BunDB().Dialect())}
return &provider{
sqlstore: sqlstore,
fmter: fmter,
settings: settings,
operator: sqlschema.NewOperator(fmter, sqlschema.OperatorSupport{
DropConstraint: true,
ColumnIfNotExistsExists: true,
AlterColumnSetNotNull: true,
}),
}, nil
}
func (provider *provider) Formatter() sqlschema.SQLFormatter {
return provider.fmter
}
func (provider *provider) Operator() sqlschema.SQLOperator {
return provider.operator
}
func (provider *provider) GetTable(ctx context.Context, tableName sqlschema.TableName) (*sqlschema.Table, []*sqlschema.UniqueConstraint, error) {
rows, err := provider.
sqlstore.
BunDB().
QueryContext(ctx, `
SELECT
c.column_name,
c.is_nullable = 'YES',
c.udt_name,
c.column_default
FROM
information_schema.columns AS c
WHERE
c.table_name = ?`, string(tableName))
if err != nil {
return nil, nil, err
}
defer func() {
if err := rows.Close(); err != nil {
provider.settings.Logger().ErrorContext(ctx, "error closing rows", "error", err)
}
}()
columns := make([]*sqlschema.Column, 0)
for rows.Next() {
var (
name string
sqlDataType string
nullable bool
defaultVal *string
)
if err := rows.Scan(&name, &nullable, &sqlDataType, &defaultVal); err != nil {
return nil, nil, err
}
columnDefault := ""
if defaultVal != nil {
columnDefault = *defaultVal
}
columns = append(columns, &sqlschema.Column{
Name: sqlschema.ColumnName(name),
Nullable: nullable,
DataType: provider.fmter.DataTypeOf(sqlDataType),
Default: columnDefault,
})
}
constraintsRows, err := provider.
sqlstore.
BunDB().
QueryContext(ctx, `
SELECT
c.column_name,
constraint_name,
constraint_type
FROM
information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_catalog, table_name, constraint_name)
JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
WHERE
c.table_name = ?`, string(tableName))
if err != nil {
return nil, nil, err
}
defer func() {
if err := constraintsRows.Close(); err != nil {
provider.settings.Logger().ErrorContext(ctx, "error closing rows", "error", err)
}
}()
var primaryKeyConstraint *sqlschema.PrimaryKeyConstraint
uniqueConstraintsMap := make(map[string]*sqlschema.UniqueConstraint)
for constraintsRows.Next() {
var (
name string
constraintName string
constraintType string
)
if err := constraintsRows.Scan(&name, &constraintName, &constraintType); err != nil {
return nil, nil, err
}
if constraintType == "PRIMARY KEY" {
if primaryKeyConstraint == nil {
primaryKeyConstraint = (&sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(name)},
}).Named(constraintName).(*sqlschema.PrimaryKeyConstraint)
} else {
primaryKeyConstraint.ColumnNames = append(primaryKeyConstraint.ColumnNames, sqlschema.ColumnName(name))
}
}
if constraintType == "UNIQUE" {
if _, ok := uniqueConstraintsMap[constraintName]; !ok {
uniqueConstraintsMap[constraintName] = (&sqlschema.UniqueConstraint{
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(name)},
}).Named(constraintName).(*sqlschema.UniqueConstraint)
} else {
uniqueConstraintsMap[constraintName].ColumnNames = append(uniqueConstraintsMap[constraintName].ColumnNames, sqlschema.ColumnName(name))
}
}
}
foreignKeyConstraintsRows, err := provider.
sqlstore.
BunDB().
QueryContext(ctx, `
SELECT
tc.constraint_name,
kcu.table_name AS referencing_table,
kcu.column_name AS referencing_column,
ccu.table_name AS referenced_table,
ccu.column_name AS referenced_column
FROM
information_schema.key_column_usage kcu
JOIN information_schema.table_constraints tc ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema
JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
WHERE
tc.constraint_type = ?
AND kcu.table_name = ?`, "FOREIGN KEY", string(tableName))
if err != nil {
return nil, nil, err
}
defer func() {
if err := foreignKeyConstraintsRows.Close(); err != nil {
provider.settings.Logger().ErrorContext(ctx, "error closing rows", "error", err)
}
}()
foreignKeyConstraints := make([]*sqlschema.ForeignKeyConstraint, 0)
for foreignKeyConstraintsRows.Next() {
var (
constraintName string
referencingTable string
referencingColumn string
referencedTable string
referencedColumn string
)
if err := foreignKeyConstraintsRows.Scan(&constraintName, &referencingTable, &referencingColumn, &referencedTable, &referencedColumn); err != nil {
return nil, nil, err
}
foreignKeyConstraints = append(foreignKeyConstraints, (&sqlschema.ForeignKeyConstraint{
ReferencingColumnName: sqlschema.ColumnName(referencingColumn),
ReferencedTableName: sqlschema.TableName(referencedTable),
ReferencedColumnName: sqlschema.ColumnName(referencedColumn),
}).Named(constraintName).(*sqlschema.ForeignKeyConstraint))
}
uniqueConstraints := make([]*sqlschema.UniqueConstraint, 0)
for _, uniqueConstraint := range uniqueConstraintsMap {
uniqueConstraints = append(uniqueConstraints, uniqueConstraint)
}
return &sqlschema.Table{
Name: tableName,
Columns: columns,
PrimaryKeyConstraint: primaryKeyConstraint,
ForeignKeyConstraints: foreignKeyConstraints,
}, uniqueConstraints, nil
}
func (provider *provider) GetIndices(ctx context.Context, name sqlschema.TableName) ([]sqlschema.Index, error) {
rows, err := provider.
sqlstore.
BunDB().
QueryContext(ctx, `
SELECT
ct.relname AS table_name,
ci.relname AS index_name,
i.indisunique AS unique,
i.indisprimary AS primary,
a.attname AS column_name
FROM
pg_index i
LEFT JOIN pg_class ct ON ct.oid = i.indrelid
LEFT JOIN pg_class ci ON ci.oid = i.indexrelid
LEFT JOIN pg_attribute a ON a.attrelid = ct.oid
LEFT JOIN pg_constraint con ON con.conindid = i.indexrelid
WHERE
a.attnum = ANY(i.indkey)
AND con.oid IS NULL
AND ct.relkind = 'r'
AND ct.relname = ?`, string(name))
if err != nil {
return nil, err
}
defer func() {
if err := rows.Close(); err != nil {
provider.settings.Logger().ErrorContext(ctx, "error closing rows", "error", err)
}
}()
uniqueIndicesMap := make(map[string]*sqlschema.UniqueIndex)
for rows.Next() {
var (
tableName string
indexName string
unique bool
primary bool
columnName string
)
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName); err != nil {
return nil, err
}
if unique {
if _, ok := uniqueIndicesMap[indexName]; !ok {
uniqueIndicesMap[indexName] = &sqlschema.UniqueIndex{
TableName: name,
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
}
} else {
uniqueIndicesMap[indexName].ColumnNames = append(uniqueIndicesMap[indexName].ColumnNames, sqlschema.ColumnName(columnName))
}
}
}
indices := make([]sqlschema.Index, 0)
for _, index := range uniqueIndicesMap {
indices = append(indices, index)
}
return indices, nil
}
func (provider *provider) ToggleFKEnforcement(_ context.Context, _ bun.IDB, _ bool) error {
return nil
}

View File

@@ -218,9 +218,7 @@
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sonarjs": "^0.12.0",
"husky": "^7.0.4",
"image-minimizer-webpack-plugin": "^4.0.0",
"imagemin": "^8.0.1",
"imagemin-svgo": "^10.0.1",
"image-webpack-loader": "8.1.0",
"is-ci": "^3.0.1",
"jest-styled-components": "^7.0.8",
"lint-staged": "^12.5.0",
@@ -237,7 +235,6 @@
"redux-mock-store": "1.5.4",
"sass": "1.66.1",
"sass-loader": "13.3.2",
"sharp": "^0.33.4",
"ts-jest": "^27.1.5",
"ts-node": "^10.2.1",
"typescript-plugin-css-modules": "5.0.1",
@@ -262,7 +259,6 @@
"cross-spawn": "7.0.5",
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0",
"got": "11.8.5"
"prismjs": "1.30.0"
}
}

View File

@@ -14,8 +14,8 @@
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
"remove_label_success": "Labels cleared",
"alert_form_step1": "Step 1 - Define the metric",
"alert_form_step2": "Step {{step}} - Define Alert Conditions",
"alert_form_step3": "Step {{step}} - Alert Configuration",
"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",

View File

@@ -7,8 +7,8 @@
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
"remove_label_success": "Labels cleared",
"alert_form_step1": "Step 1 - Define the metric",
"alert_form_step2": "Step {{step}} - Define Alert Conditions",
"alert_form_step3": "Step {{step}} - Alert Configuration",
"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",

View File

@@ -7,8 +7,8 @@
"remove_label_confirm": "This action will remove all the labels. Do you want to proceed?",
"remove_label_success": "Labels cleared",
"alert_form_step1": "Step 1 - Define the metric",
"alert_form_step2": "Step {{step}} - Define Alert Conditions",
"alert_form_step3": "Step {{step}} - Alert Configuration",
"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",

View File

@@ -191,8 +191,7 @@ function App(): JSX.Element {
// if the user is on basic plan then remove billing
if (isOnBasicPlan) {
updatedRoutes = updatedRoutes.filter(
(route) =>
route?.path !== ROUTES.BILLING && route?.path !== ROUTES.INTEGRATIONS,
(route) => route?.path !== ROUTES.BILLING,
);
}
@@ -205,8 +204,7 @@ function App(): JSX.Element {
} else {
// if not a cloud user then remove billing and add list licenses route
updatedRoutes = updatedRoutes.filter(
(route) =>
route?.path !== ROUTES.BILLING && route?.path !== ROUTES.INTEGRATIONS,
(route) => route?.path !== ROUTES.BILLING,
);
updatedRoutes = [...updatedRoutes, LIST_LICENSES];
}

View File

@@ -1,32 +1,14 @@
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import axios, { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
ChangelogSchema,
DeploymentType,
} from 'types/api/changelog/getChangelogByVersion';
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
const getChangelogByVersion = async (
versionId: string,
deployment_type?: DeploymentType,
): Promise<SuccessResponse<ChangelogSchema> | ErrorResponse> => {
try {
let queryParams = `filters[version][$eq]=${versionId}&populate[features][sort]=sort_order:asc&populate[features][populate][media][fields]=id,ext,url,mime,alternativeText`;
if (
deployment_type &&
Object.values(DeploymentType).includes(deployment_type)
) {
const excludedDeploymentType =
deployment_type === DeploymentType.CLOUD_ONLY
? DeploymentType.OSS_ONLY
: DeploymentType.CLOUD_ONLY;
queryParams = `${queryParams}&populate[features][filters][deployment_type][$notIn]=${excludedDeploymentType}`;
}
const response = await axios.get(`
https://cms.signoz.cloud/api/release-changelogs?${queryParams}
https://cms.signoz.cloud/api/release-changelogs?filters[version][$eq]=${versionId}&populate[features][sort]=sort_order:asc&populate[features][populate][media][fields]=id,ext,url,mime,alternativeText
`);
if (!Array.isArray(response.data.data) || response.data.data.length === 0) {

View File

@@ -7,16 +7,11 @@ import {
export const getKeySuggestions = (
props: QueryKeyRequestProps,
): Promise<AxiosResponse<QueryKeySuggestionsResponseProps>> => {
const {
signal = '',
searchText = '',
metricName = '',
fieldContext = '',
fieldDataType = '',
} = props;
return axios.get(
`/fields/keys?signal=${signal}&searchText=${searchText}&metricName=${metricName}&fieldContext=${fieldContext}&fieldDataType=${fieldDataType}`,
): Promise<AxiosResponse<QueryKeySuggestionsResponseProps>> =>
axios.get(
`/fields/keys?signal=${props.signal}&searchText=${
props.searchText
}&metricName=${props.metricName ?? ''}&fieldContext=${
props.fieldContext ?? ''
}&fieldDataType=${props.fieldDataType ?? ''}`,
);
};

View File

@@ -7,14 +7,5 @@ import {
export const getValueSuggestions = (
props: QueryKeyValueRequestProps,
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
const { signal, key, searchText } = props;
const encodedSignal = encodeURIComponent(signal);
const encodedKey = encodeURIComponent(key);
const encodedSearchText = encodeURIComponent(searchText);
return axios.get(
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}`,
);
};
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> =>
axios.get(`/fields/values?signal=${props.signal}&name=${props.key}`);

View File

@@ -4,36 +4,12 @@ import { MetricRangePayloadV3 } from 'types/api/metrics/getQueryRange';
import {
DistributionData,
MetricRangePayloadV5,
QueryRangeRequestV5,
RawData,
ScalarData,
TimeSeriesData,
} from 'types/api/v5/queryRange';
import { QueryDataV3 } from 'types/api/widgets/getQuery';
function getColName(
col: ScalarData['columns'][number],
legendMap: Record<string, string>,
aggregationPerQuery: Record<string, any>,
): string {
const aggregation =
aggregationPerQuery?.[col.queryName]?.[col.aggregationIndex];
const legend = legendMap[col.queryName];
const aggregationName = aggregation?.alias || aggregation?.expression || '';
if (col.columnType === 'group') {
return col.name;
}
if (aggregationName && aggregationPerQuery[col.queryName].length > 1) {
if (legend) {
return `${aggregationName}-${legend}`;
}
return `${col.queryName}.${aggregationName}`;
}
return legend || col.queryName;
}
/**
* Converts V5 TimeSeriesData to legacy format
*/
@@ -42,18 +18,11 @@ function convertTimeSeriesData(
legendMap: Record<string, string>,
): QueryDataV3 {
// Convert V5 time series format to legacy QueryDataV3 format
return {
queryName: timeSeriesData.queryName,
legend: legendMap[timeSeriesData.queryName] || timeSeriesData.queryName,
series: timeSeriesData?.aggregations?.flatMap((aggregation) => {
const { index, alias, series } = aggregation;
if (!series || !series.length) {
return [];
}
return series.map((series) => ({
series: timeSeriesData?.aggregations?.flatMap((aggregation) =>
aggregation.series.map((series) => ({
labels: series.labels
? Object.fromEntries(
series.labels.map((label) => [label.key.name, label.value]),
@@ -66,13 +35,8 @@ function convertTimeSeriesData(
timestamp: value.timestamp,
value: String(value.value),
})),
metaData: {
alias,
index,
queryName: timeSeriesData.queryName,
},
}));
}),
})),
),
list: null,
};
}
@@ -83,7 +47,6 @@ function convertTimeSeriesData(
function convertScalarDataArrayToTable(
scalarDataArray: ScalarData[],
legendMap: Record<string, string>,
aggregationPerQuery: Record<string, any>,
): QueryDataV3[] {
// If no scalar data, return empty structure
@@ -96,20 +59,9 @@ function convertScalarDataArrayToTable(
// Get query name from the first column
const queryName = scalarData?.columns?.[0]?.queryName || '';
if ((scalarData as any)?.aggregations?.length > 0) {
return {
...convertTimeSeriesData(scalarData as any, legendMap),
table: {
columns: [],
rows: [],
},
list: null,
};
}
// Collect columns for this specific query
const columns = scalarData?.columns?.map((col) => ({
name: getColName(col, legendMap, aggregationPerQuery),
name: col.columnType === 'aggregation' ? col.queryName : col.name,
queryName: col.queryName,
isValueColumn: col.columnType === 'aggregation',
}));
@@ -119,7 +71,8 @@ function convertScalarDataArrayToTable(
const rowData: Record<string, any> = {};
scalarData?.columns?.forEach((col, colIndex) => {
const columnName = getColName(col, legendMap, aggregationPerQuery);
const columnName =
col.columnType === 'aggregation' ? col.queryName : col.name;
rowData[columnName] = dataRow[colIndex];
});
@@ -139,51 +92,6 @@ function convertScalarDataArrayToTable(
});
}
function convertScalerWithFormatForWeb(
scalarDataArray: ScalarData[],
legendMap: Record<string, string>,
aggregationPerQuery: Record<string, any>,
): QueryDataV3[] {
if (!scalarDataArray || scalarDataArray.length === 0) {
return [];
}
return scalarDataArray.map((scalarData) => {
const columns =
scalarData.columns?.map((col) => {
const colName = getColName(col, legendMap, aggregationPerQuery);
return {
name: colName,
queryName: col.queryName,
isValueColumn: col.columnType === 'aggregation',
};
}) || [];
const rows =
scalarData.data?.map((dataRow) => {
const rowData: Record<string, any> = {};
columns?.forEach((col, colIndex) => {
rowData[col.name] = dataRow[colIndex];
});
return { data: rowData };
}) || [];
const queryName = scalarData.columns?.[0]?.queryName || '';
return {
queryName,
legend: legendMap[queryName] || queryName,
series: null,
list: null,
table: {
columns,
rows,
},
};
});
}
/**
* Converts V5 RawData to legacy format
*/
@@ -228,7 +136,6 @@ function convertDistributionData(
function convertV5DataByType(
v5Data: any,
legendMap: Record<string, string>,
aggregationPerQuery: Record<string, any>,
): MetricRangePayloadV3['data'] {
switch (v5Data?.type) {
case 'time_series': {
@@ -243,11 +150,7 @@ function convertV5DataByType(
case 'scalar': {
const scalarData = v5Data.data.results as ScalarData[];
// For scalar data, combine all results into separate table entries
const combinedTables = convertScalarDataArrayToTable(
scalarData,
legendMap,
aggregationPerQuery,
);
const combinedTables = convertScalarDataArrayToTable(scalarData, legendMap);
return {
resultType: 'scalar',
result: combinedTables,
@@ -280,54 +183,23 @@ function convertV5DataByType(
/**
* Converts V5 API response to legacy format expected by frontend components
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export function convertV5ResponseToLegacy(
v5Response: SuccessResponse<MetricRangePayloadV5>,
legendMap: Record<string, string>,
formatForWeb?: boolean,
// formatForWeb?: boolean,
): SuccessResponse<MetricRangePayloadV3> {
const { payload, params } = v5Response;
const { payload } = v5Response;
const v5Data = payload?.data;
const aggregationPerQuery =
(params as QueryRangeRequestV5)?.compositeQuery?.queries
?.filter((query) => query.type === 'builder_query')
.reduce((acc, query) => {
if (
query.type === 'builder_query' &&
'aggregations' in query.spec &&
query.spec.name
) {
acc[query.spec.name] = query.spec.aggregations;
}
return acc;
}, {} as Record<string, any>) || {};
// todo - sagar
// If formatForWeb is true, return as-is (like existing logic)
if (formatForWeb && v5Data?.type === 'scalar') {
const scalarData = v5Data.data.results as ScalarData[];
const webTables = convertScalerWithFormatForWeb(
scalarData,
legendMap,
aggregationPerQuery,
);
return {
...v5Response,
payload: {
data: {
resultType: 'scalar',
result: webTables,
},
},
};
}
// Exception: scalar data should always be converted to table format
// if (formatForWeb && v5Data?.type !== 'scalar') {
// return v5Response as any;
// }
// Convert based on V5 response type
const convertedData = convertV5DataByType(
v5Data,
legendMap,
aggregationPerQuery,
);
const convertedData = convertV5DataByType(v5Data, legendMap);
// Create legacy-compatible response structure
const legacyResponse: SuccessResponse<MetricRangePayloadV3> = {

View File

@@ -1,8 +1,8 @@
import { ApiV5Instance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
MetricRangePayloadV5,
QueryRangePayloadV5,
@@ -13,7 +13,7 @@ export const getQueryRangeV5 = async (
version: string,
signal: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponseV2<MetricRangePayloadV5>> => {
): Promise<SuccessResponse<MetricRangePayloadV5> | ErrorResponse> => {
try {
if (version && version === ENTITY_VERSION_V5) {
const response = await ApiV5Instance.post('/query_range', props, {
@@ -22,8 +22,11 @@ export const getQueryRangeV5 = async (
});
return {
httpStatusCode: response.status,
data: response.data,
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
}
@@ -34,11 +37,14 @@ export const getQueryRangeV5 = async (
});
return {
httpStatusCode: response.status,
data: response.data.data,
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -4,7 +4,6 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
IBuilderQuery,
QueryFunctionProps,
@@ -74,13 +73,8 @@ function createBaseSpec(
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
return {
stepInterval: queryData?.stepInterval || undefined,
stepInterval: queryData.stepInterval,
disabled: queryData.disabled,
filter: queryData?.filter?.expression ? queryData.filter : undefined,
groupBy:
@@ -103,7 +97,7 @@ function createBaseSpec(
: queryData.limit || undefined,
offset: requestType === 'raw' ? queryData.offset : undefined,
order:
queryData.orderBy?.length > 0
queryData.orderBy.length > 0
? queryData.orderBy.map(
(order: any): OrderBy => ({
key: {
@@ -128,9 +122,9 @@ function createBaseSpec(
})),
}),
),
selectFields: isEmpty(nonEmptySelectColumns)
selectFields: isEmpty(queryData.selectColumns)
? undefined
: nonEmptySelectColumns?.map(
: queryData.selectColumns?.map(
(column: any): TelemetryFieldKey => ({
name: column.name ?? column.key,
fieldDataType:
@@ -163,37 +157,14 @@ export function parseAggregations(
export function createAggregation(
queryData: any,
panelType?: PANEL_TYPES,
): TraceAggregation[] | LogAggregation[] | MetricAggregation[] {
if (!queryData) {
return [];
}
const haveReduceTo =
queryData.dataSource === DataSource.METRICS &&
panelType &&
(panelType === PANEL_TYPES.TABLE ||
panelType === PANEL_TYPES.PIE ||
panelType === PANEL_TYPES.VALUE);
if (queryData.dataSource === DataSource.METRICS) {
return [
{
metricName:
queryData?.aggregations?.[0]?.metricName ||
queryData?.aggregateAttribute?.key,
temporality:
queryData?.aggregations?.[0]?.temporality ||
queryData?.aggregateAttribute?.temporality,
timeAggregation:
queryData?.aggregations?.[0]?.timeAggregation ||
queryData?.timeAggregation,
spaceAggregation:
queryData?.aggregations?.[0]?.spaceAggregation ||
queryData?.spaceAggregation,
reduceTo: haveReduceTo
? queryData?.aggregations?.[0]?.reduceTo || queryData?.reduceTo
: undefined,
metricName: queryData?.aggregateAttribute?.key,
temporality: queryData?.aggregateAttribute?.temporality,
timeAggregation: queryData?.timeAggregation,
spaceAggregation: queryData?.spaceAggregation,
},
];
}
@@ -221,7 +192,7 @@ function convertBuilderQueriesToV5(
const baseSpec = createBaseSpec(queryData, requestType, panelType);
let spec: QueryEnvelope['spec'];
const aggregations = createAggregation(queryData, panelType);
const aggregations = createAggregation(queryData);
switch (signal) {
case 'traces':
@@ -273,7 +244,7 @@ function convertPromQueriesToV5(
name: queryName,
query: queryData.query,
disabled: queryData.disabled || false,
step: queryData?.stepInterval,
step: queryData.stepInterval,
stats: false, // PromQL specific field
},
}),
@@ -299,6 +270,24 @@ function convertClickHouseQueriesToV5(
);
}
/**
* Converts query formulas to V5 format
*/
function convertFormulasToV5(
formulas: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
): QueryEnvelope[] {
return Object.entries(formulas).map(
([queryName, formulaData]): QueryEnvelope => ({
type: 'builder_formula' as QueryType,
spec: {
name: queryName,
expression: formulaData.expression || '',
functions: formulaData.functions,
},
}),
);
}
/**
* Helper function to reduce query arrays to objects
*/
@@ -330,7 +319,6 @@ export const prepareQueryRangePayloadV5 = ({
start: startTime,
end: endTime,
formatForWeb,
originalGraphType,
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
let legendMap: Record<string, string> = {};
const requestType = mapPanelTypeToRequestType(graphType);
@@ -356,26 +344,7 @@ export const prepareQueryRangePayloadV5 = ({
);
// Convert formulas as separate query type
const formulaQueries = Object.entries(currentFormulas.data).map(
([queryName, formulaData]): QueryEnvelope => ({
type: 'builder_formula' as const,
spec: {
name: queryName,
expression: formulaData.expression || '',
disabled: formulaData.disabled,
limit: formulaData.limit ?? undefined,
order: formulaData.orderBy?.map(
// eslint-disable-next-line sonarjs/no-identical-functions
(order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}),
),
},
}),
);
const formulaQueries = convertFormulasToV5(currentFormulas.data);
// Combine both types
queries = [...builderQueries, ...formulaQueries];
@@ -413,11 +382,7 @@ export const prepareQueryRangePayloadV5 = ({
queries,
},
formatOptions: {
formatTableResultForUI:
!!formatForWeb ||
(originalGraphType
? originalGraphType === PANEL_TYPES.TABLE
: graphType === PANEL_TYPES.TABLE),
formatTableResultForUI: !!formatForWeb,
},
variables: Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = { value };

View File

@@ -64,8 +64,7 @@ export function applyCeleryFilterOnWidgetData(
...queryItem,
filters: {
...queryItem.filters,
items: [...(queryItem.filters?.items || []), ...filters],
op: queryItem.filters?.op || 'AND',
items: [...queryItem.filters.items, ...filters],
},
}
: queryItem,

View File

@@ -41,8 +41,7 @@ export function useNavigateToExplorer(): (
aggregateOperator: MetricAggregateOperator.NOOP,
filters: {
...item.filters,
items: [...(item.filters?.items || []), ...selectedFilters],
op: item.filters?.op || 'AND',
items: selectedFilters,
},
groupBy: [],
disabled: false,

View File

@@ -60,7 +60,6 @@
&-ctas {
display: flex;
margin-left: auto;
& svg {
font-size: 14px;

View File

@@ -2,59 +2,27 @@ import './ChangelogModal.styles.scss';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
import updateUserPreference from 'api/v1/user/preferences/name/update';
import cx from 'classnames';
import { USER_PREFERENCES } from 'constants/userPreferences';
import dayjs from 'dayjs';
import { ChevronsDown, ScrollText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useMutation } from 'react-query';
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
import { UserPreference } from 'types/api/preferences/preference';
import ChangelogRenderer from './components/ChangelogRenderer';
interface Props {
changelog: ChangelogSchema;
onClose: () => void;
}
function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
function ChangelogModal({ onClose }: Props): JSX.Element {
const [hasScroll, setHasScroll] = useState(false);
const changelogContentSectionRef = useRef<HTMLDivElement>(null);
const { userPreferences, updateUserPreferenceInContext } = useAppContext();
const { changelog } = useAppContext();
const formattedReleaseDate = dayjs(changelog?.release_date).format(
'MMMM D, YYYY',
);
const seenChangelogVersion = userPreferences?.find(
(preference) =>
preference.name === USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
)?.value as string;
const { mutate: updateUserPreferenceMutation } = useMutation(
updateUserPreference,
);
useEffect(() => {
// Update the seen version
if (seenChangelogVersion !== changelog.version) {
const version = {
name: USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
value: changelog.version,
};
updateUserPreferenceInContext(version as UserPreference);
updateUserPreferenceMutation(version);
}
}, [
seenChangelogVersion,
changelog.version,
updateUserPreferenceMutation,
updateUserPreferenceInContext,
]);
const checkScroll = useCallback((): void => {
if (changelogContentSectionRef.current) {
const {

View File

@@ -3,22 +3,10 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { fireEvent, render, screen } from '@testing-library/react';
import { USER_PREFERENCES } from 'constants/userPreferences';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import {
ChangelogSchema,
DeploymentType,
} from 'types/api/changelog/getChangelogByVersion';
import ChangelogModal from '../ChangelogModal';
const mockChangelog: ChangelogSchema = {
id: 1,
documentId: 'doc-1',
version: 'v1.0.0',
createdAt: '2025-06-09T12:00:00Z',
updatedAt: '2025-06-09T13:00:00Z',
publishedAt: '2025-06-09T14:00:00Z',
const mockChangelog = {
release_date: '2025-06-10',
features: [
{
@@ -26,12 +14,6 @@ const mockChangelog: ChangelogSchema = {
title: 'Feature 1',
description: 'Description for feature 1',
media: null,
documentId: 'feature-1',
sort_order: 1,
createdAt: '2025-06-09T12:00:00Z',
updatedAt: '2025-06-09T13:00:00Z',
publishedAt: '2025-06-09T14:00:00Z',
deployment_type: DeploymentType.ALL,
},
],
bug_fixes: 'Bug fix details',
@@ -46,30 +28,15 @@ jest.mock(
return <div>{children}</div>;
},
);
// mock useAppContext
jest.mock('providers/App/App', () => ({
useAppContext: jest.fn(() => ({
updateUserPreferenceInContext: jest.fn(),
userPreferences: [
{
name: USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
value: 'v1.0.0',
},
],
})),
useAppContext: jest.fn(() => ({ changelog: mockChangelog })),
}));
function renderChangelog(onClose: () => void = jest.fn()): void {
render(
<MockQueryClientProvider>
<ChangelogModal changelog={mockChangelog} onClose={onClose} />
</MockQueryClientProvider>,
);
}
describe('ChangelogModal', () => {
it('renders modal with changelog data', () => {
renderChangelog();
render(<ChangelogModal onClose={jest.fn()} />);
expect(
screen.getByText('Whats New ⎯ Changelog : June 10, 2025'),
).toBeInTheDocument();
@@ -81,14 +48,14 @@ describe('ChangelogModal', () => {
it('calls onClose when Skip for now is clicked', () => {
const onClose = jest.fn();
renderChangelog(onClose);
render(<ChangelogModal onClose={onClose} />);
fireEvent.click(screen.getByText('Skip for now'));
expect(onClose).toHaveBeenCalled();
});
it('opens migration docs when Update my workspace is clicked', () => {
window.open = jest.fn();
renderChangelog();
render(<ChangelogModal onClose={jest.fn()} />);
fireEvent.click(screen.getByText('Update my workspace'));
expect(window.open).toHaveBeenCalledWith(
'https://github.com/SigNoz/signoz/releases',
@@ -98,7 +65,7 @@ describe('ChangelogModal', () => {
});
it('scrolls for more when Scroll for more is clicked', () => {
renderChangelog();
render(<ChangelogModal onClose={jest.fn()} />);
const scrollBtn = screen.getByTestId('scroll-more-btn');
const contentDiv = screen.getByTestId('changelog-content');
if (contentDiv) {

View File

@@ -3,10 +3,6 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { render, screen } from '@testing-library/react';
import {
ChangelogSchema,
DeploymentType,
} from 'types/api/changelog/getChangelogByVersion';
import ChangelogRenderer from '../components/ChangelogRenderer';
@@ -19,19 +15,23 @@ jest.mock(
},
);
const mockChangelog: ChangelogSchema = {
const mockChangelog = {
id: 1,
documentId: 'doc-1',
version: 'v1.0.0',
documentId: 'changelog-doc-1',
version: '1.0.0',
createdAt: '2025-06-09T12:00:00Z',
updatedAt: '2025-06-09T13:00:00Z',
publishedAt: '2025-06-09T14:00:00Z',
release_date: '2025-06-10',
features: [
{
id: 1,
documentId: '1',
title: 'Feature 1',
description: 'Description for feature 1',
sort_order: 1,
createdAt: '',
updatedAt: '',
publishedAt: '',
deployment_type: 'All',
media: {
id: 1,
documentId: 'doc1',
@@ -40,15 +40,11 @@ const mockChangelog: ChangelogSchema = {
mime: 'image/webp',
alternativeText: null,
},
documentId: 'feature-1',
sort_order: 1,
createdAt: '2025-06-09T12:00:00Z',
updatedAt: '2025-06-09T13:00:00Z',
publishedAt: '2025-06-09T14:00:00Z',
deployment_type: DeploymentType.ALL,
},
],
bug_fixes: 'Bug fix details',
updatedAt: '2025-06-09T12:00:00Z',
publishedAt: '2025-06-09T12:00:00Z',
maintenance: 'Maintenance details',
};

View File

@@ -101,18 +101,13 @@
line-height: 28px;
}
.changelog-media-image,
.changelog-media-video {
.changelog-media-image {
height: auto;
width: 100%;
overflow: hidden;
border-radius: 4px;
border: 1px solid var(--bg-slate-400, #1d212d);
}
.changelog-media-video {
margin: 12px 0;
}
}
.lightMode {

View File

@@ -32,7 +32,7 @@ function renderMedia(media: Media): JSX.Element | null {
controls
controlsList="nodownload noplaybackrate"
loop
className="changelog-media-video"
className="my-3 h-auto w-full rounded"
>
<source src={media.url} type={media.mime} />
<track kind="captions" src="" label="No captions available" default />
@@ -56,7 +56,7 @@ function ChangelogRenderer({ changelog }: Props): JSX.Element {
</div>
<span className="changelog-release-date">{formattedReleaseDate}</span>
{changelog.features && changelog.features.length > 0 && (
<div className="changelog-renderer-list">
<div className="changelog-renderer-list flex flex-col gap-7">
{changelog.features.map((feature) => (
<div key={feature.id}>
<h2>{feature.title}</h2>

View File

@@ -18,7 +18,7 @@ function ErrorContent({ error }: ErrorContentProps): JSX.Element {
errors: errorMessages,
code: errorCode,
message: errorMessage,
} = error?.error?.error || {};
} = error.error.error;
return (
<section className="error-content">
{/* Summary Header */}

View File

@@ -43,13 +43,13 @@ export const omitIdFromQuery = (query: Query | null): any => ({
builder: {
...query?.builder,
queryData: query?.builder.queryData.map((queryData) => {
const { id, ...rest } = queryData.aggregateAttribute || {};
const { id, ...rest } = queryData.aggregateAttribute;
const newAggregateAttribute = rest;
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
const { id, ...rest } = groupByAttribute;
return rest;
});
const newItems = queryData.filters?.items?.map((item) => {
const newItems = queryData.filters.items.map((item) => {
const { id, ...newItem } = item;
if (item.key) {
const { id, ...rest } = item.key;

View File

@@ -74,16 +74,16 @@ function HostMetricTraces({
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items:
tracesFilters?.items?.filter((item) => item.key?.key !== 'host.name') ||
[],
items: tracesFilters.items.filter(
(item) => item.key?.key !== 'host.name',
),
op: 'AND',
},
},
],
},
}),
[currentQuery, tracesFilters?.items],
[currentQuery, tracesFilters.items],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
@@ -140,8 +140,7 @@ function HostMetricTraces({
const isDataEmpty =
!isLoading && !isFetching && !isError && traces.length === 0;
const hasAdditionalFilters =
tracesFilters?.items && tracesFilters?.items?.length > 1;
const hasAdditionalFilters = tracesFilters.items.length > 1;
const totalCount =
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
@@ -159,7 +158,7 @@ function HostMetricTraces({
<div className="filter-section">
{query && (
<QueryBuilderSearch
query={query as IBuilderQuery}
query={query}
onChange={(value): void =>
handleChangeTracesFilters(value, VIEWS.TRACES)
}
@@ -195,7 +194,7 @@ function HostMetricTraces({
{!isError && traces.length > 0 && (
<div className="host-metric-traces-table">
<TraceExplorerControls
isLoading={isFetching && traces.length === 0}
isLoading={isFetching}
totalCount={totalCount}
perPageOptions={PER_PAGE_OPTIONS}
showSizeChanger={false}
@@ -204,7 +203,7 @@ function HostMetricTraces({
tableLayout="fixed"
pagination={false}
scroll={{ x: true }}
loading={isFetching && traces.length === 0}
loading={isFetching}
dataSource={traces}
columns={traceListColumns}
onRow={(): Record<string, unknown> => ({

View File

@@ -37,7 +37,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -86,12 +86,8 @@ function HostMetricsDetails({
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
selectedTime as Time,
);
const [selectedView, setSelectedView] = useState<VIEWS>(
@@ -154,11 +150,10 @@ function HostMetricsDetails({
}, [initialFilters]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
setSelectedInterval(currentSelectedInterval as Time);
setSelectedInterval(selectedTime as Time);
if (currentSelectedInterval !== 'custom') {
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
if (selectedTime !== 'custom') {
const { maxTime, minTime } = GetMinMax(selectedTime);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -186,7 +181,6 @@ function HostMetricsDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -216,17 +210,15 @@ function HostMetricsDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogFilters((prevFilters) => {
const hostNameFilter = prevFilters?.items?.find(
const hostNameFilter = prevFilters.items.find(
(item) => item.key?.key === 'host.name',
);
const paginationFilter = value?.items?.find(
(item) => item.key?.key === 'id',
);
const newFilters = value?.items?.filter(
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
const newFilters = value.items.filter(
(item) => item.key?.key !== 'id' && item.key?.key !== 'host.name',
);
if (newFilters && newFilters?.length > 0) {
if (newFilters.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.HostEntity,
view: InfraMonitoringEvents.LogsView,
@@ -238,7 +230,7 @@ function HostMetricsDetails({
op: 'AND',
items: [
hostNameFilter,
...(newFilters || []),
...newFilters,
...(paginationFilter ? [paginationFilter] : []),
].filter((item): item is TagFilterItem => item !== undefined),
};
@@ -260,11 +252,11 @@ function HostMetricsDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setTracesFilters((prevFilters) => {
const hostNameFilter = prevFilters?.items?.find(
const hostNameFilter = prevFilters.items.find(
(item) => item.key?.key === 'host.name',
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.HostEntity,
view: InfraMonitoringEvents.TracesView,
@@ -276,7 +268,7 @@ function HostMetricsDetails({
op: 'AND',
items: [
hostNameFilter,
...(value?.items?.filter((item) => item.key?.key !== 'host.name') || []),
...value.items.filter((item) => item.key?.key !== 'host.name'),
].filter((item): item is TagFilterItem => item !== undefined),
};
@@ -313,7 +305,7 @@ function HostMetricsDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logFilters,
items: logFilters?.items?.filter((item) => item.key?.key !== 'id') || [],
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -364,7 +356,6 @@ function HostMetricsDetails({
const handleClose = (): void => {
setSelectedInterval(selectedTime as Time);
lastSelectedInterval.current = null;
setSearchParams({});
if (selectedTime !== 'custom') {

View File

@@ -52,16 +52,14 @@ function HostMetricLogsDetailedView({
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items:
logFilters?.items?.filter((item) => item.key?.key !== 'host.name') ||
[],
items: logFilters.items.filter((item) => item.key?.key !== 'host.name'),
op: 'AND',
},
},
],
},
}),
[currentQuery, logFilters?.items],
[currentQuery, logFilters.items],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
@@ -72,7 +70,7 @@ function HostMetricLogsDetailedView({
<div className="filter-section">
{query && (
<QueryBuilderSearch
query={query as IBuilderQuery}
query={query}
onChange={(value): void => handleChangeLogFilters(value, VIEWS.LOGS)}
disableNavigationShortcuts
/>

View File

@@ -13,15 +13,13 @@ import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { QueryFunctionContext, useQueries, UseQueryResult } from 'react-query';
import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
@@ -55,11 +53,6 @@ function Metrics({
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const {
visibilities,
setElement,
} = useMultiIntersectionObserver(hostWidgetInfo.length, { threshold: 0.1 });
const queryPayloads = useMemo(
() =>
getHostQueryPayload(
@@ -72,22 +65,17 @@ function Metrics({
);
const queries = useQueries(
queryPayloads.map((payload, index) => ({
queryPayloads.map((payload) => ({
queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'],
queryFn: ({
signal,
}: QueryFunctionContext): Promise<
SuccessResponse<MetricRangePayloadProps>
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, signal),
enabled: !!payload && visibilities[index],
keepPreviousData: true,
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
enabled: !!payload,
})),
);
const isDarkMode = useIsDarkMode();
const graphRef = useRef<HTMLDivElement>(null);
const dimensions = useResizeObserver(graphRef);
const { currentQuery } = useQueryBuilder();
const chartData = useMemo(
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
@@ -146,24 +134,16 @@ function Metrics({
minTimeScale: graphTimeIntervals[idx].start,
maxTimeScale: graphTimeIntervals[idx].end,
onDragSelect: (start, end) => onDragSelect(start, end, idx),
query: currentQuery,
}),
),
[
queries,
isDarkMode,
dimensions,
graphTimeIntervals,
onDragSelect,
currentQuery,
],
[queries, isDarkMode, dimensions, graphTimeIntervals, onDragSelect],
);
const renderCardContent = (
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
idx: number,
): JSX.Element => {
if ((!query.data && query.isLoading) || !visibilities[idx]) {
if (query.isLoading) {
return <Skeleton />;
}
@@ -201,7 +181,7 @@ function Metrics({
</div>
<Row gutter={24} className="host-metrics-container">
{queries.map((query, idx) => (
<Col ref={setElement(idx)} span={12} key={hostWidgetInfo[idx].title}>
<Col span={12} key={hostWidgetInfo[idx].title}>
<Typography.Text>{hostWidgetInfo[idx].title}</Typography.Text>
<Card bordered className="host-metrics-card" ref={graphRef}>
{renderCardContent(query, idx)}

View File

@@ -71,7 +71,7 @@ function LogDetail({
const [contextQuery, setContextQuery] = useState<Query | undefined>();
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
const { stagedQuery } = useQueryBuilder();
const { initialDataSource, stagedQuery } = useQueryBuilder();
const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
@@ -81,7 +81,7 @@ function LogDetail({
const { options } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: DataSource.LOGS,
dataSource: initialDataSource || DataSource.LOGS,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});

View File

@@ -1,68 +0,0 @@
import { Select } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
interface ListViewOrderByProps {
value: string;
onChange: (value: string) => void;
dataSource: DataSource;
}
function ListViewOrderBy({
value,
onChange,
dataSource,
}: ListViewOrderByProps): JSX.Element {
const [searchText, setSearchText] = useState('');
const { data } = useQuery(
['orderByKeySuggestions', dataSource, searchText],
async () => {
const response = await getKeySuggestions({
signal: dataSource,
searchText,
});
return response.data;
},
);
const options = useMemo(() => {
const keys: QueryKeyDataSuggestionsProps[] = data?.data.keys
? Object.values(data.data.keys).flat()
: [];
let displayKeys: string[];
if (searchText) {
displayKeys = [...new Set(keys.map((k) => k.name))];
} else {
displayKeys = [
'timestamp',
...keys.map((k) => k.name).filter((k) => k !== 'timestamp'),
];
}
return displayKeys.flatMap((key) => [
{ label: `${key} (desc)`, value: `${key}:desc` },
{ label: `${key} (asc)`, value: `${key}:asc` },
]);
}, [data, searchText]);
return (
<Select
showSearch
value={value}
onChange={onChange}
onSearch={setSearchText}
placeholder="Select an attribute"
style={{ width: 200 }}
options={options}
filterOption={false}
/>
);
}
export default ListViewOrderBy;

View File

@@ -185,7 +185,7 @@
.qb-formula {
.ant-row {
row-gap: 8px !important;
row-gap: 0px !important;
}
.qb-entity-options {

View File

@@ -149,7 +149,6 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
formula={formula}
index={index}
isAdditionalFilterEnable={false}
isQBV2
/>
</div>
);

View File

@@ -8,24 +8,19 @@
display: flex;
flex-direction: column;
gap: 12px;
}
.non-histogram-container {
display: flex;
flex-direction: column;
gap: 16px;
}
&:not(.is-histogram) {
.metrics-time-aggregation-section,
.metrics-space-aggregation-section {
.metrics-time-aggregation-section-title {
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
.metrics-aggregation-section-content {
flex-wrap: nowrap;
}
color: var(--Slate-50, #62687c);
font-family: 'Geist Mono';
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 150% */
letter-spacing: 0.48px;
}
}
@@ -55,10 +50,6 @@
flex-wrap: wrap;
gap: 8px;
.group-by-filter-container {
min-width: 340px !important;
}
.metrics-aggregation-section-content-item {
display: flex;
align-items: center;
@@ -72,23 +63,10 @@
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
&.main-label {
color: var(--Slate-50, #62687c);
font-family: 'Geist Mono';
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 150% */
letter-spacing: 0.48px;
display: flex;
align-items: center;
gap: 6px;
}
}
.metrics-aggregation-section-content-item-value {
min-width: 140px;
min-width: 320px;
.ant-select {
width: 100%;
@@ -99,34 +77,6 @@
border: 1.005px solid var(--Slate-400, #1d212d);
background: var(--Ink-300, #16181d);
}
.input-with-label {
.label {
min-width: 80px;
}
.input {
flex: initial;
width: 100px !important;
}
}
}
}
}
&.is-histogram {
.group-by-filter-container {
width: 420px;
}
.histogram-every-input {
.input {
flex: initial;
width: 100px !important;
}
.label {
min-width: 80px;
}
}
}
@@ -145,31 +95,3 @@
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.lightMode {
.metrics-aggregate-section {
.metrics-aggregation-section-content {
.metrics-aggregation-section-content-item {
.metrics-aggregation-section-content-item-label {
color: var(--text-ink-200);
&.main-label {
color: var(--text-slate-100);
}
}
.metrics-aggregation-section-content-item-value {
.ant-select-selector {
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
}
}
}
}
}
.metrics-operators-select {
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
color: var(--text-ink-100);
}
}

View File

@@ -1,7 +1,6 @@
import './MetricsAggregateSection.styles.scss';
import { Tooltip } from 'antd';
import cx from 'classnames';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
import { ATTRIBUTE_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import SpaceAggregationOptions from 'container/QueryBuilder/components/SpaceAggregationOptions/SpaceAggregationOptions';
@@ -10,7 +9,6 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
import { Info } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { MetricAggregation } from 'types/api/v5/queryRange';
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
@@ -38,27 +36,16 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
entityVersion: version,
});
// this function is only relevant for metrics and now operators are part of aggregations
const queryAggregation = useMemo(
() => query.aggregations?.[0] as MetricAggregation,
[query.aggregations],
);
const isHistogram = useMemo(
() => query.aggregateAttribute?.type === ATTRIBUTE_TYPES.HISTOGRAM,
[query.aggregateAttribute?.type],
);
useEffect(() => {
setAggregationOptions([
{
func: queryAggregation.spaceAggregation || 'count',
arg: queryAggregation.metricName || '',
func: query.spaceAggregation || 'count',
arg: query.aggregateAttribute.key || '',
},
]);
}, [
queryAggregation.spaceAggregation,
queryAggregation.metricName,
query.spaceAggregation,
query.aggregateAttribute.key,
setAggregationOptions,
query,
]);
@@ -87,145 +74,93 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
}, [panelType]);
const disableOperatorSelector =
!queryAggregation.metricName || queryAggregation.metricName === '';
!query?.aggregateAttribute.key || query?.aggregateAttribute.key === '';
return (
<div
className={cx('metrics-aggregate-section', {
'is-histogram': isHistogram,
})}
>
{!isHistogram && (
<div className="non-histogram-container">
<div className="metrics-time-aggregation-section">
<div className="metrics-aggregation-section-content">
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label main-label">
AGGREGATE BY TIME{' '}
<Tooltip title="AGGREGATE BY TIME">
<Info size={12} />
</Tooltip>
</div>
<div className="metrics-aggregation-section-content-item-value">
<OperatorsSelect
value={queryAggregation.timeAggregation || ''}
onChange={handleChangeOperator}
operators={operators}
className="metrics-operators-select"
/>
</div>
</div>
{showAggregationInterval && (
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">
every
</div>
<div className="metrics-aggregation-section-content-item-value">
<InputWithLabel
onChange={handleChangeAggregateEvery}
label="Seconds"
placeholder="Auto"
labelAfter
initialValue={query?.stepInterval ?? undefined}
/>
</div>
</div>
)}
</div>
</div>
<div className="metrics-space-aggregation-section">
<div className="metrics-aggregation-section-content">
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label main-label">
AGGREGATE LABELS
<Tooltip title="AGGREGATE LABELS">
<Info size={12} />
</Tooltip>
</div>
<div className="metrics-aggregation-section-content-item-value">
<SpaceAggregationOptions
panelType={panelType}
key={`${panelType}${queryAggregation.spaceAggregation}${queryAggregation.timeAggregation}`}
aggregatorAttributeType={
query?.aggregateAttribute?.type as ATTRIBUTE_TYPES
}
selectedValue={queryAggregation.spaceAggregation || ''}
disabled={disableOperatorSelector}
onSelect={handleSpaceAggregationChange}
operators={spaceAggregationOptions}
qbVersion="v3"
/>
</div>
</div>
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">by</div>
<div className="metrics-aggregation-section-content-item-value group-by-filter-container">
<GroupByFilter
disabled={!queryAggregation.metricName}
query={query}
onChange={handleChangeGroupByKeys}
/>
</div>
</div>
</div>
</div>
<div className="metrics-aggregate-section">
<div className="metrics-time-aggregation-section">
<div className="metrics-time-aggregation-section-title">
AGGREGATE BY TIME{' '}
<Tooltip title="AGGREGATE BY TIME">
<Info size={12} />
</Tooltip>
</div>
)}
{isHistogram && (
<div className="metrics-space-aggregation-section">
<div className="metrics-aggregation-section-content">
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-value">
<SpaceAggregationOptions
panelType={panelType}
key={`${panelType}${queryAggregation.spaceAggregation}${queryAggregation.timeAggregation}`}
aggregatorAttributeType={
query?.aggregateAttribute?.type as ATTRIBUTE_TYPES
}
selectedValue={queryAggregation.spaceAggregation || ''}
disabled={disableOperatorSelector}
onSelect={handleSpaceAggregationChange}
operators={spaceAggregationOptions}
qbVersion="v3"
/>
</div>
<div className="metrics-aggregation-section-content">
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">
Align with
</div>
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">by</div>
<div className="metrics-aggregation-section-content-item-value group-by-filter-container">
<GroupByFilter
disabled={!queryAggregation.metricName}
query={query}
onChange={handleChangeGroupByKeys}
/>
</div>
<div className="metrics-aggregation-section-content-item-value">
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
className="metrics-operators-select"
/>
</div>
</div>
{showAggregationInterval && (
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">
every
aggregated every
</div>
<div className="metrics-aggregation-section-content-item-value">
<InputWithLabel
onChange={handleChangeAggregateEvery}
label="Seconds"
placeholder="Auto"
placeholder="Enter a number"
labelAfter
initialValue={query?.stepInterval ?? undefined}
className="histogram-every-input"
/>
</div>
</div>
)}
</div>
</div>
<div className="metrics-space-aggregation-section">
<div className="metrics-space-aggregation-section-title">
AGGREGATE LABELS
<Tooltip title="AGGREGATE LABELS">
<Info size={12} />
</Tooltip>
</div>
<div className="metrics-aggregation-section-content">
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-value space-aggregation-select">
<SpaceAggregationOptions
panelType={panelType}
key={`${panelType}${query.spaceAggregation}${query.timeAggregation}`}
aggregatorAttributeType={
query?.aggregateAttribute.type as ATTRIBUTE_TYPES
}
selectedValue={query.spaceAggregation}
disabled={disableOperatorSelector}
onSelect={handleSpaceAggregationChange}
operators={spaceAggregationOptions}
qbVersion="v3"
/>
</div>
</div>
<div className="metrics-aggregation-section-content-item">
<div className="metrics-aggregation-section-content-item-label">by</div>
<div className="metrics-aggregation-section-content-item-value">
<GroupByFilter
disabled={!query.aggregateAttribute.key}
query={query}
onChange={handleChangeGroupByKeys}
/>
</div>
</div>
</div>
)}
</div>
</div>
);
});

View File

@@ -40,35 +40,3 @@
}
}
}
.lightMode {
.metrics-select-container {
.ant-select-selector {
border: 1px solid var(--bg-slate-300) !important;
background: var(--bg-vanilla-100);
color: var(--text-ink-100);
}
.ant-select-dropdown {
background: var(--bg-vanilla-100);
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
backdrop-filter: none;
.ant-select-item {
color: var(--text-ink-100);
&:hover,
&.ant-select-item-option-active {
background: var(--bg-vanilla-300) !important;
}
&.ant-select-item-option-selected {
background: var(--bg-vanilla-300) !important;
border: 1px solid var(--bg-slate-400);
font-weight: 600;
}
}
}
}
}

View File

@@ -6,14 +6,12 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
import { OrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter';
import { ReduceToFilter } from 'container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { isEmpty } from 'lodash-es';
import { BarChart2, ScrollText, X } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { MetricAggregation } from 'types/api/v5/queryRange';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataSource } from 'types/common/queryBuilder';
import HavingFilter from './HavingFilter/HavingFilter';
@@ -90,8 +88,6 @@ function QueryAddOns({
entityVersion: '',
});
const { handleSetQueryData } = useQueryBuilder();
useEffect(() => {
if (isListViewPanel) {
setAddOns([]);
@@ -160,19 +156,11 @@ function QueryAddOns({
[handleChangeQueryData],
);
const handleChangeReduceToV5 = useCallback(
(value: ReduceOperators) => {
handleSetQueryData(index, {
...query,
aggregations: [
{
...(query.aggregations?.[0] as MetricAggregation),
reduceTo: value,
},
],
});
const handleChangeReduceTo = useCallback(
(value: IBuilderQuery['reduceTo']) => {
handleChangeQueryData('reduceTo', value);
},
[handleSetQueryData, index, query],
[handleChangeQueryData],
);
const handleRemoveView = useCallback(
@@ -217,7 +205,7 @@ function QueryAddOns({
<GroupByFilter
disabled={
query.dataSource === DataSource.METRICS &&
!(query.aggregations?.[0] as MetricAggregation)?.metricName
!query.aggregateAttribute.key
}
query={query}
onChange={handleChangeGroupByKeys}
@@ -291,7 +279,7 @@ function QueryAddOns({
<div className="periscope-input-with-label">
<div className="label">Reduce to</div>
<div className="input">
<ReduceToFilter query={query} onChange={handleChangeReduceToV5} />
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
</div>
<Button

View File

@@ -1,5 +1,6 @@
.query-aggregation-container {
display: block;
position: relative;
.aggregation-container {
display: flex;
@@ -9,25 +10,55 @@
flex-wrap: wrap;
.query-aggregation-select-container {
flex: 1;
min-width: 400px;
}
.query-aggregation-options-input {
width: 100%;
height: 36px;
line-height: 36px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
font-family: 'Space Mono', monospace !important;
&::placeholder {
color: var(--bg-vanilla-100);
opacity: 0.5;
}
}
.query-aggregation-interval {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
max-width: 360px;
.query-aggregation-interval-input-container {
.query-aggregation-interval-input {
input {
max-width: 120px;
}
}
}
}
.query-aggregation-select-container {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
min-width: 400px;
position: relative;
.query-aggregation-select-editor {
border-radius: 2px;
flex: 1;
min-width: 0;
&.error {
.cm-editor {
.cm-content {
border-color: var(--bg-cherry-500) !important;
}
}
}
.cm-content {
padding: 0;
}
@@ -169,30 +200,6 @@
}
}
.query-aggregation-error-container {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
.query-aggregation-error-content {
padding: 8px;
max-width: 300px;
.query-aggregation-error-message {
color: var(--bg-cherry-500);
font-size: 12px;
line-height: 16px;
}
}
.query-aggregation-error-btn {
padding: 4px;
height: auto;
min-width: auto;
}
}
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--bg-slate-400);
@@ -205,38 +212,6 @@
border-bottom-left-radius: 0px;
}
}
.query-aggregation-options-input {
width: 100%;
height: 36px;
line-height: 36px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
font-family: 'Space Mono', monospace !important;
&::placeholder {
color: var(--bg-vanilla-100);
opacity: 0.5;
}
}
.query-aggregation-interval {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
max-width: 360px;
.query-aggregation-interval-input-container {
.query-aggregation-interval-input {
input {
max-width: 120px;
}
}
}
}
}
}
@@ -275,13 +250,11 @@
&:hover {
background-color: var(--bg-vanilla-300) !important;
color: var(--bg-ink-500) !important;
font-weight: 600;
}
&[aria-selected='true'] {
background: var(--bg-vanilla-300) !important;
color: var(--bg-ink-500) !important;
font-weight: 600;
}
}
}
@@ -325,12 +298,3 @@
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--bg-slate-500);
border: 1px solid var(--bg-slate-400);
border-radius: 4px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
}

View File

@@ -41,15 +41,7 @@ function QueryAggregationOptions({
return (
<div className="query-aggregation-container">
<div className="aggregation-container">
<QueryAggregationSelect
onChange={onChange}
queryData={queryData}
maxAggregations={
panelType === PANEL_TYPES.VALUE || panelType === PANEL_TYPES.PIE
? 1
: undefined
}
/>
<QueryAggregationSelect onChange={onChange} queryData={queryData} />
{showAggregationInterval && (
<div className="query-aggregation-interval">
@@ -57,7 +49,7 @@ function QueryAggregationOptions({
<div className="query-aggregation-interval-input-container">
<InputWithLabel
initialValue={
queryData?.stepInterval ? queryData?.stepInterval : undefined
queryData.stepInterval ? queryData.stepInterval : undefined
}
className="query-aggregation-interval-input"
label="Seconds"

View File

@@ -16,8 +16,7 @@ import {
startCompletion,
} from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript';
import { EditorState, RangeSetBuilder, Transaction } from '@codemirror/state';
import { Color } from '@signozhq/design-tokens';
import { RangeSetBuilder } from '@codemirror/state';
import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, {
Decoration,
@@ -26,14 +25,12 @@ import CodeMirror, {
ViewPlugin,
ViewUpdate,
} from '@uiw/react-codemirror';
import { Button, Popover } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
import { QueryBuilderKeys } from 'constants/queryBuilder';
import { tracesAggregateOperatorOptions } from 'constants/queryBuilderOperators';
import { TriangleAlert } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { TracesAggregatorOperator } from 'types/common/queryBuilder';
@@ -143,11 +140,9 @@ const stopEventsExtension = EditorView.domEventHandlers({
function QueryAggregationSelect({
onChange,
queryData,
maxAggregations,
}: {
onChange?: (value: string) => void;
queryData: IBuilderQuery;
maxAggregations?: number;
}): JSX.Element {
const { setAggregationOptions } = useQueryBuilderV2Context();
@@ -165,16 +160,9 @@ function QueryAggregationSelect({
const [functionArgPairs, setFunctionArgPairs] = useState<
{ func: string; arg: string }[]
>([]);
const [validationError, setValidationError] = useState<string | null>(null);
const editorRef = useRef<EditorView | null>(null);
const [isFocused, setIsFocused] = useState(false);
// Get valid function names (lowercase)
const validFunctions = useMemo(
() => tracesAggregateOperatorOptions.map((op) => op.value.toLowerCase()),
[],
);
// Helper function to safely start completion
const safeStartCompletion = useCallback((): void => {
requestAnimationFrame(() => {
@@ -218,76 +206,10 @@ function QueryAggregationSelect({
});
}
}
// Validation logic
const validateAggregations = (): string | null => {
// Check maxAggregations limit
if (maxAggregations !== undefined && pairs.length > maxAggregations) {
return `Maximum ${maxAggregations} aggregation${
maxAggregations === 1 ? '' : 's'
} allowed`;
}
// Check for invalid functions
const invalidFuncs = pairs.filter(
(pair) => !validFunctions.includes(pair.func),
);
if (invalidFuncs.length > 0) {
const funcs = invalidFuncs.map((f) => f.func).join(', ');
return `Invalid function${invalidFuncs.length === 1 ? '' : 's'}: ${funcs}`;
}
// Check for incomplete function calls
if (/([a-zA-Z_][\w]*)\s*\([^)]*$/g.test(input)) {
return 'Incomplete function call - missing closing parenthesis';
}
// Check for empty function calls that require arguments
const emptyFuncs = (input.match(/([a-zA-Z_][\w]*)\s*\(\s*\)/g) || [])
.map((call) => call.match(/([a-zA-Z_][\w]*)/)?.[1])
.filter((func): func is string => Boolean(func))
.filter((func) => operatorArgMeta[func.toLowerCase()]?.acceptsArgs);
if (emptyFuncs.length > 0) {
const isPlural = emptyFuncs.length > 1;
return `Function${isPlural ? 's' : ''} ${emptyFuncs.join(', ')} require${
isPlural ? '' : 's'
} arguments`;
}
return null;
};
setValidationError(validateAggregations());
setFunctionArgPairs(pairs);
setAggregationOptions(pairs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [input, maxAggregations, validFunctions]);
// Transaction filter to limit aggregations
const transactionFilterExtension = useMemo(() => {
if (maxAggregations === undefined) return [];
return EditorState.transactionFilter.of((tr: Transaction) => {
if (!tr.docChanged) return tr;
const regex = /([a-zA-Z_][\w]*)\s*\(([^)]*)\)/g;
const oldMatches = [
...tr.startState.doc.toString().matchAll(regex),
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
const newMatches = [
...tr.newDoc.toString().matchAll(regex),
].filter((match) => validFunctions.includes(match[1].toLowerCase()));
if (
newMatches.length > oldMatches.length &&
newMatches.length > maxAggregations
) {
return []; // Cancel transaction
}
return tr;
});
}, [maxAggregations, validFunctions]);
}, [input]);
// Find function context for fetching suggestions
const functionContextForFetch = getFunctionContextAtCursor(input, cursorPos);
@@ -298,25 +220,12 @@ function QueryAggregationSelect({
functionContextForFetch,
queryData.dataSource,
],
() => {
const operatorsWithoutDataType: (string | undefined)[] = [
TracesAggregatorOperator.COUNT,
TracesAggregatorOperator.COUNT_DISTINCT,
TracesAggregatorOperator.RATE,
];
const fieldDataType =
functionContextForFetch &&
operatorsWithoutDataType.includes(functionContextForFetch)
? undefined
: QUERY_BUILDER_KEY_TYPES.NUMBER;
return getKeySuggestions({
signal: queryData.dataSource,
() =>
getAggregateAttribute({
searchText: '',
fieldDataType,
});
},
aggregateOperator: functionContextForFetch as string,
dataSource: queryData.dataSource,
}),
{
enabled:
!!functionContextForFetch &&
@@ -324,6 +233,12 @@ function QueryAggregationSelect({
},
);
// Get valid function names (lowercase)
const validFunctions = useMemo(
() => tracesAggregateOperatorOptions.map((op) => op.value.toLowerCase()),
[],
);
// Memoized chipPlugin that highlights valid function calls like count(), max(arg), min(arg)
const chipPlugin = useMemo(
() =>
@@ -412,14 +327,11 @@ function QueryAggregationSelect({
// Memoize field suggestions from API (no filtering here)
const fieldSuggestions = useMemo(
() =>
Object.keys(aggregateAttributeData?.data.data.keys || {}).flatMap((key) => {
const attributeKeys = aggregateAttributeData?.data.data.keys[key];
if (!attributeKeys) return [];
return attributeKeys.map((attributeKey) => ({
label: attributeKey.name,
aggregateAttributeData?.payload?.attributeKeys?.map(
(attributeKey: BaseAutocompleteData) => ({
label: attributeKey.key,
type: 'variable',
info: attributeKey.fieldDataType,
info: attributeKey.dataType,
apply: (
view: EditorView,
completion: Completion,
@@ -446,8 +358,8 @@ function QueryAggregationSelect({
safeStartCompletion();
}, 50);
},
}));
}) || [],
}),
) || [],
[aggregateAttributeData, safeStartCompletion],
);
@@ -460,21 +372,6 @@ function QueryAggregationSelect({
const cursorPos = context.pos;
const funcName = getFunctionContextAtCursor(text, cursorPos);
// Check if over limit and not editing existing
if (maxAggregations !== undefined) {
const regex = /([a-zA-Z_][\w]*)\s*\(([^)]*)\)/g;
const matches = [...text.matchAll(regex)].filter((match) =>
validFunctions.includes(match[1].toLowerCase()),
);
if (matches.length >= maxAggregations) {
const isEditing = matches.some((match) => {
const start = match.index ?? 0;
return cursorPos >= start && cursorPos <= start + match[0].length;
});
if (!isEditing) return null;
}
}
// Do not show suggestions if inside count()
if (
funcName === TracesAggregatorOperator.COUNT &&
@@ -573,14 +470,7 @@ function QueryAggregationSelect({
maxRenderedOptions: 50,
activateOnTyping: true,
}),
[
operatorCompletions,
isLoadingFields,
fieldSuggestions,
functionArgPairs,
maxAggregations,
validFunctions,
],
[operatorCompletions, isLoadingFields, fieldSuggestions, functionArgPairs],
);
return (
@@ -591,14 +481,11 @@ function QueryAggregationSelect({
setInput(value);
onChange?.(value);
}}
className={`query-aggregation-select-editor ${
validationError ? 'error' : ''
}`}
className="query-aggregation-select-editor"
theme={copilot}
extensions={[
chipPlugin,
aggregatorAutocomplete,
transactionFilterExtension,
javascript({ jsx: false, typescript: false }),
EditorView.lineWrapping,
stopEventsExtension,
@@ -610,11 +497,7 @@ function QueryAggregationSelect({
},
]),
]}
placeholder={
maxAggregations !== undefined
? `Type aggregator functions (max ${maxAggregations}) like sum(), count_distinct(...), etc.`
: 'Type aggregator functions like sum(), count_distinct(...), etc.'
}
placeholder="Type aggregator functions like sum(), count_distinct(...), etc."
basicSetup={{
lineNumbers: false,
autocompletion: true,
@@ -636,33 +519,12 @@ function QueryAggregationSelect({
}
}}
/>
{validationError && (
<div className="query-aggregation-error-container">
<Popover
placement="bottomRight"
showArrow={false}
content={
<div className="query-aggregation-error-content">
<div className="query-aggregation-error-message">{validationError}</div>
</div>
}
overlayClassName="query-aggregation-error-popover"
>
<Button
type="text"
icon={<TriangleAlert size={14} color={Color.BG_CHERRY_500} />}
className="periscope-btn ghost query-aggregation-error-btn"
/>
</Popover>
</div>
)}
</div>
);
}
QueryAggregationSelect.defaultProps = {
onChange: undefined,
maxAggregations: undefined,
};
export default QueryAggregationSelect;

View File

@@ -579,10 +579,8 @@
background-color: var(--bg-vanilla-100) !important;
color: var(--bg-ink-300) !important;
&:hover,
&[aria-selected='true'] {
background-color: var(--bg-vanilla-300) !important;
font-weight: 600;
&:hover {
background-color: var(--bg-vanilla-200) !important;
}
}
}

View File

@@ -1,5 +1,3 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/cognitive-complexity */
import './QuerySearch.styles.scss';
import { CheckCircleFilled } from '@ant-design/icons';
@@ -14,19 +12,11 @@ import {
import { javascript } from '@codemirror/lang-javascript';
import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import CodeMirror, { EditorView, keymap } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tag } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
import {
negationQueryOperatorSuggestions,
QUERY_BUILDER_KEY_TYPES,
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE,
queryOperatorSuggestions,
} from 'constants/antlrQueryConstants';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { isNull } from 'lodash-es';
import { TriangleAlert } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
@@ -37,13 +27,18 @@ import {
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
import { validateQuery } from 'utils/antlrQueryUtils';
import {
getCurrentValueIndexAtCursor,
negationQueryOperatorSuggestions,
queryOperatorSuggestions,
validateQuery,
} from 'utils/antlrQueryUtils';
import {
getQueryContextAtCursor,
getCurrentValueIndexAtCursor,
} from 'utils/queryContextUtils';
import { queryExamples } from './constants';
import { isNull } from 'lodash-es';
const { Panel } = Collapse;
@@ -120,24 +115,14 @@ function QuerySearch({
const [isFocused, setIsFocused] = useState(false);
const [isCompleteKeysList, setIsCompleteKeysList] = useState(false);
const [isCompleteValuesList, setIsCompleteValuesList] = useState<boolean>(
false,
);
const [
isFetchingCompleteValuesList,
setIsFetchingCompleteValuesList,
] = useState<boolean>(false);
const lastPosRef = useRef<{ line: number; ch: number }>({ line: 0, ch: 0 });
// Reference to the editor view for programmatic autocompletion
const editorRef = useRef<EditorView | null>(null);
const lastKeyRef = useRef<string>('');
const lastValueRef = useRef<string>('');
const isMountedRef = useRef<boolean>(true);
const { handleRunQuery } = useQueryBuilder();
// const {
// data: queryKeySuggestions,
// refetch: refetchQueryKeySuggestions,
@@ -163,19 +148,13 @@ function QuerySearch({
const response = await getKeySuggestions({
signal: dataSource,
searchText: searchText || '',
metricName: queryData.aggregateAttribute?.key ?? undefined,
metricName: queryData.aggregateAttribute.key ?? undefined,
});
if (response.data.data) {
const { complete, keys } = response.data.data;
const options = generateOptions(keys);
// Use a Map to deduplicate by label and preserve order: new options take precedence
const merged = new Map<string, QueryKeyDataSuggestionsProps>();
options.forEach((opt) => merged.set(opt.label, opt));
(keySuggestions || []).forEach((opt) => {
if (!merged.has(opt.label)) merged.set(opt.label, opt);
});
setKeySuggestions(Array.from(merged.values()));
setKeySuggestions((prev) => [...(prev || []), ...options]);
setIsCompleteKeysList(complete);
}
};
@@ -184,7 +163,7 @@ function QuerySearch({
setKeySuggestions([]);
fetchKeySuggestions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSource, queryData.aggregateAttribute?.key]);
}, [dataSource, queryData.aggregateAttribute.key]);
// Add a state for tracking editing mode
const [editingMode, setEditingMode] = useState<
@@ -259,29 +238,17 @@ function QuerySearch({
// Use callback to prevent dependency changes on each render
const fetchValueSuggestions = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
async ({
key,
searchText,
fetchingComplete = false,
}: {
key: string;
searchText?: string;
fetchingComplete?: boolean;
}): Promise<void> => {
async (key: string): Promise<void> => {
if (
!key ||
(key === activeKey && !isLoadingSuggestions && !fetchingComplete) ||
(key === activeKey && !isLoadingSuggestions) ||
!isMountedRef.current
)
return;
// Set loading state and store the key we're fetching for
setIsLoadingSuggestions(true);
if (fetchingComplete) {
setIsFetchingCompleteValuesList(true);
}
lastKeyRef.current = key;
lastValueRef.current = searchText || '';
setActiveKey(key);
setValueSuggestions([
@@ -293,21 +260,14 @@ function QuerySearch({
},
]);
const sanitizedSearchText = searchText ? searchText?.trim() : '';
try {
const response = await getValueSuggestions({
key,
searchText: sanitizedSearchText,
signal: dataSource,
});
// Skip updates if component unmounted or key changed
if (
!isMountedRef.current ||
lastKeyRef.current !== key ||
lastValueRef.current !== sanitizedSearchText
) {
if (!isMountedRef.current || lastKeyRef.current !== key) {
return; // Skip updating if key has changed or component unmounted
}
@@ -317,10 +277,6 @@ function QuerySearch({
const stringValues = values.stringValues || [];
const numberValues = values.numberValues || [];
if (responseData.data?.complete) {
setIsCompleteValuesList(responseData.data.complete);
}
// Generate options from string values - explicitly handle empty strings
const stringOptions = stringValues
// Strict filtering for empty string - we'll handle it as a special case if needed
@@ -331,7 +287,6 @@ function QuerySearch({
.map((value: string) => ({
label: value,
type: 'value',
apply: value,
}));
// Generate options from number values
@@ -343,7 +298,6 @@ function QuerySearch({
.map((value: number) => ({
label: value.toString(),
type: 'number',
apply: value,
}));
// Combine all options and make sure we don't have duplicate labels
@@ -377,6 +331,7 @@ function QuerySearch({
}
}, 10);
}
setIsLoadingSuggestions(false);
}
} catch (error) {
console.error('Error fetching suggestions:', error);
@@ -389,10 +344,8 @@ function QuerySearch({
apply: (): boolean => false, // Prevent selection
},
]);
setIsLoadingSuggestions(false);
}
} finally {
setIsLoadingSuggestions(false);
setIsFetchingCompleteValuesList(false);
}
},
[activeKey, dataSource, isLoadingSuggestions],
@@ -522,8 +475,7 @@ function QuerySearch({
// Enhanced myCompletions function to better use context including query pairs
// eslint-disable-next-line sonarjs/cognitive-complexity
function autoSuggestions(context: CompletionContext): CompletionResult | null {
// This matches words before the cursor position
const word = context.matchBefore(/[a-zA-Z0-9_.:/?&=#%\-[\]]*/);
const word = context.matchBefore(/[.\w]*/);
if (word?.from === word?.to && !context.explicit) return null;
// Get the query context at the cursor position
@@ -579,8 +531,6 @@ function QuerySearch({
// Changes to replace the value in-place with the existing value
const isValueType = queryContext.isInValue && option.type === 'value';
const isOperatorType =
queryContext.isInOperator && option.type === 'operator';
const pair = queryContext.currentPair;
if (isValueType) {
@@ -591,12 +541,7 @@ function QuerySearch({
);
if (!isNull(idx)) {
const { start, end } = pair.valuesPosition[idx];
if (
typeof start === 'number' &&
typeof end === 'number' &&
cursorPos.ch >= start &&
cursorPos.ch <= end + 1
) {
if (typeof start === 'number' && typeof end === 'number') {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
@@ -609,12 +554,7 @@ function QuerySearch({
}
} else if (pair?.position) {
const { valueStart, valueEnd } = pair.position;
if (
typeof valueStart === 'number' &&
typeof valueEnd === 'number' &&
cursorPos.ch >= valueStart &&
cursorPos.ch <= valueEnd + 1
) {
if (typeof valueStart === 'number' && typeof valueEnd === 'number') {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
@@ -627,28 +567,6 @@ function QuerySearch({
}
}
// Changes to replace the operator in-place with the existing operator
if (isOperatorType && pair?.position) {
const { operatorStart, operatorEnd } = pair.position;
if (
typeof operatorStart === 'number' &&
typeof operatorEnd === 'number' &&
operatorStart !== 0 &&
operatorEnd !== 0 &&
cursorPos.ch >= operatorStart &&
cursorPos.ch <= operatorEnd + 1
) {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
{ apply: originalApply },
operatorStart,
operatorEnd + 1,
false,
);
}
}
if (shouldDefaultApply) {
addSpaceAfterSelection(view, { apply: originalApply }, from, to);
}
@@ -666,36 +584,21 @@ function QuerySearch({
return null;
}
const searchText = word?.text.toLowerCase().trim() ?? '';
options = (valueSuggestions || []).filter((option) =>
option.label.toLowerCase().includes(searchText),
);
if (
keyName &&
((options.length === 0 &&
(!isCompleteValuesList || lastValueRef.current !== searchText) &&
!isFetchingCompleteValuesList) ||
keyName !== activeKey ||
isLoadingSuggestions) &&
(keyName !== activeKey || isLoadingSuggestions) &&
!(isLoadingSuggestions && lastKeyRef.current === keyName)
) {
setTimeout(() => {
fetchValueSuggestions({
key: keyName,
searchText,
fetchingComplete: true,
});
}, 300);
fetchValueSuggestions(keyName);
}
// For values in bracket list, just add quotes without enclosing in brackets
const processedOptions = options.map((option) => {
const processedOptions = valueSuggestions.map((option) => {
// Clone the option to avoid modifying the original
const processedOption = { ...option };
// Skip processing for non-selectable items
if (!option.apply || typeof option.apply === 'function') {
if (option.apply === false || typeof option.apply === 'function') {
return option;
}
@@ -784,49 +687,28 @@ function QuerySearch({
// Filter operators based on key type
if (keyType) {
if (keyType === QUERY_BUILDER_KEY_TYPES.NUMBER) {
if (keyType === 'number') {
// Prioritize numeric operators
options = options
.filter((op) =>
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE[
QUERY_BUILDER_KEY_TYPES.NUMBER
].includes(op.label),
)
.map((op) => ({
...op,
boost: ['>', '<', '>=', '<=', '=', '!=', 'BETWEEN'].includes(op.label)
? 100
: 0,
}));
} else if (
keyType === QUERY_BUILDER_KEY_TYPES.STRING ||
keyType === 'keyword'
) {
options = options.map((op) => ({
...op,
boost: ['>', '<', '>=', '<=', '=', '!=', 'BETWEEN'].includes(op.label)
? 100
: 0,
}));
} else if (keyType === 'string' || keyType === 'keyword') {
// Prioritize string operators
options = options
.filter((op) =>
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE[
QUERY_BUILDER_KEY_TYPES.STRING
].includes(op.label),
)
.map((op) => ({
...op,
boost: ['=', '!=', 'LIKE', 'ILIKE', 'CONTAINS', 'IN'].includes(op.label)
? 100
: 0,
}));
} else if (keyType === QUERY_BUILDER_KEY_TYPES.BOOLEAN) {
options = options.map((op) => ({
...op,
boost: ['=', '!=', 'LIKE', 'ILIKE', 'CONTAINS', 'IN'].includes(op.label)
? 100
: 0,
}));
} else if (keyType === 'boolean') {
// Prioritize boolean operators
options = options
.filter((op) =>
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE[
QUERY_BUILDER_KEY_TYPES.BOOLEAN
].includes(op.label),
)
.map((op) => ({
...op,
boost: ['=', '!='].includes(op.label) ? 100 : 0,
}));
options = options.map((op) => ({
...op,
boost: ['=', '!='].includes(op.label) ? 100 : 0,
}));
}
}
@@ -856,38 +738,23 @@ function QuerySearch({
if (!keyName) {
return null;
}
const searchText = word?.text.toLowerCase().trim() ?? '';
options = (valueSuggestions || []).filter((option) =>
option.label.toLowerCase().includes(searchText),
);
// Trigger fetch only if needed
if (
keyName &&
((options.length === 0 &&
(!isCompleteValuesList || lastValueRef.current !== searchText) &&
!isFetchingCompleteValuesList) ||
keyName !== activeKey ||
isLoadingSuggestions) &&
(keyName !== activeKey || isLoadingSuggestions) &&
!(isLoadingSuggestions && lastKeyRef.current === keyName)
) {
setTimeout(() => {
fetchValueSuggestions({
key: keyName,
searchText,
fetchingComplete: true,
});
}, 300);
fetchValueSuggestions(keyName);
}
// Process options to add appropriate formatting when selected
const processedOptions = options.map((option) => {
const processedOptions = valueSuggestions.map((option) => {
// Clone the option to avoid modifying the original
const processedOption = { ...option };
// Skip processing for non-selectable items
if (!option.apply || typeof option.apply === 'function') {
if (option.apply === false || typeof option.apply === 'function') {
return option;
}
@@ -1077,7 +944,7 @@ function QuerySearch({
// Only fetch if needed and if we have a valid key
if (key && key !== activeKey && !isLoadingSuggestions) {
fetchValueSuggestions({ key });
fetchValueSuggestions(key);
}
}
}, [queryContext, activeKey, isLoadingSuggestions, fetchValueSuggestions]);
@@ -1143,39 +1010,13 @@ function QuerySearch({
javascript({ jsx: false, typescript: false }),
EditorView.lineWrapping,
stopEventsExtension,
Prec.highest(
keymap.of([
...completionKeymap,
{
key: 'Escape',
run: closeCompletion,
},
{
key: 'Enter',
preventDefault: true,
// Prevent default behavior of Enter to add new line
// and instead run a custom action
run: (): boolean => true,
},
{
key: 'Mod-Enter',
preventDefault: true,
// Prevent default behavior of Mod-Enter to add new line
// and instead run a custom action
// Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS
run: (): boolean => {
handleRunQuery(true, true);
return true;
},
},
{
key: 'Shift-Enter',
preventDefault: true,
// Prevent default behavior of Shift-Enter to add new line
run: (): boolean => true,
},
]),
),
keymap.of([
...completionKeymap,
{
key: 'Escape',
run: closeCompletion,
},
]),
]}
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
basicSetup={{

View File

@@ -1,6 +1,6 @@
import { Dropdown } from 'antd';
import cx from 'classnames';
import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import QBEntityOptions from 'container/QueryBuilder/components/QBEntityOptions/QBEntityOptions';
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
@@ -170,11 +170,7 @@ export const QueryV2 = memo(function QueryV2({
<div className="qb-search-container">
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect
query={query}
index={index}
version={ENTITY_VERSION_V5}
/>
<MetricsSelect query={query} index={index} version="v4" />
</div>
)}

View File

@@ -1,26 +1,11 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
import { OPERATORS } from 'constants/antlrQueryConstants';
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { cloneDeep } from 'lodash-es';
import { IQueryPair } from 'types/antlrQueryTypes';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
Having,
IBuilderQuery,
Query,
TagFilter,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import { Having, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import {
LogAggregation,
MetricAggregation,
TraceAggregation,
} from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { extractQueryPairs } from 'utils/queryContextUtils';
import { v4 as uuid } from 'uuid';
/**
* Check if an operator requires array values (like IN, NOT IN)
@@ -28,7 +13,7 @@ import { v4 as uuid } from 'uuid';
* @returns True if the operator requires array values
*/
const isArrayOperator = (operator: string): boolean => {
const arrayOperators = ['in', 'not in', 'IN', 'NOT IN'];
const arrayOperators = ['in', 'nin', 'IN', 'NOT IN'];
return arrayOperators.includes(operator);
};
@@ -95,330 +80,6 @@ export const convertFiltersToExpression = (
};
};
function unquote(str: string): string {
if (typeof str !== 'string') return str;
const startsWithQuote = str.startsWith('"') || str.startsWith("'");
const endsWithSameQuote =
(str.endsWith('"') && str[0] === '"') ||
(str.endsWith("'") && str[0] === "'");
if (startsWithQuote && endsWithSameQuote && str.length >= 2) {
return str.slice(1, -1);
}
return str;
}
const formatValuesForFilter = (value: string | string[]): string | string[] => {
if (Array.isArray(value)) {
return value.map((v) => (typeof v === 'string' ? unquote(v) : String(v)));
}
if (typeof value === 'string') {
return unquote(value);
}
return String(value);
};
export const convertFiltersToExpressionWithExistingQuery = (
filters: TagFilter,
existingQuery: string | undefined,
): { filters: TagFilter; filter: { expression: string } } => {
if (!existingQuery) {
// If no existing query, return filters with a newly generated expression
return {
filters,
filter: convertFiltersToExpression(filters),
};
}
// Extract query pairs from the existing query
const queryPairs = extractQueryPairs(existingQuery.trim());
let queryPairsMap: Map<string, IQueryPair> = new Map();
const updatedFilters = cloneDeep(filters); // Clone filters to avoid direct mutation
const nonExistingFilters: TagFilterItem[] = [];
let modifiedQuery = existingQuery; // We'll modify this query as we proceed
const visitedPairs: Set<string> = new Set(); // Set to track visited query pairs
// Map extracted query pairs to key-specific pair information for faster access
if (queryPairs.length > 0) {
queryPairsMap = new Map(
queryPairs.map((pair) => {
const key = pair.hasNegation
? `${pair.key}-not ${pair.operator}`.trim().toLowerCase()
: `${pair.key}-${pair.operator}`.trim().toLowerCase();
return [key, pair];
}),
);
}
filters?.items?.forEach((filter) => {
const { key, op, value } = filter;
// Skip invalid filters with no key
if (!key) return;
let shouldAddToNonExisting = true; // Flag to decide if the filter should be added to non-existing filters
const sanitizedOperator = op.trim().toUpperCase();
// Check if the operator is IN or NOT IN
if (
[OPERATORS.IN, `${OPERATORS.NOT} ${OPERATORS.IN}`].includes(
sanitizedOperator,
)
) {
const existingPair = queryPairsMap.get(
`${key.key}-${op}`.trim().toLowerCase(),
);
const formattedValue = formatValueForExpression(value, op);
// If a matching query pair exists, modify the query
if (
existingPair &&
existingPair.position?.valueStart &&
existingPair.position?.valueEnd
) {
visitedPairs.add(`${key.key}-${op}`.trim().toLowerCase());
modifiedQuery =
modifiedQuery.slice(0, existingPair.position.valueStart) +
formattedValue +
modifiedQuery.slice(existingPair.position.valueEnd + 1);
return;
}
// Handle the different cases for IN operator
switch (sanitizedOperator) {
case OPERATORS.IN:
// If there's a NOT IN or equal operator, merge the filter
if (
queryPairsMap.has(
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
)
) {
const notInPair = queryPairsMap.get(
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
);
visitedPairs.add(
`${key.key}-${OPERATORS.NOT} ${op}`.trim().toLowerCase(),
);
if (notInPair?.position?.valueEnd) {
modifiedQuery = `${modifiedQuery.slice(
0,
notInPair.position.negationStart,
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
notInPair.position.valueEnd + 1,
)}`;
}
shouldAddToNonExisting = false; // Don't add this to non-existing filters
} else if (
queryPairsMap.has(`${key.key}-${OPERATORS['=']}`.trim().toLowerCase())
) {
const equalsPair = queryPairsMap.get(
`${key.key}-${OPERATORS['=']}`.trim().toLowerCase(),
);
visitedPairs.add(`${key.key}-${OPERATORS['=']}`.trim().toLowerCase());
if (equalsPair?.position?.valueEnd) {
modifiedQuery = `${modifiedQuery.slice(
0,
equalsPair.position.operatorStart,
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
equalsPair.position.valueEnd + 1,
)}`;
}
shouldAddToNonExisting = false; // Don't add this to non-existing filters
} else if (
queryPairsMap.has(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase())
) {
const notEqualsPair = queryPairsMap.get(
`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase(),
);
visitedPairs.add(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase());
if (notEqualsPair?.position?.valueEnd) {
modifiedQuery = `${modifiedQuery.slice(
0,
notEqualsPair.position.operatorStart,
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
notEqualsPair.position.valueEnd + 1,
)}`;
}
shouldAddToNonExisting = false; // Don't add this to non-existing filters
}
break;
case `${OPERATORS.NOT} ${OPERATORS.IN}`:
if (
queryPairsMap.has(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase())
) {
const notEqualsPair = queryPairsMap.get(
`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase(),
);
visitedPairs.add(`${key.key}-${OPERATORS['!=']}`.trim().toLowerCase());
if (notEqualsPair?.position?.valueEnd) {
modifiedQuery = `${modifiedQuery.slice(
0,
notEqualsPair.position.operatorStart,
)}${OPERATORS.NOT} ${
OPERATORS.IN
} ${formattedValue} ${modifiedQuery.slice(
notEqualsPair.position.valueEnd + 1,
)}`;
}
shouldAddToNonExisting = false; // Don't add this to non-existing filters
}
break; // No operation needed for NOT IN case
default:
break;
}
}
if (
queryPairsMap.has(`${filter.key?.key}-${filter.op}`.trim().toLowerCase())
) {
visitedPairs.add(`${filter.key?.key}-${filter.op}`.trim().toLowerCase());
}
// Add filters that don't have an existing pair to non-existing filters
if (
shouldAddToNonExisting &&
!queryPairsMap.has(`${filter.key?.key}-${filter.op}`.trim().toLowerCase())
) {
nonExistingFilters.push(filter);
}
});
// Create new filters from non-visited query pairs
const newFilterItems: TagFilterItem[] = [];
queryPairsMap.forEach((pair, key) => {
if (!visitedPairs.has(key)) {
const operator = pair.hasNegation
? getOperatorValue(`NOT_${pair.operator}`.toUpperCase())
: getOperatorValue(pair.operator.toUpperCase());
newFilterItems.push({
id: uuid(),
op: operator,
key: {
id: pair.key,
key: pair.key,
type: '',
},
value: pair.isMultiValue
? formatValuesForFilter(pair.valueList as string[]) ?? ''
: formatValuesForFilter(pair.value as string) ?? '',
});
}
});
// Merge new filter items with existing ones
if (newFilterItems.length > 0 && updatedFilters?.items) {
updatedFilters.items = [...updatedFilters.items, ...newFilterItems];
}
// If no non-existing filters, return the modified query directly
if (nonExistingFilters.length === 0) {
return {
filters: updatedFilters,
filter: { expression: modifiedQuery },
};
}
// Convert non-existing filters to an expression and append to the modified query
const nonExistingFilterExpression = convertFiltersToExpression({
items: nonExistingFilters,
op: filters.op || 'AND',
});
if (nonExistingFilterExpression.expression) {
return {
filters: updatedFilters,
filter: {
expression: `${modifiedQuery.trim()} ${
nonExistingFilterExpression.expression
}`,
},
};
}
// Return the final result with the modified query
return {
filters: updatedFilters,
filter: { expression: modifiedQuery || '' },
};
};
/**
* Removes specified key-value pairs from a logical query expression string.
*
* This function parses the given query expression and removes any query pairs
* whose keys match those in the `keysToRemove` array. It also removes any trailing
* logical conjunctions (e.g., `AND`, `OR`) and whitespace that follow the matched pairs,
* ensuring that the resulting expression remains valid and clean.
*
* @param expression - The full query string.
* @param keysToRemove - An array of keys (case-insensitive) that should be removed from the expression.
* @returns A new expression string with the specified keys and their associated clauses removed.
*/
export const removeKeysFromExpression = (
expression: string,
keysToRemove: string[],
): string => {
if (!keysToRemove || keysToRemove.length === 0) {
return expression;
}
let updatedExpression = expression;
if (updatedExpression) {
keysToRemove.forEach((key) => {
// Extract key-value query pairs from the expression
const exisitingQueryPairs = extractQueryPairs(updatedExpression);
let queryPairsMap: Map<string, IQueryPair>;
if (exisitingQueryPairs.length > 0) {
// Build a map for quick lookup of query pairs by their lowercase trimmed keys
queryPairsMap = new Map(
exisitingQueryPairs.map((pair) => {
const key = pair.key.trim().toLowerCase();
return [key, pair];
}),
);
// Lookup the current query pair using the attribute key (case-insensitive)
const currentQueryPair = queryPairsMap.get(`${key}`.trim().toLowerCase());
if (currentQueryPair && currentQueryPair.isComplete) {
// Determine the start index of the query pair (fallback order: key → operator → value)
const queryPairStart =
currentQueryPair.position.keyStart ??
currentQueryPair.position.operatorStart ??
currentQueryPair.position.valueStart;
// Determine the end index of the query pair (fallback order: value → operator → key)
let queryPairEnd =
currentQueryPair.position.valueEnd ??
currentQueryPair.position.operatorEnd ??
currentQueryPair.position.keyEnd;
// Get the part of the expression that comes after the current query pair
const expressionAfterPair = `${expression.slice(queryPairEnd + 1)}`;
// Match optional spaces and an optional conjunction (AND/OR), case-insensitive
const conjunctionOrSpacesRegex = /^(\s*((AND|OR)\s+)?)/i;
const match = expressionAfterPair.match(conjunctionOrSpacesRegex);
if (match && match.length > 0) {
// If match is found, extend the queryPairEnd to include the matched part
queryPairEnd += match[0].length;
}
// Remove the full query pair (including any conjunction/whitespace) from the expression
updatedExpression = `${expression.slice(
0,
queryPairStart,
)}${expression.slice(queryPairEnd + 1)}`.trim();
}
}
});
}
return updatedExpression;
};
/**
* Convert old having format to new having format
* @param having - Array of old having objects with columnName, op, and value
@@ -522,135 +183,3 @@ export const convertAggregationToExpression = (
} as LogAggregation,
];
};
export const getQueryTitles = (currentQuery: Query): string[] => {
if (currentQuery.queryType === EQueryType.QUERY_BUILDER) {
const queryTitles: string[] = [];
// Handle builder queries with multiple aggregations
currentQuery.builder.queryData.forEach((q) => {
const aggregationCount = q.aggregations?.length || 1;
if (aggregationCount > 1) {
// If multiple aggregations, create titles like A.0, A.1, A.2
for (let i = 0; i < aggregationCount; i++) {
queryTitles.push(`${q.queryName}.${i}`);
}
} else {
// Single aggregation, just use query name
queryTitles.push(q.queryName);
}
});
// Handle formulas (they don't have aggregations, so just use query name)
const formulas = currentQuery.builder.queryFormulas.map((q) => q.queryName);
return [...queryTitles, ...formulas];
}
if (currentQuery.queryType === EQueryType.CLICKHOUSE) {
return currentQuery.clickhouse_sql.map((q) => q.name);
}
return currentQuery.promql.map((q) => q.name);
};
// function to give you label value for query name taking multiaggregation into account
export function getQueryLabelWithAggregation(
queryData: IBuilderQuery[],
legendMap: Record<string, string> = {},
): { label: string; value: string }[] {
const labels: { label: string; value: string }[] = [];
const aggregationPerQuery =
queryData.reduce((acc, query) => {
if (query.queryName && query.aggregations?.length) {
acc[query.queryName] = createAggregation(query).map((a: any) => ({
alias: a.alias,
expression: a.expression,
}));
}
return acc;
}, {} as Record<string, any>) || {};
Object.entries(aggregationPerQuery).forEach(([queryName, aggregations]) => {
const legend = legendMap[queryName];
if (aggregations.length > 1) {
aggregations.forEach((agg: any, index: number) => {
const aggregationName = agg.alias || agg.expression || '';
const label = `${queryName}.${index}`;
const value = legend
? `${aggregationName}-${legend}`
: `${queryName}.${aggregationName}`;
labels.push({
label,
value,
});
});
} else if (aggregations.length === 1) {
const label = legend || queryName;
const value = legend || queryName;
labels.push({
label,
value,
});
}
});
return labels;
}
export const adjustQueryForV5 = (currentQuery: Query): Query => {
if (currentQuery.queryType === EQueryType.QUERY_BUILDER) {
console.log('currentQuery', currentQuery);
const newQueryData = currentQuery.builder.queryData.map((query) => {
const aggregations = query.aggregations?.map((aggregation) => {
if (query.dataSource === DataSource.METRICS) {
const metricAggregation = aggregation as MetricAggregation;
return {
...aggregation,
metricName:
metricAggregation.metricName || query.aggregateAttribute?.key || '',
timeAggregation:
metricAggregation.timeAggregation || query.timeAggregation || '',
spaceAggregation:
metricAggregation.spaceAggregation || query.spaceAggregation || '',
reduceTo: metricAggregation.reduceTo || query.reduceTo || 'avg',
};
}
return aggregation;
});
const {
aggregateAttribute,
aggregateOperator,
timeAggregation,
spaceAggregation,
reduceTo,
filters,
...retainedQuery
} = query;
const newAggregations =
query.dataSource === DataSource.METRICS
? (aggregations as MetricAggregation[])
: (aggregations as (TraceAggregation | LogAggregation)[]);
return {
...retainedQuery,
aggregations: newAggregations,
};
});
return {
...currentQuery,
builder: {
...currentQuery.builder,
queryData: newQueryData,
},
};
}
return currentQuery;
};

View File

@@ -6,13 +6,14 @@ import './Checkbox.styles.scss';
import { Button, Checkbox, Input, Skeleton, Typography } from 'antd';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
import {
IQuickFiltersConfig,
QuickFiltersSource,
} from 'components/QuickFilters/types';
import { OPERATORS } from 'constants/antlrQueryConstants';
import { DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY } from 'constants/queryBuilder';
import {
DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY,
OPERATORS,
} from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { useGetAggregateValues } from 'hooks/queryBuilder/useGetAggregateValues';
@@ -29,7 +30,7 @@ import { v4 as uuid } from 'uuid';
import LogsQuickFilterEmptyState from './LogsQuickFilterEmptyState';
const SELECTED_OPERATORS = [OPERATORS['='], 'in'];
const NON_SELECTED_OPERATORS = [OPERATORS['!='], 'not in'];
const NON_SELECTED_OPERATORS = [OPERATORS['!='], 'nin'];
const SOURCES_WITH_EMPTY_STATE_ENABLED = [QuickFiltersSource.LOGS_EXPLORER];
@@ -167,20 +168,14 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
...currentQuery.builder,
queryData: currentQuery.builder.queryData.map((item, idx) => ({
...item,
filter: {
expression: removeKeysFromExpression(item.filter?.expression ?? '', [
filter.attributeKey.key,
]),
},
filters: {
...item.filters,
items:
idx === lastUsedQuery
? item.filters?.items?.filter(
? item.filters.items.filter(
(fil) => !isEqual(fil.key?.key, filter.attributeKey.key),
) || []
: [...(item.filters?.items || [])],
op: item.filters?.op || 'AND',
)
: [...item.filters.items],
},
})),
},
@@ -218,14 +213,6 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
query.filters.items = query.filters.items.filter(
(q) => !isEqual(q.key?.key, filter.attributeKey.key),
);
if (query.filter?.expression) {
query.filter.expression = removeKeysFromExpression(
query.filter.expression,
[filter.attributeKey.key],
);
}
if (isOnlyOrAll === 'Only') {
const newFilterItem: TagFilterItem = {
id: uuid(),
@@ -306,7 +293,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
}
}
break;
case 'not in':
case 'nin':
// if the current running operator is NIN then when unchecking the value it gets
// added to the clause like key NIN [value1 , currentUnselectedValue]
if (!checked) {
@@ -385,7 +372,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
if (!checked) {
const newFilter = {
...currentFilter,
op: getOperatorValue('NOT_IN'),
op: getOperatorValue(OPERATORS.NIN),
value: [currentFilter.value as string, value],
};
query.filters.items = query.filters.items.map((item) => {
@@ -408,7 +395,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
// case - when there is no filter for the current key that means all are selected right now.
const newFilterItem: TagFilterItem = {
id: uuid(),
op: getOperatorValue('NOT_IN'),
op: getOperatorValue(OPERATORS.NIN),
key: filter.attributeKey,
value,
};

View File

@@ -178,12 +178,10 @@ function Duration({
...data,
filters: {
...data.filters,
items:
data.filters?.items?.map((item) => ({
...item,
id: '',
})) || [],
op: data.filters?.op || 'AND',
items: data.filters?.items?.map((item) => ({
...item,
id: '',
})),
},
}));
return clonedQuery;
@@ -201,12 +199,11 @@ function Duration({
...item.filters,
items: props?.resetAll
? []
: (unionTagFilterItems(item.filters?.items || [], preparePostData())
: (unionTagFilterItems(item.filters?.items, preparePostData())
.map((item) =>
item.key?.key === props?.clearByType ? undefined : item,
)
.filter((i) => i) as TagFilterItem[]),
op: item.filters?.op || 'AND',
},
})),
},

View File

@@ -92,8 +92,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
...item,
filters: {
...item.filters,
items: idx === lastUsedQuery ? [] : [...(item.filters?.items || [])],
op: item.filters?.op || 'AND',
items: idx === lastUsedQuery ? [] : [...item.filters.items],
},
})),
},

View File

@@ -1,10 +1,5 @@
import { Tabs, TabsProps } from 'antd';
import {
generatePath,
matchPath,
useLocation,
useParams,
} from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom';
import { RouteTabProps } from './types';
@@ -22,13 +17,20 @@ function RouteTab({
const params = useParams<Params>();
const location = useLocation();
// Replace dynamic parameters in routes
const routesWithParams = routes.map((route) => ({
...route,
route: route.route.replace(
/:(\w+)/g,
(match, param) => params[param] || match,
),
}));
// Find the matching route for the current pathname
const currentRoute = routes.find((route) => {
const routePath = route.route.split('?')[0];
return matchPath(location.pathname, {
path: routePath,
exact: true,
});
const currentRoute = routesWithParams.find((route) => {
const routePattern = route.route.replace(/:(\w+)/g, '([^/]+)');
const regex = new RegExp(`^${routePattern}$`);
return regex.test(location.pathname);
});
const onChange = (activeRoute: string): void => {
@@ -36,15 +38,14 @@ function RouteTab({
onChangeHandler(activeRoute);
}
const selectedRoute = routes.find((e) => e.key === activeRoute);
const selectedRoute = routesWithParams.find((e) => e.key === activeRoute);
if (selectedRoute) {
const resolvedRoute = generatePath(selectedRoute.route, params);
history.push(resolvedRoute);
history.push(selectedRoute.route);
}
};
const items = routes.map(({ Component, name, route, key }) => ({
const items = routesWithParams.map(({ Component, name, route, key }) => ({
label: name,
key,
tabKey: route,

View File

@@ -1,77 +0,0 @@
export const OPERATORS = {
IN: 'IN',
LIKE: 'LIKE',
ILIKE: 'ILIKE',
REGEXP: 'REGEXP',
EXISTS: 'EXISTS',
CONTAINS: 'CONTAINS',
BETWEEN: 'BETWEEN',
NOT: 'NOT',
'=': '=',
'!=': '!=',
'>=': '>=',
'>': '>',
'<=': '<=',
'<': '<',
};
export const NON_VALUE_OPERATORS = [OPERATORS.EXISTS];
export enum QUERY_BUILDER_KEY_TYPES {
STRING = 'string',
NUMBER = 'number',
BOOLEAN = 'boolean',
}
export const QUERY_BUILDER_OPERATORS_BY_KEY_TYPE = {
[QUERY_BUILDER_KEY_TYPES.STRING]: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.LIKE,
OPERATORS.ILIKE,
OPERATORS.CONTAINS,
OPERATORS.EXISTS,
OPERATORS.REGEXP,
OPERATORS.NOT,
],
[QUERY_BUILDER_KEY_TYPES.NUMBER]: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS['>='],
OPERATORS['>'],
OPERATORS['<='],
OPERATORS['<'],
OPERATORS.IN,
OPERATORS.EXISTS,
OPERATORS.BETWEEN,
OPERATORS.NOT,
],
[QUERY_BUILDER_KEY_TYPES.BOOLEAN]: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.EXISTS,
OPERATORS.NOT,
],
};
export const negationQueryOperatorSuggestions = [
{ label: OPERATORS['LIKE'], type: 'operator', info: 'Like' },
{ label: OPERATORS['ILIKE'], type: 'operator', info: 'Case insensitive like' },
{ label: OPERATORS['EXISTS'], type: 'operator', info: 'Exists' },
{ label: OPERATORS['BETWEEN'], type: 'operator', info: 'Between' },
{ label: OPERATORS['IN'], type: 'operator', info: 'In' },
{ label: OPERATORS['REGEXP'], type: 'operator', info: 'Regular expression' },
{ label: OPERATORS['CONTAINS'], type: 'operator', info: 'Contains' },
];
export const queryOperatorSuggestions = [
{ label: OPERATORS['='], type: 'operator', info: 'Equal to' },
{ label: OPERATORS['!='], type: 'operator', info: 'Not equal to' },
{ label: OPERATORS['>'], type: 'operator', info: 'Greater than' },
{ label: OPERATORS['<'], type: 'operator', info: 'Less than' },
{ label: OPERATORS['>='], type: 'operator', info: 'Greater than or equal to' },
{ label: OPERATORS['<='], type: 'operator', info: 'Less than or equal to' },
{ label: OPERATORS['NOT'], type: 'operator', info: 'Not' },
...negationQueryOperatorSuggestions,
];

View File

@@ -46,6 +46,5 @@ export enum QueryParams {
msgSystem = 'msgSystem',
destination = 'destination',
kindString = 'kindString',
tab = 'tab',
selectedExplorerView = 'selectedExplorerView',
}

View File

@@ -170,15 +170,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
timeAggregation: MetricAggregateOperator.RATE,
spaceAggregation: MetricAggregateOperator.SUM,
filter: { expression: '' },
aggregations: [
{
metricName: '',
temporality: '',
timeAggregation: MetricAggregateOperator.COUNT,
spaceAggregation: MetricAggregateOperator.SUM,
reduceTo: 'avg',
},
],
aggregations: [{ expression: 'count() ' }],
havingExpression: { expression: '' },
functions: [],
filters: { items: [], op: 'AND' },
@@ -187,7 +179,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
sourceNames: alphabet,
}),
disabled: false,
stepInterval: undefined,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
@@ -199,14 +191,12 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
const initialQueryBuilderFormLogsValues: IBuilderQuery = {
...initialQueryBuilderFormValues,
aggregateOperator: LogsAggregatorOperator.COUNT,
aggregations: [{ expression: 'count() ' }],
dataSource: DataSource.LOGS,
};
const initialQueryBuilderFormTracesValues: IBuilderQuery = {
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
aggregations: [{ expression: 'count() ' }],
dataSource: DataSource.TRACES,
};

View File

@@ -29,7 +29,7 @@ export const CompositeQueryOperatorsConfig: Array<{
traceValue: 'In',
},
{
label: 'not in',
label: 'nin',
metricValue: '!~',
traceValue: 'NotIn',
},
@@ -49,7 +49,7 @@ export const CompositeQueryOperatorsConfig: Array<{
traceValue: 'Exists',
},
{
label: 'not exists',
label: 'nexists',
metricValue: '!~',
traceValue: 'NotExists',
},
@@ -59,7 +59,7 @@ export const CompositeQueryOperatorsConfig: Array<{
traceValue: 'Contains',
},
{
label: 'not contains',
label: 'ncontains',
metricValue: '!~',
traceValue: 'NotContains',
},

View File

@@ -1,5 +1,4 @@
export const USER_PREFERENCES = {
SIDENAV_PINNED: 'sidenav_pinned',
NAV_SHORTCUTS: 'nav_shortcuts',
LAST_SEEN_CHANGELOG_VERSION: 'last_seen_changelog_version',
};

View File

@@ -131,7 +131,7 @@ function AllErrors(): JSX.Element {
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters?.items || [],
compositeData?.builder.queryData?.[0]?.filters.items,
),
}),
enabled: !loading,
@@ -152,7 +152,7 @@ function AllErrors(): JSX.Element {
exceptionType: getUpdatedExceptionType,
serviceName: getUpdatedServiceName,
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters?.items || [],
compositeData?.builder.queryData?.[0]?.filters.items,
),
}),
enabled: !loading,
@@ -454,11 +454,10 @@ function AllErrors(): JSX.Element {
logEvent('Exception: List page visited', {
numberOfExceptions: errorCountResponse?.data?.payload,
selectedEnvironments,
resourceAttributeUsed: !!(
compositeData?.builder.queryData?.[0]?.filters?.items?.length || 0
),
resourceAttributeUsed: !!compositeData?.builder.queryData?.[0]?.filters
.items?.length,
tags: convertCompositeQueryToTraceSelectedTags(
compositeData?.builder.queryData?.[0]?.filters?.items || [],
compositeData?.builder.queryData?.[0]?.filters.items,
),
});
}

View File

@@ -101,14 +101,14 @@ describe('API Monitoring Utils', () => {
// Check that each query includes the domainName filter
result.query.builder.queryData.forEach((query) => {
const serverNameFilter = query.filters?.items?.find(
const serverNameFilter = query.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.SERVER_NAME,
);
expect(serverNameFilter).toBeDefined();
expect(serverNameFilter?.value).toBe(domainName);
// Check that the custom filters were included
const testFilter = query.filters?.items?.find(
const testFilter = query.filters.items.find(
(item) => item.id === 'test-filter',
);
expect(testFilter).toBeDefined();
@@ -210,13 +210,13 @@ describe('API Monitoring Utils', () => {
// Assert
expect(result).toBeDefined();
expect(result?.op).toBe('AND');
expect(result.op).toBe('AND');
// The implementation includes all keys from rowData, not just those in groupBy
expect(result?.items?.length).toBeGreaterThanOrEqual(3);
expect(result.items.length).toBeGreaterThanOrEqual(3);
// Verify each filter matches the corresponding groupBy
expect(
result?.items?.some(
result.items.some(
(item) =>
item.key &&
item.key.key === 'http.method' &&
@@ -226,7 +226,7 @@ describe('API Monitoring Utils', () => {
).toBe(true);
expect(
result?.items?.some(
result.items.some(
(item) =>
item.key &&
item.key.key === 'http.status_code' &&
@@ -236,7 +236,7 @@ describe('API Monitoring Utils', () => {
).toBe(true);
expect(
result?.items?.some(
result.items.some(
(item) =>
item.key &&
item.key.key === 'service.name' &&
@@ -272,10 +272,10 @@ describe('API Monitoring Utils', () => {
// Assert
expect(result).toBeDefined();
// The implementation includes all keys from rowData, not just those in groupBy
expect(result?.items?.length).toBeGreaterThanOrEqual(1);
expect(result.items.length).toBeGreaterThanOrEqual(1);
// Should include the known field with the proper dataType from groupBy
const knownField = result?.items?.find(
const knownField = result.items.find(
(item) => item.key && item.key.key === 'http.method',
);
expect(knownField).toBeDefined();
@@ -285,7 +285,7 @@ describe('API Monitoring Utils', () => {
}
// Should include the unknown field
const unknownField = result?.items?.find(
const unknownField = result.items.find(
(item) => item.key && item.key.key === 'unknown.field',
);
expect(unknownField).toBeDefined();
@@ -304,8 +304,8 @@ describe('API Monitoring Utils', () => {
// Assert
expect(result).toBeDefined();
expect(result?.op).toBe('AND');
expect(result?.items).toHaveLength(0);
expect(result.op).toBe('AND');
expect(result.items).toHaveLength(0);
});
});
@@ -386,11 +386,11 @@ describe('API Monitoring Utils', () => {
// Assert
expect(result).toBeDefined();
expect(result?.op).toBe('AND');
expect(result?.items?.length).toBeGreaterThanOrEqual(3);
expect(result.op).toBe('AND');
expect(result.items.length).toBeGreaterThanOrEqual(3);
// Check domain filter
const domainFilter = result?.items?.find(
const domainFilter = result.items.find(
(item) =>
item.key &&
item.key.key === SPAN_ATTRIBUTES.SERVER_NAME &&
@@ -399,7 +399,7 @@ describe('API Monitoring Utils', () => {
expect(domainFilter).toBeDefined();
// Check endpoint filter
const endpointFilter = result?.items?.find(
const endpointFilter = result.items.find(
(item) =>
item.key &&
item.key.key === SPAN_ATTRIBUTES.URL_PATH &&
@@ -408,7 +408,7 @@ describe('API Monitoring Utils', () => {
expect(endpointFilter).toBeDefined();
// Check status code filter
const statusFilter = result?.items?.find(
const statusFilter = result.items.find(
(item) =>
item.key &&
item.key.key === SPAN_ATTRIBUTES.RESPONSE_STATUS_CODE &&
@@ -469,7 +469,7 @@ describe('API Monitoring Utils', () => {
expect(queryData.filters).toBeDefined();
// Check for domain filter
const domainFilter = queryData.filters?.items?.find(
const domainFilter = queryData.filters.items.find(
// eslint-disable-next-line sonarjs/no-identical-functions
(item) =>
item.key &&
@@ -479,7 +479,7 @@ describe('API Monitoring Utils', () => {
expect(domainFilter).toBeDefined();
// Check that custom filters were included
const testFilter = queryData.filters?.items?.find(
const testFilter = queryData.filters.items.find(
(item) => item.id === 'test-filter',
);
expect(testFilter).toBeDefined();
@@ -583,7 +583,7 @@ describe('API Monitoring Utils', () => {
} = query;
queryData.forEach((qd) => {
if (qd.filters && qd.filters.items) {
const serverNameFilter = qd.filters?.items?.find(
const serverNameFilter = qd.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.SERVER_NAME,
);
expect(serverNameFilter).toBeDefined();
@@ -595,7 +595,7 @@ describe('API Monitoring Utils', () => {
}
// Should include our custom filter
const customFilter = qd.filters?.items?.find(
const customFilter = qd.filters.items.find(
(item) => item.id === 'test-filter',
);
expect(customFilter).toBeDefined();
@@ -631,7 +631,7 @@ describe('API Monitoring Utils', () => {
const queryData = result.query.builder.queryData[0];
// Should have domain filter
const domainFilter = queryData.filters?.items?.find(
const domainFilter = queryData.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.SERVER_NAME,
);
expect(domainFilter).toBeDefined();
@@ -698,7 +698,7 @@ describe('API Monitoring Utils', () => {
const queryData = result.query.builder.queryData[0];
// Should have domain filter
const domainFilter = queryData.filters?.items?.find(
const domainFilter = queryData.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.SERVER_NAME,
);
expect(domainFilter).toBeDefined();
@@ -1375,7 +1375,7 @@ describe('API Monitoring Utils', () => {
const queryData = result.query.builder.queryData[0];
// Should have domain filter
const domainFilter = queryData.filters?.items?.find(
const domainFilter = queryData.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.SERVER_NAME,
);
expect(domainFilter).toBeDefined();
@@ -1384,7 +1384,7 @@ describe('API Monitoring Utils', () => {
}
// Should have endpoint filter if provided
const endpointFilter = queryData.filters?.items?.find(
const endpointFilter = queryData.filters.items.find(
(item) => item.key && item.key.key === SPAN_ATTRIBUTES.URL_PATH,
);
expect(endpointFilter).toBeDefined();
@@ -1422,7 +1422,7 @@ describe('API Monitoring Utils', () => {
const queryData = result.query.builder.queryData[0];
// Should include our custom filter
const includedFilter = queryData.filters?.items?.find(
const includedFilter = queryData.filters.items.find(
(item) => item.id === 'custom-filter',
);
expect(includedFilter).toBeDefined();

View File

@@ -64,7 +64,7 @@ function AllEndPoints({
return params.allEndpointsLocalFilters;
}
// Initialize filters based on the initial endPointName prop
const initialItems = [...(initialFilters?.items || [])];
const initialItems = [...initialFilters.items];
return { op: 'AND', items: initialItems };
});
@@ -204,8 +204,8 @@ function AllEndPoints({
setSelectedEndPointName(props[SPAN_ATTRIBUTES.URL_PATH] as string);
setSelectedView(VIEWS.ENDPOINT_STATS);
const initialItems = [
...(filters?.items || []),
...(getGroupByFiltersFromGroupByValues(props, groupBy)?.items || []),
...filters.items,
...getGroupByFiltersFromGroupByValues(props, groupBy).items,
];
setInitialFiltersEndPointStats({
items: initialItems,

View File

@@ -68,8 +68,8 @@ function EndPointDetails({
const [filters, setFilters] = useState<IBuilderQuery['filters']>(() => {
// Initialize filters based on the initial endPointName prop
const initialItems = params.endPointDetailsLocalFilters
? [...(params.endPointDetailsLocalFilters?.items || [])]
: [...(initialFilters?.items || [])];
? [...params.endPointDetailsLocalFilters.items]
: [...initialFilters.items];
if (endPointName) {
initialItems.push({
id: '92b8a1c1',
@@ -84,7 +84,7 @@ function EndPointDetails({
// Effect to synchronize local filters when the endPointName prop changes (e.g., from dropdown)
useEffect(() => {
setFilters((currentFilters) => {
const existingHttpUrlFilter = currentFilters?.items?.find(
const existingHttpUrlFilter = currentFilters.items.find(
(item) => item.key?.key === httpUrlKey.key,
);
const existingHttpUrlValue = (existingHttpUrlFilter?.value as string) || '';
@@ -95,10 +95,10 @@ function EndPointDetails({
}
// Rebuild filters: Keep non-http.url filters and add/update http.url filter based on prop
const otherFilters = currentFilters?.items?.filter(
const otherFilters = currentFilters.items.filter(
(item) => item.key?.key !== httpUrlKey.key,
);
const newItems = [...(otherFilters || [])];
const newItems = [...otherFilters];
if (endPointName) {
newItems.push({
id: '92b8a1c1',
@@ -115,8 +115,7 @@ function EndPointDetails({
useEffect(() => {
const filtersWithoutHttpUrl = {
op: 'AND',
items:
filters?.items?.filter((item) => item.key?.key !== httpUrlKey.key) || [],
items: filters.items.filter((item) => item.key?.key !== httpUrlKey.key),
};
setParams({ endPointDetailsLocalFilters: filtersWithoutHttpUrl });
}, [filters, setParams]);
@@ -129,14 +128,12 @@ function EndPointDetails({
// Filter out http.url filter before saving to params
const filteredNewFilters = {
op: 'AND',
items:
newFilters?.items?.filter((item) => item.key?.key !== httpUrlKey.key) ||
[],
items: newFilters.items.filter((item) => item.key?.key !== httpUrlKey.key),
};
setParams({ endPointDetailsLocalFilters: filteredNewFilters });
// 2. Derive the endpoint name from the *new* filters state
const httpUrlFilter = newFilters?.items?.find(
const httpUrlFilter = newFilters.items.find(
(item) => item.key?.key === httpUrlKey.key,
);
const derivedEndPointName = (httpUrlFilter?.value as string) || '';
@@ -171,7 +168,7 @@ function EndPointDetails({
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
const isServicesFilterApplied = useMemo(
() => filters?.items?.some((item) => item.key?.key === 'service.name'),
() => filters.items.some((item) => item.key?.key === 'service.name'),
[filters],
);
@@ -185,7 +182,7 @@ function EndPointDetails({
queryKey: [
END_POINT_DETAILS_QUERY_KEYS_ARRAY[index],
payload,
filters?.items, // Include filters.items in queryKey for better caching
filters.items, // Include filters.items in queryKey for better caching
ENTITY_VERSION_V4,
],
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>

View File

@@ -66,9 +66,9 @@ function TopErrors({
op: '=',
value: endPointName,
},
...(initialFilters?.items || []),
...initialFilters.items,
]
: [...(initialFilters?.items || [])],
: [...initialFilters.items],
op: 'AND',
},
showStatusCodeErrors,
@@ -236,7 +236,7 @@ function TopErrors({
record.statusCode,
);
navigateToExplorer({
filters: [...(filters?.items || [])],
filters: [...filters.items],
dataSource: DataSource.TRACES,
startTime: minTime,
endTime: maxTime,

View File

@@ -63,7 +63,7 @@ function ExpandedRow({
(queryData) => ({
...queryData,
filters: {
items: [...(queryData.filters?.items || []), ...(filters?.items || [])],
items: [...(queryData.filters?.items || []), ...filters.items],
op: 'AND',
},
}),

View File

@@ -14,7 +14,6 @@ import {
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useNotifications } from 'hooks/useNotifications';
@@ -113,7 +112,6 @@ function StatusCodeBarCharts({
});
const navigateToExplorer = useNavigateToExplorer();
const { currentQuery } = useQueryBuilder();
const navigateToExplorerPages = useNavigateToExplorerPages();
const { notifications } = useNotifications();
@@ -138,8 +136,8 @@ function StatusCodeBarCharts({
const widget = useMemo<Widgets>(
() =>
getStatusCodeBarChartWidgetData(domainName, endPointName, {
items: [...(filters?.items || [])],
op: filters?.op || 'AND',
items: [...filters.items],
op: filters.op,
}),
[domainName, endPointName, filters],
);
@@ -206,7 +204,6 @@ function StatusCodeBarCharts({
customSeries: getCustomSeries,
onDragSelect,
colorMapping,
query: currentQuery,
}),
[
minTime,
@@ -220,7 +217,6 @@ function StatusCodeBarCharts({
getCustomSeries,
onDragSelect,
colorMapping,
currentQuery,
],
);

View File

@@ -21,7 +21,6 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -65,8 +64,7 @@ function DomainList(): JSX.Element {
dataSource: DataSource.TRACES,
aggregateOperator: 'noop',
aggregateAttribute: {
...(initialQueriesMap.traces.builder.queryData[0]
.aggregateAttribute as BaseAutocompleteData),
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
},
},
],
@@ -105,7 +103,7 @@ function DomainList(): JSX.Element {
op: '=',
value: 'Client',
},
...(compositeData?.builder?.queryData[0]?.filters?.items || []),
...(compositeData?.builder?.queryData[0]?.filters.items || []),
],
},
};

View File

@@ -374,7 +374,7 @@ export const getDomainMetricsQueryPayload = (
op: '=',
value: domainName,
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -416,7 +416,7 @@ export const getDomainMetricsQueryPayload = (
op: '=',
value: domainName,
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -470,7 +470,7 @@ export const getDomainMetricsQueryPayload = (
op: '=',
value: 'true',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -512,7 +512,7 @@ export const getDomainMetricsQueryPayload = (
op: '=',
value: domainName,
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -993,7 +993,7 @@ export const getTopErrorsQueryPayload = (
op: '=',
value: true,
},
...(filters?.items || []),
...filters.items,
],
},
expression: 'A',
@@ -1586,7 +1586,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1640,7 +1640,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1706,7 +1706,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1760,7 +1760,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1814,7 +1814,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1909,7 +1909,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -1971,7 +1971,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2036,7 +2036,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2132,7 +2132,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2226,7 +2226,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2289,7 +2289,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2352,7 +2352,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2427,7 +2427,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2531,7 +2531,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -2627,7 +2627,7 @@ export const getEndPointDetailsQueryPayload = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3318,7 +3318,7 @@ export const getStatusCodeBarChartWidgetData = (
},
]
: []),
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3462,7 +3462,7 @@ export const getAllEndpointsWidgetData = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3518,7 +3518,7 @@ export const getAllEndpointsWidgetData = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3574,7 +3574,7 @@ export const getAllEndpointsWidgetData = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3642,7 +3642,7 @@ export const getAllEndpointsWidgetData = (
op: '=',
value: 'Client',
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3832,7 +3832,7 @@ export const getRateOverTimeWidgetData = (
op: '=',
value: domainName,
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},
@@ -3897,7 +3897,7 @@ export const getLatencyOverTimeWidgetData = (
op: '=',
value: domainName,
},
...(filters?.items || []),
...filters.items,
],
op: 'AND',
},

View File

@@ -1,7 +1,4 @@
// Earlier we were having app-banner-container class
// we change it to app-banner-wrapper as the adblocker was blocking the app-banner-container class
// Keep an eye on What classnames are used in the codebase
.app-banner-wrapper {
.app-banner-container {
position: relative;
width: 100%;
}

View File

@@ -13,7 +13,6 @@ import manageCreditCardApi from 'api/v1/portal/create';
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
import getUserVersion from 'api/v1/version/getVersion';
import cx from 'classnames';
import ChangelogModal from 'components/ChangelogModal/ChangelogModal';
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { Events } from 'constants/events';
@@ -55,10 +54,7 @@ import {
} from 'types/actions/app';
import { ErrorResponse, SuccessResponse, SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import {
ChangelogSchema,
DeploymentType,
} from 'types/api/changelog/getChangelogByVersion';
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
import APIError from 'types/api/error';
import {
LicenseEvent,
@@ -67,6 +63,7 @@ import {
} from 'types/api/licensesV3/getActive';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { checkVersionState } from 'utils/app';
import { eventEmitter } from 'utils/getEventEmitter';
import {
getFormattedDate,
@@ -90,9 +87,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
featureFlagsFetchError,
userPreferences,
updateChangelog,
toggleChangelogModal,
showChangelogModal,
changelog,
} = useAppContext();
const { notifications } = useNotifications();
@@ -104,11 +98,16 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const [showSlowApiWarning, setShowSlowApiWarning] = useState(false);
const [slowApiWarningShown, setSlowApiWarningShown] = useState(false);
const [shouldFetchChangelog, setShouldFetchChangelog] = useState<boolean>(
false,
);
const { latestVersion } = useSelector<AppState, AppReducer>(
const { currentVersion, latestVersion } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const isLatestVersion = checkVersionState(currentVersion, latestVersion);
const handleBillingOnSuccess = (
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
): void => {
@@ -145,15 +144,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const changelogForTenant = isCloudUserVal
? DeploymentType.CLOUD_ONLY
: DeploymentType.OSS_ONLY;
const seenChangelogVersion = userPreferences?.find(
(preference) =>
preference.name === USER_PREFERENCES.LAST_SEEN_CHANGELOG_VERSION,
)?.value as string;
const [
getUserVersionResponse,
getUserLatestVersionResponse,
@@ -171,35 +161,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
},
{
queryFn: (): Promise<SuccessResponse<ChangelogSchema> | ErrorResponse> =>
getChangelogByVersion(latestVersion, changelogForTenant),
queryKey: ['getChangelogByVersion', latestVersion, changelogForTenant],
enabled: isLoggedIn && Boolean(latestVersion),
getChangelogByVersion(latestVersion),
queryKey: ['getChangelogByVersion', latestVersion],
enabled: isLoggedIn && !isCloudUserVal && shouldFetchChangelog,
},
]);
useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
if (
isCloudUserVal &&
Boolean(latestVersion) &&
latestVersion !== seenChangelogVersion
) {
// Automatically open the changelog modal for cloud users after 1s, if they've not seen this version before.
timer = setTimeout(() => {
toggleChangelogModal();
}, 1000);
}
return (): void => {
clearInterval(timer);
};
}, [
isCloudUserVal,
latestVersion,
seenChangelogVersion,
toggleChangelogModal,
]);
useEffect(() => {
if (getUserLatestVersionResponse.status === 'idle' && isLoggedIn) {
getUserLatestVersionResponse.refetch();
@@ -256,7 +223,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (
getUserVersionResponse.isFetched &&
getUserVersionResponse.isSuccess &&
getUserLatestVersionResponse.isSuccess &&
getUserVersionResponse.data &&
getUserVersionResponse.data.payload
) {
@@ -294,13 +261,18 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserVersionResponse.isLoading,
getUserVersionResponse.isError,
getUserVersionResponse.data,
getUserVersionResponse.isSuccess,
getUserLatestVersionResponse.isFetched,
getUserVersionResponse.isFetched,
getUserLatestVersionResponse.isSuccess,
notifications,
]);
useEffect(() => {
if (!isLatestVersion) {
setShouldFetchChangelog(true);
}
}, [isLatestVersion]);
useEffect(() => {
if (
getChangelogByVersionResponse.isFetched &&
@@ -641,7 +613,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
</Helmet>
{isLoggedIn && (
<div className={cx('app-banner-wrapper')}>
<div className={cx('app-banner-container')}>
{SHOW_TRIAL_EXPIRY_BANNER && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
@@ -722,9 +694,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
</Flex>
{showAddCreditCardModal && <ChatSupportGateway />}
{showChangelogModal && changelog && (
<ChangelogModal changelog={changelog} onClose={toggleChangelogModal} />
)}
</Layout>
);
}

View File

@@ -1,173 +1,30 @@
.empty-logs-search {
&__container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 240px;
}
&__content {
.empty-logs-search-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 240px;
.empty-logs-search-container-content {
display: flex;
flex-direction: column;
gap: 4px;
color: var(--text-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
line-height: 18px; /* 128.571% */
letter-spacing: -0.07px;
align-items: flex-start;
.empty-state-svg {
height: 50px;
width: 50px;
}
}
&__sub-text {
font-weight: 600;
}
&__container {
&--custom-message {
height: 445px;
.empty-state-svg {
height: 32px;
width: 32px;
}
.empty-logs-search {
&__header {
display: flex;
align-items: center;
gap: 4px;
}
&__title {
color: var(--bg-vanilla-100);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
&__subtitle {
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.07px;
}
&__description {
font-size: 14px;
color: var(--text-vanilla-400);
line-height: 20px;
}
&__description-list {
margin: 0;
margin-top: 8px;
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.07px;
display: flex;
flex-direction: column;
gap: 6px;
list-style: none;
padding: 0;
font-family: Inter;
}
&__description-list li {
position: relative;
padding-left: 20px;
}
&__description-list li::before {
content: '';
font-family: Inter;
position: absolute;
left: 0;
color: var(--bg-robin-400);
font-weight: bold;
font-size: 16px;
line-height: 20px;
}
&__clear-filters-btn {
display: flex;
width: 468px;
font-family: Inter;
padding: 12px;
justify-content: space-between;
align-items: flex-start;
border-radius: 3px;
border: 1px dashed var(--bg-slate-500);
background: transparent;
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: -0.07px;
cursor: pointer;
margin-top: 12px;
}
&__clear-filters-btn-icon {
display: flex;
align-items: center;
gap: 6px;
}
&__row {
display: flex;
flex-direction: row;
align-items: flex-end;
max-width: 825px;
gap: 25px;
justify-content: center;
margin-left: 21px;
}
&__content {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 260px;
}
&__resources-card {
background: var(--bg-ink-400);
border: 1px solid var(--bg-slate-500);
border-radius: 4px;
width: 332px;
}
&__resources-title {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 11px;
font-weight: 600;
line-height: 18px;
letter-spacing: 0.88px;
text-transform: uppercase;
padding: 16px 16px 12px;
border-bottom: 1px solid var(--bg-slate-500);
height: 46px;
}
&__resources-links {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
.learn-more {
height: 18px;
}
}
}
.sub-text {
font-weight: 600;
}
}
}

View File

@@ -2,24 +2,16 @@ import './EmptyLogsSearch.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import LearnMore from 'components/LearnMore/LearnMore';
import { EmptyLogsListConfig } from 'container/LogsExplorerList/utils';
import { Delete } from 'lucide-react';
import { useEffect, useRef } from 'react';
import { DataSource, PanelTypeKeys } from 'types/common/queryBuilder';
interface EmptyLogsSearchProps {
dataSource: DataSource;
panelType: PanelTypeKeys;
customMessage?: EmptyLogsListConfig;
}
export default function EmptyLogsSearch({
dataSource,
panelType,
customMessage,
}: EmptyLogsSearchProps): JSX.Element {
}: {
dataSource: DataSource;
panelType: PanelTypeKeys;
}): JSX.Element {
const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current) {
@@ -38,80 +30,18 @@ export default function EmptyLogsSearch({
}, []);
return (
<div
className={cx('empty-logs-search__container', {
'empty-logs-search__container--custom-message': !!customMessage,
})}
>
<div className="empty-logs-search__row">
<div className="empty-logs-search__content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
{customMessage ? (
<>
<div className="empty-logs-search__header">
<Typography.Text className="empty-logs-search__title">
{customMessage.title}
</Typography.Text>
{customMessage.subTitle && (
<Typography.Text className="empty-logs-search__subtitle">
{customMessage.subTitle}
</Typography.Text>
)}
</div>
{Array.isArray(customMessage.description) ? (
<ul className="empty-logs-search__description-list">
{customMessage.description.map((desc) => (
<li key={desc}>{desc}</li>
))}
</ul>
) : (
<Typography.Text className="empty-logs-search__description">
{customMessage.description}
</Typography.Text>
)}
{/* Clear filters button */}
{customMessage.showClearFiltersButton && (
<button
type="button"
className="empty-logs-search__clear-filters-btn"
onClick={customMessage.onClearFilters}
>
{customMessage.clearFiltersButtonText}
<span className="empty-logs-search__clear-filters-btn-icon">
<Delete size={14} />
Clear filters
</span>
</button>
)}
</>
) : (
<Typography.Text>
<span className="empty-logs-search__sub-text">
This query had no results.{' '}
</span>
Edit your query and try again!
</Typography.Text>
)}
</div>
{customMessage?.documentationLinks && (
<div className="empty-logs-search__resources-card">
<div className="empty-logs-search__resources-title">RESOURCES</div>
<div className="empty-logs-search__resources-links">
{customMessage.documentationLinks.map((link) => (
<LearnMore key={link.text} text={link.text} url={link.url} />
))}
</div>
</div>
)}
<div className="empty-logs-search-container">
<div className="empty-logs-search-container-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text>
<span className="sub-text">This query had no results. </span>
Edit your query and try again!
</Typography.Text>
</div>
</div>
);
}
EmptyLogsSearch.defaultProps = {
customMessage: null,
};

View File

@@ -1,12 +1,14 @@
.explorer-options-container {
position: fixed;
bottom: 24px;
bottom: 8px;
left: calc(50% + 240px);
transform: translate(calc(-50% - 120px), 0);
transition: left 0.2s linear;
border-radius: 6px;
background: var(--Ink-300, #16181d);
display: flex;
gap: 16px;
background-color: transparent;
.multi-alert-button,
@@ -32,19 +34,15 @@
.explorer-update {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 10px 10px;
border-radius: 50px;
border: 1px solid var(--bg-slate-400);
background: rgba(22, 24, 29, 0.6);
backdrop-filter: blur(20px);
gap: 4px;
padding: 8px;
background: var(--Ink-300, #16181d);
.action-icon {
display: flex;
justify-content: center;
align-items: center;
padding: 8px;
border-radius: 50px;
padding: 6px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-slate-500);
cursor: pointer;
@@ -64,10 +62,8 @@
.explorer-options {
padding: 10px 12px;
border: 1px solid var(--bg-slate-400);
border-radius: 50px;
background: rgba(22, 24, 29, 0.6);
backdrop-filter: blur(20px);
background: var(--Ink-300, #16181d);
border-radius: 2px;
cursor: default;
display: flex;
@@ -96,27 +92,6 @@
align-items: center;
gap: 8px;
button {
display: flex;
justify-content: center;
align-items: center;
border: none;
border: 1px solid #1d2023;
box-shadow: none !important;
&.ant-btn-round {
padding: 8px 12px 8px 10px;
font-weight: 500;
}
&.ant-btn-round:disabled {
background-color: rgba(209, 209, 209, 0.074);
color: #5f5f5f;
}
}
.ant-select-focused {
border-color: transparent !important;

View File

@@ -1,12 +1,10 @@
/* eslint-disable react/jsx-props-no-spreading */
import './ExplorerOptions.styles.scss';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import {
Button,
ColorPicker,
Divider,
Input,
Modal,
RefSelectProps,
@@ -46,14 +44,7 @@ import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { cloneDeep, isEqual, omit } from 'lodash-es';
import {
Check,
ConciergeBell,
Disc3,
PanelBottomClose,
Plus,
X,
} from 'lucide-react';
import { Check, ConciergeBell, Disc3, Plus, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { FormattingOptions } from 'providers/preferences/types';
import {
@@ -78,10 +69,8 @@ import ExplorerOptionsHideArea from './ExplorerOptionsHideArea';
import { PreservedViewsInLocalStorage } from './types';
import {
DATASOURCE_VS_ROUTES,
generateRGBAFromHex,
getRandomColor,
saveNewViewHandler,
setExplorerToolBarVisibility,
} from './utils';
const allowedRoles = [USER_ROLES.ADMIN, USER_ROLES.AUTHOR, USER_ROLES.EDITOR];
@@ -253,12 +242,6 @@ function ExplorerOptions({
const extraData = viewsData?.data?.data?.find((view) => view.id === viewKey)
?.extraData;
const extraDataColor = extraData ? JSON.parse(extraData).color : '';
const rgbaColor = generateRGBAFromHex(
extraDataColor || Color.BG_SIENNA_500,
0.08,
);
const { options, handleOptionsChange } = useOptionsMenu({
storageKey:
sourcepage === DataSource.TRACES
@@ -640,27 +623,6 @@ function ExplorerOptions({
viewsData?.data?.data,
]);
const infoIconText = useMemo(() => {
if (isLogsExplorer) {
return 'Learn more about Logs explorer';
}
if (isMetricsExplorer) {
return 'Learn more about Metrics explorer';
}
return 'Learn more about Traces explorer';
}, [isLogsExplorer, isMetricsExplorer]);
const infoIconLink = useMemo(() => {
if (isLogsExplorer) {
return 'https://signoz.io/docs/product-features/logs-explorer/?utm_source=product&utm_medium=logs-explorer-toolbar';
}
// TODO: Add metrics explorer info icon link
if (isMetricsExplorer) {
return '';
}
return 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar';
}, [isLogsExplorer, isMetricsExplorer]);
const getQueryName = (query: Query): string => {
if (query.builder.queryFormulas.length > 0) {
return `Formula ${query.builder.queryFormulas[0].queryName}`;
@@ -673,11 +635,10 @@ function ExplorerOptions({
const selectLabel = (
<Button
disabled={disabled}
shape="round"
className="periscope-btn ghost"
shape="default"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
/>
);
return (
<Select
@@ -706,12 +667,11 @@ function ExplorerOptions({
return (
<Button
disabled={disabled}
shape="round"
shape="default"
className="periscope-btn ghost"
onClick={(): void => onCreateAlertsHandler(query)}
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
/>
);
}, [
disabled,
@@ -725,14 +685,11 @@ function ExplorerOptions({
if (isOneChartPerQuery) {
const selectLabel = (
<Button
type="primary"
className="periscope-btn ghost"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
icon={<Plus size={12} />}
/>
);
return (
<Select
@@ -764,24 +721,14 @@ function ExplorerOptions({
}
return (
<Button
type="primary"
className="periscope-btn ghost"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
/>
);
}, [disabled, isOneChartPerQuery, onAddToDashboard, splitedQueries]);
const hideToolbar = (): void => {
setExplorerToolBarVisibility(false, sourcepage);
if (setIsExplorerOptionHidden) {
setIsExplorerOptionHidden(true);
}
};
return (
<div className="explorer-options-container">
{
@@ -797,41 +744,31 @@ function ExplorerOptions({
>
<Tooltip title="Clear this view" placement="top">
<Button
className="action-icon"
className="periscope-btn ghost"
onClick={handleClearSelect}
icon={<X size={14} />}
icon={<X size={16} />}
/>
</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(
'periscope-btn ghost',
isEditDeleteSupported ? '' : 'hidden',
)}
disabled={isViewUpdating}
onClick={onUpdateQueryHandler}
icon={<Disc3 size={16} />}
/>
<Tooltip title="Update this view" placement="top">
<Button
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
disabled={isViewUpdating}
onClick={onUpdateQueryHandler}
icon={<Disc3 size={14} />}
/>
</Tooltip>
</>
</Tooltip>
)}
</div>
)}
{!isExplorerOptionHidden && (
<div
className="explorer-options"
style={{
background: extraData
? `linear-gradient(90deg, rgba(0,0,0,0) -5%, ${rgbaColor} 9%, rgba(0,0,0,0) 30%)`
: 'transparent',
}}
>
<div className="explorer-options">
<div className="view-options">
<Select<string, { key: string; value: string }>
showSearch
@@ -872,49 +809,23 @@ function ExplorerOptions({
</Select>
<Button
shape="round"
shape="default"
className={cx(
'periscope-btn secondary',
isEditDeleteSupported ? '' : 'hidden',
)}
onClick={handleSaveViewModalToggle}
className={isEditDeleteSupported ? '' : 'hidden'}
disabled={viewsIsLoading || isRefetching}
icon={<Disc3 size={16} />}
icon={<Disc3 size={12} />}
>
Save this view
</Button>
</div>
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
{alertButton}
{dashboardButton}
</div>
<div className="actions">
{/* Hide the info icon for metrics explorer until we get the docs link */}
{!isMetricsExplorer && (
<Tooltip
title={
<div>
{infoIconText}
<Typography.Link href={infoIconLink} target="_blank">
{' '}
here
</Typography.Link>{' '}
</div>
}
>
<InfoCircleOutlined className="info-icon" />
</Tooltip>
)}
<Tooltip title="Hide">
<Button
disabled={disabled}
shape="circle"
onClick={hideToolbar}
icon={<PanelBottomClose size={16} />}
data-testid="hide-toolbar"
/>
</Tooltip>
</div>
</div>
)}
<ExplorerOptionsHideArea

View File

@@ -20,9 +20,9 @@ function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
const { data, isFetching } = useGetAggregateKeys(
{
aggregateAttribute: query.aggregateAttribute?.key || '',
aggregateAttribute: query.aggregateAttribute.key,
dataSource: query.dataSource,
aggregateOperator: query.aggregateOperator || '',
aggregateOperator: query.aggregateOperator,
searchText: debouncedSearchText,
},
{

View File

@@ -1,6 +1,6 @@
import { Button, Typography } from 'antd';
import createDashboard from 'api/v1/dashboards/create';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useCallback, useMemo, useState } from 'react';
@@ -75,7 +75,7 @@ function ExportPanelContainer({
ns: 'dashboard',
}),
uploadedGrafana: false,
version: ENTITY_VERSION_V5,
version: ENTITY_VERSION_V4,
});
} catch (error) {
showErrorModal(error as APIError);

View File

@@ -17,7 +17,6 @@ import {
Time as TimeV2,
} from 'container/TopNav/DateTimeSelectionV2/config';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -80,7 +79,6 @@ function ChartPreview({
const threshold = alertDef?.condition.target || 0;
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { currentQuery } = useQueryBuilder();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
@@ -158,6 +156,7 @@ function ChartPreview({
maxTime,
alertDef?.ruleType,
],
retry: false,
enabled: canQuery,
},
);
@@ -255,8 +254,6 @@ function ChartPreview({
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
currentQuery,
query: query || currentQuery,
}),
[
yAxisUnit,
@@ -272,8 +269,6 @@ function ChartPreview({
alertDef?.condition.targetUnit,
graphType,
timezone.value,
currentQuery,
query,
],
);

View File

@@ -212,12 +212,9 @@ function QuerySection({
return null;
}
};
const step2Label = alertDef.alertType === 'METRIC_BASED_ALERT' ? '2' : '1';
return (
<>
<StepHeading> {t('alert_form_step2', { step: step2Label })}</StepHeading>
<StepHeading> {t('alert_form_step2')}</StepHeading>
<FormContainer className="alert-query-section-container">
<div>{renderTabs(alertType)}</div>
{renderQuerySection(currentTab)}

View File

@@ -371,11 +371,9 @@ function RuleOptions({
selectedCategory?.name,
);
const step3Label = alertDef.alertType === 'METRIC_BASED_ALERT' ? '3' : '2';
return (
<>
<StepHeading>{t('alert_form_step3', { step: step3Label })}</StepHeading>
<StepHeading>{t('alert_form_step3')}</StepHeading>
<FormContainer>
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
{queryCategory !== EQueryType.PROM &&

View File

@@ -69,7 +69,6 @@ function GridCardGraph({
GlobalReducer
>((state) => state.globalTime);
const queryClient = useQueryClient();
console.log('widget', widget.title, widget.query);
const handleBackNavigation = (): void => {
const searchParams = new URLSearchParams(window.location.search);
@@ -138,7 +137,6 @@ function GridCardGraph({
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
start: customTimeRange?.startTime || start,
end: customTimeRange?.endTime || end,
originalGraphType: widget.panelTypes,
};
}
updatedQuery.builder.queryData[0].pageSize = 10;

View File

@@ -2,7 +2,6 @@
overflow: auto;
margin: 8px -8px;
margin-right: 0;
margin-bottom: 64px;
.react-grid-layout {
border: none !important;

View File

@@ -46,7 +46,6 @@ function useUpdatedQuery(): UseUpdatedQueryResult {
selectedTime: widgetConfig.timePreferance,
globalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data?.variables),
originalGraphType: widgetConfig.panelTypes,
});
// Execute query and process results

View File

@@ -101,7 +101,7 @@ export function updateStepInterval(
// if user haven't enter anything manually, that is we have default value of 60 then do the interval adjustment for bar otherwise apply the user's value
const getSteps = (queryData: IBuilderQuery): number =>
queryData?.stepInterval === 60
queryData.stepInterval === 60
? stepIntervalPoints || 60
: queryData?.stepInterval || 60;

View File

@@ -84,12 +84,11 @@ function GridTableComponent({
const newValue = { ...val };
Object.keys(val).forEach((k) => {
if (columnUnits[k]) {
// the check below takes care of not adding units for rows that have n/a or null values
if (val[k] !== 'n/a' && val[k] !== null) {
newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
} else if (val[k] === null) {
newValue[k] = 'n/a';
}
// the check below takes care of not adding units for rows that have n/a values
newValue[k] =
val[k] !== 'n/a'
? getYAxisFormattedValue(String(val[k]), columnUnits[k])
: val[k];
newValue[`${k}_without_unit`] = val[k];
}
});
@@ -102,7 +101,6 @@ function GridTableComponent({
[columnUnits],
);
console.log('columnUnits', columnUnits, originalDataSource);
const dataSource = useMemo(() => applyColumnUnits(originalDataSource), [
applyColumnUnits,
originalDataSource,

View File

@@ -1,7 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { ColumnsType, ColumnType } from 'antd/es/table';
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { convertUnit } from 'container/NewWidget/RightContainer/dataFormatCategories';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
@@ -185,20 +183,10 @@ export function createColumnsAndDataSource(
? getQueryLegend(currentQuery, item.queryName)
: undefined;
const isMultipleAggregations =
createAggregation(
currentQuery.queryType === EQueryType.QUERY_BUILDER
? currentQuery.builder?.queryData?.find(
(query) => query.queryName === item.queryName,
)
: undefined,
PANEL_TYPES.TABLE,
)?.length > 1;
const column: ColumnType<RowData> = {
dataIndex: item.name,
// if no legend present then rely on the column name value
title: !isMultipleAggregations && !isEmpty(legend) ? legend : item.name,
title: !isEmpty(legend) ? legend : item.name,
width: QUERY_TABLE_CONFIG.width,
render: renderColumnCell && renderColumnCell[item.name],
sorter: (a: RowData, b: RowData): number => sortFunction(a, b, item),

View File

@@ -96,41 +96,11 @@ function HostsList(): JSX.Element {
};
}, [pageSize, currentPage, filters, minTime, maxTime, orderBy]);
const queryKey = useMemo(() => {
if (selectedHostName) {
return [
'hostList',
String(pageSize),
String(currentPage),
JSON.stringify(filters),
JSON.stringify(orderBy),
];
}
return [
'hostList',
String(pageSize),
String(currentPage),
JSON.stringify(filters),
JSON.stringify(orderBy),
String(minTime),
String(maxTime),
];
}, [
pageSize,
currentPage,
filters,
orderBy,
selectedHostName,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetHostList(
query as HostListPayload,
{
queryKey,
queryKey: ['hostList', query],
enabled: !!query,
keepPreviousData: true,
},
);
@@ -153,7 +123,7 @@ function HostsList(): JSX.Element {
const handleFiltersChange = useCallback(
(value: IBuilderQuery['filters']): void => {
const isNewFilterAdded = value?.items?.length !== filters?.items?.length;
const isNewFilterAdded = value.items.length !== filters.items.length;
setFilters(value);
handleChangeQueryData('filters', value);
setSearchParams({
@@ -163,7 +133,7 @@ function HostsList(): JSX.Element {
if (isNewFilterAdded) {
setCurrentPage(1);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.HostEntity,
page: InfraMonitoringEvents.ListPage,
@@ -242,7 +212,6 @@ function HostsList(): JSX.Element {
<HostsListControls
filters={filters}
handleFiltersChange={handleFiltersChange}
showAutoRefresh={!selectedHostData}
/>
</div>
<HostsListTable
@@ -251,7 +220,7 @@ function HostsList(): JSX.Element {
isError={isError}
tableData={data}
hostMetricsData={hostMetricsData}
filters={filters || { items: [], op: 'AND' }}
filters={filters}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
onHostClick={handleHostClick}

View File

@@ -11,11 +11,9 @@ import { DataSource } from 'types/common/queryBuilder';
function HostsListControls({
handleFiltersChange,
filters,
showAutoRefresh,
}: {
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
filters: IBuilderQuery['filters'];
showAutoRefresh: boolean;
}): JSX.Element {
const currentQuery = initialQueriesMap[DataSource.METRICS];
const updatedCurrentQuery = useMemo(
@@ -50,7 +48,7 @@ function HostsListControls({
<div className="hosts-list-controls">
<div className="hosts-list-controls-left">
<QueryBuilderSearch
query={query as IBuilderQuery}
query={query}
onChange={handleChangeTagFilters}
isInfraMonitoring
disableNavigationShortcuts
@@ -60,7 +58,7 @@ function HostsListControls({
<div className="time-selector">
<DateTimeSelectionV2
showAutoRefresh={showAutoRefresh}
showAutoRefresh
showRefreshText={false}
hideShareModal
/>

View File

@@ -93,13 +93,9 @@ export default function HostsListTable({
const showHostsEmptyState =
!isFetching &&
!isLoading &&
formattedHostMetricsData.length === 0 &&
(!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) &&
!filters.items.length;
const showTableLoadingState =
(isLoading || isFetching) && formattedHostMetricsData.length === 0;
if (isError) {
return <Typography>{data?.error || 'Something went wrong'}</Typography>;
}
@@ -131,7 +127,7 @@ export default function HostsListTable({
);
}
if (showTableLoadingState) {
if (isLoading || isFetching) {
return (
<div className="hosts-list-loading-state">
<Skeleton.Input
@@ -159,7 +155,7 @@ export default function HostsListTable({
return (
<Table
className="hosts-list-table"
dataSource={showTableLoadingState ? [] : formattedHostMetricsData}
dataSource={isLoading || isFetching ? [] : formattedHostMetricsData}
columns={columns}
pagination={{
current: currentPage,
@@ -174,7 +170,7 @@ export default function HostsListTable({
}}
scroll={{ x: true }}
loading={{
spinning: showTableLoadingState,
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
tableLayout="fixed"

View File

@@ -28,7 +28,6 @@ describe('HostsListControls', () => {
<HostsListControls
handleFiltersChange={mockHandleFiltersChange}
filters={mockFilters}
showAutoRefresh={false}
/>,
);

View File

@@ -59,27 +59,13 @@ describe('HostsListTable', () => {
setPageSize: mockSetPageSize,
} as any;
it('renders loading state if isLoading is true and tableData is empty', () => {
const { container } = render(
<HostsListTable
{...mockProps}
isLoading
hostMetricsData={[]}
tableData={{ payload: { data: { hosts: [] } } }}
/>,
);
it('renders loading state if isLoading is true', () => {
const { container } = render(<HostsListTable {...mockProps} isLoading />);
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
});
it('renders loading state if isFetching is true and tableData is empty', () => {
const { container } = render(
<HostsListTable
{...mockProps}
isFetching
hostMetricsData={[]}
tableData={{ payload: { data: { hosts: [] } } }}
/>,
);
it('renders loading state if isFetching is true', () => {
const { container } = render(<HostsListTable {...mockProps} isFetching />);
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
});
@@ -89,17 +75,7 @@ describe('HostsListTable', () => {
});
it('renders empty state if no hosts are found', () => {
const { container } = render(
<HostsListTable
{...mockProps}
hostMetricsData={[]}
tableData={{
payload: {
data: { hosts: [] },
},
}}
/>,
);
const { container } = render(<HostsListTable {...mockProps} />);
expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
});
@@ -107,7 +83,6 @@ describe('HostsListTable', () => {
const { container } = render(
<HostsListTable
{...mockProps}
hostMetricsData={[]}
tableData={{
...mockTableData,
payload: {
@@ -115,7 +90,6 @@ describe('HostsListTable', () => {
data: {
...mockTableData.payload.data,
sentAnyHostMetricsData: false,
hosts: [],
},
},
}}
@@ -128,7 +102,6 @@ describe('HostsListTable', () => {
const { container } = render(
<HostsListTable
{...mockProps}
hostMetricsData={[]}
tableData={{
...mockTableData,
payload: {
@@ -136,7 +109,6 @@ describe('HostsListTable', () => {
data: {
...mockTableData.payload.data,
isSendingIncorrectK8SAgentMetrics: true,
hosts: [],
},
},
}}

View File

@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -85,12 +85,8 @@ function ClusterDetails({
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
selectedTime as Time,
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -199,11 +195,10 @@ function ClusterDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
setSelectedInterval(currentSelectedInterval as Time);
setSelectedInterval(selectedTime as Time);
if (currentSelectedInterval !== 'custom') {
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
if (selectedTime !== 'custom') {
const { maxTime, minTime } = GetMinMax(selectedTime);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -231,7 +226,6 @@ function ClusterDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -263,18 +257,16 @@ function ClusterDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
);
const paginationFilter = value?.items?.find(
(item) => item.key?.key === 'id',
);
const newFilters = value?.items?.filter(
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
const newFilters = value.items.filter(
(item) =>
item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME,
);
if (newFilters && newFilters?.length > 0) {
if (newFilters.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -287,8 +279,8 @@ function ClusterDetails({
op: 'AND',
items: filterDuplicateFilters(
[
...(primaryFilters || []),
...(newFilters || []),
...primaryFilters,
...newFilters,
...(paginationFilter ? [paginationFilter] : []),
].filter((item): item is TagFilterItem => item !== undefined),
),
@@ -312,11 +304,11 @@ function ClusterDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -329,10 +321,10 @@ function ClusterDetails({
op: 'AND',
items: filterDuplicateFilters(
[
...(primaryFilters || []),
...(value?.items?.filter(
...primaryFilters,
...value.items.filter(
(item) => item.key?.key !== QUERY_KEYS.K8S_CLUSTER_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
),
};
@@ -355,14 +347,14 @@ function ClusterDetails({
const handleChangeEventsFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setEventsFilters((prevFilters) => {
const clusterKindFilter = prevFilters?.items?.find(
const clusterKindFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
);
const clusterNameFilter = prevFilters?.items?.find(
const clusterNameFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -377,11 +369,11 @@ function ClusterDetails({
[
clusterKindFilter,
clusterNameFilter,
...(value?.items?.filter(
...value.items.filter(
(item) =>
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
),
};
@@ -420,9 +412,7 @@ function ClusterDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logsAndTracesFilters,
items:
logsAndTracesFilters?.items?.filter((item) => item.key?.key !== 'id') ||
[],
items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -472,7 +462,6 @@ function ClusterDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -51,8 +51,8 @@ export const getClusterMetricsQueryPayload = (
const getKey = (dotKey: string, underscoreKey: string): string =>
dotMetricsEnabled ? dotKey : underscoreKey;
const k8sPodCpuUtilizationKey = getKey(
'k8s.pod.cpu.usage',
'k8s_pod_cpu_usage',
'k8s.pod.cpu.utilization',
'k8s_pod_cpu_utilization',
);
const k8sNodeAllocatableCpuKey = getKey(
'k8s.node.allocatable_cpu',
@@ -146,7 +146,7 @@ export const getClusterMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sPodCpuUtilizationKey,
@@ -189,7 +189,7 @@ export const getClusterMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sPodCpuUtilizationKey,
@@ -232,7 +232,7 @@ export const getClusterMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sPodCpuUtilizationKey,
@@ -731,7 +731,7 @@ export const getClusterMetricsQueryPayload = (
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TABLE,
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
@@ -751,7 +751,7 @@ export const getClusterMetricsQueryPayload = (
filters: {
items: [
{
id: 'a7da59c7',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -786,12 +786,12 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'available',
legend: `{{${k8sDeploymentNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
},
@@ -804,14 +804,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sDeploymentDesiredKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: '55110885',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -846,14 +846,14 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'desired',
legend: `{{${k8sDeploymentNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'avg',
timeAggregation: 'latest',
},
],
queryFormulas: [],
@@ -890,13 +890,13 @@ export const getClusterMetricsQueryPayload = (
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: true,
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TABLE,
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
@@ -909,14 +909,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sStatefulsetCurrentPodsKey,
type: 'Gauge',
},
aggregateOperator: 'max',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '3c57b4d1',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -951,14 +951,14 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'current',
legend: `{{${k8sStatefulsetNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'max',
timeAggregation: 'latest',
},
{
aggregateAttribute: {
@@ -969,14 +969,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sStatefulsetDesiredPodsKey,
type: 'Gauge',
},
aggregateOperator: 'max',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: '0f49fe64',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1011,14 +1011,14 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'desired',
legend: `{{${k8sStatefulsetNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'max',
timeAggregation: 'latest',
},
{
aggregateAttribute: {
@@ -1029,14 +1029,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sStatefulsetReadyPodsKey,
type: 'Gauge',
},
aggregateOperator: 'max',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'C',
filters: {
items: [
{
id: '0bebf625',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1071,14 +1071,14 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'ready',
legend: `{{${k8sStatefulsetNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'max',
timeAggregation: 'latest',
},
{
aggregateAttribute: {
@@ -1089,14 +1089,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sStatefulsetUpdatedPodsKey,
type: 'Gauge',
},
aggregateOperator: 'max',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'D',
filters: {
items: [
{
id: '1ddacbbe',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1131,14 +1131,14 @@ export const getClusterMetricsQueryPayload = (
},
],
having: [],
legend: 'updated',
legend: `{{${k8sStatefulsetNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: 'last',
spaceAggregation: 'sum',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'max',
timeAggregation: 'latest',
},
],
queryFormulas: [],
@@ -1199,13 +1199,13 @@ export const getClusterMetricsQueryPayload = (
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: true,
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TABLE,
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
@@ -1218,14 +1218,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetCurrentScheduledNodesKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: 'e0bea554',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1250,16 +1250,24 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: 'current_nodes',
legend: `{{${k8sDaemonsetNameKey}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
spaceAggregation: 'avg',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'avg',
timeAggregation: 'latest',
},
{
aggregateAttribute: {
@@ -1270,14 +1278,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetDesiredScheduledNodesKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: '741052f7',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1302,16 +1310,24 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: 'desired_nodes',
legend: `{{${k8sDaemonsetNameKey}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'last',
spaceAggregation: 'avg',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'avg',
timeAggregation: 'latest',
},
{
aggregateAttribute: {
@@ -1322,14 +1338,14 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetReadyNodesKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'C',
filters: {
items: [
{
id: 'f23759f2',
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
@@ -1354,16 +1370,24 @@ export const getClusterMetricsQueryPayload = (
key: k8sDaemonsetNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: 'ready_nodes',
legend: `{{${k8sDaemonsetNameKey}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: 'last',
spaceAggregation: 'avg',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'avg',
timeAggregation: 'latest',
},
],
queryFormulas: [],
@@ -1412,7 +1436,316 @@ export const getClusterMetricsQueryPayload = (
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: true,
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_active_pods--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sJobActivePodsKey,
type: 'Gauge',
},
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
type: 'tag',
},
op: '=',
value: cluster.meta.k8s_cluster_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: `{{${k8sJobNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
},
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_successful_pods--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sJobSuccessfulPodsKey,
type: 'Gauge',
},
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
type: 'tag',
},
op: '=',
value: cluster.meta.k8s_cluster_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: `{{${k8sJobNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
},
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_failed_pods--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sJobFailedPodsKey,
type: 'Gauge',
},
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'C',
filters: {
items: [
{
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
type: 'tag',
},
op: '=',
value: cluster.meta.k8s_cluster_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: `{{${k8sJobNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'C',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
},
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_desired_successful_pods--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sJobDesiredSuccessfulPodsKey,
type: 'Gauge',
},
aggregateOperator: 'latest',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'D',
filters: {
items: [
{
id: 'd7779183',
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
type: 'tag',
},
op: '=',
value: cluster.meta.k8s_cluster_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
],
having: [],
legend: `{{${k8sJobNameKey}}} ({{${k8sNamespaceNameKey}})`,
limit: null,
orderBy: [],
queryName: 'D',
reduceTo: 'avg',
spaceAggregation: 'max',
stepInterval: 60,
timeAggregation: 'latest',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
{
disabled: false,
legend: '',
name: 'B',
query: '',
},
{
disabled: false,
legend: '',
name: 'C',
query: '',
},
{
disabled: false,
legend: '',
name: 'D',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
{
disabled: false,
legend: '',
name: 'B',
query: '',
},
{
disabled: false,
legend: '',
name: 'C',
query: '',
},
{
disabled: false,
legend: '',
name: 'D',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
},
@@ -1444,7 +1777,7 @@ export const getClusterMetricsQueryPayload = (
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
key: 'k8s_cluster_name',
type: 'tag',
},
op: '=',
@@ -1504,7 +1837,7 @@ export const getClusterMetricsQueryPayload = (
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
key: 'k8s_cluster_name',
type: 'tag',
},
op: '=',
@@ -1564,7 +1897,7 @@ export const getClusterMetricsQueryPayload = (
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
key: 'k8s_cluster_name',
type: 'tag',
},
op: '=',
@@ -1624,7 +1957,7 @@ export const getClusterMetricsQueryPayload = (
id: 'k8s_cluster_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sClusterNameKey,
key: 'k8s_cluster_name',
type: 'tag',
},
op: '=',
@@ -1672,24 +2005,6 @@ export const getClusterMetricsQueryPayload = (
name: 'A',
query: '',
},
{
disabled: false,
legend: '',
name: 'B',
query: '',
},
{
disabled: false,
legend: '',
name: 'C',
query: '',
},
{
disabled: false,
legend: '',
name: 'D',
query: '',
},
],
id: v4(),
promql: [
@@ -1699,24 +2014,6 @@ export const getClusterMetricsQueryPayload = (
name: 'A',
query: '',
},
{
disabled: false,
legend: '',
name: 'B',
query: '',
},
{
disabled: false,
legend: '',
name: 'C',
query: '',
},
{
disabled: false,
legend: '',
name: 'D',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},

View File

@@ -189,32 +189,6 @@ function K8sClustersList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedClusterName) {
return [
'clusterList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'clusterList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedClusterName,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -224,7 +198,7 @@ function K8sClustersList({
} = useGetK8sClustersList(
fetchGroupedByRowDataQuery as K8sClustersListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['clusterList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -280,44 +254,11 @@ function K8sClustersList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const queryKey = useMemo(() => {
if (selectedClusterName) {
return [
'clusterList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'clusterList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedClusterName,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
query as K8sClustersListPayload,
{
queryKey,
queryKey: ['clusterList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -404,7 +345,7 @@ function K8sClustersList({
setFiltersInitialised(true);
}
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
category: InfraMonitoringEvents.Cluster,
@@ -642,9 +583,6 @@ function K8sClustersList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedClustersData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -657,13 +595,12 @@ function K8sClustersList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.NODES}
showAutoRefresh={!selectedClusterData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table
className="k8s-list-table clusters-list-table"
dataSource={showTableLoadingState ? [] : formattedClustersData}
dataSource={isFetching || isLoading ? [] : formattedClustersData}
columns={columns}
pagination={{
current: currentPage,
@@ -675,25 +612,26 @@ function K8sClustersList({
}}
scroll={{ x: true }}
loading={{
spinning: showTableLoadingState,
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
locale={{
emptyText: showTableLoadingState ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
emptyText:
isFetching || isLoading ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again!
</Typography.Text>
<Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div>
</div>
</div>
),
),
}}
tableLayout="fixed"
onChange={handleTableChange}

View File

@@ -33,7 +33,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -84,12 +84,8 @@ function DaemonSetDetails({
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
selectedTime as Time,
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -215,11 +211,10 @@ function DaemonSetDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
setSelectedInterval(currentSelectedInterval as Time);
setSelectedInterval(selectedTime as Time);
if (currentSelectedInterval !== 'custom') {
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
if (selectedTime !== 'custom') {
const { maxTime, minTime } = GetMinMax(selectedTime);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -247,7 +242,6 @@ function DaemonSetDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -279,21 +273,19 @@ function DaemonSetDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
),
);
const paginationFilter = value?.items?.find(
(item) => item.key?.key === 'id',
);
const newFilters = value?.items?.filter(
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
const newFilters = value.items.filter(
(item) =>
item.key?.key !== 'id' &&
item.key?.key !== QUERY_KEYS.K8S_DAEMON_SET_NAME,
);
if (newFilters && newFilters?.length > 0) {
if (newFilters.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -305,8 +297,8 @@ function DaemonSetDetails({
const updatedFilters = {
op: 'AND',
items: [
...(primaryFilters || []),
...(newFilters || []),
...primaryFilters,
...newFilters,
...(paginationFilter ? [paginationFilter] : []),
].filter((item): item is TagFilterItem => item !== undefined),
};
@@ -328,13 +320,13 @@ function DaemonSetDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
),
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -346,10 +338,10 @@ function DaemonSetDetails({
const updatedFilters = {
op: 'AND',
items: [
...(primaryFilters || []),
...(value?.items?.filter(
...primaryFilters,
...value.items.filter(
(item) => item.key?.key !== QUERY_KEYS.K8S_DAEMON_SET_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
};
@@ -371,14 +363,14 @@ function DaemonSetDetails({
const handleChangeEventsFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setEventsFilters((prevFilters) => {
const daemonSetKindFilter = prevFilters?.items?.find(
const daemonSetKindFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
);
const daemonSetNameFilter = prevFilters?.items?.find(
const daemonSetNameFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -392,11 +384,11 @@ function DaemonSetDetails({
items: [
daemonSetKindFilter,
daemonSetNameFilter,
...(value?.items?.filter(
...value.items.filter(
(item) =>
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
};
@@ -434,7 +426,7 @@ function DaemonSetDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logAndTracesFilters,
items: logAndTracesFilters?.items?.filter((item) => item.key?.key !== 'id'),
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -484,7 +476,6 @@ function DaemonSetDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -33,8 +33,8 @@ export const getDaemonSetMetricsQueryPayload = (
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sPodCpuUtilizationKey = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const k8sContainerCpuRequestKey = dotMetricsEnabled
? 'k8s.container.cpu_request'
@@ -84,7 +84,7 @@ export const getDaemonSetMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sPodCpuUtilizationKey,

View File

@@ -191,32 +191,6 @@ function K8sDaemonSetsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedDaemonSetUID) {
return [
'daemonSetList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'daemonSetList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedDaemonSetUID,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -226,7 +200,7 @@ function K8sDaemonSetsList({
} = useGetK8sDaemonSetsList(
fetchGroupedByRowDataQuery as K8sDaemonSetsListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['daemonSetList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -277,44 +251,11 @@ function K8sDaemonSetsList({
[groupedByRowData, groupBy],
);
const queryKey = useMemo(() => {
if (selectedDaemonSetUID) {
return [
'daemonSetList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'daemonSetList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedDaemonSetUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sDaemonSetsList(
query as K8sDaemonSetsListPayload,
{
queryKey,
queryKey: ['daemonSetList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -408,7 +349,7 @@ function K8sDaemonSetsList({
setFiltersInitialised(true);
}
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.ListPage,
@@ -650,9 +591,6 @@ function K8sDaemonSetsList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedDaemonSetsData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -665,7 +603,6 @@ function K8sDaemonSetsList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.DAEMONSETS}
showAutoRefresh={!selectedDaemonSetData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
@@ -673,7 +610,7 @@ function K8sDaemonSetsList({
className={classNames('k8s-list-table', 'daemonSets-list-table', {
'expanded-daemonsets-list-table': isGroupedByAttribute,
})}
dataSource={showTableLoadingState ? [] : formattedDaemonSetsData}
dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
columns={columns}
pagination={{
current: currentPage,
@@ -685,25 +622,26 @@ function K8sDaemonSetsList({
}}
scroll={{ x: true }}
loading={{
spinning: showTableLoadingState,
spinning: isFetching || isLoading,
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
}}
locale={{
emptyText: showTableLoadingState ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
emptyText:
isFetching || isLoading ? null : (
<div className="no-filtered-hosts-message-container">
<div className="no-filtered-hosts-message-content">
<img
src="/Icons/emptyState.svg"
alt="thinking-emoji"
className="empty-state-svg"
/>
<Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again!
</Typography.Text>
<Typography.Text className="no-filtered-hosts-message">
This query had no results. Edit your query and try again!
</Typography.Text>
</div>
</div>
</div>
),
),
}}
tableLayout="fixed"
onChange={handleTableChange}

View File

@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -88,12 +88,8 @@ function DeploymentDetails({
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
selectedTime as Time,
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -219,11 +215,10 @@ function DeploymentDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
setSelectedInterval(currentSelectedInterval as Time);
setSelectedInterval(selectedTime as Time);
if (currentSelectedInterval !== 'custom') {
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
if (selectedTime !== 'custom') {
const { maxTime, minTime } = GetMinMax(selectedTime);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -251,7 +246,6 @@ function DeploymentDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -283,21 +277,19 @@ function DeploymentDetails({
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
),
);
const paginationFilter = value?.items?.find(
(item) => item.key?.key === 'id',
);
const newFilters = value?.items?.filter(
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
const newFilters = value.items.filter(
(item) =>
item.key?.key !== 'id' &&
item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME,
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -310,8 +302,8 @@ function DeploymentDetails({
op: 'AND',
items: filterDuplicateFilters(
[
...(primaryFilters || []),
...(newFilters || []),
...primaryFilters,
...newFilters,
...(paginationFilter ? [paginationFilter] : []),
].filter((item): item is TagFilterItem => item !== undefined),
),
@@ -335,13 +327,13 @@ function DeploymentDetails({
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters?.items?.filter((item) =>
const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '',
),
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -354,10 +346,10 @@ function DeploymentDetails({
op: 'AND',
items: filterDuplicateFilters(
[
...(primaryFilters || []),
...(value?.items?.filter(
...primaryFilters,
...value.items.filter(
(item) => item.key?.key !== QUERY_KEYS.K8S_DEPLOYMENT_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
),
};
@@ -380,14 +372,14 @@ function DeploymentDetails({
const handleChangeEventsFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setEventsFilters((prevFilters) => {
const deploymentKindFilter = prevFilters?.items?.find(
const deploymentKindFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
);
const deploymentNameFilter = prevFilters?.items?.find(
const deploymentNameFilter = prevFilters.items.find(
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_NAME,
);
if (value?.items && value?.items?.length > 0) {
if (value.items.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
@@ -402,11 +394,11 @@ function DeploymentDetails({
[
deploymentKindFilter,
deploymentNameFilter,
...(value?.items?.filter(
...value.items.filter(
(item) =>
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
) || []),
),
].filter((item): item is TagFilterItem => item !== undefined),
),
};
@@ -445,7 +437,7 @@ function DeploymentDetails({
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logAndTracesFilters,
items: logAndTracesFilters?.items?.filter((item) => item.key?.key !== 'id'),
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
};
const compositeQuery = {
@@ -495,7 +487,6 @@ function DeploymentDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

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