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
239 changed files with 4475 additions and 8799 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.88.1
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.88.1
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.88.1}
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.88.1}
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

@@ -0,0 +1,154 @@
# QuerySearch Component Documentation
## Overview
The QuerySearch component is a sophisticated query builder interface that allows users to construct complex search queries with real-time validation and autocomplete functionality.
## Dependencies
```typescript
// Core UI
import { Card, Collapse, Space, Tag, Typography } from 'antd';
// Code Editor
import {
autocompletion,
CompletionContext,
CompletionResult,
startCompletion,
} from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript';
import { ViewPlugin, ViewUpdate } from '@codemirror/view';
import { copilot } from '@uiw/codemirror-theme-copilot';
import CodeMirror, { EditorView, Extension } from '@uiw/react-codemirror';
// Custom Hooks and Utilities
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils';
import { getQueryContextAtCursor } from 'utils/queryContextUtils';
```
## Key Features
1. Real-time query validation
2. Context-aware autocompletion
3. Support for various query operators (=, !=, IN, LIKE, etc.)
4. Support for complex conditions with AND/OR operators
5. Support for functions (HAS, HASANY, HASALL)
6. Support for parentheses and nested conditions
7. Query examples for common use cases
## State Management
```typescript
const [query, setQuery] = useState<string>('');
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
const [activeKey, setActiveKey] = useState<string>('');
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
const [queryContext, setQueryContext] = useState<IQueryContext | null>(null);
const [validation, setValidation] = useState<IValidationResult>({...});
const [editingMode, setEditingMode] = useState<'key' | 'operator' | 'value' | 'conjunction' | 'function' | 'parenthesis' | 'bracketList' | null>(null);
```
## Core Functions
### 1. Autocomplete Handler
```typescript
function myCompletions(context: CompletionContext): CompletionResult | null {
// Handles autocomplete suggestions based on context
// Supports different contexts: key, operator, value, function, etc.
}
```
### 2. Value Suggestions Fetcher
```typescript
const fetchValueSuggestions = useCallback(
async (key: string): Promise<void> => {
// Fetches value suggestions for a given key
// Handles loading states and error cases
},
[activeKey, isLoadingSuggestions],
);
```
### 3. Query Change Handler
```typescript
const handleQueryChange = useCallback(async (newQuery: string) => {
// Updates query and validates it
// Handles validation errors
}, []);
```
## Query Context Types
1. Key context: When editing a field name
2. Operator context: When selecting an operator
3. Value context: When entering a value
4. Conjunction context: When using AND/OR
5. Function context: When using functions
6. Parenthesis context: When using parentheses
7. Bracket list context: When using IN operator
## Example Queries
```typescript
const queryExamples = [
{ label: 'Basic Query', query: "status = 'error'" },
{ label: 'Multiple Conditions', query: "status = 'error' AND service = 'frontend'" },
{ label: 'IN Operator', query: "status IN ['error', 'warning']" },
{ label: 'Function Usage', query: "HAS(service, 'frontend')" },
{ label: 'Numeric Comparison', query: 'duration > 1000' },
// ... more examples
];
```
## Performance Optimizations
1. Uses `useCallback` for memoized functions
2. Tracks component mount state to prevent updates after unmount
3. Debounces suggestion fetching
4. Caches key suggestions
## Error Handling
```typescript
try {
const validationResponse = validateQuery(newQuery);
setValidation(validationResponse);
} catch (error) {
setValidation({
isValid: false,
message: 'Failed to process query',
errors: [error as IDetailedError],
});
}
```
## Usage Example
```typescript
<QuerySearch />
```
## Styling
- Uses SCSS for styling
- Custom classes for different components
- Theme integration with CodeMirror
## Best Practices
1. Always validate queries before submission
2. Handle loading states appropriately
3. Provide clear error messages
4. Use appropriate operators for different data types
5. Consider performance implications of complex queries
## Common Issues and Solutions
1. Query validation errors
- Check syntax and operator usage
- Verify data types match operator requirements
2. Performance issues
- Optimize suggestion fetching
- Cache frequently used values
3. UI/UX issues
- Ensure clear error messages
- Provide helpful suggestions
- Show appropriate loading states
## Future Improvements
1. Add more query examples
2. Enhance error messages
3. Improve performance for large datasets
4. Add more operator support
5. Enhance UI/UX features

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

