Compare commits

..

78 Commits

Author SHA1 Message Date
ahrefabhi
f9414d2e8a feat: enhance keymap handling in QuerySearch to prevent default line breaks on Enter keys 2025-07-07 15:56:12 +05:30
ahrefabhi
6d110870a4 feat: enhance value fetching logic with search text and loading states 2025-07-07 15:55:47 +05:30
ahrefabhi
5bebc815e3 fix: segment fragment to allow hyphen without trailing dot in FilterQuery grammar 2025-07-07 15:46:58 +05:30
ahrefabhi
b6fe0619b0 feat: enhance query processing to support negation context and improve space handling 2025-07-07 15:24:55 +05:30
ahrefabhi
bb8f6b9eb1 chore: added grammer parity for frontend grammer with main grammer 2025-07-07 15:24:55 +05:30
ahrefabhi
c783c5c7ee feat: add support for negation context in query processing 2025-07-07 15:24:55 +05:30
ahrefabhi
6f08aff58b feat: enhance query context to support detection of values wrapped in quotes 2025-07-07 15:24:55 +05:30
ahrefabhi
86229dd55b chore: removed IS_NULL and IS_NOT_NULL and added NOT_EXISTS 2025-07-07 15:24:55 +05:30
ahrefabhi
d9519d6896 feat: added IS_NULL and IS_NOT_NULL operators and fixed support for not value operators 2025-07-07 15:24:55 +05:30
ahrefabhi
3da7ce5f03 chore: updated grammer for value, added parsetree for finding current context 2025-07-07 15:24:55 +05:30
SagarRajput-7
e2498863e7 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:23:14 +05:30
SagarRajput-7
25c94dfa93 feat: resolved conflicts 2025-06-27 18:23:14 +05:30
SagarRajput-7
abe617bcd0 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:23:14 +05:30
Yunus M
d8dd3cddd8 feat: fetch more keys is complete list not already fetched 2025-06-27 18:23:14 +05:30
SagarRajput-7
6ccb44a3df 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:23:14 +05:30
Yunus M
e30953fc93 fix: responsiveness issues 2025-06-27 18:23:14 +05:30
Yunus M
9010220b44 feat: where clause key updates 2025-06-27 18:23:14 +05:30
Yunus M
48cdf439a9 feat: update styles for light mode 2025-06-27 18:23:14 +05:30
Yunus M
52656b2ee8 feat: show errors 2025-06-27 18:23:14 +05:30
Yunus M
51a7dae5d9 feat: update context and show suggestions on select 2025-06-27 18:23:14 +05:30
Yunus M
b27901cd70 feat: add a space after selecting a value from suggestion 2025-06-27 18:23:14 +05:30
Yunus M
e0c30a0e4b feat: improve suggestion ux in query search 2025-06-27 18:23:14 +05:30
Yunus M
cd29cfb609 feat: ui improvements 2025-06-27 18:23:13 +05:30
Yunus M
0325a5f64d feat: handle close on blur 2025-06-27 18:23:13 +05:30
Yunus M
af2814a535 feat: query search component clean up 2025-06-27 18:23:13 +05:30
Yunus M
8d49c7934b feat: handle having option autocomplete ux 2025-06-27 18:23:13 +05:30
Yunus M
8c992d10be feat: disable clicking on placeholder items in suggestions 2025-06-27 18:23:13 +05:30
Yunus M
aa6008649d feat: improve having suggestions 2025-06-27 18:23:13 +05:30
Yunus M
8d73f79299 feat: handle add ons 2025-06-27 18:23:13 +05:30
Yunus M
62ecc44510 feat: handle list panel type options 2025-06-27 18:23:13 +05:30
Yunus M
1e7b2a7734 feat: pass index to query addons 2025-06-27 18:23:13 +05:30
Yunus M
35c49c45da feat: update qb elements based on panel type 2025-06-27 18:23:13 +05:30
Yunus M
e9bed072e4 feat: hide extra qb elements 2025-06-27 18:23:13 +05:30
Yunus M
ab32765dec feat: use qb-v2 in explorers and alerts 2025-06-27 18:23:13 +05:30
Yunus M
959e518907 feat: update explorer views 2025-06-27 18:23:13 +05:30
Yunus M
255fe1b70a feat: update logs, metrics and traces qb 2025-06-27 18:23:13 +05:30
Yunus M
44eb60fef9 feat: query builder layout updates 2025-06-27 18:23:13 +05:30
Yunus M
14e4273ae9 fix: minor fixes 2025-06-27 18:23:13 +05:30
Yunus M
7a98bed694 feat: create separate containers for traces, logs and metrics qbs 2025-06-27 18:23:13 +05:30
Yunus M
231b9072b1 feat: metrics qb 2025-06-27 18:23:13 +05:30
Yunus M
5eca9efcd5 fix: update dropdown css 2025-06-27 18:23:13 +05:30
Yunus M
a9303d3482 feat: remove () from suggestions 2025-06-27 18:23:13 +05:30
Yunus M
5da45d8ade feat: handle parenthesis and conjunction operators 2025-06-27 18:23:13 +05:30
Yunus M
c07ada0d71 feat: support multiple having key value pairs 2025-06-27 18:23:13 +05:30
Yunus M
47ca071d36 feat: move state to context 2025-06-27 18:23:13 +05:30
Yunus M
51d697c91a feat: handle having options creation 2025-06-27 18:23:13 +05:30
Yunus M
00ce2e3034 feat: hide already used variables 2025-06-27 18:23:13 +05:30
Yunus M
1104eca146 fix: show operator suggestions only on manual trigger or valid key 2025-06-27 18:23:13 +05:30
Yunus M
ab594cd9ba fix: handle autocomplete 2025-06-27 18:23:13 +05:30
Yunus M
de3497d439 fix: update styles 2025-06-27 18:23:13 +05:30
Yunus M
e319394ac4 fix: update css 2025-06-27 18:23:13 +05:30
Yunus M
559be9a571 feat: handle multie select functions 2025-06-27 18:23:12 +05:30
Yunus M
9c685837eb feat: handle field suggestions for aggregate operators 2025-06-27 18:23:12 +05:30
Yunus M
bc3d162016 feat: support aggregation function with values 2025-06-27 18:23:12 +05:30
Yunus M
6b406ec9de feat: add groupBy, having, order by, limit and legend format 2025-06-27 18:23:12 +05:30
Yunus M
b7e08a746c feat: handle multie select values better 2025-06-27 18:23:12 +05:30
Yunus M
2119bc4c26 feat: improve suggestions 2025-06-27 18:23:12 +05:30
Yunus M
6b936ea5e1 feat: console log context based on cursor position 2025-06-27 18:23:12 +05:30
Yunus M
39eaa722ac fix: handle . notation keywords better 2025-06-27 18:23:12 +05:30
Yunus M
5c2b7e545e feat: remove card container above where clause 2025-06-27 18:23:12 +05:30
Yunus M
0da6e76ba6 feat: use new qb in logs explorer 2025-06-27 18:23:12 +05:30
Yunus M
4f82e9cd9c feat: handle parenthesis 2025-06-27 18:23:12 +05:30
Yunus M
6cfaba6bd7 feat: handle value selection 2025-06-27 18:23:12 +05:30
Yunus M
2c177232b1 feat: styling updates 2025-06-27 18:23:12 +05:30
Yunus M
efb82abee7 feat: handle string and number values correctly 2025-06-27 18:23:12 +05:30
Yunus M
159ff84768 feat: handle async value fetching 2025-06-27 18:23:12 +05:30
Yunus M
2961581a52 feat: update the context with additonal properties 2025-06-27 18:23:12 +05:30
Yunus M
91a9ca8cf8 feat: styling updates 2025-06-27 18:23:12 +05:30
Yunus M
b43f7cfb87 feat: update theme and syntax highlighting 2025-06-27 18:23:12 +05:30
Yunus M
aa321a82e1 feat: handle context switch 2025-06-27 18:23:12 +05:30
Yunus M
6e09bca806 feat: handle multiple spaces 2025-06-27 18:23:12 +05:30
Yunus M
c9e61e3d66 feat: integrate the apis 2025-06-27 18:23:12 +05:30
Yunus M
e7d17c54a7 feat: update context logic and return auto-suggestions based on context 2025-06-27 18:23:12 +05:30
Yunus M
abc934d88f feat: add apis and hooks 2025-06-27 18:23:12 +05:30
Yunus M
19eec82770 feat: update context to recognise conjunction operator 2025-06-27 18:23:12 +05:30
Yunus M
b43891193e feat: add codemirror 2025-06-27 18:23:12 +05:30
Yunus M
36003324f3 feat: add types, base components 2025-06-27 18:23:12 +05:30
Yunus M
3e5ff4cdda feat: add antlr4, parser files and grammar 2025-06-27 18:23:12 +05:30
286 changed files with 7042 additions and 9516 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,5 @@ 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}&name=${props.name}`);

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

@@ -74,13 +74,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,15 +123,13 @@ function createBaseSpec(
})),
}),
),
selectFields: isEmpty(nonEmptySelectColumns)
selectFields: isEmpty(queryData.selectColumns)
? undefined
: nonEmptySelectColumns?.map(
(column: any): TelemetryFieldKey => ({
name: column.name ?? column.key,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
: queryData.selectColumns?.map(
(column: BaseAutocompleteData): TelemetryFieldKey => ({
name: column.key,
fieldDataType: column?.dataType as FieldDataType,
fieldContext: column?.type as FieldContext,
}),
),
};
@@ -164,9 +157,6 @@ export function parseAggregations(
export function createAggregation(
queryData: any,
): TraceAggregation[] | LogAggregation[] | MetricAggregation[] {
if (!queryData) {
return [];
}
if (queryData.dataSource === DataSource.METRICS) {
return [
{
@@ -253,7 +243,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 +318,6 @@ export const prepareQueryRangePayloadV5 = ({
start: startTime,
end: endTime,
formatForWeb,
originalGraphType,
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
let legendMap: Record<string, string> = {};
const requestType = mapPanelTypeToRequestType(graphType);
@@ -392,11 +381,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

@@ -410,18 +410,18 @@ export default function LogsFormatOptionsMenu({
)}
<div className="column-format">
{addColumn?.value?.map(({ name }) => (
<div className="column-name" key={name}>
{addColumn?.value?.map(({ key, id }) => (
<div className="column-name" key={id}>
<div className="name">
<Tooltip placement="left" title={name}>
{name}
<Tooltip placement="left" title={key}>
{key}
</Tooltip>
</div>
{addColumn?.value?.length > 1 && (
<X
className="delete-btn"
size={14}
onClick={(): void => addColumn.onRemove(name)}
onClick={(): void => addColumn.onRemove(id as string)}
/>
)}
</div>

View File

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

View File

@@ -1,18 +1,15 @@
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';
import { GroupByFilter, OperatorsSelect } from 'container/QueryBuilder/filters';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { Info } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
const MetricsAggregateSection = memo(function MetricsAggregateSection({
query,
index,
@@ -24,7 +21,6 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
version: string;
panelType: PANEL_TYPES | null;
}): JSX.Element {
const { setAggregationOptions } = useQueryBuilderV2Context();
const {
operators,
spaceAggregationOptions,
@@ -37,25 +33,6 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
entityVersion: version,
});
const isHistogram = useMemo(
() => query.aggregateAttribute.type === ATTRIBUTE_TYPES.HISTOGRAM,
[query.aggregateAttribute.type],
);
useEffect(() => {
setAggregationOptions([
{
func: query.spaceAggregation || 'count',
arg: query.aggregateAttribute.key || '',
},
]);
}, [
query.spaceAggregation,
query.aggregateAttribute.key,
setAggregationOptions,
query,
]);
const handleChangeGroupByKeys = useCallback(
(value: IBuilderQuery['groupBy']) => {
handleChangeQueryData('groupBy', value);
@@ -83,142 +60,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

@@ -53,6 +53,17 @@ const havingOperators = [
},
];
// Add common value suggestions
const commonValues = [
{ label: '0', value: '0 ' },
{ label: '1', value: '1 ' },
{ label: '5', value: '5 ' },
{ label: '10', value: '10 ' },
{ label: '50', value: '50 ' },
{ label: '100', value: '100 ' },
{ label: '1000', value: '1000 ' },
];
const conjunctions = [
{ label: 'AND', value: 'AND ' },
{ label: 'OR', value: 'OR ' },
@@ -239,18 +250,22 @@ function HavingFilter({
};
}
// Close dropdown after operator to allow custom value entry
// Show value suggestions after operator
if (isAfterOperator(tokens)) {
return null;
}
// Hide suggestions while typing a value after an operator
if (
!text.endsWith(' ') &&
tokens.length >= 2 &&
havingOperators.some((op) => op.value === tokens[tokens.length - 2])
) {
return null;
return {
from: context.pos,
options: [
...commonValues.map((value) => ({
...value,
apply: applyValueCompletion,
})),
{
label: 'Enter a custom number value',
type: 'text',
apply: applyValueCompletion,
},
],
};
}
// Suggest key/operator pairs and ( for grouping
@@ -331,7 +346,6 @@ function HavingFilter({
havingAutocomplete,
javascript({ jsx: false, typescript: false }),
stopEventsExtension,
EditorView.lineWrapping,
keymap.of([
...completionKeymap,
{

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';
@@ -18,7 +17,6 @@ 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,14 @@ 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';
import {
getCurrentValueIndexAtCursor,
getQueryContextAtCursor,
} from 'utils/queryContextUtils';
validateQuery,
} from 'utils/antlrQueryUtils';
import { getQueryContextAtCursor } from 'utils/queryContextUtils';
import { queryExamples } from './constants';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
const { Panel } = Collapse;
@@ -135,8 +127,6 @@ function QuerySearch({
const lastValueRef = useRef<string>('');
const isMountedRef = useRef<boolean>(true);
const { handleRunQuery } = useQueryBuilder();
// const {
// data: queryKeySuggestions,
// refetch: refetchQueryKeySuggestions,
@@ -161,20 +151,13 @@ function QuerySearch({
const fetchKeySuggestions = async (searchText?: string): Promise<void> => {
const response = await getKeySuggestions({
signal: dataSource,
searchText: searchText || '',
metricName: queryData.aggregateAttribute.key ?? undefined,
name: searchText || '',
});
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);
}
};
@@ -183,7 +166,7 @@ function QuerySearch({
setKeySuggestions([]);
fetchKeySuggestions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSource, queryData.aggregateAttribute.key]);
}, [dataSource]);
// Add a state for tracking editing mode
const [editingMode, setEditingMode] = useState<
@@ -239,7 +222,10 @@ function QuerySearch({
// If we're already inside bracket list for IN operator and it's a string value
// just wrap in quotes but not brackets (we're already in brackets)
if (type === 'value' || type === 'keyword') {
if (
(type === 'value' || type === 'keyword') &&
!/^[a-zA-Z0-9_][a-zA-Z0-9_.\[\]]*$/.test(value)
) {
return wrapStringValueInQuotes(value);
}
@@ -316,10 +302,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 +312,6 @@ function QuerySearch({
.map((value: string) => ({
label: value,
type: 'value',
apply: value,
}));
// Generate options from number values
@@ -342,7 +323,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 +356,7 @@ function QuerySearch({
}
}, 10);
}
setIsLoadingSuggestions(false);
}
} catch (error) {
console.error('Error fetching suggestions:', error);
@@ -388,6 +369,7 @@ function QuerySearch({
apply: (): boolean => false, // Prevent selection
},
]);
setIsLoadingSuggestions(false);
}
} finally {
setIsLoadingSuggestions(false);
@@ -546,7 +528,7 @@ function QuerySearch({
completion: any,
from: number,
to: number,
shouldAddSpace = true,
shouldAddSpace: boolean = true,
): void => {
view.dispatch({
changes: {
@@ -574,81 +556,24 @@ function QuerySearch({
from: number,
to: number,
): void => {
let shouldDefaultApply = true;
// 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) {
if (queryContext.isInBracketList && pair?.valuesPosition) {
const idx = getCurrentValueIndexAtCursor(
pair.valuesPosition,
cursorPos.ch,
);
if (!isNull(idx)) {
const { start, end } = pair.valuesPosition[idx];
if (
typeof start === 'number' &&
typeof end === 'number' &&
cursorPos.ch >= start &&
cursorPos.ch <= end + 1
) {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
{ apply: originalApply },
start,
end + 1,
false,
);
}
}
} else if (pair?.position) {
const { valueStart, valueEnd } = pair.position;
if (
typeof valueStart === 'number' &&
typeof valueEnd === 'number' &&
cursorPos.ch >= valueStart &&
cursorPos.ch <= valueEnd + 1
) {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
{ apply: originalApply },
valueStart,
valueEnd + 1,
false,
);
}
}
}
// Changes to replace the operator in-place with the existing operator
if (isOperatorType && pair?.position) {
const { operatorStart, operatorEnd } = pair.position;
if (queryContext.isInValue && option.type === 'value') {
if (
typeof operatorStart === 'number' &&
typeof operatorEnd === 'number' &&
operatorStart !== 0 &&
operatorEnd !== 0 &&
cursorPos.ch >= operatorStart &&
cursorPos.ch <= operatorEnd + 1
queryContext.currentPair?.position &&
queryContext.currentPair.position.valueStart &&
queryContext.currentPair.position.valueEnd
) {
shouldDefaultApply = false;
const { valueStart, valueEnd } = queryContext.currentPair.position;
addSpaceAfterSelection(
view,
{ apply: originalApply },
operatorStart,
operatorEnd + 1,
valueStart,
valueEnd + 1,
false,
);
} else {
addSpaceAfterSelection(view, { apply: originalApply }, from, to);
}
}
if (shouldDefaultApply) {
} else {
addSpaceAfterSelection(view, { apply: originalApply }, from, to);
}
},
@@ -783,49 +708,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,
}));
}
}
@@ -881,12 +785,12 @@ function QuerySearch({
}
// 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;
}
@@ -1042,7 +946,7 @@ function QuerySearch({
// options: optionsWithSpace,
// };
// Don't show anything if no context detected
//Don't show anything if no context detected
return {
from: word?.from ?? 0,
options: [],
@@ -1165,7 +1069,6 @@ function QuerySearch({
// 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;
},
},

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

@@ -6,6 +6,10 @@ import {
import { SelectOption } from 'types/common/select';
export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: MetricAggregateOperator.NOOP,
label: 'NOOP',
},
{
value: MetricAggregateOperator.COUNT,
label: 'Count',
@@ -126,6 +130,10 @@ export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
];
export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: TracesAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: TracesAggregatorOperator.COUNT,
label: 'Count',
@@ -209,6 +217,10 @@ export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
];
export const logsAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: LogsAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: LogsAggregatorOperator.COUNT,
label: 'Count',

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,
@@ -15,7 +13,6 @@ import {
Typography,
} from 'antd';
import logEvent from 'api/common/logEvent';
import { TelemetryFieldKey } from 'api/v5/v5';
import axios from 'axios';
import cx from 'classnames';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
@@ -46,14 +43,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 {
@@ -68,6 +58,7 @@ import {
} from 'react';
import { useHistory } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { ViewProps } from 'types/api/saveViews/types';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
@@ -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
@@ -270,7 +253,7 @@ function ExplorerOptions({
const getUpdatedExtraData = (
extraData: string | undefined,
newSelectedColumns: TelemetryFieldKey[],
newSelectedColumns: BaseAutocompleteData[],
formattingOptions?: FormattingOptions,
): string => {
let updatedExtraData;
@@ -354,7 +337,7 @@ function ExplorerOptions({
const { handleExplorerTabChange } = useHandleExplorerTabChange();
type ExtraData = {
selectColumns?: TelemetryFieldKey[];
selectColumns?: BaseAutocompleteData[];
version?: number;
};
@@ -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,

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