@@ -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:
@@ -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:
@@ -164,9 +158,6 @@ export function parseAggregations(
export function createAggregation(
queryData: any,
): TraceAggregation[] | LogAggregation[] | MetricAggregation[] {
if (!queryData) {
return [];
}
if (queryData.dataSource === DataSource.METRICS) {
return [
{
@@ -253,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
},
}),
@@ -328,7 +319,6 @@ export const prepareQueryRangePayloadV5 = ({
start: startTime,
end: endTime,
formatForWeb,
originalGraphType,
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
let legendMap: Record<string, string> = {};
const requestType = mapPanelTypeToRequestType(graphType);
@@ -392,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

@@ -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

@@ -194,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}
@@ -203,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) {
@@ -362,7 +356,6 @@ function HostMetricsDetails({
const handleClose = (): void => {
setSelectedInterval(selectedTime as Time);
lastSelectedInterval.current = null;
setSearchParams({});
if (selectedTime !== 'custom') {

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

@@ -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;
}
}
}

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';
@@ -37,11 +36,6 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
entityVersion: version,
});
const isHistogram = useMemo(
() => query.aggregateAttribute.type === ATTRIBUTE_TYPES.HISTOGRAM,
[query.aggregateAttribute.type],
);
useEffect(() => {
setAggregationOptions([
{
@@ -83,142 +77,90 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
!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={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
</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}${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 group-by-filter-container">
<GroupByFilter
disabled={!query.aggregateAttribute.key}
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}${query.spaceAggregation}${query.timeAggregation}`}
aggregatorAttributeType={
query?.aggregateAttribute.type as ATTRIBUTE_TYPES
}
selectedValue={query.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={!query.aggregateAttribute.key}
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

@@ -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;
}
}
}
}
}
}
@@ -323,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,13 +25,12 @@ import CodeMirror, {
ViewPlugin,
ViewUpdate,
} from '@uiw/react-codemirror';
import { Button, Popover } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
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';
@@ -142,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();
@@ -164,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(() => {
@@ -217,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);
@@ -297,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
: 'number';
return getKeySuggestions({
signal: queryData.dataSource,
() =>
getAggregateAttribute({
searchText: '',
fieldDataType,
});
},
aggregateOperator: functionContextForFetch as string,
dataSource: queryData.dataSource,
}),
{
enabled:
!!functionContextForFetch &&
@@ -323,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(
() =>
@@ -411,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,
@@ -445,8 +358,8 @@ function QueryAggregationSelect({
safeStartCompletion();
}, 50);
},
}));
}) || [],
}),
) || [],
[aggregateAttributeData, safeStartCompletion],
);
@@ -459,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 &&
@@ -572,14 +470,7 @@ function QueryAggregationSelect({
maxRenderedOptions: 50,
activateOnTyping: true,
}),
[
operatorCompletions,
isLoadingFields,
fieldSuggestions,
functionArgPairs,
maxAggregations,
validFunctions,
],
[operatorCompletions, isLoadingFields, fieldSuggestions, functionArgPairs],
);
return (
@@ -590,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,
@@ -609,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,
@@ -635,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

@@ -1,4 +1,3 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './QuerySearch.styles.scss';
import { CheckCircleFilled } from '@ant-design/icons';
@@ -13,12 +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 { isNull } from 'lodash-es';
import { TriangleAlert } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import {
@@ -29,20 +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 {
negationQueryOperatorSuggestions,
QUERY_BUILDER_KEY_TYPES,
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE,
queryOperatorSuggestions,
} from 'constants/antlrQueryConstants';
validateQuery,
} from 'utils/antlrQueryUtils';
import {
getCurrentValueIndexAtCursor,
getQueryContextAtCursor,
getCurrentValueIndexAtCursor,
} from 'utils/queryContextUtils';
import { queryExamples } from './constants';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { isNull } from 'lodash-es';
const { Panel } = Collapse;
@@ -119,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,
@@ -168,13 +154,7 @@ function QuerySearch({
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);
}
};
@@ -258,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([
@@ -292,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
}
@@ -316,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
@@ -330,7 +287,6 @@ function QuerySearch({
.map((value: string) => ({
label: value,
type: 'value',
apply: value,
}));
// Generate options from number values
@@ -342,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
@@ -376,6 +331,7 @@ function QuerySearch({
}
}, 10);
}
setIsLoadingSuggestions(false);
}
} catch (error) {
console.error('Error fetching suggestions:', error);
@@ -388,10 +344,8 @@ function QuerySearch({
apply: (): boolean => false, // Prevent selection
},
]);
setIsLoadingSuggestions(false);
}
} finally {
setIsLoadingSuggestions(false);
setIsFetchingCompleteValuesList(false);
}
},
[activeKey, dataSource, isLoadingSuggestions],
@@ -521,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
@@ -578,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) {
@@ -590,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,
@@ -608,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,
@@ -626,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);
}
@@ -665,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;
}
@@ -783,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,
}));
}
}
@@ -855,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;
}
@@ -1076,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]);
@@ -1142,43 +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 => {
return 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 => {
return true;
},
},
]),
),
keymap.of([
...completionKeymap,
{
key: 'Escape',
run: closeCompletion,
},
]),
]}
placeholder="Enter your query (e.g., status = 'error' AND service = 'frontend')"
basicSetup={{

View File

@@ -1,17 +1,10 @@
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
Having,
IBuilderQuery,
Query,
TagFilter,
} 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';
/**
@@ -190,81 +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;
}

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

@@ -179,7 +179,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
sourceNames: alphabet,
}),
disabled: false,
stepInterval: undefined,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],

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();
@@ -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

@@ -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

@@ -63,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,
@@ -97,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 => {
@@ -157,7 +163,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryFn: (): Promise<SuccessResponse<ChangelogSchema> | ErrorResponse> =>
getChangelogByVersion(latestVersion),
queryKey: ['getChangelogByVersion', latestVersion],
enabled: isLoggedIn && !isCloudUserVal && Boolean(latestVersion),
enabled: isLoggedIn && !isCloudUserVal && shouldFetchChangelog,
},
]);
@@ -217,7 +223,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (
getUserVersionResponse.isFetched &&
getUserVersionResponse.isSuccess &&
getUserLatestVersionResponse.isSuccess &&
getUserVersionResponse.data &&
getUserVersionResponse.data.payload
) {
@@ -255,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 &&
@@ -602,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{' '}

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

@@ -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,
@@ -256,8 +254,6 @@ function ChartPreview({
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
currentQuery,
query: query || currentQuery,
}),
[
yAxisUnit,
@@ -273,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

@@ -137,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,6 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { ColumnsType, ColumnType } from 'antd/es/table';
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
import { convertUnit } from 'container/NewWidget/RightContainer/dataFormatCategories';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
@@ -184,19 +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,
)?.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,
},
);
@@ -242,7 +212,6 @@ function HostsList(): JSX.Element {
<HostsListControls
filters={filters}
handleFiltersChange={handleFiltersChange}
showAutoRefresh={!selectedHostData}
/>
</div>
<HostsListTable

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(
@@ -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) {
@@ -468,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,
@@ -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) {
@@ -482,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,
@@ -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) {
@@ -493,7 +487,6 @@ function DeploymentDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -33,8 +33,8 @@ export const getDeploymentMetricsQueryPayload = (
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'
@@ -80,7 +80,7 @@ export const getDeploymentMetricsQueryPayload = (
{
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

@@ -192,32 +192,6 @@ function K8sDeploymentsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedDeploymentUID) {
return [
'deploymentList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'deploymentList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedDeploymentUID,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -227,7 +201,7 @@ function K8sDeploymentsList({
} = useGetK8sDeploymentsList(
fetchGroupedByRowDataQuery as K8sDeploymentsListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -278,44 +252,11 @@ function K8sDeploymentsList({
[groupedByRowData, groupBy],
);
const queryKey = useMemo(() => {
if (selectedDeploymentUID) {
return [
'deploymentList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'deploymentList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedDeploymentUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList(
query as K8sDeploymentsListPayload,
{
queryKey,
queryKey: ['deploymentList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -655,9 +596,6 @@ function K8sDeploymentsList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedDeploymentsData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -670,7 +608,6 @@ function K8sDeploymentsList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.NODES}
showAutoRefresh={!selectedDeploymentData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
@@ -678,7 +615,7 @@ function K8sDeploymentsList({
className={classNames('k8s-list-table', 'deployments-list-table', {
'expanded-deployments-list-table': isGroupedByAttribute,
})}
dataSource={showTableLoadingState ? [] : formattedDeploymentsData}
dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
columns={columns}
pagination={{
current: currentPage,
@@ -690,25 +627,26 @@ function K8sDeploymentsList({
}}
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

@@ -270,7 +270,7 @@ export default function Events({
</div>
</div>
{isLoading && formattedEntityEvents.length === 0 && <LoadingContainer />}
{isLoading && <LoadingContainer />}
{!isLoading && !isError && formattedEntityEvents.length === 0 && (
<EntityDetailsEmptyContainer category={category} view="events" />

View File

@@ -15,7 +15,6 @@ 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 {
@@ -25,13 +24,12 @@ import {
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';
import { Options } from 'uplot';
import { FeatureKeys } from '../../../../constants/features';
import { useMultiIntersectionObserver } from '../../../../hooks/useMultiIntersectionObserver';
import { useAppContext } from '../../../../providers/App/App';
interface EntityMetricsProps<T> {
@@ -75,12 +73,6 @@ function EntityMetrics<T>({
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const {
visibilities,
setElement,
} = useMultiIntersectionObserver(entityWidgetInfo.length, { threshold: 0.1 });
const queryPayloads = useMemo(
() =>
getEntityQueryPayload(
@@ -99,22 +91,17 @@ function EntityMetrics<T>({
);
const queries = useQueries(
queryPayloads.map((payload, index) => ({
queryPayloads.map((payload) => ({
queryKey: [queryKey, payload, ENTITY_VERSION_V4, category],
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(
() =>
@@ -183,7 +170,6 @@ function EntityMetrics<T>({
minTimeScale: graphTimeIntervals[idx].start,
maxTimeScale: graphTimeIntervals[idx].end,
onDragSelect: (start, end) => onDragSelect(start, end, idx),
query: currentQuery,
});
}),
[
@@ -193,7 +179,6 @@ function EntityMetrics<T>({
entityWidgetInfo,
graphTimeIntervals,
onDragSelect,
currentQuery,
],
);
@@ -201,7 +186,7 @@ function EntityMetrics<T>({
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
idx: number,
): JSX.Element => {
if ((!query.data && query.isLoading) || !visibilities[idx]) {
if (query.isLoading) {
return <Skeleton />;
}
@@ -211,7 +196,7 @@ function EntityMetrics<T>({
return <div>{errorMessage}</div>;
}
const panelType = (query.data?.params as any)?.compositeQuery?.panelType;
const { panelType } = (query.data?.params as any).compositeQuery;
return (
<div
@@ -249,7 +234,7 @@ function EntityMetrics<T>({
</div>
<Row gutter={24} className="entity-metrics-container">
{queries.map((query, idx) => (
<Col ref={setElement(idx)} span={12} key={entityWidgetInfo[idx].title}>
<Col span={12} key={entityWidgetInfo[idx].title}>
<Typography.Text>{entityWidgetInfo[idx].title}</Typography.Text>
<Card bordered className="entity-metrics-card" ref={graphRef}>
{renderCardContent(query, idx)}

View File

@@ -203,7 +203,7 @@ function EntityTraces({
{!isError && traces.length > 0 && (
<div className="entity-traces-table">
<TraceExplorerControls
isLoading={isFetching && traces.length === 0}
isLoading={isFetching}
totalCount={totalCount}
perPageOptions={PER_PAGE_OPTIONS}
showSizeChanger={false}
@@ -212,7 +212,7 @@ function EntityTraces({
tableLayout="fixed"
pagination={false}
scroll={{ x: true }}
loading={isFetching && traces.length === 0}
loading={isFetching}
dataSource={traces}
columns={traceListColumns}
onRow={(): Record<string, unknown> => ({

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';
@@ -81,12 +81,8 @@ function JobDetails({
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();
@@ -208,11 +204,10 @@ function JobDetails({
}, [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),
@@ -240,7 +235,6 @@ function JobDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -475,7 +469,6 @@ function JobDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -33,8 +33,8 @@ export const getJobMetricsQueryPayload = (
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sPodCpuUtilizationKey = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const k8sPodMemoryUsageKey = dotMetricsEnabled
? 'k8s.pod.memory.usage'
: 'k8s_pod_memory_usage';
@@ -59,7 +59,7 @@ export const getJobMetricsQueryPayload = (
{
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

@@ -186,25 +186,6 @@ function K8sJobsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedJobUID) {
return [
'jobList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'jobList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [queryFilters, orderBy, selectedJobUID, minTime, maxTime, selectedRowData]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -214,7 +195,7 @@ function K8sJobsList({
} = useGetK8sJobsList(
fetchGroupedByRowDataQuery as K8sJobsListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['jobList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -270,44 +251,11 @@ function K8sJobsList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const queryKey = useMemo(() => {
if (selectedJobUID) {
return [
'jobList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'jobList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedJobUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sJobsList(
query as K8sJobsListPayload,
{
queryKey,
queryKey: ['jobList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -633,7 +581,6 @@ function K8sJobsList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.JOBS}
showAutoRefresh={!selectedJobData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}

View File

@@ -30,7 +30,6 @@ interface K8sHeaderProps {
handleFilterVisibilityChange: () => void;
isFiltersVisible: boolean;
entity: K8sCategory;
showAutoRefresh: boolean;
}
function K8sHeader({
@@ -47,7 +46,6 @@ function K8sHeader({
handleFilterVisibilityChange,
isFiltersVisible,
entity,
showAutoRefresh,
}: K8sHeaderProps): JSX.Element {
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
@@ -138,7 +136,7 @@ function K8sHeader({
<div className="k8s-list-controls-right">
<DateTimeSelectionV2
showAutoRefresh={showAutoRefresh}
showAutoRefresh
showRefreshText={false}
hideShareModal
/>

View File

@@ -190,32 +190,6 @@ function K8sNamespacesList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedNamespaceUID) {
return [
'namespaceList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'namespaceList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedNamespaceUID,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -225,7 +199,7 @@ function K8sNamespacesList({
} = useGetK8sNamespacesList(
fetchGroupedByRowDataQuery as K8sNamespacesListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -276,44 +250,11 @@ function K8sNamespacesList({
[groupedByRowData, groupBy],
);
const queryKey = useMemo(() => {
if (selectedNamespaceUID) {
return [
'namespaceList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'namespaceList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedNamespaceUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList(
query as K8sNamespacesListPayload,
{
queryKey,
queryKey: ['namespaceList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -651,9 +592,6 @@ function K8sNamespacesList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedNamespacesData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -666,13 +604,12 @@ function K8sNamespacesList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.NODES}
showAutoRefresh={!selectedNamespaceData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table
className="k8s-list-table namespaces-list-table"
dataSource={showTableLoadingState ? [] : formattedNamespacesData}
dataSource={isFetching || isLoading ? [] : formattedNamespacesData}
columns={columns}
pagination={{
current: currentPage,
@@ -684,25 +621,26 @@ function K8sNamespacesList({
}}
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

@@ -35,7 +35,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 NamespaceDetails({
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 NamespaceDetails({
}, [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 NamespaceDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -467,7 +461,6 @@ function NamespaceDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -59,8 +59,8 @@ export const getNamespaceMetricsQueryPayload = (
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 k8sContainerCpuRequestKey = getKey(
'k8s.container.cpu_request',

View File

@@ -184,32 +184,6 @@ function K8sNodesList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedNodeUID) {
return [
'nodeList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'nodeList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedNodeUID,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -219,7 +193,7 @@ function K8sNodesList({
} = useGetK8sNodesList(
fetchGroupedByRowDataQuery as K8sNodesListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['nodeList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -275,44 +249,11 @@ function K8sNodesList({
[groupedByRowData, groupBy],
);
const queryKey = useMemo(() => {
if (selectedNodeUID) {
return [
'nodeList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'nodeList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedNodeUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sNodesList(
query as K8sNodesListPayload,
{
queryKey,
queryKey: ['nodeList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -630,9 +571,6 @@ function K8sNodesList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedNodesData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -645,13 +583,12 @@ function K8sNodesList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.NODES}
showAutoRefresh={!selectedNodeData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
<Table
className="k8s-list-table nodes-list-table"
dataSource={showTableLoadingState ? [] : formattedNodesData}
dataSource={isFetching || isLoading ? [] : formattedNodesData}
columns={columns}
pagination={{
current: currentPage,
@@ -663,25 +600,26 @@ function K8sNodesList({
}}
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';
@@ -85,12 +85,8 @@ function NodeDetails({
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 NodeDetails({
}, [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 NodeDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -470,7 +464,6 @@ function NodeDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -59,8 +59,8 @@ export const getNodeMetricsQueryPayload = (
const getKey = (dotKey: string, underscoreKey: string): string =>
dotMetricsEnabled ? dotKey : underscoreKey;
const k8sNodeCpuUtilizationKey = getKey(
'k8s.node.cpu.usage',
'k8s_node_cpu_usage',
'k8s.node.cpu.utilization',
'k8s_node_cpu_utilization',
);
const k8sNodeAllocatableCpuKey = getKey(
@@ -99,8 +99,8 @@ export const getNodeMetricsQueryPayload = (
);
const k8sPodCpuUtilizationKey = getKey(
'k8s.pod.cpu.usage',
'k8s_pod_cpu_usage',
'k8s.pod.cpu.utilization',
'k8s_pod_cpu_utilization',
);
const k8sPodMemoryUsageKey = getKey(
@@ -147,7 +147,7 @@ export const getNodeMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_cpu_usage--float64--Gauge--true',
id: 'k8s_node_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sNodeCpuUtilizationKey,
@@ -276,7 +276,7 @@ export const getNodeMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_cpu_usage--float64--Gauge--true',
id: 'k8s_node_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sNodeCpuUtilizationKey,
@@ -319,7 +319,7 @@ export const getNodeMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_cpu_usage--float64--Gauge--true',
id: 'k8s_node_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sNodeCpuUtilizationKey,
@@ -729,7 +729,7 @@ export const getNodeMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_cpu_usage--float64--Gauge--true',
id: 'k8s_node_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sNodeCpuUtilizationKey,
@@ -1079,7 +1079,7 @@ export const getNodeMetricsQueryPayload = (
{
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

@@ -205,44 +205,11 @@ function K8sPodsList({
return queryPayload;
}, [pageSize, currentPage, queryFilters, minTime, maxTime, orderBy, groupBy]);
const queryKey = useMemo(() => {
if (selectedPodUID) {
return [
'podList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'podList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedPodUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sPodsList(
query as K8sPodsListPayload,
{
queryKey,
queryKey: ['hostList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -294,25 +261,6 @@ function K8sPodsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedPodUID) {
return [
'podList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'podList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [queryFilters, orderBy, selectedPodUID, minTime, maxTime, selectedRowData]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -322,7 +270,7 @@ function K8sPodsList({
} = useGetK8sPodsList(
fetchGroupedByRowDataQuery as K8sPodsListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['hostList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -681,9 +629,6 @@ function K8sPodsList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedPodsData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -700,7 +645,6 @@ function K8sPodsList({
onAddColumn={handleAddColumn}
onRemoveColumn={handleRemoveColumn}
entity={K8sCategory.PODS}
showAutoRefresh={!selectedPodData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
@@ -708,7 +652,7 @@ function K8sPodsList({
className={classNames('k8s-list-table', {
'expanded-k8s-list-table': isGroupedByAttribute,
})}
dataSource={showTableLoadingState ? [] : formattedPodsData}
dataSource={isFetching || isLoading ? [] : formattedPodsData}
columns={columns}
pagination={{
current: currentPage,
@@ -719,25 +663,26 @@ function K8sPodsList({
onChange: onPaginationChange,
}}
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>
),
),
}}
scroll={{ x: true }}
tableLayout="fixed"

View File

@@ -39,7 +39,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';
@@ -89,12 +89,8 @@ function PodDetails({
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();
@@ -216,11 +212,10 @@ function PodDetails({
}, [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 / TimeRangeOffset),
@@ -248,7 +243,6 @@ function PodDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -491,7 +485,6 @@ function PodDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -72,7 +72,10 @@ export const getPodMetricsQueryPayload = (
dotMetricsEnabled ? dotKey : underscoreKey;
const k8sContainerNameKey = getKey('k8s.container.name', 'k8s_container_name');
const k8sPodCpuUtilKey = getKey('k8s.pod.cpu.usage', 'k8s_pod_cpu_usage');
const k8sPodCpuUtilKey = getKey(
'k8s.pod.cpu.utilization',
'k8s_pod_cpu_utilization',
);
const k8sPodCpuReqUtilKey = getKey(
'k8s.pod.cpu_request_utilization',
@@ -112,8 +115,8 @@ export const getPodMetricsQueryPayload = (
);
const containerCpuUtilKey = getKey(
'container.cpu.usage',
'container_cpu_usage',
'container.cpu.utilization',
'container_cpu_utilization',
);
const k8sContainerCpuRequestKey = getKey(
@@ -186,7 +189,7 @@ export const getPodMetricsQueryPayload = (
{
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: k8sPodCpuUtilKey,
@@ -242,7 +245,7 @@ export const getPodMetricsQueryPayload = (
{
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: k8sPodCpuUtilKey,
@@ -298,7 +301,7 @@ export const getPodMetricsQueryPayload = (
{
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: k8sPodCpuUtilKey,
@@ -1567,7 +1570,7 @@ export const getPodMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'container_cpu_usage--float64--Gauge--true',
id: 'container_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: containerCpuUtilKey,
@@ -1665,7 +1668,7 @@ export const getPodMetricsQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'container_cpu_usage--float64--Gauge--true',
id: 'container_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: containerCpuUtilKey,

View File

@@ -191,32 +191,6 @@ function K8sStatefulSetsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
const groupedByRowDataQueryKey = useMemo(() => {
if (selectedStatefulSetUID) {
return [
'statefulSetList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
];
}
return [
'statefulSetList',
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(selectedRowData),
String(minTime),
String(maxTime),
];
}, [
queryFilters,
orderBy,
selectedStatefulSetUID,
minTime,
maxTime,
selectedRowData,
]);
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -226,7 +200,7 @@ function K8sStatefulSetsList({
} = useGetK8sStatefulSetsList(
fetchGroupedByRowDataQuery as K8sStatefulSetsListPayload,
{
queryKey: groupedByRowDataQueryKey,
queryKey: ['statefulSetList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -282,44 +256,11 @@ function K8sStatefulSetsList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
const queryKey = useMemo(() => {
if (selectedStatefulSetUID) {
return [
'statefulSetList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
];
}
return [
'statefulSetList',
String(pageSize),
String(currentPage),
JSON.stringify(queryFilters),
JSON.stringify(orderBy),
JSON.stringify(groupBy),
String(minTime),
String(maxTime),
];
}, [
selectedStatefulSetUID,
pageSize,
currentPage,
queryFilters,
orderBy,
groupBy,
minTime,
maxTime,
]);
const { data, isFetching, isLoading, isError } = useGetK8sStatefulSetsList(
query as K8sStatefulSetsListPayload,
{
queryKey,
queryKey: ['statefulSetList', query],
enabled: !!query,
keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -651,9 +592,6 @@ function K8sStatefulSetsList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedStatefulSetsData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -666,7 +604,6 @@ function K8sStatefulSetsList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.STATEFULSETS}
showAutoRefresh={!selectedStatefulSetData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
@@ -674,7 +611,7 @@ function K8sStatefulSetsList({
className={classNames('k8s-list-table', 'statefulSets-list-table', {
'expanded-statefulsets-list-table': isGroupedByAttribute,
})}
dataSource={showTableLoadingState ? [] : formattedStatefulSetsData}
dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
columns={columns}
pagination={{
current: currentPage,
@@ -686,25 +623,26 @@ function K8sStatefulSetsList({
}}
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';
@@ -84,12 +84,8 @@ function StatefulSetDetails({
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 StatefulSetDetails({
}, [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 StatefulSetDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -483,7 +477,6 @@ function StatefulSetDetails({
};
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -49,8 +49,8 @@ export const getStatefulSetMetricsQueryPayload = (
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
const k8sPodCpuUtilKey = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const k8sContainerCpuRequestKey = dotMetricsEnabled
? 'k8s.container.cpu_request'
: 'k8s_container_cpu_request';

View File

@@ -574,9 +574,6 @@ function K8sVolumesList({
});
};
const showTableLoadingState =
(isFetching || isLoading) && formattedVolumesData.length === 0;
return (
<div className="k8s-list">
<K8sHeader
@@ -589,7 +586,6 @@ function K8sVolumesList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.NODES}
showAutoRefresh={!selectedVolumeData}
/>
{isError && <Typography>{data?.error || 'Something went wrong'}</Typography>}
@@ -597,7 +593,7 @@ function K8sVolumesList({
className={classNames('k8s-list-table', 'volumes-list-table', {
'expanded-volumes-list-table': isGroupedByAttribute,
})}
dataSource={showTableLoadingState ? [] : formattedVolumesData}
dataSource={isFetching || isLoading ? [] : formattedVolumesData}
columns={columns}
pagination={{
current: currentPage,
@@ -609,25 +605,26 @@ function K8sVolumesList({
}}
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

@@ -13,7 +13,7 @@ import {
import { useIsDarkMode } from 'hooks/useDarkMode';
import GetMinMax from 'lib/getMinMax';
import { 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 { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -44,12 +44,8 @@ function VolumeDetails({
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
selectedTime as Time,
);
const isDarkMode = useIsDarkMode();
@@ -66,11 +62,10 @@ function VolumeDetails({
}, [volume]);
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),
@@ -81,7 +76,6 @@ function VolumeDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -110,7 +104,6 @@ function VolumeDetails({
);
const handleClose = (): void => {
lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {

View File

@@ -38,28 +38,28 @@ export const K8sCategories = {
export const underscoreMap = {
[K8sCategory.HOSTS]: 'system_cpu_load_average_15m',
[K8sCategory.PODS]: 'k8s_pod_cpu_usage',
[K8sCategory.NODES]: 'k8s_node_cpu_usage',
[K8sCategory.NAMESPACES]: 'k8s_pod_cpu_usage',
[K8sCategory.CLUSTERS]: 'k8s_node_cpu_usage',
[K8sCategory.DEPLOYMENTS]: 'k8s_pod_cpu_usage',
[K8sCategory.STATEFULSETS]: 'k8s_pod_cpu_usage',
[K8sCategory.DAEMONSETS]: 'k8s_pod_cpu_usage',
[K8sCategory.CONTAINERS]: 'k8s_pod_cpu_usage',
[K8sCategory.PODS]: 'k8s_pod_cpu_utilization',
[K8sCategory.NODES]: 'k8s_node_cpu_utilization',
[K8sCategory.NAMESPACES]: 'k8s_pod_cpu_utilization',
[K8sCategory.CLUSTERS]: 'k8s_node_cpu_utilization',
[K8sCategory.DEPLOYMENTS]: 'k8s_pod_cpu_utilization',
[K8sCategory.STATEFULSETS]: 'k8s_pod_cpu_utilization',
[K8sCategory.DAEMONSETS]: 'k8s_pod_cpu_utilization',
[K8sCategory.CONTAINERS]: 'k8s_pod_cpu_utilization',
[K8sCategory.JOBS]: 'k8s_job_desired_successful_pods',
[K8sCategory.VOLUMES]: 'k8s_volume_capacity',
};
export const dotMap = {
[K8sCategory.HOSTS]: 'system.cpu.load_average.15m',
[K8sCategory.PODS]: 'k8s.pod.cpu.usage',
[K8sCategory.NODES]: 'k8s.node.cpu.usage',
[K8sCategory.NAMESPACES]: 'k8s.pod.cpu.usage',
[K8sCategory.CLUSTERS]: 'k8s.node.cpu.usage',
[K8sCategory.DEPLOYMENTS]: 'k8s.pod.cpu.usage',
[K8sCategory.STATEFULSETS]: 'k8s.pod.cpu.usage',
[K8sCategory.DAEMONSETS]: 'k8s.pod.cpu.usage',
[K8sCategory.CONTAINERS]: 'k8s.pod.cpu.usage',
[K8sCategory.PODS]: 'k8s.pod.cpu.utilization',
[K8sCategory.NODES]: 'k8s.node.cpu.utilization',
[K8sCategory.NAMESPACES]: 'k8s.pod.cpu.utilization',
[K8sCategory.CLUSTERS]: 'k8s.node.cpu.utilization',
[K8sCategory.DEPLOYMENTS]: 'k8s.pod.cpu.utilization',
[K8sCategory.STATEFULSETS]: 'k8s.pod.cpu.utilization',
[K8sCategory.DAEMONSETS]: 'k8s.pod.cpu.utilization',
[K8sCategory.CONTAINERS]: 'k8s.pod.cpu.utilization',
[K8sCategory.JOBS]: 'k8s.job.desired_successful_pods',
[K8sCategory.VOLUMES]: 'k8s.volume.capacity',
};
@@ -96,8 +96,8 @@ export function GetPodsQuickFiltersConfig(
// Define aggregate attribute (metric) name
const cpuUtilizationMetric = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
return [
{
@@ -252,8 +252,8 @@ export function GetNodesQuickFiltersConfig(
// Define aggregate metric name for node CPU utilization
const cpuUtilMetric = dotMetricsEnabled
? 'k8s.node.cpu.usage'
: 'k8s_node_cpu_usage';
? 'k8s.node.cpu.utilization'
: 'k8s_node_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -314,8 +314,8 @@ export function GetNamespaceQuickFiltersConfig(
: 'k8s_namespace_name';
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const cpuUtilMetric = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -373,8 +373,8 @@ export function GetClustersQuickFiltersConfig(
): IQuickFiltersConfig[] {
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const cpuUtilMetric = dotMetricsEnabled
? 'k8s.node.cpu.usage'
: 'k8s_node_cpu_usage';
? 'k8s.node.cpu.utilization'
: 'k8s_node_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -541,7 +541,9 @@ export function GetDeploymentsQuickFiltersConfig(
? 'k8s.namespace.name'
: 'k8s_namespace_name';
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const metric = dotMetricsEnabled ? 'k8s.pod.cpu.usage' : 'k8s_pod_cpu_usage';
const metric = dotMetricsEnabled
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -620,7 +622,9 @@ export function GetStatefulsetsQuickFiltersConfig(
? 'k8s.namespace.name'
: 'k8s_namespace_name';
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const metric = dotMetricsEnabled ? 'k8s.pod.cpu.usage' : 'k8s_pod_cpu_usage';
const metric = dotMetricsEnabled
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -700,8 +704,8 @@ export function GetDaemonsetsQuickFiltersConfig(
: 'k8s_namespace_name';
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const metricName = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
@@ -777,8 +781,8 @@ export function GetJobsQuickFiltersConfig(
: 'k8s_namespace_name';
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
const metricName = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
? 'k8s.pod.cpu.utilization'
: 'k8s_pod_cpu_utilization';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';

View File

@@ -32,7 +32,7 @@ function ContextLogRenderer({
const [afterLogPage, setAfterLogPage] = useState<number>(1);
const [logs, setLogs] = useState<ILog[]>([log]);
const { stagedQuery } = useQueryBuilder();
const { initialDataSource, stagedQuery } = useQueryBuilder();
const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
@@ -42,7 +42,7 @@ function ContextLogRenderer({
const { options } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: DataSource.LOGS,
dataSource: initialDataSource || DataSource.METRICS,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});

View File

@@ -3,7 +3,6 @@ import cx from 'classnames';
import Uplot from 'components/Uplot';
import { ENTITY_VERSION_V4 } from 'constants/app';
import dayjs from 'dayjs';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
@@ -90,7 +89,6 @@ function NodeMetrics({
);
const { timezone } = useTimezone();
const { currentQuery } = useQueryBuilder();
const options = useMemo(
() =>
@@ -108,7 +106,6 @@ function NodeMetrics({
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
query: currentQuery,
}),
),
[
@@ -120,7 +117,6 @@ function NodeMetrics({
verticalLineTimestamp,
end,
timezone.value,
currentQuery,
],
);

View File

@@ -3,7 +3,6 @@ import cx from 'classnames';
import Uplot from 'components/Uplot';
import { ENTITY_VERSION_V4 } from 'constants/app';
import dayjs from 'dayjs';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
@@ -72,7 +71,6 @@ function PodMetrics({
[queries],
);
const { timezone } = useTimezone();
const { currentQuery } = useQueryBuilder();
const options = useMemo(
() =>
@@ -90,7 +88,6 @@ function PodMetrics({
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
query: currentQuery,
}),
),
[
@@ -101,7 +98,6 @@ function PodMetrics({
end,
verticalLineTimestamp,
timezone.value,
currentQuery,
],
);

View File

@@ -17,8 +17,8 @@ export const getPodQueryPayload = (
: 'k8s_cluster_name';
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
const containerCpuUtilKey = dotMetricsEnabled
? 'container.cpu.usage'
: 'container_cpu_usage';
? 'container.cpu.utilization'
: 'container_cpu_utilization';
const containerMemUsageKey = dotMetricsEnabled
? 'container.memory.usage'
: 'container_memory_usage';
@@ -63,7 +63,7 @@ export const getPodQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'container_cpu_usage--float64--Gauge--true',
id: 'container_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: containerCpuUtilKey,
@@ -231,7 +231,7 @@ export const getPodQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'container_cpu_usage--float64--Gauge--true',
id: 'container_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: containerCpuUtilKey,
@@ -385,7 +385,7 @@ export const getPodQueryPayload = (
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'container_cpu_usage--float64--Gauge--true',
id: 'container_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: containerCpuUtilKey,

View File

@@ -32,7 +32,7 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { ActionItemProps } from './ActionItem';
import FieldRenderer from './FieldRenderer';
import TableViewActions from './TableView/TableViewActions';
import { TableViewActions } from './TableView/TableViewActions';
import {
filterKeyForField,
findKeyPath,

View File

@@ -1,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import './TableViewActions.styles.scss';
import { Color } from '@signozhq/design-tokens';
@@ -12,9 +11,10 @@ import { OPERATORS } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import dompurify from 'dompurify';
import { isEmpty } from 'lodash-es';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -24,11 +24,12 @@ import {
escapeHtml,
filterKeyForField,
getFieldAttributes,
jsonToDataNodes,
parseFieldValue,
recursiveParseJSON,
removeEscapeCharacters,
unescapeString,
} from '../utils';
import useAsyncJSONProcessing from './useAsyncJSONProcessing';
interface ITableViewActionsProps {
fieldData: Record<string, string>;
@@ -51,64 +52,7 @@ interface ITableViewActionsProps {
const convert = new Convert();
// Memoized Tree Component
const MemoizedTree = React.memo<{ treeData: any[] }>(({ treeData }) => (
<Tree defaultExpandAll showLine treeData={treeData} />
));
MemoizedTree.displayName = 'MemoizedTree';
// Body Content Component
const BodyContent: React.FC<{
fieldData: Record<string, string>;
record: DataType;
bodyHtml: { __html: string };
}> = React.memo(({ fieldData, record, bodyHtml }) => {
const { isLoading, treeData, error } = useAsyncJSONProcessing(
fieldData.value,
record.field === 'body',
);
// Show JSON tree if available, otherwise show HTML content
if (record.field === 'body' && treeData) {
return <MemoizedTree treeData={treeData} />;
}
if (record.field === 'body' && isLoading) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Spin size="small" />
<span style={{ color: Color.BG_SIENNA_400 }}>Processing JSON...</span>
</div>
);
}
if (record.field === 'body' && error) {
return (
<span
style={{ color: Color.BG_SIENNA_400, whiteSpace: 'pre-wrap', tabSize: 4 }}
>
Error parsing Body JSON
</span>
);
}
if (record.field === 'body') {
return (
<span
style={{ color: Color.BG_SIENNA_400, whiteSpace: 'pre-wrap', tabSize: 4 }}
>
<span dangerouslySetInnerHTML={bodyHtml} />
</span>
);
}
return null;
});
BodyContent.displayName = 'BodyContent';
export default function TableViewActions(
export function TableViewActions(
props: ITableViewActionsProps,
): React.ReactElement {
const {
@@ -134,42 +78,44 @@ export default function TableViewActions(
const { formatTimezoneAdjustedTimestamp } = useTimezone();
// Memoize bodyHtml computation
const bodyHtml = useMemo(() => {
if (record.field !== 'body') return { __html: '' };
return {
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
};
}, [record.field, record.value]);
const fieldFilterKey = filterKeyForField(fieldData.field);
// Memoize textToCopy computation
const textToCopy = useMemo(() => {
let text = fieldData.value;
try {
text = text.replace(/^"|"$/g, '');
} catch (error) {
console.error(
'Failed to remove starting and ending quotes from the value',
error,
if (record.field === 'body') {
const parsedBody = recursiveParseJSON(fieldData.value);
if (!isEmpty(parsedBody)) {
return (
<Tree defaultExpandAll showLine treeData={jsonToDataNodes(parsedBody)} />
);
}
return text;
}, [fieldData.value]);
}
const bodyHtml =
record.field === 'body'
? {
__html: convert.toHtml(
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}),
),
}
: { __html: '' };
// Memoize cleanTimestamp computation
const cleanTimestamp = useMemo(() => {
if (record.field !== 'timestamp') return '';
return fieldData.value.replace(/^["']|["']$/g, '');
}, [record.field, fieldData.value]);
const fieldFilterKey = filterKeyForField(fieldData.field);
let textToCopy = fieldData.value;
const renderFieldContent = useCallback((): JSX.Element => {
// remove starting and ending quotes from the value
try {
textToCopy = textToCopy.replace(/^"|"$/g, '');
} catch (error) {
console.error(
'Failed to remove starting and ending quotes from the value',
error,
);
}
let cleanTimestamp: string;
if (record.field === 'timestamp') {
cleanTimestamp = fieldData.value.replace(/^["']|["']$/g, '');
}
const renderFieldContent = (): JSX.Element => {
const commonStyles: React.CSSProperties = {
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
@@ -178,9 +124,7 @@ export default function TableViewActions(
switch (record.field) {
case 'body':
return (
<BodyContent fieldData={fieldData} record={record} bodyHtml={bodyHtml} />
);
return <span style={commonStyles} dangerouslySetInnerHTML={bodyHtml} />;
case 'timestamp':
return (
@@ -197,93 +141,7 @@ export default function TableViewActions(
<span style={commonStyles}>{removeEscapeCharacters(fieldData.value)}</span>
);
}
}, [
record,
fieldData,
bodyHtml,
formatTimezoneAdjustedTimestamp,
cleanTimestamp,
]);
// Early return for body field with async processing
if (record.field === 'body') {
return (
<div className={cx('value-field', isOpen ? 'open-popover' : '')}>
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
<BodyContent fieldData={fieldData} record={record} bodyHtml={bodyHtml} />
</CopyClipboardHOC>
{!isListViewPanel && !RESTRICTED_SELECTED_FIELDS.includes(fieldFilterKey) && (
<span className="action-btn">
<Tooltip title="Filter for value">
<Button
className="filter-btn periscope-btn"
icon={
isfilterInLoading ? (
<Spin size="small" />
) : (
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
)
}
onClick={onClickHandler(
OPERATORS['='],
fieldFilterKey,
parseFieldValue(fieldData.value),
dataType,
)}
/>
</Tooltip>
<Tooltip title="Filter out value">
<Button
className="filter-btn periscope-btn"
icon={
isfilterOutLoading ? (
<Spin size="small" />
) : (
<ArrowUpFromDot size={14} style={{ transform: 'rotate(90deg)' }} />
)
}
onClick={onClickHandler(
OPERATORS['!='],
fieldFilterKey,
parseFieldValue(fieldData.value),
dataType,
)}
/>
</Tooltip>
{!isOldLogsExplorerOrLiveLogsPage && (
<Popover
open={isOpen}
onOpenChange={setIsOpen}
arrow={false}
content={
<div>
<Button
className="group-by-clause"
type="text"
icon={<GroupByIcon />}
onClick={(): Promise<void> | void =>
onGroupByAttribute?.(fieldFilterKey)
}
>
Group By Attribute
</Button>
</div>
}
rootClassName="table-view-actions-content"
trigger="hover"
placement="bottomLeft"
>
<Button
icon={<Ellipsis size={14} />}
className="filter-btn periscope-btn"
/>
</Popover>
)}
</span>
)}
</div>
);
}
};
return (
<div className={cx('value-field', isOpen ? 'open-popover' : '')}>

View File

@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
import TableViewActions from '../TableViewActions';
import { TableViewActions } from '../TableViewActions';
// Mock the components and hooks
jest.mock('components/Logs/CopyClipboardHOC', () => ({

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