Compare commits
78 Commits
feat/meter
...
improveTra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
334b717f23 | ||
|
|
d08b3313bb | ||
|
|
606f7c1272 | ||
|
|
92e5986af2 | ||
|
|
fa8cc92105 | ||
|
|
47a03e8f7e | ||
|
|
ba68efd60f | ||
|
|
dce7ded49b | ||
|
|
3f66c20316 | ||
|
|
912a34da8d | ||
|
|
8b99ba0f9f | ||
|
|
841abf8c0b | ||
|
|
df54e6350d | ||
|
|
f6bc30050b | ||
|
|
1e76046c7c | ||
|
|
910751713d | ||
|
|
85c671c8d5 | ||
|
|
4d2094b4ce | ||
|
|
32410baa72 | ||
|
|
2a5fb9fd6f | ||
|
|
514bceca34 | ||
|
|
ac7d8bcde2 | ||
|
|
88312e971d | ||
|
|
17533b2f1c | ||
|
|
c4044fa2c5 | ||
|
|
deddf47e84 | ||
|
|
08323e4dfd | ||
|
|
ee19f1749b | ||
|
|
b21db878e8 | ||
|
|
a7ddd2ddf0 | ||
|
|
4d72f47758 | ||
|
|
b5b513f1e0 | ||
|
|
4878f725ea | ||
|
|
eca13075e9 | ||
|
|
e5ab664483 | ||
|
|
a3f32b3d85 | ||
|
|
9c2f127282 | ||
|
|
e30de5f13e | ||
|
|
019083983a | ||
|
|
fdcad997f5 | ||
|
|
03359a40a2 | ||
|
|
4f45801729 | ||
|
|
674556d672 | ||
|
|
af987e53ce | ||
|
|
59d5accd33 | ||
|
|
5a7ad670d8 | ||
|
|
9d04b397ac | ||
|
|
a4f3be5e46 | ||
|
|
8f833fa62c | ||
|
|
7029233596 | ||
|
|
d26efd2833 | ||
|
|
0e3ac2a179 | ||
|
|
249f8be845 | ||
|
|
9c952942ad | ||
|
|
dac46d82ff | ||
|
|
802ce6de01 | ||
|
|
6853f0c99d | ||
|
|
3f8a2870e4 | ||
|
|
5fa70ea802 | ||
|
|
3a952fa330 | ||
|
|
6d97db1d9d | ||
|
|
5412e7f70b | ||
|
|
8e5cb9046d | ||
|
|
760eabb2dc | ||
|
|
35ddaaa2fc | ||
|
|
a51ee66c02 | ||
|
|
75d189162b | ||
|
|
932918e3a4 | ||
|
|
aa3bc16dcb | ||
|
|
b5098e00a3 | ||
|
|
20dc561bfe | ||
|
|
99bbb87738 | ||
|
|
f1ce93171c | ||
|
|
92794389d6 | ||
|
|
bd02848623 | ||
|
|
b5016b061b | ||
|
|
c308e8668c | ||
|
|
41ee4176ad |
@@ -24,7 +24,7 @@ services:
|
||||
depends_on:
|
||||
- zookeeper
|
||||
zookeeper:
|
||||
image: bitnami/zookeeper:3.7.1
|
||||
image: signoz/zookeeper:3.7.1
|
||||
container_name: zookeeper
|
||||
volumes:
|
||||
- ${PWD}/fs/tmp/zookeeper:/bitnami/zookeeper
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
schema-migrator-sync:
|
||||
image: signoz/signoz-schema-migrator:v0.128.2
|
||||
image: signoz/signoz-schema-migrator:v0.129.2
|
||||
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.2
|
||||
image: signoz/signoz-schema-migrator:v0.129.2
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
29
.devenv/docker/signoz-otel-collector/compose.yaml
Normal file
29
.devenv/docker/signoz-otel-collector/compose.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
signoz-otel-collector:
|
||||
image: signoz/signoz-otel-collector:v0.128.2
|
||||
container_name: signoz-otel-collector-dev
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
environment:
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||
ports:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
- "13133:13133" # health check extension
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- wget
|
||||
- --spider
|
||||
- -q
|
||||
- localhost:13133
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
@@ -0,0 +1,96 @@
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
prometheus:
|
||||
config:
|
||||
global:
|
||||
scrape_interval: 60s
|
||||
scrape_configs:
|
||||
- job_name: otel-collector
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:8888
|
||||
labels:
|
||||
job_name: otel-collector
|
||||
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
timeout: 10s
|
||||
resourcedetection:
|
||||
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
|
||||
detectors: [env, system]
|
||||
timeout: 2s
|
||||
signozspanmetrics/delta:
|
||||
metrics_exporter: 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
|
||||
aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA
|
||||
enable_exp_histogram: true
|
||||
dimensions:
|
||||
- name: service.namespace
|
||||
default: default
|
||||
- name: deployment.environment
|
||||
default: default
|
||||
# This is added to ensure the uniqueness of the timeseries
|
||||
# Otherwise, identical timeseries produced by multiple replicas of
|
||||
# collectors result in incorrect APM metrics
|
||||
- name: signoz.collector.id
|
||||
- name: service.version
|
||||
- name: browser.platform
|
||||
- name: browser.mobile
|
||||
- name: k8s.cluster.name
|
||||
- name: k8s.node.name
|
||||
- name: k8s.namespace.name
|
||||
- name: host.name
|
||||
- name: host.type
|
||||
- name: container.name
|
||||
|
||||
extensions:
|
||||
health_check:
|
||||
endpoint: 0.0.0.0:13133
|
||||
pprof:
|
||||
endpoint: 0.0.0.0:1777
|
||||
|
||||
exporters:
|
||||
clickhousetraces:
|
||||
datasource: tcp://host.docker.internal:9000/signoz_traces
|
||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||
use_new_schema: true
|
||||
signozclickhousemetrics:
|
||||
dsn: tcp://host.docker.internal:9000/signoz_metrics
|
||||
clickhouselogsexporter:
|
||||
dsn: tcp://host.docker.internal:9000/signoz_logs
|
||||
timeout: 10s
|
||||
use_new_schema: true
|
||||
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
encoding: json
|
||||
extensions:
|
||||
- health_check
|
||||
- pprof
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [signozspanmetrics/delta, batch]
|
||||
exporters: [clickhousetraces]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [batch]
|
||||
exporters: [signozclickhousemetrics]
|
||||
logs:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [clickhouselogsexporter]
|
||||
11
Makefile
11
Makefile
@@ -61,6 +61,17 @@ devenv-postgres: ## Run postgres in devenv
|
||||
@cd .devenv/docker/postgres; \
|
||||
docker compose -f compose.yaml up -d
|
||||
|
||||
.PHONY: devenv-signoz-otel-collector
|
||||
devenv-signoz-otel-collector: ## Run signoz-otel-collector in devenv (requires clickhouse to be running)
|
||||
@cd .devenv/docker/signoz-otel-collector; \
|
||||
docker compose -f compose.yaml up -d
|
||||
|
||||
.PHONY: devenv-up
|
||||
devenv-up: devenv-clickhouse devenv-signoz-otel-collector ## Start both clickhouse and signoz-otel-collector for local development
|
||||
@echo "Development environment is ready!"
|
||||
@echo " - ClickHouse: http://localhost:8123"
|
||||
@echo " - Signoz OTel Collector: grpc://localhost:4317, http://localhost:4318"
|
||||
|
||||
##############################################################
|
||||
# go commands
|
||||
##############################################################
|
||||
|
||||
@@ -121,6 +121,8 @@ telemetrystore:
|
||||
timeout_before_checking_execution_speed: 0
|
||||
max_bytes_to_read: 0
|
||||
max_result_rows: 0
|
||||
ignore_data_skipping_indices: ""
|
||||
secondary_indices_enable_bulk_filtering: false
|
||||
|
||||
##################### Prometheus #####################
|
||||
prometheus:
|
||||
|
||||
@@ -39,7 +39,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
||||
hard: 262144
|
||||
x-zookeeper-defaults: &zookeeper-defaults
|
||||
!!merge <<: *common
|
||||
image: bitnami/zookeeper:3.7.1
|
||||
image: signoz/zookeeper:3.7.1
|
||||
user: root
|
||||
deploy:
|
||||
labels:
|
||||
@@ -174,7 +174,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.91.0
|
||||
image: signoz/signoz:v0.93.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
@@ -207,7 +207,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.128.2
|
||||
image: signoz/signoz-otel-collector:v0.129.2
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
@@ -231,7 +231,7 @@ services:
|
||||
- signoz
|
||||
schema-migrator:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:v0.128.2
|
||||
image: signoz/signoz-schema-migrator:v0.129.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -38,7 +38,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
||||
hard: 262144
|
||||
x-zookeeper-defaults: &zookeeper-defaults
|
||||
!!merge <<: *common
|
||||
image: bitnami/zookeeper:3.7.1
|
||||
image: signoz/zookeeper:3.7.1
|
||||
user: root
|
||||
deploy:
|
||||
labels:
|
||||
@@ -115,7 +115,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.91.0
|
||||
image: signoz/signoz:v0.93.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
@@ -148,7 +148,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.128.2
|
||||
image: signoz/signoz-otel-collector:v0.129.2
|
||||
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.2
|
||||
image: signoz/signoz-schema-migrator:v0.129.2
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -42,7 +42,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
||||
hard: 262144
|
||||
x-zookeeper-defaults: &zookeeper-defaults
|
||||
!!merge <<: *common
|
||||
image: bitnami/zookeeper:3.7.1
|
||||
image: signoz/zookeeper:3.7.1
|
||||
user: root
|
||||
labels:
|
||||
signoz.io/scrape: "true"
|
||||
@@ -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.91.0}
|
||||
image: signoz/signoz:${VERSION:-v0.93.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -211,7 +211,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.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
@@ -237,7 +237,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.2}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -248,7 +248,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.2}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -38,7 +38,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
||||
hard: 262144
|
||||
x-zookeeper-defaults: &zookeeper-defaults
|
||||
!!merge <<: *common
|
||||
image: bitnami/zookeeper:3.7.1
|
||||
image: signoz/zookeeper:3.7.1
|
||||
user: root
|
||||
labels:
|
||||
signoz.io/scrape: "true"
|
||||
@@ -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.91.0}
|
||||
image: signoz/signoz:${VERSION:-v0.93.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -143,7 +143,7 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.128.2}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
@@ -165,7 +165,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.2}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -177,7 +177,7 @@ services:
|
||||
restart: on-failure
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.128.2}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -44,20 +44,35 @@ Before diving in, make sure you have these tools installed:
|
||||
|
||||
SigNoz has three main components: Clickhouse, Backend, and Frontend. Let's set them up one by one.
|
||||
|
||||
### 1. Setting up Clickhouse
|
||||
### 1. Setting up ClickHouse
|
||||
|
||||
First, we need to get Clickhouse running:
|
||||
First, we need to get ClickHouse running:
|
||||
|
||||
```bash
|
||||
make devenv-clickhouse
|
||||
```
|
||||
|
||||
This command:
|
||||
- Starts Clickhouse in a single-shard, single-replica cluster
|
||||
- Starts ClickHouse in a single-shard, single-replica cluster
|
||||
- Sets up Zookeeper
|
||||
- Runs the latest schema migrations
|
||||
|
||||
### 2. Starting the Backend
|
||||
### 2. Setting up SigNoz OpenTelemetry Collector
|
||||
|
||||
Next, start the OpenTelemetry Collector to receive telemetry data:
|
||||
|
||||
```bash
|
||||
make devenv-signoz-otel-collector
|
||||
```
|
||||
|
||||
This command:
|
||||
- Starts the SigNoz OpenTelemetry Collector
|
||||
- Listens on port 4317 (gRPC) and 4318 (HTTP) for incoming telemetry data
|
||||
- Forwards data to ClickHouse for storage
|
||||
|
||||
> 💡 **Quick Setup**: Use `make devenv-up` to start both ClickHouse and OTel Collector together
|
||||
|
||||
### 3. Starting the Backend
|
||||
|
||||
1. Run the backend server:
|
||||
```bash
|
||||
@@ -73,7 +88,7 @@ This command:
|
||||
|
||||
> 💡 **Tip**: The API server runs at `http://localhost:8080/` by default
|
||||
|
||||
### 3. Setting up the Frontend
|
||||
### 4. Setting up the Frontend
|
||||
|
||||
1. Navigate to the frontend directory:
|
||||
```bash
|
||||
@@ -98,3 +113,25 @@ This command:
|
||||
> 💡 **Tip**: `yarn dev` will automatically rebuild when you make changes to the code
|
||||
|
||||
Now you're all set to start developing! Happy coding! 🎉
|
||||
|
||||
## Verifying Your Setup
|
||||
To verify everything is working correctly:
|
||||
|
||||
1. **Check ClickHouse**: `curl http://localhost:8123/ping` (should return "Ok.")
|
||||
2. **Check OTel Collector**: `curl http://localhost:13133` (should return health status)
|
||||
3. **Check Backend**: `curl http://localhost:8080/api/v1/health` (should return `{"status":"ok"}`)
|
||||
4. **Check Frontend**: Open `http://localhost:3301` in your browser
|
||||
|
||||
## How to send test data?
|
||||
|
||||
You can now send telemetry data to your local SigNoz instance:
|
||||
|
||||
- **OTLP gRPC**: `localhost:4317`
|
||||
- **OTLP HTTP**: `localhost:4318`
|
||||
|
||||
For example, using `curl` to send a test trace:
|
||||
```bash
|
||||
curl -X POST http://localhost:4318/v1/traces \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test-service"}}]},"scopeSpans":[{"spans":[{"traceId":"12345678901234567890123456789012","spanId":"1234567890123456","name":"test-span","startTimeUnixNano":"1609459200000000000","endTimeUnixNano":"1609459201000000000"}]}]}]}'
|
||||
```
|
||||
|
||||
@@ -257,6 +257,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
s.config.APIServer.Timeout.Max,
|
||||
).Wrap)
|
||||
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
r.Use(middleware.NewComment().Wrap)
|
||||
|
||||
apiHandler.RegisterRoutes(r, am)
|
||||
apiHandler.RegisterLogsRoutes(r, am)
|
||||
|
||||
@@ -16,6 +16,7 @@ const config: Config.InitialOptions = {
|
||||
'ts-jest': {
|
||||
useESM: true,
|
||||
isolatedModules: true,
|
||||
tsconfig: '<rootDir>/tsconfig.jest.json',
|
||||
},
|
||||
},
|
||||
testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],
|
||||
@@ -25,7 +26,7 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)',
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -43,11 +43,17 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
"@sentry/webpack-plugin": "2.22.6",
|
||||
"@signozhq/badge": "0.0.2",
|
||||
"@signozhq/calendar": "0.0.0",
|
||||
"@signozhq/design-tokens": "1.1.4",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/table": "0.3.4",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"@tanstack/react-virtual": "3.11.2",
|
||||
"@uiw/codemirror-theme-github": "4.24.1",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
"@uiw/codemirror-theme-github": "4.24.1",
|
||||
"@uiw/react-codemirror": "4.23.10",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@visx/group": "3.3.0",
|
||||
@@ -92,6 +98,7 @@
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"jest": "^27.5.1",
|
||||
"js-base64": "^3.7.2",
|
||||
"kbar": "0.1.0-beta.48",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -48,6 +48,6 @@
|
||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
|
||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_BASE": "SigNoz | Meter Explorer"
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer Views",
|
||||
"METER": "SigNoz | Meter"
|
||||
}
|
||||
|
||||
@@ -71,6 +71,6 @@
|
||||
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
|
||||
"API_MONITORING": "SigNoz | External APIs",
|
||||
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_BASE": "SigNoz | Meter Explorer"
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer Views",
|
||||
"METER": "SigNoz | Meter"
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import AppLoading from 'components/AppLoading/AppLoading';
|
||||
import KBarCommandPalette from 'components/KBarCommandPalette/KBarCommandPalette';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import UserpilotRouteTracker from 'components/UserpilotRouteTracker/UserpilotRouteTracker';
|
||||
@@ -25,6 +26,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { IUser } from 'providers/App/types';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { KBarCommandPaletteProvider } from 'providers/KBarCommandPaletteProvider';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
@@ -368,39 +370,42 @@ function App(): JSX.Element {
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<CompatRouter>
|
||||
<UserpilotRouteTracker />
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
<KBarCommandPaletteProvider>
|
||||
<UserpilotRouteTracker />
|
||||
<KBarCommandPalette />
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<DashboardProvider>
|
||||
<KeyboardHotkeysProvider>
|
||||
<AlertRuleProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</AlertRuleProvider>
|
||||
</KeyboardHotkeysProvider>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
</KBarCommandPaletteProvider>
|
||||
</CompatRouter>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
|
||||
@@ -437,10 +437,10 @@ const routes: AppRoutes[] = [
|
||||
},
|
||||
|
||||
{
|
||||
path: ROUTES.METER_EXPLORER_BASE,
|
||||
path: ROUTES.METER,
|
||||
exact: true,
|
||||
component: MeterExplorer,
|
||||
key: 'METER_EXPLORER_BASE',
|
||||
key: 'METER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,14 +8,15 @@ import {
|
||||
export const getValueSuggestions = (
|
||||
props: QueryKeyValueRequestProps,
|
||||
): Promise<AxiosResponse<QueryKeyValueSuggestionsResponseProps>> => {
|
||||
const { signal, key, searchText, signalSource } = props;
|
||||
const { signal, key, searchText, signalSource, metricName } = props;
|
||||
|
||||
const encodedSignal = encodeURIComponent(signal);
|
||||
const encodedKey = encodeURIComponent(key);
|
||||
const encodedMetricName = encodeURIComponent(metricName || '');
|
||||
const encodedSearchText = encodeURIComponent(searchText);
|
||||
const encodedSource = encodeURIComponent(signalSource || '');
|
||||
|
||||
return axios.get(
|
||||
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&source=${encodedSource}`,
|
||||
`/fields/values?signal=${encodedSignal}&name=${encodedKey}&searchText=${encodedSearchText}&metricName=${encodedMetricName}&source=${encodedSource}`,
|
||||
);
|
||||
};
|
||||
|
||||
284
frontend/src/api/v5/queryRange/convertV5Response.test.ts
Normal file
284
frontend/src/api/v5/queryRange/convertV5Response.test.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricRangePayloadV5,
|
||||
QueryBuilderFormula,
|
||||
QueryRangeRequestV5,
|
||||
QueryRangeResponseV5,
|
||||
RequestType,
|
||||
ScalarData,
|
||||
TelemetryFieldKey,
|
||||
TimeSeries,
|
||||
TimeSeriesData,
|
||||
TimeSeriesValue,
|
||||
} from 'types/api/v5/queryRange';
|
||||
|
||||
import { convertV5ResponseToLegacy } from './convertV5Response';
|
||||
|
||||
describe('convertV5ResponseToLegacy', () => {
|
||||
function makeBaseSuccess<T>(
|
||||
payload: T,
|
||||
params: QueryRangeRequestV5,
|
||||
): SuccessResponse<T, QueryRangeRequestV5> {
|
||||
return {
|
||||
statusCode: 200,
|
||||
message: 'success',
|
||||
payload,
|
||||
error: null,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
function makeBaseParams(
|
||||
requestType: RequestType,
|
||||
queries: QueryRangeRequestV5['compositeQuery']['queries'],
|
||||
): QueryRangeRequestV5 {
|
||||
return {
|
||||
schemaVersion: 'v1',
|
||||
start: 1,
|
||||
end: 2,
|
||||
requestType,
|
||||
compositeQuery: { queries },
|
||||
variables: {},
|
||||
formatOptions: { formatTableResultForUI: false, fillGaps: false },
|
||||
};
|
||||
}
|
||||
|
||||
it('converts time_series response into legacy series structure', () => {
|
||||
const timeSeries: TimeSeriesData = {
|
||||
queryName: 'A',
|
||||
aggregations: [
|
||||
{
|
||||
index: 0,
|
||||
alias: '__result_0',
|
||||
meta: {},
|
||||
series: [
|
||||
({
|
||||
labels: [
|
||||
{
|
||||
key: ({ name: 'service.name' } as unknown) as TelemetryFieldKey,
|
||||
value: 'adservice',
|
||||
},
|
||||
],
|
||||
values: [
|
||||
({ timestamp: 1000, value: 10 } as unknown) as TimeSeriesValue,
|
||||
({ timestamp: 2000, value: 12 } as unknown) as TimeSeriesValue,
|
||||
],
|
||||
} as unknown) as TimeSeries,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'time_series',
|
||||
data: { results: [timeSeries] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('time_series', [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: {
|
||||
name: 'A',
|
||||
signal: 'traces',
|
||||
stepInterval: 60,
|
||||
disabled: false,
|
||||
aggregations: [{ expression: 'count()' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
|
||||
const legendMap = { A: '{{service.name}}' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, false);
|
||||
|
||||
expect(result.payload.data.resultType).toBe('time_series');
|
||||
expect(result.payload.data.result).toHaveLength(1);
|
||||
const q = result.payload.data.result[0];
|
||||
expect(q.queryName).toBe('A');
|
||||
expect(q.legend).toBe('{{service.name}}');
|
||||
expect(q.series?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
labels: { 'service.name': 'adservice' },
|
||||
values: [
|
||||
{ timestamp: 1000, value: '10' },
|
||||
{ timestamp: 2000, value: '12' },
|
||||
],
|
||||
metaData: expect.objectContaining({
|
||||
alias: '__result_0',
|
||||
index: 0,
|
||||
queryName: 'A',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('converts scalar to legacy table (formatForWeb=false) with names/ids resolved from aggregations', () => {
|
||||
const scalar: ScalarData = {
|
||||
columns: [
|
||||
// group column
|
||||
({
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'group',
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// aggregation 0
|
||||
({
|
||||
name: '__result_0',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'aggregation',
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// aggregation 1
|
||||
({
|
||||
name: '__result_1',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 1,
|
||||
columnType: 'aggregation',
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
// formula F1
|
||||
({
|
||||
name: '__result',
|
||||
queryName: 'F1',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'aggregation',
|
||||
} as unknown) as ScalarData['columns'][number],
|
||||
],
|
||||
data: [['adservice', 606, 1.452, 151.5]],
|
||||
};
|
||||
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'scalar',
|
||||
data: { results: [scalar] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('scalar', [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: {
|
||||
name: 'A',
|
||||
signal: 'traces',
|
||||
stepInterval: 60,
|
||||
disabled: false,
|
||||
aggregations: [
|
||||
{ expression: 'count()' },
|
||||
{ expression: 'avg(app.ads.count)', alias: 'avg' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'builder_formula',
|
||||
spec: ({
|
||||
name: 'F1',
|
||||
expression: 'A * 0.25',
|
||||
} as unknown) as QueryBuilderFormula,
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
const legendMap = { A: '{{service.name}}', F1: '' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, false);
|
||||
|
||||
expect(result.payload.data.resultType).toBe('scalar');
|
||||
const [tableEntry] = result.payload.data.result;
|
||||
expect(tableEntry.table?.columns).toEqual([
|
||||
{
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
isValueColumn: false,
|
||||
id: 'service.name',
|
||||
},
|
||||
{ name: 'count()', queryName: 'A', isValueColumn: true, id: 'A.count()' },
|
||||
{
|
||||
name: 'avg',
|
||||
queryName: 'A',
|
||||
isValueColumn: true,
|
||||
id: 'A.avg(app.ads.count)',
|
||||
},
|
||||
{ name: 'F1', queryName: 'F1', isValueColumn: true, id: 'F1' },
|
||||
]);
|
||||
expect(tableEntry.table?.rows?.[0]).toEqual({
|
||||
data: {
|
||||
'service.name': 'adservice',
|
||||
'A.count()': 606,
|
||||
'A.avg(app.ads.count)': 1.452,
|
||||
F1: 151.5,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('converts scalar with formatForWeb=true to UI-friendly table', () => {
|
||||
const scalar: ScalarData = {
|
||||
columns: [
|
||||
{
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'group',
|
||||
} as any,
|
||||
{
|
||||
name: '__result_0',
|
||||
queryName: 'A',
|
||||
aggregationIndex: 0,
|
||||
columnType: 'aggregation',
|
||||
} as any,
|
||||
],
|
||||
data: [['adservice', 580]],
|
||||
};
|
||||
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'scalar',
|
||||
data: { results: [scalar] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('scalar', [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: {
|
||||
name: 'A',
|
||||
signal: 'traces',
|
||||
stepInterval: 60,
|
||||
disabled: false,
|
||||
aggregations: [{ expression: 'count()' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const input: SuccessResponse<
|
||||
MetricRangePayloadV5,
|
||||
QueryRangeRequestV5
|
||||
> = makeBaseSuccess({ data: v5Data }, params);
|
||||
const legendMap = { A: '{{service.name}}' };
|
||||
const result = convertV5ResponseToLegacy(input, legendMap, true);
|
||||
|
||||
expect(result.payload.data.resultType).toBe('scalar');
|
||||
const [tableEntry] = result.payload.data.result;
|
||||
expect(tableEntry.table?.columns).toEqual([
|
||||
{
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
isValueColumn: false,
|
||||
id: 'service.name',
|
||||
},
|
||||
// Single aggregation: name resolves to legend, id resolves to queryName
|
||||
{ name: '{{service.name}}', queryName: 'A', isValueColumn: true, id: 'A' },
|
||||
]);
|
||||
expect(tableEntry.table?.rows?.[0]).toEqual({
|
||||
data: {
|
||||
'service.name': 'adservice',
|
||||
A: 580,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -385,7 +385,7 @@ export function convertV5ResponseToLegacy(
|
||||
data: {
|
||||
resultType: 'scalar',
|
||||
result: webTables,
|
||||
warnings: v5Data?.data?.warnings || [],
|
||||
warnings: v5Data?.data?.warning || [],
|
||||
},
|
||||
warning: v5Data?.warning || undefined,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,633 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string, simple-import-sort/imports, @typescript-eslint/indent, no-mixed-spaces-and-tabs */
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
IBuilderFormula,
|
||||
IBuilderQuery,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import {
|
||||
ClickHouseQuery,
|
||||
LogAggregation,
|
||||
LogBuilderQuery,
|
||||
MetricBuilderQuery,
|
||||
PromQuery,
|
||||
QueryBuilderFormula as V5QueryBuilderFormula,
|
||||
QueryEnvelope,
|
||||
QueryRangePayloadV5,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
|
||||
import { prepareQueryRangePayloadV5 } from './prepareQueryRangePayloadV5';
|
||||
|
||||
jest.mock('lib/getStartEndRangeTime', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => ({ start: '100', end: '200' })),
|
||||
}));
|
||||
|
||||
describe('prepareQueryRangePayloadV5', () => {
|
||||
const start = 1_710_000_000; // seconds
|
||||
const end = 1_710_000_600; // seconds
|
||||
|
||||
const baseBuilderQuery = (
|
||||
overrides?: Partial<IBuilderQuery>,
|
||||
): IBuilderQuery => ({
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregations: [
|
||||
{
|
||||
metricName: 'cpu_usage',
|
||||
temporality: '',
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'avg',
|
||||
reduceTo: 'avg',
|
||||
},
|
||||
],
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'avg',
|
||||
temporality: '',
|
||||
functions: [
|
||||
{
|
||||
name: 'timeShift',
|
||||
args: [{ value: '5m' }],
|
||||
},
|
||||
],
|
||||
filter: { expression: '' },
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
having: [],
|
||||
limit: null,
|
||||
stepInterval: 600,
|
||||
orderBy: [],
|
||||
reduceTo: 'avg',
|
||||
legend: 'Legend A',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const baseFormula = (
|
||||
overrides?: Partial<IBuilderFormula>,
|
||||
): IBuilderFormula => ({
|
||||
expression: 'A + 1',
|
||||
disabled: false,
|
||||
queryName: 'F1',
|
||||
legend: 'Formula Legend',
|
||||
limit: undefined,
|
||||
having: [],
|
||||
stepInterval: undefined,
|
||||
orderBy: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
it('builds payload for builder queries with formulas and variables', () => {
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
id: 'q1',
|
||||
unit: undefined,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: {
|
||||
queryData: [baseBuilderQuery()],
|
||||
queryFormulas: [baseFormula()],
|
||||
},
|
||||
},
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
start,
|
||||
end,
|
||||
variables: { svc: 'api', count: 5, flag: true },
|
||||
fillGaps: true,
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A', F1: 'Formula Legend' },
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'builder_query',
|
||||
spec: expect.objectContaining({
|
||||
name: 'A',
|
||||
signal: 'metrics',
|
||||
stepInterval: 600,
|
||||
functions: [{ name: 'timeShift', args: [{ value: '5m' }] }],
|
||||
aggregations: [
|
||||
expect.objectContaining({
|
||||
metricName: 'cpu_usage',
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'avg',
|
||||
reduceTo: undefined,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'builder_formula',
|
||||
spec: expect.objectContaining({
|
||||
name: 'F1',
|
||||
expression: 'A + 1',
|
||||
legend: 'Formula Legend',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
requestType: 'time_series',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: false,
|
||||
fillGaps: true,
|
||||
}),
|
||||
start: start * 1000,
|
||||
end: end * 1000,
|
||||
variables: expect.objectContaining({
|
||||
svc: { value: 'api' },
|
||||
count: { value: 5 },
|
||||
flag: { value: true },
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Legend map combines builder and formulas
|
||||
expect(result.legendMap).toEqual({ A: 'Legend A', F1: 'Formula Legend' });
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
|
||||
expect(payload.schemaVersion).toBe('v1');
|
||||
expect(payload.start).toBe(start * 1000);
|
||||
expect(payload.end).toBe(end * 1000);
|
||||
expect(payload.requestType).toBe('time_series');
|
||||
expect(payload.formatOptions?.formatTableResultForUI).toBe(false);
|
||||
expect(payload.formatOptions?.fillGaps).toBe(true);
|
||||
|
||||
// Variables mapped as { key: { value } }
|
||||
expect(payload.variables).toEqual({
|
||||
svc: { value: 'api' },
|
||||
count: { value: 5 },
|
||||
flag: { value: true },
|
||||
});
|
||||
|
||||
// Queries include one builder_query and one builder_formula
|
||||
expect(payload.compositeQuery.queries).toHaveLength(2);
|
||||
|
||||
const builderQuery = payload.compositeQuery.queries.find(
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const builderSpec = builderQuery.spec as MetricBuilderQuery;
|
||||
expect(builderSpec.name).toBe('A');
|
||||
expect(builderSpec.signal).toBe('metrics');
|
||||
expect(builderSpec.aggregations?.[0]).toMatchObject({
|
||||
metricName: 'cpu_usage',
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'avg',
|
||||
});
|
||||
// reduceTo should not be present for non-scalar panels
|
||||
expect(builderSpec.aggregations?.[0].reduceTo).toBeUndefined();
|
||||
// functions should be preserved/normalized
|
||||
expect(builderSpec.functions?.[0]?.name).toBe('timeShift');
|
||||
|
||||
const formulaQuery = payload.compositeQuery.queries.find(
|
||||
(q) => q.type === 'builder_formula',
|
||||
) as QueryEnvelope;
|
||||
const formulaSpec = formulaQuery.spec as V5QueryBuilderFormula;
|
||||
expect(formulaSpec.name).toBe('F1');
|
||||
expect(formulaSpec.expression).toBe('A + 1');
|
||||
expect(formulaSpec.legend).toBe('Formula Legend');
|
||||
});
|
||||
|
||||
it('builds payload for PromQL queries and respects originalGraphType for formatting', () => {
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.PROM,
|
||||
id: 'q2',
|
||||
unit: undefined,
|
||||
promql: [
|
||||
{
|
||||
name: 'A',
|
||||
query: 'up',
|
||||
disabled: false,
|
||||
legend: 'LP',
|
||||
},
|
||||
],
|
||||
clickhouse_sql: [],
|
||||
builder: { queryData: [], queryFormulas: [] },
|
||||
},
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
originalGraphType: PANEL_TYPES.TABLE,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'LP' },
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: [
|
||||
{
|
||||
type: 'promql',
|
||||
spec: expect.objectContaining({
|
||||
name: 'A',
|
||||
query: 'up',
|
||||
legend: 'LP',
|
||||
stats: false,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
requestType: 'time_series',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: true,
|
||||
fillGaps: false,
|
||||
}),
|
||||
start: start * 1000,
|
||||
end: end * 1000,
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.legendMap).toEqual({ A: 'LP' });
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.requestType).toBe('time_series');
|
||||
expect(payload.formatOptions?.formatTableResultForUI).toBe(true);
|
||||
expect(payload.compositeQuery.queries).toHaveLength(1);
|
||||
|
||||
const prom = payload.compositeQuery.queries[0];
|
||||
expect(prom.type).toBe('promql');
|
||||
const promSpec = prom.spec as PromQuery;
|
||||
expect(promSpec.name).toBe('A');
|
||||
expect(promSpec.query).toBe('up');
|
||||
expect(promSpec.legend).toBe('LP');
|
||||
expect(promSpec.stats).toBe(false);
|
||||
});
|
||||
|
||||
it('builds payload for ClickHouse queries and maps requestType from panel', () => {
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
id: 'q3',
|
||||
unit: undefined,
|
||||
promql: [],
|
||||
clickhouse_sql: [
|
||||
{
|
||||
name: 'Q',
|
||||
query: 'SELECT 1',
|
||||
disabled: false,
|
||||
legend: 'LC',
|
||||
},
|
||||
],
|
||||
builder: { queryData: [], queryFormulas: [] },
|
||||
},
|
||||
graphType: PANEL_TYPES.TABLE,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { Q: 'LC' },
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: [
|
||||
{
|
||||
type: 'clickhouse_sql',
|
||||
spec: expect.objectContaining({
|
||||
name: 'Q',
|
||||
query: 'SELECT 1',
|
||||
legend: 'LC',
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
requestType: 'scalar',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: true,
|
||||
fillGaps: false,
|
||||
}),
|
||||
start: start * 1000,
|
||||
end: end * 1000,
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.legendMap).toEqual({ Q: 'LC' });
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.requestType).toBe('scalar');
|
||||
expect(payload.compositeQuery.queries).toHaveLength(1);
|
||||
const ch = payload.compositeQuery.queries[0];
|
||||
expect(ch.type).toBe('clickhouse_sql');
|
||||
const chSpec = ch.spec as ClickHouseQuery;
|
||||
expect(chSpec.name).toBe('Q');
|
||||
expect(chSpec.query).toBe('SELECT 1');
|
||||
expect(chSpec.legend).toBe('LC');
|
||||
});
|
||||
|
||||
it('uses getStartEndRangeTime when start/end are not provided', () => {
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
id: 'q4',
|
||||
unit: undefined,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: { queryData: [], queryFormulas: [] },
|
||||
},
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: {},
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: { queries: [] },
|
||||
requestType: 'time_series',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: false,
|
||||
fillGaps: false,
|
||||
}),
|
||||
start: 100 * 1000,
|
||||
end: 200 * 1000,
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.start).toBe(100 * 1000);
|
||||
expect(payload.end).toBe(200 * 1000);
|
||||
});
|
||||
|
||||
it('includes reduceTo for metrics in scalar panels (TABLE)', () => {
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
id: 'q5',
|
||||
unit: undefined,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: {
|
||||
queryData: [baseBuilderQuery()],
|
||||
queryFormulas: [],
|
||||
},
|
||||
},
|
||||
graphType: PANEL_TYPES.TABLE,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A' },
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: expect.objectContaining({
|
||||
name: 'A',
|
||||
signal: 'metrics',
|
||||
stepInterval: 600,
|
||||
functions: [{ name: 'timeShift', args: [{ value: '5m' }] }],
|
||||
aggregations: [
|
||||
expect.objectContaining({
|
||||
metricName: 'cpu_usage',
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'avg',
|
||||
reduceTo: 'avg',
|
||||
temporality: undefined,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
requestType: 'scalar',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: true,
|
||||
fillGaps: false,
|
||||
}),
|
||||
start: start * 1000,
|
||||
end: end * 1000,
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
const builderQuery = payload.compositeQuery.queries.find(
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const builderSpec = builderQuery.spec as MetricBuilderQuery;
|
||||
expect(builderSpec.aggregations?.[0].reduceTo).toBe('avg');
|
||||
});
|
||||
|
||||
it('omits aggregations for raw request type (LIST panel)', () => {
|
||||
const logAgg: LogAggregation[] = [{ expression: 'count()' }];
|
||||
const logsQuery = baseBuilderQuery({
|
||||
dataSource: DataSource.LOGS,
|
||||
aggregations: logAgg,
|
||||
} as Partial<IBuilderQuery>);
|
||||
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
id: 'q6',
|
||||
unit: undefined,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: {
|
||||
queryData: [logsQuery],
|
||||
queryFormulas: [],
|
||||
},
|
||||
},
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
start,
|
||||
end,
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A' },
|
||||
queryPayload: expect.objectContaining({
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: expect.objectContaining({
|
||||
name: 'A',
|
||||
signal: 'logs',
|
||||
stepInterval: 600,
|
||||
functions: [{ name: 'timeShift', args: [{ value: '5m' }] }],
|
||||
aggregations: undefined,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
requestType: 'raw',
|
||||
formatOptions: expect.objectContaining({
|
||||
formatTableResultForUI: false,
|
||||
fillGaps: false,
|
||||
}),
|
||||
start: start * 1000,
|
||||
end: end * 1000,
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.requestType).toBe('raw');
|
||||
const builderQuery = payload.compositeQuery.queries.find(
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
// For RAW request type, aggregations should be omitted
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.aggregations).toBeUndefined();
|
||||
});
|
||||
|
||||
it('maps groupBy, order, having, aggregations and filter for logs builder query', () => {
|
||||
const getStartEndRangeTime = jest.requireMock('lib/getStartEndRangeTime')
|
||||
.default as jest.Mock;
|
||||
getStartEndRangeTime.mockReturnValueOnce({
|
||||
start: '1754623641',
|
||||
end: '1754645241',
|
||||
});
|
||||
|
||||
const props: GetQueryResultsProps = {
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
id: 'e643e387-1996-4449-97b6-9ef4498a0573',
|
||||
unit: undefined,
|
||||
promql: [{ name: 'A', query: '', legend: '', disabled: false }],
|
||||
clickhouse_sql: [{ name: 'A', legend: '', disabled: false, query: '' }],
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
dataSource: DataSource.LOGS,
|
||||
queryName: 'A',
|
||||
aggregateOperator: 'count',
|
||||
aggregateAttribute: {
|
||||
key: '',
|
||||
dataType: DataTypes.EMPTY,
|
||||
type: '',
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
filter: { expression: "service.name = 'adservice'" },
|
||||
aggregations: [
|
||||
{ expression: 'count() as cnt avg(code.lineno) ' } as LogAggregation,
|
||||
],
|
||||
functions: [],
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '14c790ec-54d1-42f0-a889-3b4f0fb79852',
|
||||
op: '=',
|
||||
key: { id: 'service.name', key: 'service.name', type: '' },
|
||||
value: 'adservice',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
stepInterval: 80,
|
||||
having: { expression: 'count() > 0' },
|
||||
limit: 600,
|
||||
orderBy: [{ columnName: 'service.name', order: 'desc' }],
|
||||
groupBy: [
|
||||
{
|
||||
key: 'service.name',
|
||||
type: '',
|
||||
},
|
||||
],
|
||||
legend: '{{service.name}}',
|
||||
reduceTo: 'avg',
|
||||
offset: 0,
|
||||
pageSize: 100,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
},
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: 'custom' as never,
|
||||
variables: {},
|
||||
};
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: '{{service.name}}' },
|
||||
queryPayload: expect.objectContaining({
|
||||
schemaVersion: 'v1',
|
||||
start: 1754623641000,
|
||||
end: 1754645241000,
|
||||
requestType: 'time_series',
|
||||
compositeQuery: expect.objectContaining({
|
||||
queries: [
|
||||
{
|
||||
type: 'builder_query',
|
||||
spec: expect.objectContaining({
|
||||
name: 'A',
|
||||
signal: 'logs',
|
||||
stepInterval: 80,
|
||||
disabled: false,
|
||||
filter: { expression: "service.name = 'adservice'" },
|
||||
groupBy: [
|
||||
{
|
||||
name: 'service.name',
|
||||
fieldDataType: '',
|
||||
fieldContext: '',
|
||||
},
|
||||
],
|
||||
limit: 600,
|
||||
order: [
|
||||
{
|
||||
key: { name: 'service.name' },
|
||||
direction: 'desc',
|
||||
},
|
||||
],
|
||||
legend: '{{service.name}}',
|
||||
having: { expression: 'count() > 0' },
|
||||
aggregations: [
|
||||
{ expression: 'count()', alias: 'cnt' },
|
||||
{ expression: 'avg(code.lineno)' },
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
formatOptions: { formatTableResultForUI: false, fillGaps: false },
|
||||
variables: {},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -66,9 +66,46 @@ function getSignalType(dataSource: string): 'traces' | 'logs' | 'metrics' {
|
||||
return 'metrics';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates base spec for builder queries
|
||||
*/
|
||||
function isDeprecatedField(fieldName: string): boolean {
|
||||
const deprecatedIntrinsicFields = [
|
||||
'traceID',
|
||||
'spanID',
|
||||
'parentSpanID',
|
||||
'spanKind',
|
||||
'durationNano',
|
||||
'statusCode',
|
||||
'statusMessage',
|
||||
'statusCodeString',
|
||||
];
|
||||
|
||||
const deprecatedCalculatedFields = [
|
||||
'responseStatusCode',
|
||||
'externalHttpUrl',
|
||||
'httpUrl',
|
||||
'externalHttpMethod',
|
||||
'httpMethod',
|
||||
'httpHost',
|
||||
'dbName',
|
||||
'dbOperation',
|
||||
'hasError',
|
||||
'isRemote',
|
||||
'serviceName',
|
||||
'httpRoute',
|
||||
'msgSystem',
|
||||
'msgOperation',
|
||||
'dbSystem',
|
||||
'rpcSystem',
|
||||
'rpcService',
|
||||
'rpcMethod',
|
||||
'peerService',
|
||||
];
|
||||
|
||||
return (
|
||||
deprecatedIntrinsicFields.includes(fieldName) ||
|
||||
deprecatedCalculatedFields.includes(fieldName)
|
||||
);
|
||||
}
|
||||
|
||||
function createBaseSpec(
|
||||
queryData: IBuilderQuery,
|
||||
requestType: RequestType,
|
||||
@@ -80,7 +117,7 @@ function createBaseSpec(
|
||||
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
|
||||
|
||||
return {
|
||||
stepInterval: queryData?.stepInterval || undefined,
|
||||
stepInterval: queryData?.stepInterval || null,
|
||||
disabled: queryData.disabled,
|
||||
filter: queryData?.filter?.expression ? queryData.filter : undefined,
|
||||
groupBy:
|
||||
@@ -88,8 +125,8 @@ function createBaseSpec(
|
||||
? queryData.groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
fieldDataType: item?.dataType || '',
|
||||
fieldContext: item?.type || '',
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
@@ -140,19 +177,33 @@ function createBaseSpec(
|
||||
selectFields: isEmpty(nonEmptySelectColumns)
|
||||
? 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,
|
||||
}),
|
||||
(column: any): TelemetryFieldKey => {
|
||||
const fieldName = column.name ?? column.key;
|
||||
const isDeprecated = isDeprecatedField(fieldName);
|
||||
|
||||
const fieldObj: TelemetryFieldKey = {
|
||||
name: fieldName,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
signal: column?.signal ?? undefined,
|
||||
};
|
||||
|
||||
// Only add fieldContext if the field is NOT deprecated
|
||||
if (!isDeprecated && fieldName !== 'name') {
|
||||
fieldObj.fieldContext =
|
||||
column?.fieldContext ?? (column?.type as FieldContext);
|
||||
}
|
||||
|
||||
return fieldObj;
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// Utility to parse aggregation expressions with optional alias
|
||||
export function parseAggregations(
|
||||
expression: string,
|
||||
availableAlias?: string,
|
||||
): { expression: string; alias?: string }[] {
|
||||
const result: { expression: string; alias?: string }[] = [];
|
||||
// Matches function calls like "count()" or "sum(field)" with optional alias like "as 'alias'"
|
||||
@@ -161,7 +212,7 @@ export function parseAggregations(
|
||||
let match = regex.exec(expression);
|
||||
while (match !== null) {
|
||||
const expr = match[1];
|
||||
let alias = match[2];
|
||||
let alias = match[2] || availableAlias; // Use provided alias or availableAlias if not matched
|
||||
if (alias) {
|
||||
// Remove quotes if present
|
||||
alias = alias.replace(/^['"]|['"]$/g, '');
|
||||
@@ -212,9 +263,14 @@ export function createAggregation(
|
||||
}
|
||||
|
||||
if (queryData.aggregations?.length > 0) {
|
||||
return isEmpty(parseAggregations(queryData.aggregations?.[0].expression))
|
||||
? [{ expression: 'count()' }]
|
||||
: parseAggregations(queryData.aggregations?.[0].expression);
|
||||
return queryData.aggregations.flatMap(
|
||||
(agg: { expression: string; alias?: string }) => {
|
||||
const parsedAggregations = parseAggregations(agg.expression, agg?.alias);
|
||||
return isEmpty(parsedAggregations)
|
||||
? [{ expression: 'count()' }]
|
||||
: parsedAggregations;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return [{ expression: 'count()' }];
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import getLocal from '../../../api/browser/localstorage/get';
|
||||
import AppLoading from '../AppLoading';
|
||||
|
||||
// Mock the localStorage API
|
||||
const mockGet = jest.fn();
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
jest.mock('../../../api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: mockGet,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
// Access the mocked function
|
||||
const mockGet = (getLocal as unknown) as jest.Mock;
|
||||
|
||||
describe('AppLoading', () => {
|
||||
const SIGNOZ_TEXT = 'SigNoz';
|
||||
const TAGLINE_TEXT =
|
||||
|
||||
@@ -32,8 +32,6 @@ export const celeryAllStateWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -50,8 +48,6 @@ export const celeryAllStateWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -88,7 +84,6 @@ export const celeryRetryStateWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -103,8 +98,6 @@ export const celeryRetryStateWidgetData = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -119,8 +112,6 @@ export const celeryRetryStateWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -153,8 +144,6 @@ export const celeryFailedStateWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -169,8 +158,6 @@ export const celeryFailedStateWidgetData = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -185,8 +172,6 @@ export const celeryFailedStateWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -219,8 +204,6 @@ export const celerySuccessStateWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -235,8 +218,6 @@ export const celerySuccessStateWidgetData = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -251,8 +232,6 @@ export const celerySuccessStateWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -284,7 +263,6 @@ export const celeryTasksByWorkerWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -301,8 +279,6 @@ export const celeryTasksByWorkerWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -338,8 +314,6 @@ export const celeryErrorByWorkerWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: 'string',
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -353,8 +327,6 @@ export const celeryErrorByWorkerWidgetData = (
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
id: 'has_error--bool----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
@@ -373,8 +345,6 @@ export const celeryErrorByWorkerWidgetData = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
@@ -390,8 +360,6 @@ export const celeryErrorByWorkerWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: 'string',
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -411,8 +379,6 @@ export const celeryErrorByWorkerWidgetData = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
@@ -445,8 +411,6 @@ export const celeryLatencyByWorkerWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -463,8 +427,6 @@ export const celeryLatencyByWorkerWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.hostname--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.hostname',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -498,8 +460,6 @@ export const celeryActiveTasksWidgetData = (
|
||||
dataType: DataTypes.Float64,
|
||||
id:
|
||||
'flower_worker_number_of_currently_executing_tasks--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'flower_worker_number_of_currently_executing_tasks',
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -516,8 +476,6 @@ export const celeryActiveTasksWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'worker--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'worker',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -551,8 +509,6 @@ export const celeryTaskLatencyWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -569,8 +525,6 @@ export const celeryTaskLatencyWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -606,8 +560,6 @@ export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -624,8 +576,6 @@ export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -660,8 +610,6 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -676,8 +624,6 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -692,8 +638,6 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -729,8 +673,6 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -745,8 +687,6 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -761,8 +701,6 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -796,8 +734,6 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -812,8 +748,6 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -828,8 +762,6 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -869,8 +801,6 @@ export const celeryTimeSeriesTablesWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -885,8 +815,6 @@ export const celeryTimeSeriesTablesWidgetData = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: `${entity}--string--tag--false`,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: `${entity}`,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -901,8 +829,6 @@ export const celeryTimeSeriesTablesWidgetData = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -933,8 +859,6 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -972,8 +896,6 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -988,8 +910,6 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1025,8 +945,6 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -1041,8 +959,6 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1078,7 +994,6 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -1093,8 +1008,6 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.state',
|
||||
type: 'tag',
|
||||
},
|
||||
|
||||
@@ -39,8 +39,6 @@ export function getFiltersFromQueryParams(
|
||||
key,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--string--tag--false`,
|
||||
},
|
||||
op: '=',
|
||||
@@ -100,8 +98,7 @@ export const createFiltersFromData = (
|
||||
key: string;
|
||||
dataType: DataTypes;
|
||||
type: string;
|
||||
isColumn: boolean;
|
||||
isJSON: boolean;
|
||||
|
||||
id: string;
|
||||
};
|
||||
op: string;
|
||||
@@ -119,8 +116,6 @@ export const createFiltersFromData = (
|
||||
key,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--string--tag--false`,
|
||||
},
|
||||
op: '=',
|
||||
|
||||
@@ -137,5 +137,11 @@
|
||||
h6 {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +241,6 @@ function ClientSideQBSearch(
|
||||
key: 'body',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'body--string----true',
|
||||
},
|
||||
op: OPERATORS.CONTAINS,
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
.custom-time-picker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.timeSelection-input {
|
||||
&:hover {
|
||||
border-color: #1d212d !important;
|
||||
}
|
||||
}
|
||||
|
||||
.time-input-suffix {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.time-options-container {
|
||||
@@ -135,6 +145,7 @@
|
||||
align-items: center;
|
||||
color: var(--bg-vanilla-400);
|
||||
gap: 6px;
|
||||
|
||||
.timezone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -163,6 +174,27 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.time-input-suffix-icon-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
border-radius: 2px;
|
||||
background: rgba(171, 189, 255, 0.04);
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: -0.06px;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(171, 189, 255, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.date-time-popover__footer {
|
||||
border-color: var(--bg-vanilla-400);
|
||||
@@ -180,8 +212,26 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-time-picker {
|
||||
.timeSelection-input {
|
||||
&:hover {
|
||||
border-color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timezone-badge {
|
||||
color: var(--bg-ink-100);
|
||||
background: rgb(179 179 179 / 15%);
|
||||
}
|
||||
|
||||
.time-input-suffix-icon-badge {
|
||||
color: var(--bg-ink-100);
|
||||
background: rgb(179 179 179 / 15%);
|
||||
|
||||
&:hover {
|
||||
background: rgb(179 179 179 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import './CustomTimePicker.styles.scss';
|
||||
import { Input, Popover, Tooltip, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import {
|
||||
CustomTimeType,
|
||||
FixedDurationSuggestionOptions,
|
||||
Options,
|
||||
RelativeDurationSuggestionOptions,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { isValidTimeFormat } from 'lib/getMinMax';
|
||||
@@ -28,7 +27,10 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
|
||||
@@ -58,10 +60,6 @@ interface CustomTimePickerProps {
|
||||
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
|
||||
handleGoLive?: () => void;
|
||||
onTimeChange?: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
function CustomTimePicker({
|
||||
@@ -79,13 +77,16 @@ function CustomTimePicker({
|
||||
setCustomDTPickerVisible,
|
||||
onCustomDateHandler,
|
||||
handleGoLive,
|
||||
onTimeChange,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [
|
||||
selectedTimePlaceholderValue,
|
||||
setSelectedTimePlaceholderValue,
|
||||
] = useState('Select / Enter Time Range');
|
||||
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>('');
|
||||
const [inputErrorMessage, setInputErrorMessage] = useState<string | null>(
|
||||
@@ -256,6 +257,11 @@ function CustomTimePicker({
|
||||
};
|
||||
|
||||
const handleSelect = (label: string, value: string): void => {
|
||||
if (label === 'Custom') {
|
||||
setCustomDTPickerVisible?.(true);
|
||||
return;
|
||||
}
|
||||
|
||||
onSelect(value);
|
||||
setSelectedTimePlaceholderValue(label);
|
||||
setInputStatus('');
|
||||
@@ -318,84 +324,105 @@ function CustomTimePicker({
|
||||
);
|
||||
};
|
||||
|
||||
const getTooltipTitle = (): string => {
|
||||
if (selectedTime === 'custom' && inputValue === '' && !open) {
|
||||
return `${dayjs(minTime / 1000_000)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs(
|
||||
maxTime / 1000_000,
|
||||
)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)}`;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="custom-time-picker">
|
||||
<Popover
|
||||
className={cx(
|
||||
'timeSelection-input-container',
|
||||
selectedTime === 'custom' && inputValue === '' ? 'custom-time' : '',
|
||||
)}
|
||||
placement="bottomRight"
|
||||
getPopupContainer={popupContainer}
|
||||
rootClassName="date-time-root"
|
||||
content={
|
||||
newPopover ? (
|
||||
<CustomTimePickerPopoverContent
|
||||
setIsOpen={setOpen}
|
||||
customDateTimeVisible={defaultTo(customDateTimeVisible, false)}
|
||||
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
|
||||
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
|
||||
onSelectHandler={handleSelect}
|
||||
handleGoLive={defaultTo(handleGoLive, noop)}
|
||||
options={items}
|
||||
selectedTime={selectedTime}
|
||||
activeView={activeView}
|
||||
setActiveView={setActiveView}
|
||||
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
||||
isOpenedFromFooter={isOpenedFromFooter}
|
||||
onTimeChange={onTimeChange}
|
||||
/>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
arrow={false}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
style={{
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
className="timeSelection-input"
|
||||
type="text"
|
||||
status={inputValue && inputStatus === 'error' ? 'error' : ''}
|
||||
placeholder={
|
||||
isInputFocused
|
||||
? 'Time Format (1m or 2h or 3d or 4w)'
|
||||
: selectedTimePlaceholderValue
|
||||
}
|
||||
value={inputValue}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleInputChange}
|
||||
data-1p-ignore
|
||||
prefix={
|
||||
inputValue && inputStatus === 'success' ? (
|
||||
<CheckCircle size={14} color="#51E7A8" />
|
||||
<Tooltip title={getTooltipTitle()} placement="top">
|
||||
<Popover
|
||||
className={cx(
|
||||
'timeSelection-input-container',
|
||||
selectedTime === 'custom' && inputValue === '' ? 'custom-time' : '',
|
||||
)}
|
||||
placement="bottomRight"
|
||||
getPopupContainer={popupContainer}
|
||||
rootClassName="date-time-root"
|
||||
content={
|
||||
newPopover ? (
|
||||
<CustomTimePickerPopoverContent
|
||||
setIsOpen={setOpen}
|
||||
customDateTimeVisible={defaultTo(customDateTimeVisible, false)}
|
||||
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
|
||||
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
|
||||
onSelectHandler={handleSelect}
|
||||
handleGoLive={defaultTo(handleGoLive, noop)}
|
||||
options={items}
|
||||
selectedTime={selectedTime}
|
||||
activeView={activeView}
|
||||
setActiveView={setActiveView}
|
||||
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
||||
isOpenedFromFooter={isOpenedFromFooter}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
|
||||
<Clock size={14} />
|
||||
</Tooltip>
|
||||
content
|
||||
)
|
||||
}
|
||||
suffix={
|
||||
<>
|
||||
{!!isTimezoneOverridden && activeTimezoneOffset && (
|
||||
<div className="timezone-badge" onClick={handleTimezoneHintClick}>
|
||||
<span>{activeTimezoneOffset}</span>
|
||||
</div>
|
||||
)}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
onClick={(): void => handleViewChange('datetime')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
arrow={false}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
style={{
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
className="timeSelection-input"
|
||||
type="text"
|
||||
status={inputValue && inputStatus === 'error' ? 'error' : ''}
|
||||
placeholder={
|
||||
isInputFocused
|
||||
? 'Time Format (1m or 2h or 3d or 4w)'
|
||||
: selectedTimePlaceholderValue
|
||||
}
|
||||
value={inputValue}
|
||||
onFocus={handleFocus}
|
||||
onClick={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleInputChange}
|
||||
data-1p-ignore
|
||||
prefix={
|
||||
<div className="time-input-prefix">
|
||||
{inputValue && inputStatus === 'success' ? (
|
||||
<CheckCircle size={14} color="#51E7A8" />
|
||||
) : (
|
||||
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
|
||||
<Clock size={14} className="cursor-pointer" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
suffix={
|
||||
<div className="time-input-suffix">
|
||||
{!!isTimezoneOverridden && activeTimezoneOffset && (
|
||||
<div className="timezone-badge" onClick={handleTimezoneHintClick}>
|
||||
<span>{activeTimezoneOffset}</span>
|
||||
</div>
|
||||
)}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className="cursor-pointer time-input-suffix-icon-badge"
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
handleViewChange('datetime');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
{inputStatus === 'error' && inputErrorMessage && (
|
||||
<Typography.Title level={5} className="valid-format-error">
|
||||
{inputErrorMessage}
|
||||
@@ -414,5 +441,4 @@ CustomTimePicker.defaultProps = {
|
||||
onCustomDateHandler: noop,
|
||||
handleGoLive: noop,
|
||||
onCustomTimeStatusUpdate: noop,
|
||||
onTimeChange: undefined,
|
||||
};
|
||||
|
||||
@@ -4,21 +4,22 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import DatePickerV2 from 'components/DatePickerV2/DatePickerV2';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import {
|
||||
CustomTimeType,
|
||||
LexicalContext,
|
||||
Option,
|
||||
RelativeDurationSuggestionOptions,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { Clock, PenLine } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { getCustomTimeRanges } from 'utils/customTimeRangeUtils';
|
||||
|
||||
import RangePickerModal from './RangePickerModal';
|
||||
import TimezonePicker from './TimezonePicker';
|
||||
|
||||
interface CustomTimePickerPopoverContentProps {
|
||||
@@ -37,10 +38,14 @@ interface CustomTimePickerPopoverContentProps {
|
||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||
isOpenedFromFooter: boolean;
|
||||
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
|
||||
onTimeChange?: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface RecentlyUsedDateTimeRange {
|
||||
label: string;
|
||||
value: number;
|
||||
timestamp: number;
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
@@ -57,16 +62,42 @@ function CustomTimePickerPopoverContent({
|
||||
setActiveView,
|
||||
isOpenedFromFooter,
|
||||
setIsOpenedFromFooter,
|
||||
onTimeChange,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||
pathname,
|
||||
]);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
const activeTimezoneOffset = timezone.offset;
|
||||
|
||||
const [recentlyUsedTimeRanges, setRecentlyUsedTimeRanges] = useState<
|
||||
RecentlyUsedDateTimeRange[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customDateTimeVisible) {
|
||||
const customTimeRanges = getCustomTimeRanges();
|
||||
|
||||
const formattedCustomTimeRanges: RecentlyUsedDateTimeRange[] = customTimeRanges.map(
|
||||
(range) => ({
|
||||
label: `${dayjs(range.from)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)} - ${dayjs(range.to)
|
||||
.tz(timezone.value)
|
||||
.format(DATE_TIME_FORMATS.DD_MMM_YYYY_HH_MM_SS)}`,
|
||||
from: range.from,
|
||||
to: range.to,
|
||||
value: range.timestamp,
|
||||
timestamp: range.timestamp,
|
||||
}),
|
||||
);
|
||||
|
||||
setRecentlyUsedTimeRanges(formattedCustomTimeRanges);
|
||||
}
|
||||
}, [customDateTimeVisible, timezone.value]);
|
||||
|
||||
function getTimeChips(options: Option[]): JSX.Element {
|
||||
return (
|
||||
<div className="relative-date-time-section">
|
||||
@@ -112,50 +143,77 @@ function CustomTimePickerPopoverContent({
|
||||
return (
|
||||
<>
|
||||
<div className="date-time-popover">
|
||||
<div className="date-time-options">
|
||||
{isLogsExplorerPage && (
|
||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||
Live
|
||||
</Button>
|
||||
)}
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-options-btn',
|
||||
customDateTimeVisible
|
||||
? option.value === 'custom' && 'active'
|
||||
: selectedTime === option.value && 'active',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{!customDateTimeVisible && (
|
||||
<div className="date-time-options">
|
||||
{isLogsExplorerPage && (
|
||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||
Live
|
||||
</Button>
|
||||
)}
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
onClick={(): void => {
|
||||
onSelectHandler(option.label, option.value);
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-options-btn',
|
||||
customDateTimeVisible
|
||||
? option.value === 'custom' && 'active'
|
||||
: selectedTime === option.value && 'active',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'relative-date-time',
|
||||
selectedTime === 'custom' || customDateTimeVisible
|
||||
? 'date-picker'
|
||||
: 'relative-times',
|
||||
customDateTimeVisible ? 'date-picker' : 'relative-times',
|
||||
)}
|
||||
>
|
||||
{selectedTime === 'custom' || customDateTimeVisible ? (
|
||||
<RangePickerModal
|
||||
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||
{customDateTimeVisible ? (
|
||||
<DatePickerV2
|
||||
onSetCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||
setIsOpen={setIsOpen}
|
||||
onCustomDateHandler={onCustomDateHandler}
|
||||
selectedTime={selectedTime}
|
||||
onTimeChange={onTimeChange}
|
||||
/>
|
||||
) : (
|
||||
<div className="relative-times-container">
|
||||
<div className="time-heading">RELATIVE TIMES</div>
|
||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||
<div className="time-selector-container">
|
||||
<div className="relative-times-container">
|
||||
<div className="time-heading">RELATIVE TIMES</div>
|
||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||
</div>
|
||||
|
||||
<div className="recently-used-container">
|
||||
<div className="time-heading">RECENTLY USED</div>
|
||||
<div className="recently-used-range">
|
||||
{recentlyUsedTimeRanges.map((range: RecentlyUsedDateTimeRange) => (
|
||||
<div
|
||||
className="recently-used-range-item"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
onCustomDateHandler([dayjs(range.from), dayjs(range.to)]);
|
||||
setIsOpen(false);
|
||||
}
|
||||
}}
|
||||
key={range.value}
|
||||
onClick={(): void => {
|
||||
onCustomDateHandler([dayjs(range.from), dayjs(range.to)]);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{range.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -189,8 +247,4 @@ function CustomTimePickerPopoverContent({
|
||||
);
|
||||
}
|
||||
|
||||
CustomTimePickerPopoverContent.defaultProps = {
|
||||
onTimeChange: undefined,
|
||||
};
|
||||
|
||||
export default CustomTimePickerPopoverContent;
|
||||
|
||||
114
frontend/src/components/DatePickerV2/DatePickerV2.styles.scss
Normal file
114
frontend/src/components/DatePickerV2/DatePickerV2.styles.scss
Normal file
@@ -0,0 +1,114 @@
|
||||
.date-picker-v2-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.custom-date-time-picker-v2 {
|
||||
padding: 12px;
|
||||
|
||||
.periscope-calendar {
|
||||
border-radius: 4px;
|
||||
border: none !important;
|
||||
background: none !important;
|
||||
padding: 8px 0 !important;
|
||||
}
|
||||
|
||||
.periscope-calendar-day {
|
||||
background: none !important;
|
||||
|
||||
&.periscope-calendar-today {
|
||||
&.text-accent-foreground {
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
background-color: var(--bg-robin-500) !important;
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-time-selector {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.time-input {
|
||||
border-radius: 4px;
|
||||
border: none !important;
|
||||
background: none !important;
|
||||
padding: 8px 4px !important;
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
|
||||
&::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-date-time-picker-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
|
||||
.next-btn {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-date-range-tooltip {
|
||||
.ant-tooltip-inner {
|
||||
color: var(--bg-sakura-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.custom-date-time-picker-v2 {
|
||||
.periscope-calendar-day {
|
||||
&.periscope-calendar-today {
|
||||
&.text-accent-foreground {
|
||||
color: var(--bg-ink-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
background-color: var(--bg-robin-500) !important;
|
||||
color: var(--bg-ink-500) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-time-selector {
|
||||
.time-input {
|
||||
color: var(--bg-ink-500) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
311
frontend/src/components/DatePickerV2/DatePickerV2.tsx
Normal file
311
frontend/src/components/DatePickerV2/DatePickerV2.tsx
Normal file
@@ -0,0 +1,311 @@
|
||||
import './DatePickerV2.styles.scss';
|
||||
|
||||
import { Calendar } from '@signozhq/calendar';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { CornerUpLeft, MoveRight } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { addCustomTimeRange } from 'utils/customTimeRangeUtils';
|
||||
|
||||
function DatePickerV2({
|
||||
onSetCustomDTPickerVisible,
|
||||
setIsOpen,
|
||||
onCustomDateHandler,
|
||||
}: {
|
||||
onSetCustomDTPickerVisible: (visible: boolean) => void;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
onCustomDateHandler: (
|
||||
dateTimeRange: DateTimeRangeType,
|
||||
lexicalContext?: LexicalContext,
|
||||
) => void;
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const timeInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const [selectedDateTimeFor, setSelectedDateTimeFor] = useState<'to' | 'from'>(
|
||||
'from',
|
||||
);
|
||||
|
||||
const [selectedFromDateTime, setSelectedFromDateTime] = useState<Dayjs | null>(
|
||||
dayjs(minTime / 1000_000).tz(timezone.value),
|
||||
);
|
||||
|
||||
const [selectedToDateTime, setSelectedToDateTime] = useState<Dayjs | null>(
|
||||
dayjs(maxTime / 1000_000).tz(timezone.value),
|
||||
);
|
||||
|
||||
const handleNext = (): void => {
|
||||
if (selectedDateTimeFor === 'to') {
|
||||
onCustomDateHandler([selectedFromDateTime, selectedToDateTime]);
|
||||
|
||||
addCustomTimeRange([selectedFromDateTime, selectedToDateTime]);
|
||||
|
||||
setIsOpen(false);
|
||||
onSetCustomDTPickerVisible(false);
|
||||
setSelectedDateTimeFor('from');
|
||||
} else {
|
||||
setSelectedDateTimeFor('to');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateChange = (date: Date | undefined): void => {
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDateTimeFor === 'from') {
|
||||
const prevFromDateTime = selectedFromDateTime;
|
||||
|
||||
const newDate = dayjs(date);
|
||||
|
||||
const updatedFromDateTime = prevFromDateTime
|
||||
? prevFromDateTime
|
||||
.year(newDate.year())
|
||||
.month(newDate.month())
|
||||
.date(newDate.date())
|
||||
: dayjs(date).tz(timezone.value);
|
||||
|
||||
setSelectedFromDateTime(updatedFromDateTime);
|
||||
} else {
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
setSelectedToDateTime((prev) => {
|
||||
const newDate = dayjs(date);
|
||||
|
||||
// Update only the date part, keeping time from existing state
|
||||
return prev
|
||||
? prev.year(newDate.year()).month(newDate.month()).date(newDate.date())
|
||||
: dayjs(date).tz(timezone.value);
|
||||
});
|
||||
}
|
||||
|
||||
// focus the time input
|
||||
timeInputRef?.current?.focus();
|
||||
};
|
||||
|
||||
const handleTimeChange = (time: string): void => {
|
||||
// time should have format HH:mm:ss
|
||||
if (!/^\d{2}:\d{2}:\d{2}$/.test(time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDateTimeFor === 'from') {
|
||||
setSelectedFromDateTime((prev) => {
|
||||
if (prev) {
|
||||
return prev
|
||||
.set('hour', parseInt(time.split(':')[0], 10))
|
||||
.set('minute', parseInt(time.split(':')[1], 10))
|
||||
.set('second', parseInt(time.split(':')[2], 10));
|
||||
}
|
||||
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
if (selectedDateTimeFor === 'to') {
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
setSelectedToDateTime((prev) => {
|
||||
if (prev) {
|
||||
return prev
|
||||
.set('hour', parseInt(time.split(':')[0], 10))
|
||||
.set('minute', parseInt(time.split(':')[1], 10))
|
||||
.set('second', parseInt(time.split(':')[2], 10));
|
||||
}
|
||||
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaultMonth = (): Date => {
|
||||
let defaultDate = null;
|
||||
|
||||
if (selectedDateTimeFor === 'from') {
|
||||
defaultDate = selectedFromDateTime?.toDate();
|
||||
} else if (selectedDateTimeFor === 'to') {
|
||||
defaultDate = selectedToDateTime?.toDate();
|
||||
}
|
||||
|
||||
return defaultDate ?? new Date();
|
||||
};
|
||||
|
||||
const isValidRange = (): boolean => {
|
||||
if (selectedDateTimeFor === 'to') {
|
||||
return selectedToDateTime?.isAfter(selectedFromDateTime) ?? false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleBack = (): void => {
|
||||
setSelectedDateTimeFor('from');
|
||||
};
|
||||
|
||||
const handleHideCustomDTPicker = (): void => {
|
||||
onSetCustomDTPickerVisible(false);
|
||||
};
|
||||
|
||||
const handleSelectDateTimeFor = (selectedDateTimeFor: 'to' | 'from'): void => {
|
||||
setSelectedDateTimeFor(selectedDateTimeFor);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="date-picker-v2-container">
|
||||
<div className="date-time-custom-options-container">
|
||||
<div
|
||||
className="back-btn"
|
||||
onClick={handleHideCustomDTPicker}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
handleHideCustomDTPicker();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CornerUpLeft size={16} />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
|
||||
<div className="date-time-custom-options">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSelectDateTimeFor('from');
|
||||
}
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-custom-option-from',
|
||||
selectedDateTimeFor === 'from' && 'active',
|
||||
)}
|
||||
onClick={(): void => {
|
||||
handleSelectDateTimeFor('from');
|
||||
}}
|
||||
>
|
||||
<div className="date-time-custom-option-from-title">FROM</div>
|
||||
<div className="date-time-custom-option-from-value">
|
||||
{selectedFromDateTime?.format('YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSelectDateTimeFor('to');
|
||||
}
|
||||
}}
|
||||
className={cx(
|
||||
'date-time-custom-option-to',
|
||||
selectedDateTimeFor === 'to' && 'active',
|
||||
)}
|
||||
onClick={(): void => {
|
||||
handleSelectDateTimeFor('to');
|
||||
}}
|
||||
>
|
||||
<div className="date-time-custom-option-to-title">TO</div>
|
||||
<div className="date-time-custom-option-to-value">
|
||||
{selectedToDateTime?.format('YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="custom-date-time-picker-v2">
|
||||
<Calendar
|
||||
mode="single"
|
||||
required
|
||||
selected={
|
||||
selectedDateTimeFor === 'from'
|
||||
? selectedFromDateTime?.toDate()
|
||||
: selectedToDateTime?.toDate()
|
||||
}
|
||||
key={selectedDateTimeFor + selectedDateTimeFor}
|
||||
onSelect={handleDateChange}
|
||||
defaultMonth={getDefaultMonth()}
|
||||
disabled={(current): boolean => {
|
||||
if (selectedDateTimeFor === 'to') {
|
||||
// disable dates after today and before selectedFromDateTime
|
||||
const currentDay = dayjs(current);
|
||||
return currentDay.isAfter(dayjs()) || false;
|
||||
}
|
||||
|
||||
if (selectedDateTimeFor === 'from') {
|
||||
// disable dates after selectedToDateTime
|
||||
|
||||
return dayjs(current).isAfter(dayjs()) || false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}}
|
||||
className="rounded-md border"
|
||||
navLayout="after"
|
||||
/>
|
||||
|
||||
<div className="custom-time-selector">
|
||||
<label className="text-xs font-normal block" htmlFor="time-picker">
|
||||
Timestamp
|
||||
</label>
|
||||
|
||||
<MoveRight size={16} />
|
||||
|
||||
<div className="time-input-container">
|
||||
<Input
|
||||
type="time"
|
||||
ref={timeInputRef}
|
||||
className="time-input"
|
||||
value={
|
||||
selectedDateTimeFor === 'from'
|
||||
? selectedFromDateTime?.format('HH:mm:ss')
|
||||
: selectedToDateTime?.format('HH:mm:ss')
|
||||
}
|
||||
onChange={(e): void => handleTimeChange(e.target.value)}
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="custom-date-time-picker-footer">
|
||||
{selectedDateTimeFor === 'to' && (
|
||||
<Button
|
||||
className="periscope-btn secondary clear-btn"
|
||||
type="default"
|
||||
onClick={handleBack}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Tooltip
|
||||
title={
|
||||
!isValidRange() ? 'Invalid range: TO date should be after FROM date' : ''
|
||||
}
|
||||
overlayClassName="invalid-date-range-tooltip"
|
||||
>
|
||||
<Button
|
||||
className="periscope-btn primary next-btn"
|
||||
type="primary"
|
||||
onClick={handleNext}
|
||||
disabled={!isValidRange()}
|
||||
>
|
||||
{selectedDateTimeFor === 'from' ? 'Next' : 'Apply'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatePickerV2;
|
||||
@@ -55,37 +55,31 @@ export const selectedColumns: BaseAutocompleteData[] = [
|
||||
key: 'timestamp',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
{
|
||||
key: 'serviceName',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
{
|
||||
key: 'durationNano',
|
||||
dataType: DataTypes.Float64,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
{
|
||||
key: 'httpMethod',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
{
|
||||
key: 'responseStatusCode',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -108,9 +102,7 @@ export const getHostTracesQueryPayload = (
|
||||
id: '------false',
|
||||
dataType: DataTypes.EMPTY,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
@@ -154,8 +146,6 @@ export const getHostTracesQueryPayload = (
|
||||
key: 'serviceName',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'serviceName--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
@@ -163,8 +153,6 @@ export const getHostTracesQueryPayload = (
|
||||
key: 'name',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'name--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
@@ -172,8 +160,6 @@ export const getHostTracesQueryPayload = (
|
||||
key: 'durationNano',
|
||||
dataType: 'float64',
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'durationNano--float64--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
@@ -181,8 +167,6 @@ export const getHostTracesQueryPayload = (
|
||||
key: 'httpMethod',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'httpMethod--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
@@ -190,8 +174,6 @@ export const getHostTracesQueryPayload = (
|
||||
key: 'responseStatusCode',
|
||||
dataType: 'string',
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'responseStatusCode--string--tag--true',
|
||||
isIndexed: false,
|
||||
},
|
||||
|
||||
@@ -119,8 +119,6 @@ function HostMetricsDetails({
|
||||
key: 'host.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'host.name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
|
||||
@@ -26,9 +26,7 @@ export const getHostLogsQueryPayload = (
|
||||
id: '------false',
|
||||
dataType: DataTypes.String,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
|
||||
50
frontend/src/components/HttpStatusBadge/HttpStatusBadge.tsx
Normal file
50
frontend/src/components/HttpStatusBadge/HttpStatusBadge.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Badge } from '@signozhq/badge';
|
||||
|
||||
type BadgeColor =
|
||||
| 'vanilla'
|
||||
| 'robin'
|
||||
| 'forest'
|
||||
| 'amber'
|
||||
| 'sienna'
|
||||
| 'cherry'
|
||||
| 'sakura'
|
||||
| 'aqua';
|
||||
|
||||
interface HttpStatusBadgeProps {
|
||||
statusCode: string | number;
|
||||
}
|
||||
|
||||
function getStatusCodeColor(statusCode: number): BadgeColor {
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
return 'forest'; // Success - green
|
||||
}
|
||||
if (statusCode >= 300 && statusCode < 400) {
|
||||
return 'robin'; // Redirect - blue
|
||||
}
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
return 'amber'; // Client error - amber
|
||||
}
|
||||
if (statusCode >= 500) {
|
||||
return 'cherry'; // Server error - red
|
||||
}
|
||||
if (statusCode >= 100 && statusCode < 200) {
|
||||
return 'vanilla'; // Informational - neutral
|
||||
}
|
||||
return 'robin'; // Default fallback
|
||||
}
|
||||
|
||||
function HttpStatusBadge({
|
||||
statusCode,
|
||||
}: HttpStatusBadgeProps): JSX.Element | null {
|
||||
const numericStatusCode = Number(statusCode);
|
||||
|
||||
if (!numericStatusCode || numericStatusCode <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = getStatusCodeColor(numericStatusCode);
|
||||
|
||||
return <Badge color={color}>{statusCode}</Badge>;
|
||||
}
|
||||
|
||||
export default HttpStatusBadge;
|
||||
@@ -17,7 +17,7 @@ function InputWithLabel({
|
||||
closeIcon,
|
||||
}: {
|
||||
label: string;
|
||||
initialValue?: string | number;
|
||||
initialValue?: string | number | null;
|
||||
placeholder: string;
|
||||
type?: string;
|
||||
onClose?: () => void;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
.kbar-command-palette__positioner {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(6px);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.kbar-command-palette__animator {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.kbar-command-palette__card {
|
||||
background: var(--bg-ink-500);
|
||||
color: var(--text-vanilla-100);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kbar-command-palette__search {
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border-ink-200);
|
||||
color: var(--text-vanilla-100);
|
||||
outline: none;
|
||||
background-color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.kbar-command-palette__section {
|
||||
padding: 8px 16px 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-robin-500);
|
||||
font-family: 'Inter', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.kbar-command-palette__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.kbar-command-palette__item:hover,
|
||||
.kbar-command-palette__item--active {
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.kbar-command-palette__icon {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.kbar-command-palette__shortcut {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kbar-command-palette__key {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-ink-300);
|
||||
color: var(--text-vanilla-300);
|
||||
text-transform: uppercase;
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
|
||||
.kbar-command-palette__results-container {
|
||||
div {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
height: 0.3rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-slate-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-slate-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.kbar-command-palette__positioner {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.kbar-command-palette__card {
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.kbar-command-palette__search {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
color: var(--text-ink-500);
|
||||
background-color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.kbar-command-palette__item {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
|
||||
.kbar-command-palette__item:hover,
|
||||
.kbar-command-palette__item--active {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.kbar-command-palette__icon {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.kbar-command-palette__key {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.kbar-command-palette__results-container {
|
||||
div {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import './KBarCommandPalette.scss';
|
||||
|
||||
import {
|
||||
KBarAnimator,
|
||||
KBarPortal,
|
||||
KBarPositioner,
|
||||
KBarResults,
|
||||
KBarSearch,
|
||||
useMatches,
|
||||
} from 'kbar';
|
||||
|
||||
function Results(): JSX.Element {
|
||||
const { results } = useMatches();
|
||||
|
||||
const renderResults = ({
|
||||
item,
|
||||
active,
|
||||
}: {
|
||||
item: any;
|
||||
active: boolean;
|
||||
}): JSX.Element =>
|
||||
typeof item === 'string' ? (
|
||||
<div className="kbar-command-palette__section">{item}</div>
|
||||
) : (
|
||||
<div
|
||||
className={`kbar-command-palette__item ${
|
||||
active ? 'kbar-command-palette__item--active' : ''
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.name}</span>
|
||||
{item.shortcut?.length ? (
|
||||
<span className="kbar-command-palette__shortcut">
|
||||
{item.shortcut.map((sc: string) => (
|
||||
<kbd key={sc} className="kbar-command-palette__key">
|
||||
{sc}
|
||||
</kbd>
|
||||
))}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="kbar-command-palette__results-container">
|
||||
<KBarResults items={results} onRender={renderResults} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function KBarCommandPalette(): JSX.Element {
|
||||
return (
|
||||
<KBarPortal>
|
||||
<KBarPositioner className="kbar-command-palette__positioner">
|
||||
<KBarAnimator className="kbar-command-palette__animator">
|
||||
<div className="kbar-command-palette__card">
|
||||
<KBarSearch
|
||||
className="kbar-command-palette__search"
|
||||
placeholder="Search or type a command..."
|
||||
/>
|
||||
<Results />
|
||||
</div>
|
||||
</KBarAnimator>
|
||||
</KBarPositioner>
|
||||
</KBarPortal>
|
||||
);
|
||||
}
|
||||
|
||||
export default KBarCommandPalette;
|
||||
@@ -10,11 +10,7 @@ import { VIEWS } from './constants';
|
||||
export type LogDetailProps = {
|
||||
log: ILog | null;
|
||||
selectedTab: VIEWS;
|
||||
onGroupByAttribute?: (
|
||||
fieldKey: string,
|
||||
isJSON?: boolean,
|
||||
dataType?: DataTypes,
|
||||
) => Promise<void>;
|
||||
onGroupByAttribute?: (fieldKey: string, dataType?: DataTypes) => Promise<void>;
|
||||
isListViewPanel?: boolean;
|
||||
listViewPanelSelectedFields?: IField[] | null;
|
||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'container/LogDetailedView/utils';
|
||||
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -39,7 +40,7 @@ import {
|
||||
TextSelect,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard, useLocation } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
@@ -94,6 +95,8 @@ function LogDetailInner({
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { onLogCopy } = useCopyLogLink(log?.id);
|
||||
|
||||
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
|
||||
|
||||
const handleModeChange = (e: RadioChangeEvent): void => {
|
||||
@@ -146,6 +149,34 @@ function LogDetailInner({
|
||||
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
|
||||
};
|
||||
|
||||
const handleQueryExpressionChange = useCallback(
|
||||
(value: string, queryIndex: number) => {
|
||||
// update the query at the given index
|
||||
setContextQuery((prev) => {
|
||||
if (!prev) return prev;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
builder: {
|
||||
...prev.builder,
|
||||
queryData: prev.builder.queryData.map((query, idx) =>
|
||||
idx === queryIndex
|
||||
? {
|
||||
...query,
|
||||
filter: {
|
||||
...query.filter,
|
||||
expression: value,
|
||||
},
|
||||
}
|
||||
: query,
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleRunQuery = (expression: string): void => {
|
||||
let updatedContextQuery = cloneDeep(contextQuery);
|
||||
|
||||
@@ -305,11 +336,19 @@ function LogDetailInner({
|
||||
onClick={handleFilterVisible}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Tooltip title="Copy Log Link" placement="left" aria-label="Copy Log Link">
|
||||
<Button
|
||||
className="action-btn"
|
||||
icon={<Copy size={16} />}
|
||||
onClick={onLogCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{isFilterVisible && contextQuery?.builder.queryData[0] && (
|
||||
<div className="log-detail-drawer-query-container">
|
||||
<QuerySearch
|
||||
onChange={(): void => {}}
|
||||
onChange={(value): void => handleQueryExpressionChange(value, 0)}
|
||||
dataSource={DataSource.LOGS}
|
||||
queryData={contextQuery?.builder.queryData[0]}
|
||||
onRun={handleRunQuery}
|
||||
|
||||
@@ -17,7 +17,7 @@ function AddToQueryHOC({
|
||||
}: AddToQueryHOCProps): JSX.Element {
|
||||
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
|
||||
event.stopPropagation();
|
||||
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], undefined, dataType);
|
||||
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], dataType);
|
||||
};
|
||||
|
||||
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
|
||||
@@ -41,7 +41,6 @@ export interface AddToQueryHOCProps {
|
||||
fieldKey: string,
|
||||
fieldValue: string,
|
||||
operator: string,
|
||||
isJSON?: boolean,
|
||||
dataType?: DataTypes,
|
||||
) => void;
|
||||
fontSize: FontSize;
|
||||
|
||||
@@ -56,6 +56,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
.map(({ name }) => ({
|
||||
title: name,
|
||||
dataIndex: name,
|
||||
accessorKey: name,
|
||||
id: name.toLowerCase().replace(/\./g, '_'),
|
||||
key: name,
|
||||
render: (field): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
props: {
|
||||
@@ -83,7 +85,10 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
// We do not need any title and data index for the log state indicator
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
key: 'state-indicator',
|
||||
accessorKey: 'state-indicator',
|
||||
id: 'state-indicator',
|
||||
render: (_, item): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
children: (
|
||||
<div className={cx('state-indicator', fontSize)}>
|
||||
@@ -101,6 +106,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
title: 'timestamp',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
accessorKey: 'timestamp',
|
||||
id: 'timestamp',
|
||||
// https://github.com/ant-design/ant-design/discussions/36886
|
||||
render: (
|
||||
field: string | number,
|
||||
@@ -135,6 +142,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
||||
title: 'body',
|
||||
dataIndex: 'body',
|
||||
key: 'body',
|
||||
accessorKey: 'body',
|
||||
id: 'body',
|
||||
render: (
|
||||
field: string | number,
|
||||
): ColumnTypeRender<Record<string, unknown>> => ({
|
||||
|
||||
@@ -160,7 +160,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
||||
label="Seconds"
|
||||
placeholder="Auto"
|
||||
labelAfter
|
||||
initialValue={query?.stepInterval ?? undefined}
|
||||
initialValue={query?.stepInterval ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,7 +283,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
||||
label="Seconds"
|
||||
placeholder="Auto"
|
||||
labelAfter
|
||||
initialValue={query?.stepInterval ?? undefined}
|
||||
initialValue={query?.stepInterval ?? null}
|
||||
className="histogram-every-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -44,13 +44,14 @@
|
||||
.lightMode {
|
||||
.metrics-select-container {
|
||||
.ant-select-selector {
|
||||
border: 1px solid var(--bg-slate-300) !important;
|
||||
border: 1px solid var(--bg-vanilla-300) !important;
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--text-ink-100);
|
||||
}
|
||||
|
||||
.ant-select-dropdown {
|
||||
background: var(--bg-vanilla-100);
|
||||
border: 1px solid var(--bg-vanilla-300) !important;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
backdrop-filter: none;
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6);
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
|
||||
ul {
|
||||
width: 100% !important;
|
||||
@@ -162,7 +163,6 @@
|
||||
overflow: hidden;
|
||||
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
color: var(--bg-vanilla-100) !important;
|
||||
|
||||
.cm-completionIcon {
|
||||
display: none !important;
|
||||
@@ -331,13 +331,14 @@
|
||||
|
||||
ul {
|
||||
li {
|
||||
color: var(--bg-ink-300) !important;
|
||||
&:hover {
|
||||
background: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
color: var(--bg-ink-500) !important;
|
||||
background: var(--bg-vanilla-300) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,16 +271,13 @@
|
||||
|
||||
ul {
|
||||
li {
|
||||
&:hover {
|
||||
background-color: var(--bg-vanilla-300) !important;
|
||||
color: var(--bg-ink-500) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
color: var(--bg-ink-300) !important;
|
||||
|
||||
&:hover,
|
||||
&[aria-selected='true'] {
|
||||
background: var(--bg-vanilla-300) !important;
|
||||
color: var(--bg-ink-500) !important;
|
||||
font-weight: 600;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@ function QueryAggregationOptions({
|
||||
|
||||
<div className="query-aggregation-interval-input-container">
|
||||
<InputWithLabel
|
||||
initialValue={
|
||||
queryData?.stepInterval ? queryData?.stepInterval : undefined
|
||||
}
|
||||
initialValue={queryData?.stepInterval ? queryData?.stepInterval : null}
|
||||
className="query-aggregation-interval-input"
|
||||
label="Seconds"
|
||||
placeholder="Auto"
|
||||
|
||||
@@ -154,15 +154,23 @@ function QueryAggregationSelect({
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { setAggregationOptions } = useQueryBuilderV2Context();
|
||||
|
||||
const formatAggregations = useCallback(
|
||||
(aggregations: any[] | undefined): string =>
|
||||
aggregations
|
||||
?.map(({ expression, alias }: any) =>
|
||||
alias ? `${expression} as ${alias}` : expression,
|
||||
)
|
||||
.join(' ') || '',
|
||||
[],
|
||||
);
|
||||
|
||||
const [input, setInput] = useState(
|
||||
queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '',
|
||||
formatAggregations(queryData?.aggregations),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setInput(
|
||||
queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '',
|
||||
);
|
||||
}, [queryData?.aggregations]);
|
||||
setInput(formatAggregations(queryData?.aggregations));
|
||||
}, [queryData?.aggregations, formatAggregations]);
|
||||
|
||||
const [cursorPos, setCursorPos] = useState(0);
|
||||
const [functionArgPairs, setFunctionArgPairs] = useState<
|
||||
|
||||
@@ -585,7 +585,7 @@
|
||||
&:hover,
|
||||
&[aria-selected='true'] {
|
||||
background-color: var(--bg-vanilla-300) !important;
|
||||
font-weight: 600;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ function QuerySearch({
|
||||
// Reference to the editor view for programmatic autocompletion
|
||||
const editorRef = useRef<EditorView | null>(null);
|
||||
const lastKeyRef = useRef<string>('');
|
||||
const lastFetchedKeyRef = useRef<string>('');
|
||||
const lastValueRef = useRef<string>('');
|
||||
const isMountedRef = useRef<boolean>(true);
|
||||
|
||||
@@ -212,6 +213,9 @@ function QuerySearch({
|
||||
setKeySuggestions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
lastFetchedKeyRef.current = searchText || '';
|
||||
|
||||
const response = await getKeySuggestions({
|
||||
signal: dataSource,
|
||||
searchText: searchText || '',
|
||||
@@ -379,6 +383,7 @@ function QuerySearch({
|
||||
searchText: sanitizedSearchText,
|
||||
signal: dataSource,
|
||||
signalSource: signalSource as 'meter' | '',
|
||||
metricName: debouncedMetricName ?? undefined,
|
||||
});
|
||||
|
||||
// Skip updates if component unmounted or key changed
|
||||
@@ -470,6 +475,7 @@ function QuerySearch({
|
||||
activeKey,
|
||||
dataSource,
|
||||
isLoadingSuggestions,
|
||||
debouncedMetricName,
|
||||
signalSource,
|
||||
toggleSuggestions,
|
||||
],
|
||||
@@ -815,7 +821,7 @@ function QuerySearch({
|
||||
option.label.toLowerCase().includes(searchText),
|
||||
);
|
||||
|
||||
if (options.length === 0 && lastKeyRef.current !== searchText) {
|
||||
if (options.length === 0 && lastFetchedKeyRef.current !== searchText) {
|
||||
debouncedFetchKeySuggestions(searchText);
|
||||
}
|
||||
|
||||
@@ -1286,7 +1292,7 @@ function QuerySearch({
|
||||
if (onRun && typeof onRun === 'function') {
|
||||
onRun(query);
|
||||
} else {
|
||||
handleRunQuery(true, true);
|
||||
handleRunQuery();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
771
frontend/src/components/QueryBuilderV2/__tests__/utils.test.ts
Normal file
771
frontend/src/components/QueryBuilderV2/__tests__/utils.test.ts
Normal file
@@ -0,0 +1,771 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
/* eslint-disable import/no-unresolved */
|
||||
import { negateOperator, OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { extractQueryPairs } from 'utils/queryContextUtils';
|
||||
|
||||
import {
|
||||
convertFiltersToExpression,
|
||||
convertFiltersToExpressionWithExistingQuery,
|
||||
} from '../utils';
|
||||
|
||||
describe('convertFiltersToExpression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should handle empty, null, and undefined inputs', () => {
|
||||
// Test null and undefined
|
||||
expect(convertFiltersToExpression(null as any)).toEqual({ expression: '' });
|
||||
expect(convertFiltersToExpression(undefined as any)).toEqual({
|
||||
expression: '',
|
||||
});
|
||||
|
||||
// Test empty filters
|
||||
expect(convertFiltersToExpression({ items: [], op: 'AND' })).toEqual({
|
||||
expression: '',
|
||||
});
|
||||
expect(
|
||||
convertFiltersToExpression({ items: undefined, op: 'AND' } as any),
|
||||
).toEqual({ expression: '' });
|
||||
});
|
||||
|
||||
it('should convert basic comparison operators with proper value formatting', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: '=',
|
||||
value: 'api-gateway',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'status', type: 'string' },
|
||||
op: '!=',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'duration', type: 'number' },
|
||||
op: '>',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'count', type: 'number' },
|
||||
op: '<=',
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'is_active', type: 'boolean' },
|
||||
op: '=',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
key: { key: 'enabled', type: 'boolean' },
|
||||
op: '=',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
key: { key: 'count', type: 'number' },
|
||||
op: '=',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
key: { key: 'regex', type: 'string' },
|
||||
op: 'regex',
|
||||
value: '.*',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"service = 'api-gateway' AND status != 'error' AND duration > 100 AND count <= 50 AND is_active = true AND enabled = false AND count = 0 AND regex REGEXP '.*'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle string value formatting and escaping', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'message', type: 'string' },
|
||||
op: '=',
|
||||
value: "user's data",
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'description', type: 'string' },
|
||||
op: '=',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'path', type: 'string' },
|
||||
op: '=',
|
||||
value: '/api/v1/users',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"message = 'user\\'s data' AND description = '' AND path = '/api/v1/users'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle IN operator with various value types and array formatting', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ['api-gateway', 'user-service', 'auth-service'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'status', type: 'string' },
|
||||
op: 'IN',
|
||||
value: 'success', // Single value should be converted to array
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'IN',
|
||||
value: [], // Empty array
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'name', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ["John's", "Mary's", 'Bob'], // Values with quotes
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"service in ['api-gateway', 'user-service', 'auth-service'] AND status in ['success'] AND tags in [] AND name in ['John\\'s', 'Mary\\'s', 'Bob']",
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert deprecated operators to their modern equivalents', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: 'nin',
|
||||
value: ['api-gateway', 'user-service'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'message', type: 'string' },
|
||||
op: 'nlike',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'path', type: 'string' },
|
||||
op: 'nregex',
|
||||
value: '/api/.*',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: 'NIN', // Test case insensitivity
|
||||
value: ['api-gateway'],
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'user_id', type: 'string' },
|
||||
op: 'nexists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
key: { key: 'description', type: 'string' },
|
||||
op: 'ncontains',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'nhas',
|
||||
value: 'production',
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
key: { key: 'labels', type: 'string' },
|
||||
op: 'nhasany',
|
||||
value: ['env:prod', 'service:api'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"service NOT IN ['api-gateway', 'user-service'] AND message NOT LIKE 'error' AND path NOT REGEXP '/api/.*' AND service NOT IN ['api-gateway'] AND user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-value operators and function operators', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'user_id', type: 'string' },
|
||||
op: 'EXISTS',
|
||||
value: '', // Value should be ignored for EXISTS
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'user_id', type: 'string' },
|
||||
op: 'EXISTS',
|
||||
value: 'some-value', // Value should be ignored for EXISTS
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'has',
|
||||
value: 'production',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'hasAny',
|
||||
value: ['production', 'staging'],
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'hasAll',
|
||||
value: ['production', 'monitoring'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"user_id exists AND user_id exists AND has(tags, 'production') AND hasAny(tags, ['production', 'staging']) AND hasAll(tags, ['production', 'monitoring'])",
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter out invalid filters and handle edge cases', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: '=',
|
||||
value: 'api-gateway',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: undefined, // Invalid filter - should be skipped
|
||||
op: '=',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: '', type: 'string' }, // Invalid filter with empty key - should be skipped
|
||||
op: '=',
|
||||
value: 'test',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'status', type: 'string' },
|
||||
op: ' = ', // Test whitespace handling
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: 'In', // Test mixed case handling
|
||||
value: ['api-gateway'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"service = 'api-gateway' AND status = 'success' AND service in ['api-gateway']",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle complex mixed operator scenarios with proper joining', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'service', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ['api-gateway', 'user-service'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'user_id', type: 'string' },
|
||||
op: 'EXISTS',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'has',
|
||||
value: 'production',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'duration', type: 'number' },
|
||||
op: '>',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'status', type: 'string' },
|
||||
op: 'nin',
|
||||
value: ['error', 'timeout'],
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
key: { key: 'method', type: 'string' },
|
||||
op: '=',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"service in ['api-gateway', 'user-service'] AND user_id exists AND has(tags, 'production') AND duration > 100 AND status NOT IN ['error', 'timeout'] AND method = 'POST'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle all numeric comparison operators and edge cases', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'count', type: 'number' },
|
||||
op: '=',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'score', type: 'number' },
|
||||
op: '>',
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'limit', type: 'number' },
|
||||
op: '>=',
|
||||
value: 50,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'threshold', type: 'number' },
|
||||
op: '<',
|
||||
value: 1000,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'max_value', type: 'number' },
|
||||
op: '<=',
|
||||
value: 999,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
key: { key: 'values', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ['1', '2', '3', '4', '5'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"count = 0 AND score > 100 AND limit >= 50 AND threshold < 1000 AND max_value <= 999 AND values in ['1', '2', '3', '4', '5']",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle boolean values and string comparisons with special characters', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'is_active', type: 'boolean' },
|
||||
op: '=',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'is_deleted', type: 'boolean' },
|
||||
op: '=',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'email', type: 'string' },
|
||||
op: '=',
|
||||
value: 'user@example.com',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'description', type: 'string' },
|
||||
op: '=',
|
||||
value: 'Contains "quotes" and \'apostrophes\'',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'path', type: 'string' },
|
||||
op: '=',
|
||||
value: '/api/v1/users/123?filter=true',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"is_active = true AND is_deleted = false AND email = 'user@example.com' AND description = 'Contains \"quotes\" and \\'apostrophes\\'' AND path = '/api/v1/users/123?filter=true'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle all function operators and complex array scenarios', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'has',
|
||||
value: 'production',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'labels', type: 'string' },
|
||||
op: 'hasAny',
|
||||
value: ['env:prod', 'service:api'],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'metadata', type: 'string' },
|
||||
op: 'hasAll',
|
||||
value: ['version:1.0', 'team:backend'],
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'services', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ['api-gateway', 'user-service', 'auth-service', 'payment-service'],
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
key: { key: 'excluded_services', type: 'string' },
|
||||
op: 'nin',
|
||||
value: ['legacy-service', 'deprecated-service'],
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
key: { key: 'status_codes', type: 'string' },
|
||||
op: 'IN',
|
||||
value: ['200', '201', '400', '500'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"has(tags, 'production') AND hasAny(labels, ['env:prod', 'service:api']) AND hasAll(metadata, ['version:1.0', 'team:backend']) AND services in ['api-gateway', 'user-service', 'auth-service', 'payment-service'] AND excluded_services NOT IN ['legacy-service', 'deprecated-service'] AND status_codes in ['200', '201', '400', '500']",
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle specific deprecated operators: nhas, ncontains, nexists', () => {
|
||||
const filters: TagFilter = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'user_id', type: 'string' },
|
||||
op: 'nexists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
key: { key: 'description', type: 'string' },
|
||||
op: 'ncontains',
|
||||
value: 'error',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
key: { key: 'tags', type: 'string' },
|
||||
op: 'nhas',
|
||||
value: 'production',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
key: { key: 'labels', type: 'string' },
|
||||
op: 'nhasany',
|
||||
value: ['env:prod', 'service:api'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expression:
|
||||
"user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||
});
|
||||
});
|
||||
|
||||
it('should return filters with new expression when no existing query', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'service.name', key: 'service.name', type: 'string' },
|
||||
op: OPERATORS['='],
|
||||
value: 'test-service',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.filters).toEqual(filters);
|
||||
expect(result.filter.expression).toBe("service.name = 'test-service'");
|
||||
});
|
||||
|
||||
it('should handle empty filters', () => {
|
||||
const filters = {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.filters).toEqual(filters);
|
||||
expect(result.filter.expression).toBe('');
|
||||
});
|
||||
|
||||
it('should handle existing query with matching filters', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'service.name', key: 'service.name', type: 'string' },
|
||||
op: OPERATORS['='],
|
||||
value: 'updated-service',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name = 'old-service'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters).toBeDefined();
|
||||
expect(result.filter).toBeDefined();
|
||||
expect(result.filter.expression).toBe("service.name = 'updated-service'");
|
||||
// Ensure parser can parse the existing query
|
||||
expect(extractQueryPairs(existingQuery)).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: 'service.name',
|
||||
operator: '=',
|
||||
value: "'old-service'",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle IN operator with existing query', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'service.name', key: 'service.name', type: 'string' },
|
||||
op: OPERATORS.IN,
|
||||
value: ['service1', 'service2'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name IN ['old-service']";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters).toBeDefined();
|
||||
expect(result.filter).toBeDefined();
|
||||
expect(result.filter.expression).toBe(
|
||||
"service.name IN ['service1', 'service2']",
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle IN operator conversion from equals', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'service.name', key: 'service.name', type: 'string' },
|
||||
op: OPERATORS.IN,
|
||||
value: ['service1', 'service2'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name = 'old-service'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters.items).toHaveLength(1);
|
||||
expect(result.filter.expression).toBe(
|
||||
"service.name IN ['service1', 'service2'] ",
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle NOT IN operator conversion from not equals', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'service.name', key: 'service.name', type: 'string' },
|
||||
op: negateOperator(OPERATORS.IN),
|
||||
value: ['service1', 'service2'],
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name != 'old-service'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters.items).toHaveLength(1);
|
||||
expect(result.filter.expression).toBe(
|
||||
"service.name NOT IN ['service1', 'service2'] ",
|
||||
);
|
||||
});
|
||||
|
||||
it('should add new filters when they do not exist in existing query', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'new.key', key: 'new.key', type: 'string' },
|
||||
op: OPERATORS['='],
|
||||
value: 'new-value',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name = 'old-service'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters.items).toHaveLength(2); // Original + new filter
|
||||
expect(result.filter.expression).toBe(
|
||||
"service.name = 'old-service' new.key = 'new-value'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle simple value replacement', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { id: 'status', key: 'status', type: 'string' },
|
||||
op: OPERATORS['='],
|
||||
value: 'error',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "status = 'success'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters.items).toHaveLength(1);
|
||||
expect(result.filter.expression).toBe("status = 'error'");
|
||||
});
|
||||
|
||||
it('should handle filters with no key gracefully', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: undefined,
|
||||
op: OPERATORS['='],
|
||||
value: 'test-value',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const existingQuery = "service.name = 'old-service'";
|
||||
|
||||
const result = convertFiltersToExpressionWithExistingQuery(
|
||||
filters,
|
||||
existingQuery,
|
||||
);
|
||||
|
||||
expect(result.filters.items).toHaveLength(2);
|
||||
expect(result.filter.expression).toBe("service.name = 'old-service'");
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,12 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { createAggregation } from 'api/v5/queryRange/prepareQueryRangePayloadV5';
|
||||
import { OPERATORS } from 'constants/antlrQueryConstants';
|
||||
import {
|
||||
DEPRECATED_OPERATORS_MAP,
|
||||
OPERATORS,
|
||||
QUERY_BUILDER_FUNCTIONS,
|
||||
} from 'constants/antlrQueryConstants';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { cloneDeep, isEqual, sortBy } from 'lodash-es';
|
||||
import { IQueryPair } from 'types/antlrQueryTypes';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@@ -21,7 +25,7 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { extractQueryPairs } from 'utils/queryContextUtils';
|
||||
import { unquote } from 'utils/stringUtils';
|
||||
import { isFunctionOperator } from 'utils/tokenUtils';
|
||||
import { isFunctionOperator, isNonValueOperator } from 'utils/tokenUtils';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
/**
|
||||
@@ -87,12 +91,32 @@ export const convertFiltersToExpression = (
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isFunctionOperator(op)) {
|
||||
return `${op}(${key.key}, ${value})`;
|
||||
let operator = op.trim().toLowerCase();
|
||||
if (Object.keys(DEPRECATED_OPERATORS_MAP).includes(operator)) {
|
||||
operator =
|
||||
DEPRECATED_OPERATORS_MAP[
|
||||
operator as keyof typeof DEPRECATED_OPERATORS_MAP
|
||||
];
|
||||
}
|
||||
|
||||
const formattedValue = formatValueForExpression(value, op);
|
||||
return `${key.key} ${op} ${formattedValue}`;
|
||||
if (isNonValueOperator(operator)) {
|
||||
return `${key.key} ${operator}`;
|
||||
}
|
||||
|
||||
if (isFunctionOperator(operator)) {
|
||||
// Get the proper function name from QUERY_BUILDER_FUNCTIONS
|
||||
const functionOperators = Object.values(QUERY_BUILDER_FUNCTIONS);
|
||||
const properFunctionName =
|
||||
functionOperators.find(
|
||||
(func: string) => func.toLowerCase() === operator.toLowerCase(),
|
||||
) || operator;
|
||||
|
||||
const formattedValue = formatValueForExpression(value, operator);
|
||||
return `${properFunctionName}(${key.key}, ${formattedValue})`;
|
||||
}
|
||||
|
||||
const formattedValue = formatValueForExpression(value, operator);
|
||||
return `${key.key} ${operator} ${formattedValue}`;
|
||||
})
|
||||
.filter((expression) => expression !== ''); // Remove empty expressions
|
||||
|
||||
@@ -117,7 +141,6 @@ export const convertExpressionToFilters = (
|
||||
if (!expression) return [];
|
||||
|
||||
const queryPairs = extractQueryPairs(expression);
|
||||
|
||||
const filters: TagFilterItem[] = [];
|
||||
|
||||
queryPairs.forEach((pair) => {
|
||||
@@ -140,39 +163,57 @@ export const convertExpressionToFilters = (
|
||||
|
||||
return filters;
|
||||
};
|
||||
const getQueryPairsMap = (query: string): Map<string, IQueryPair> => {
|
||||
const queryPairs = extractQueryPairs(query);
|
||||
const queryPairsMap: Map<string, IQueryPair> = new Map();
|
||||
|
||||
queryPairs.forEach((pair) => {
|
||||
const key = pair.hasNegation
|
||||
? `${pair.key}-not ${pair.operator}`.trim().toLowerCase()
|
||||
: `${pair.key}-${pair.operator}`.trim().toLowerCase();
|
||||
queryPairsMap.set(key, pair);
|
||||
});
|
||||
|
||||
return queryPairsMap;
|
||||
};
|
||||
|
||||
export const convertFiltersToExpressionWithExistingQuery = (
|
||||
filters: TagFilter,
|
||||
existingQuery: string | undefined,
|
||||
): { filters: TagFilter; filter: { expression: string } } => {
|
||||
// Check for deprecated operators and replace them with new operators
|
||||
const updatedFilters = cloneDeep(filters);
|
||||
|
||||
// Replace deprecated operators in filter items
|
||||
if (updatedFilters?.items) {
|
||||
updatedFilters.items = updatedFilters.items.map((item) => {
|
||||
const opLower = item.op?.toLowerCase();
|
||||
if (Object.keys(DEPRECATED_OPERATORS_MAP).includes(opLower)) {
|
||||
return {
|
||||
...item,
|
||||
op: DEPRECATED_OPERATORS_MAP[
|
||||
opLower as keyof typeof DEPRECATED_OPERATORS_MAP
|
||||
].toLowerCase(),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
if (!existingQuery) {
|
||||
// If no existing query, return filters with a newly generated expression
|
||||
return {
|
||||
filters,
|
||||
filter: convertFiltersToExpression(filters),
|
||||
filters: updatedFilters,
|
||||
filter: convertFiltersToExpression(updatedFilters),
|
||||
};
|
||||
}
|
||||
|
||||
// Extract query pairs from the existing query
|
||||
const queryPairs = extractQueryPairs(existingQuery.trim());
|
||||
let queryPairsMap: Map<string, IQueryPair> = new Map();
|
||||
|
||||
const updatedFilters = cloneDeep(filters); // Clone filters to avoid direct mutation
|
||||
const nonExistingFilters: TagFilterItem[] = [];
|
||||
let modifiedQuery = existingQuery; // We'll modify this query as we proceed
|
||||
const visitedPairs: Set<string> = new Set(); // Set to track visited query pairs
|
||||
|
||||
// Map extracted query pairs to key-specific pair information for faster access
|
||||
if (queryPairs.length > 0) {
|
||||
queryPairsMap = new Map(
|
||||
queryPairs.map((pair) => {
|
||||
const key = pair.hasNegation
|
||||
? `${pair.key}-not ${pair.operator}`.trim().toLowerCase()
|
||||
: `${pair.key}-${pair.operator}`.trim().toLowerCase();
|
||||
return [key, pair];
|
||||
}),
|
||||
);
|
||||
}
|
||||
let queryPairsMap = getQueryPairsMap(existingQuery.trim());
|
||||
|
||||
filters?.items?.forEach((filter) => {
|
||||
const { key, op, value } = filter;
|
||||
@@ -201,10 +242,37 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
existingPair.position?.valueEnd
|
||||
) {
|
||||
visitedPairs.add(`${key.key}-${op}`.trim().toLowerCase());
|
||||
|
||||
// Check if existing values match current filter values (for array-based operators)
|
||||
if (existingPair.valueList && filter.value && Array.isArray(filter.value)) {
|
||||
// Clean quotes from string values for comparison
|
||||
const cleanValues = (values: any[]): any[] =>
|
||||
values.map((val) => (typeof val === 'string' ? unquote(val) : val));
|
||||
|
||||
const cleanExistingValues = cleanValues(existingPair.valueList);
|
||||
const cleanFilterValues = cleanValues(filter.value);
|
||||
|
||||
// Compare arrays (order-independent) - if identical, keep existing value
|
||||
const isSameValues =
|
||||
cleanExistingValues.length === cleanFilterValues.length &&
|
||||
isEqual(sortBy(cleanExistingValues), sortBy(cleanFilterValues));
|
||||
|
||||
if (isSameValues) {
|
||||
// Values are identical, preserve existing formatting
|
||||
modifiedQuery =
|
||||
modifiedQuery.slice(0, existingPair.position.valueStart) +
|
||||
existingPair.value +
|
||||
modifiedQuery.slice(existingPair.position.valueEnd + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
modifiedQuery =
|
||||
modifiedQuery.slice(0, existingPair.position.valueStart) +
|
||||
formattedValue +
|
||||
modifiedQuery.slice(existingPair.position.valueEnd + 1);
|
||||
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,6 +298,7 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
notInPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery.trim());
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
} else if (
|
||||
@@ -246,6 +315,7 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
equalsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery);
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
} else if (
|
||||
@@ -262,6 +332,7 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
)}${OPERATORS.IN} ${formattedValue} ${modifiedQuery.slice(
|
||||
notEqualsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery);
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
}
|
||||
@@ -283,6 +354,7 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
} ${formattedValue} ${modifiedQuery.slice(
|
||||
notEqualsPair.position.valueEnd + 1,
|
||||
)}`;
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery);
|
||||
}
|
||||
shouldAddToNonExisting = false; // Don't add this to non-existing filters
|
||||
}
|
||||
@@ -295,6 +367,23 @@ export const convertFiltersToExpressionWithExistingQuery = (
|
||||
if (
|
||||
queryPairsMap.has(`${filter.key?.key}-${filter.op}`.trim().toLowerCase())
|
||||
) {
|
||||
const existingPair = queryPairsMap.get(
|
||||
`${filter.key?.key}-${filter.op}`.trim().toLowerCase(),
|
||||
);
|
||||
if (
|
||||
existingPair &&
|
||||
existingPair.position?.valueStart &&
|
||||
existingPair.position?.valueEnd
|
||||
) {
|
||||
const formattedValue = formatValueForExpression(value, op);
|
||||
// replace the value with the new value
|
||||
modifiedQuery =
|
||||
modifiedQuery.slice(0, existingPair.position.valueStart) +
|
||||
formattedValue +
|
||||
modifiedQuery.slice(existingPair.position.valueEnd + 1);
|
||||
queryPairsMap = getQueryPairsMap(modifiedQuery);
|
||||
}
|
||||
|
||||
visitedPairs.add(`${filter.key?.key}-${filter.op}`.trim().toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ import { SignalType } from 'components/QuickFilters/types';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
||||
import { useGetAttributeSuggestions } from 'hooks/queryBuilder/useGetAttributeSuggestions';
|
||||
import { useGetQueryKeySuggestions } from 'hooks/querySuggestions/useGetQueryKeySuggestions';
|
||||
import { useMemo } from 'react';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -40,6 +43,10 @@ function OtherFilters({
|
||||
() => SIGNAL_DATA_SOURCE_MAP[signal as SignalType] === DataSource.LOGS,
|
||||
[signal],
|
||||
);
|
||||
const isMeterDataSource = useMemo(
|
||||
() => signal && signal === SignalType.METER_EXPLORER,
|
||||
[signal],
|
||||
);
|
||||
|
||||
const {
|
||||
data: suggestionsData,
|
||||
@@ -69,7 +76,22 @@ function OtherFilters({
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && !isLogDataSource,
|
||||
enabled: !!signal && !isLogDataSource && !isMeterDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: fieldKeysData,
|
||||
isLoading: isLoadingFieldKeys,
|
||||
} = useGetQueryKeySuggestions(
|
||||
{
|
||||
searchText: inputValue,
|
||||
signal: SIGNAL_DATA_SOURCE_MAP[signal as SignalType],
|
||||
signalSource: 'meter',
|
||||
},
|
||||
{
|
||||
queryKey: [REACT_QUERY_KEY.GET_OTHER_FILTERS, inputValue],
|
||||
enabled: !!signal && isMeterDataSource,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -77,13 +99,33 @@ function OtherFilters({
|
||||
let filterAttributes;
|
||||
if (isLogDataSource) {
|
||||
filterAttributes = suggestionsData?.payload?.attributes || [];
|
||||
} else if (isMeterDataSource) {
|
||||
const fieldKeys: QueryKeyDataSuggestionsProps[] = Object.values(
|
||||
fieldKeysData?.data?.data?.keys || {},
|
||||
)?.flat();
|
||||
filterAttributes = fieldKeys.map(
|
||||
(attr) =>
|
||||
({
|
||||
key: attr.name,
|
||||
dataType: attr.fieldDataType,
|
||||
type: attr.fieldContext,
|
||||
signal: attr.signal,
|
||||
} as BaseAutocompleteData),
|
||||
);
|
||||
} else {
|
||||
filterAttributes = aggregateKeysData?.payload?.attributeKeys || [];
|
||||
}
|
||||
return filterAttributes?.filter(
|
||||
(attr) => !addedFilters.some((filter) => filter.key === attr.key),
|
||||
);
|
||||
}, [suggestionsData, aggregateKeysData, addedFilters, isLogDataSource]);
|
||||
}, [
|
||||
suggestionsData,
|
||||
aggregateKeysData,
|
||||
addedFilters,
|
||||
isLogDataSource,
|
||||
fieldKeysData,
|
||||
isMeterDataSource,
|
||||
]);
|
||||
|
||||
const handleAddFilter = (filter: FilterType): void => {
|
||||
setAddedFilters((prev) => [
|
||||
@@ -91,15 +133,14 @@ function OtherFilters({
|
||||
{
|
||||
key: filter.key,
|
||||
dataType: filter.dataType,
|
||||
isColumn: filter.isColumn,
|
||||
isJSON: filter.isJSON,
|
||||
type: filter.type,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const renderFilters = (): React.ReactNode => {
|
||||
const isLoading = isFetchingSuggestions || isFetchingAggregateKeys;
|
||||
const isLoading =
|
||||
isFetchingSuggestions || isFetchingAggregateKeys || isLoadingFieldKeys;
|
||||
if (isLoading) return <OtherFiltersSkeleton />;
|
||||
if (!otherFilters?.length)
|
||||
return <div className="no-values-found">No values found</div>;
|
||||
|
||||
@@ -54,6 +54,7 @@ const quickFiltersListURL = `${BASE_URL}/api/v1/orgs/me/filters/${SIGNAL}`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v1/orgs/me/filters`;
|
||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v3/filter_suggestions`;
|
||||
const quickFiltersAttributeValuesURL = `${BASE_URL}/api/v3/autocomplete/attribute_values`;
|
||||
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||
|
||||
const FILTER_OS_DESCRIPTION = 'os.description';
|
||||
const FILTER_K8S_DEPLOYMENT_NAME = 'k8s.deployment.name';
|
||||
@@ -77,7 +78,11 @@ const setupServer = (): void => {
|
||||
putHandler(await req.json());
|
||||
return res(ctx.status(200), ctx.json({}));
|
||||
}),
|
||||
rest.get(quickFiltersAttributeValuesURL, (_, res, ctx) =>
|
||||
|
||||
rest.get(quickFiltersAttributeValuesURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||
),
|
||||
rest.get(fieldsValuesURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(quickFiltersAttributeValuesResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -10,8 +10,6 @@ export const QuickFiltersConfig = [
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -22,8 +20,6 @@ export const QuickFiltersConfig = [
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
defaultOpen: false,
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export enum SignalType {
|
||||
LOGS = 'logs',
|
||||
API_MONITORING = 'api_monitoring',
|
||||
EXCEPTIONS = 'exceptions',
|
||||
METER_EXPLORER = 'meter_explorer',
|
||||
METER_EXPLORER = 'meter',
|
||||
}
|
||||
|
||||
export interface IQuickFiltersConfig {
|
||||
@@ -54,5 +54,5 @@ export enum QuickFiltersSource {
|
||||
TRACES_EXPLORER = 'traces-explorer',
|
||||
API_MONITORING = 'api-monitoring',
|
||||
EXCEPTIONS = 'exceptions',
|
||||
METER_EXPLORER = 'meter-explorer',
|
||||
METER_EXPLORER = 'meter',
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ export const getFilterConfig = (
|
||||
key: att.key,
|
||||
dataType: att.dataType,
|
||||
type: att.type,
|
||||
isColumn: att.isColumn,
|
||||
isJSON: att.isJSON,
|
||||
},
|
||||
defaultOpen: index < 2,
|
||||
} as IQuickFiltersConfig),
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import './styles.scss';
|
||||
|
||||
import { Select } from 'antd';
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
|
||||
import { UniversalYAxisUnitMappings, Y_AXIS_CATEGORIES } from './constants';
|
||||
import { UniversalYAxisUnit, YAxisUnitSelectorProps } from './types';
|
||||
import { mapMetricUnitToUniversalUnit } from './utils';
|
||||
|
||||
function YAxisUnitSelector({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = 'Please select a unit',
|
||||
loading = false,
|
||||
}: YAxisUnitSelectorProps): JSX.Element {
|
||||
const universalUnit = mapMetricUnitToUniversalUnit(value);
|
||||
|
||||
const handleSearch = (
|
||||
searchTerm: string,
|
||||
currentOption: DefaultOptionType | undefined,
|
||||
): boolean => {
|
||||
if (!currentOption?.value) return false;
|
||||
|
||||
const search = searchTerm.toLowerCase();
|
||||
const unitId = currentOption.value.toString().toLowerCase();
|
||||
const unitLabel = currentOption.children?.toString().toLowerCase() || '';
|
||||
|
||||
// Check label and id
|
||||
if (unitId.includes(search) || unitLabel.includes(search)) return true;
|
||||
|
||||
// Check aliases (from the mapping) using array iteration
|
||||
const aliases = Array.from(
|
||||
UniversalYAxisUnitMappings[currentOption.value as UniversalYAxisUnit] ?? [],
|
||||
);
|
||||
|
||||
return aliases.some((alias) => alias.toLowerCase().includes(search));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="y-axis-unit-selector-component">
|
||||
<Select
|
||||
showSearch
|
||||
value={universalUnit}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
filterOption={(input, option): boolean => handleSearch(input, option)}
|
||||
loading={loading}
|
||||
>
|
||||
{Y_AXIS_CATEGORIES.map((category) => (
|
||||
<Select.OptGroup key={category.name} label={category.name}>
|
||||
{category.units.map((unit) => (
|
||||
<Select.Option key={unit.id} value={unit.id}>
|
||||
{unit.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select.OptGroup>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default YAxisUnitSelector;
|
||||
@@ -0,0 +1,68 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
import YAxisUnitSelector from '../YAxisUnitSelector';
|
||||
|
||||
describe('YAxisUnitSelector', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnChange.mockClear();
|
||||
});
|
||||
|
||||
it('renders with default placeholder', () => {
|
||||
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
|
||||
expect(screen.getByText('Please select a unit')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with custom placeholder', () => {
|
||||
render(
|
||||
<YAxisUnitSelector
|
||||
value=""
|
||||
onChange={mockOnChange}
|
||||
placeholder="Custom placeholder"
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText('Custom placeholder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onChange when a value is selected', () => {
|
||||
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
|
||||
const select = screen.getByRole('combobox');
|
||||
|
||||
fireEvent.mouseDown(select);
|
||||
const option = screen.getByText('Bytes (B)');
|
||||
fireEvent.click(option);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith('By', {
|
||||
children: 'Bytes (B)',
|
||||
key: 'By',
|
||||
value: 'By',
|
||||
});
|
||||
});
|
||||
|
||||
it('filters options based on search input', () => {
|
||||
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
|
||||
const select = screen.getByRole('combobox');
|
||||
|
||||
fireEvent.mouseDown(select);
|
||||
const input = screen.getByRole('combobox');
|
||||
fireEvent.change(input, { target: { value: 'byte' } });
|
||||
|
||||
expect(screen.getByText('Bytes/sec')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows all categories and their units', () => {
|
||||
render(<YAxisUnitSelector value="" onChange={mockOnChange} />);
|
||||
const select = screen.getByRole('combobox');
|
||||
|
||||
fireEvent.mouseDown(select);
|
||||
|
||||
// Check for category headers
|
||||
expect(screen.getByText('Data')).toBeInTheDocument();
|
||||
expect(screen.getByText('Time')).toBeInTheDocument();
|
||||
|
||||
// Check for some common units
|
||||
expect(screen.getByText('Bytes (B)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Seconds (s)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
getUniversalNameFromMetricUnit,
|
||||
mapMetricUnitToUniversalUnit,
|
||||
} from '../utils';
|
||||
|
||||
describe('YAxisUnitSelector utils', () => {
|
||||
describe('mapMetricUnitToUniversalUnit', () => {
|
||||
it('maps known units correctly', () => {
|
||||
expect(mapMetricUnitToUniversalUnit('bytes')).toBe('By');
|
||||
expect(mapMetricUnitToUniversalUnit('seconds')).toBe('s');
|
||||
expect(mapMetricUnitToUniversalUnit('bytes_per_second')).toBe('By/s');
|
||||
});
|
||||
|
||||
it('returns null or self for unknown units', () => {
|
||||
expect(mapMetricUnitToUniversalUnit('unknown_unit')).toBe('unknown_unit');
|
||||
expect(mapMetricUnitToUniversalUnit('')).toBe(null);
|
||||
expect(mapMetricUnitToUniversalUnit(undefined)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUniversalNameFromMetricUnit', () => {
|
||||
it('returns human readable names for known units', () => {
|
||||
expect(getUniversalNameFromMetricUnit('bytes')).toBe('Bytes (B)');
|
||||
expect(getUniversalNameFromMetricUnit('seconds')).toBe('Seconds (s)');
|
||||
expect(getUniversalNameFromMetricUnit('bytes_per_second')).toBe('Bytes/sec');
|
||||
});
|
||||
|
||||
it('returns original unit for unknown units', () => {
|
||||
expect(getUniversalNameFromMetricUnit('unknown_unit')).toBe('unknown_unit');
|
||||
expect(getUniversalNameFromMetricUnit('')).toBe('-');
|
||||
expect(getUniversalNameFromMetricUnit(undefined)).toBe('-');
|
||||
});
|
||||
|
||||
it('handles case variations', () => {
|
||||
expect(getUniversalNameFromMetricUnit('bytes')).toBe('Bytes (B)');
|
||||
expect(getUniversalNameFromMetricUnit('s')).toBe('Seconds (s)');
|
||||
});
|
||||
});
|
||||
});
|
||||
627
frontend/src/components/YAxisUnitSelector/constants.ts
Normal file
627
frontend/src/components/YAxisUnitSelector/constants.ts
Normal file
@@ -0,0 +1,627 @@
|
||||
import { UniversalYAxisUnit, YAxisUnit } from './types';
|
||||
|
||||
// Mapping of universal y-axis units to their AWS, UCUM, and OpenMetrics equivalents
|
||||
export const UniversalYAxisUnitMappings: Record<
|
||||
UniversalYAxisUnit,
|
||||
Set<YAxisUnit>
|
||||
> = {
|
||||
// Time
|
||||
[UniversalYAxisUnit.NANOSECONDS]: new Set([
|
||||
YAxisUnit.UCUM_NANOSECONDS,
|
||||
YAxisUnit.OPEN_METRICS_NANOSECONDS,
|
||||
]),
|
||||
[UniversalYAxisUnit.MICROSECONDS]: new Set([
|
||||
YAxisUnit.AWS_MICROSECONDS,
|
||||
YAxisUnit.UCUM_MICROSECONDS,
|
||||
YAxisUnit.OPEN_METRICS_MICROSECONDS,
|
||||
]),
|
||||
[UniversalYAxisUnit.MILLISECONDS]: new Set([
|
||||
YAxisUnit.AWS_MILLISECONDS,
|
||||
YAxisUnit.UCUM_MILLISECONDS,
|
||||
YAxisUnit.OPEN_METRICS_MILLISECONDS,
|
||||
]),
|
||||
[UniversalYAxisUnit.SECONDS]: new Set([
|
||||
YAxisUnit.AWS_SECONDS,
|
||||
YAxisUnit.UCUM_SECONDS,
|
||||
YAxisUnit.OPEN_METRICS_SECONDS,
|
||||
]),
|
||||
[UniversalYAxisUnit.MINUTES]: new Set([
|
||||
YAxisUnit.UCUM_MINUTES,
|
||||
YAxisUnit.OPEN_METRICS_MINUTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.HOURS]: new Set([
|
||||
YAxisUnit.UCUM_HOURS,
|
||||
YAxisUnit.OPEN_METRICS_HOURS,
|
||||
]),
|
||||
[UniversalYAxisUnit.DAYS]: new Set([
|
||||
YAxisUnit.UCUM_DAYS,
|
||||
YAxisUnit.OPEN_METRICS_DAYS,
|
||||
]),
|
||||
[UniversalYAxisUnit.WEEKS]: new Set([YAxisUnit.UCUM_WEEKS]),
|
||||
|
||||
// Data
|
||||
[UniversalYAxisUnit.BYTES]: new Set([
|
||||
YAxisUnit.AWS_BYTES,
|
||||
YAxisUnit.UCUM_BYTES,
|
||||
YAxisUnit.OPEN_METRICS_BYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.KILOBYTES]: new Set([
|
||||
YAxisUnit.AWS_KILOBYTES,
|
||||
YAxisUnit.UCUM_KILOBYTES,
|
||||
YAxisUnit.OPEN_METRICS_KILOBYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.MEGABYTES]: new Set([
|
||||
YAxisUnit.AWS_MEGABYTES,
|
||||
YAxisUnit.UCUM_MEGABYTES,
|
||||
YAxisUnit.OPEN_METRICS_MEGABYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.GIGABYTES]: new Set([
|
||||
YAxisUnit.AWS_GIGABYTES,
|
||||
YAxisUnit.UCUM_GIGABYTES,
|
||||
YAxisUnit.OPEN_METRICS_GIGABYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.TERABYTES]: new Set([
|
||||
YAxisUnit.AWS_TERABYTES,
|
||||
YAxisUnit.UCUM_TERABYTES,
|
||||
YAxisUnit.OPEN_METRICS_TERABYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.PETABYTES]: new Set([
|
||||
YAxisUnit.AWS_PETABYTES,
|
||||
YAxisUnit.UCUM_PEBIBYTES,
|
||||
YAxisUnit.OPEN_METRICS_PEBIBYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.EXABYTES]: new Set([
|
||||
YAxisUnit.AWS_EXABYTES,
|
||||
YAxisUnit.UCUM_EXABYTES,
|
||||
YAxisUnit.OPEN_METRICS_EXABYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.ZETTABYTES]: new Set([
|
||||
YAxisUnit.AWS_ZETTABYTES,
|
||||
YAxisUnit.UCUM_ZETTABYTES,
|
||||
YAxisUnit.OPEN_METRICS_ZETTABYTES,
|
||||
]),
|
||||
[UniversalYAxisUnit.YOTTABYTES]: new Set([
|
||||
YAxisUnit.AWS_YOTTABYTES,
|
||||
YAxisUnit.UCUM_YOTTABYTES,
|
||||
YAxisUnit.OPEN_METRICS_YOTTABYTES,
|
||||
]),
|
||||
|
||||
// Data Rate
|
||||
[UniversalYAxisUnit.BYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_BYTES_SECOND,
|
||||
YAxisUnit.UCUM_BYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_BYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.KILOBYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_KILOBYTES_SECOND,
|
||||
YAxisUnit.UCUM_KILOBYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_KILOBYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.MEGABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_MEGABYTES_SECOND,
|
||||
YAxisUnit.UCUM_MEGABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_MEGABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.GIGABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_GIGABYTES_SECOND,
|
||||
YAxisUnit.UCUM_GIGABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_GIGABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.TERABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_TERABYTES_SECOND,
|
||||
YAxisUnit.UCUM_TERABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_TERABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.PETABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_PETABYTES_SECOND,
|
||||
YAxisUnit.UCUM_PETABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_PETABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.EXABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_EXABYTES_SECOND,
|
||||
YAxisUnit.UCUM_EXABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_EXABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.ZETTABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_ZETTABYTES_SECOND,
|
||||
YAxisUnit.UCUM_ZETTABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_ZETTABYTES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.YOTTABYTES_SECOND]: new Set([
|
||||
YAxisUnit.AWS_YOTTABYTES_SECOND,
|
||||
YAxisUnit.UCUM_YOTTABYTES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_YOTTABYTES_SECOND,
|
||||
]),
|
||||
|
||||
// Bits
|
||||
[UniversalYAxisUnit.BITS]: new Set([
|
||||
YAxisUnit.AWS_BITS,
|
||||
YAxisUnit.UCUM_BITS,
|
||||
YAxisUnit.OPEN_METRICS_BITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.KILOBITS]: new Set([
|
||||
YAxisUnit.AWS_KILOBITS,
|
||||
YAxisUnit.UCUM_KILOBITS,
|
||||
YAxisUnit.OPEN_METRICS_KILOBITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.MEGABITS]: new Set([
|
||||
YAxisUnit.AWS_MEGABITS,
|
||||
YAxisUnit.UCUM_MEGABITS,
|
||||
YAxisUnit.OPEN_METRICS_MEGABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.GIGABITS]: new Set([
|
||||
YAxisUnit.AWS_GIGABITS,
|
||||
YAxisUnit.UCUM_GIGABITS,
|
||||
YAxisUnit.OPEN_METRICS_GIGABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.TERABITS]: new Set([
|
||||
YAxisUnit.AWS_TERABITS,
|
||||
YAxisUnit.UCUM_TERABITS,
|
||||
YAxisUnit.OPEN_METRICS_TERABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.PETABITS]: new Set([
|
||||
YAxisUnit.AWS_PETABITS,
|
||||
YAxisUnit.UCUM_PETABITS,
|
||||
YAxisUnit.OPEN_METRICS_PETABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.EXABITS]: new Set([
|
||||
YAxisUnit.AWS_EXABITS,
|
||||
YAxisUnit.UCUM_EXABITS,
|
||||
YAxisUnit.OPEN_METRICS_EXABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.ZETTABITS]: new Set([
|
||||
YAxisUnit.AWS_ZETTABITS,
|
||||
YAxisUnit.UCUM_ZETTABITS,
|
||||
YAxisUnit.OPEN_METRICS_ZETTABITS,
|
||||
]),
|
||||
[UniversalYAxisUnit.YOTTABITS]: new Set([
|
||||
YAxisUnit.AWS_YOTTABITS,
|
||||
YAxisUnit.UCUM_YOTTABITS,
|
||||
YAxisUnit.OPEN_METRICS_YOTTABITS,
|
||||
]),
|
||||
|
||||
// Bit Rate
|
||||
[UniversalYAxisUnit.BITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_BITS_SECOND,
|
||||
YAxisUnit.UCUM_BITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_BITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.KILOBITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_KILOBITS_SECOND,
|
||||
YAxisUnit.UCUM_KILOBITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_KILOBITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.MEGABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_MEGABITS_SECOND,
|
||||
YAxisUnit.UCUM_MEGABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_MEGABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.GIGABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_GIGABITS_SECOND,
|
||||
YAxisUnit.UCUM_GIGABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_GIGABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.TERABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_TERABITS_SECOND,
|
||||
YAxisUnit.UCUM_TERABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_TERABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.PETABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_PETABITS_SECOND,
|
||||
YAxisUnit.UCUM_PETABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_PETABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.EXABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_EXABITS_SECOND,
|
||||
YAxisUnit.UCUM_EXABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_EXABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.ZETTABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_ZETTABITS_SECOND,
|
||||
YAxisUnit.UCUM_ZETTABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_ZETTABITS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.YOTTABITS_SECOND]: new Set([
|
||||
YAxisUnit.AWS_YOTTABITS_SECOND,
|
||||
YAxisUnit.UCUM_YOTTABITS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_YOTTABITS_SECOND,
|
||||
]),
|
||||
|
||||
// Count
|
||||
[UniversalYAxisUnit.COUNT]: new Set([
|
||||
YAxisUnit.AWS_COUNT,
|
||||
YAxisUnit.UCUM_COUNT,
|
||||
YAxisUnit.OPEN_METRICS_COUNT,
|
||||
]),
|
||||
[UniversalYAxisUnit.COUNT_SECOND]: new Set([
|
||||
YAxisUnit.AWS_COUNT_SECOND,
|
||||
YAxisUnit.UCUM_COUNT_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_COUNT_SECOND,
|
||||
]),
|
||||
|
||||
// Percent
|
||||
[UniversalYAxisUnit.PERCENT]: new Set([
|
||||
YAxisUnit.AWS_PERCENT,
|
||||
YAxisUnit.UCUM_PERCENT,
|
||||
YAxisUnit.OPEN_METRICS_PERCENT,
|
||||
]),
|
||||
[UniversalYAxisUnit.NONE]: new Set([
|
||||
YAxisUnit.AWS_NONE,
|
||||
YAxisUnit.UCUM_NONE,
|
||||
YAxisUnit.OPEN_METRICS_NONE,
|
||||
]),
|
||||
[UniversalYAxisUnit.PERCENT_UNIT]: new Set([
|
||||
YAxisUnit.OPEN_METRICS_PERCENT_UNIT,
|
||||
]),
|
||||
|
||||
// Count Rate
|
||||
[UniversalYAxisUnit.COUNT_MINUTE]: new Set([
|
||||
YAxisUnit.UCUM_COUNTS_MINUTE,
|
||||
YAxisUnit.OPEN_METRICS_COUNTS_MINUTE,
|
||||
]),
|
||||
[UniversalYAxisUnit.OPS_SECOND]: new Set([
|
||||
YAxisUnit.UCUM_OPS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_OPS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.OPS_MINUTE]: new Set([
|
||||
YAxisUnit.UCUM_OPS_MINUTE,
|
||||
YAxisUnit.OPEN_METRICS_OPS_MINUTE,
|
||||
]),
|
||||
[UniversalYAxisUnit.REQUESTS_SECOND]: new Set([
|
||||
YAxisUnit.UCUM_REQUESTS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_REQUESTS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.REQUESTS_MINUTE]: new Set([
|
||||
YAxisUnit.UCUM_REQUESTS_MINUTE,
|
||||
YAxisUnit.OPEN_METRICS_REQUESTS_MINUTE,
|
||||
]),
|
||||
[UniversalYAxisUnit.READS_SECOND]: new Set([
|
||||
YAxisUnit.UCUM_READS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_READS_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.WRITES_SECOND]: new Set([
|
||||
YAxisUnit.UCUM_WRITES_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_WRITES_SECOND,
|
||||
]),
|
||||
[UniversalYAxisUnit.READS_MINUTE]: new Set([
|
||||
YAxisUnit.UCUM_READS_MINUTE,
|
||||
YAxisUnit.OPEN_METRICS_READS_MINUTE,
|
||||
]),
|
||||
[UniversalYAxisUnit.WRITES_MINUTE]: new Set([
|
||||
YAxisUnit.UCUM_WRITES_MINUTE,
|
||||
YAxisUnit.OPEN_METRICS_WRITES_MINUTE,
|
||||
]),
|
||||
[UniversalYAxisUnit.IOOPS_SECOND]: new Set([
|
||||
YAxisUnit.UCUM_IOPS_SECOND,
|
||||
YAxisUnit.OPEN_METRICS_IOPS_SECOND,
|
||||
]),
|
||||
};
|
||||
|
||||
// Mapping of universal y-axis units to their display labels
|
||||
export const Y_AXIS_UNIT_NAMES: Record<UniversalYAxisUnit, string> = {
|
||||
[UniversalYAxisUnit.SECONDS]: 'Seconds (s)',
|
||||
[UniversalYAxisUnit.MILLISECONDS]: 'Milliseconds (ms)',
|
||||
[UniversalYAxisUnit.MICROSECONDS]: 'Microseconds (µs)',
|
||||
[UniversalYAxisUnit.BYTES]: 'Bytes (B)',
|
||||
[UniversalYAxisUnit.KILOBYTES]: 'Kilobytes (KB)',
|
||||
[UniversalYAxisUnit.MEGABYTES]: 'Megabytes (MB)',
|
||||
[UniversalYAxisUnit.GIGABYTES]: 'Gigabytes (GB)',
|
||||
[UniversalYAxisUnit.TERABYTES]: 'Terabytes (TB)',
|
||||
[UniversalYAxisUnit.PETABYTES]: 'Petabytes (PB)',
|
||||
[UniversalYAxisUnit.EXABYTES]: 'Exabytes (EB)',
|
||||
[UniversalYAxisUnit.ZETTABYTES]: 'Zettabytes (ZB)',
|
||||
[UniversalYAxisUnit.YOTTABYTES]: 'Yottabytes (YB)',
|
||||
[UniversalYAxisUnit.BITS]: 'Bits (b)',
|
||||
[UniversalYAxisUnit.KILOBITS]: 'Kilobits (Kb)',
|
||||
[UniversalYAxisUnit.MEGABITS]: 'Megabits (Mb)',
|
||||
[UniversalYAxisUnit.GIGABITS]: 'Gigabits (Gb)',
|
||||
[UniversalYAxisUnit.TERABITS]: 'Terabits (Tb)',
|
||||
[UniversalYAxisUnit.PETABITS]: 'Petabits (Pb)',
|
||||
[UniversalYAxisUnit.EXABITS]: 'Exabits (Eb)',
|
||||
[UniversalYAxisUnit.ZETTABITS]: 'Zettabits (Zb)',
|
||||
[UniversalYAxisUnit.YOTTABITS]: 'Yottabits (Yb)',
|
||||
[UniversalYAxisUnit.BYTES_SECOND]: 'Bytes/sec',
|
||||
[UniversalYAxisUnit.KILOBYTES_SECOND]: 'Kilobytes/sec',
|
||||
[UniversalYAxisUnit.MEGABYTES_SECOND]: 'Megabytes/sec',
|
||||
[UniversalYAxisUnit.GIGABYTES_SECOND]: 'Gigabytes/sec',
|
||||
[UniversalYAxisUnit.TERABYTES_SECOND]: 'Terabytes/sec',
|
||||
[UniversalYAxisUnit.PETABYTES_SECOND]: 'Petabytes/sec',
|
||||
[UniversalYAxisUnit.EXABYTES_SECOND]: 'Exabytes/sec',
|
||||
[UniversalYAxisUnit.ZETTABYTES_SECOND]: 'Zettabytes/sec',
|
||||
[UniversalYAxisUnit.YOTTABYTES_SECOND]: 'Yottabytes/sec',
|
||||
[UniversalYAxisUnit.BITS_SECOND]: 'Bits/sec',
|
||||
[UniversalYAxisUnit.KILOBITS_SECOND]: 'Kilobits/sec',
|
||||
[UniversalYAxisUnit.MEGABITS_SECOND]: 'Megabits/sec',
|
||||
[UniversalYAxisUnit.GIGABITS_SECOND]: 'Gigabits/sec',
|
||||
[UniversalYAxisUnit.TERABITS_SECOND]: 'Terabits/sec',
|
||||
[UniversalYAxisUnit.PETABITS_SECOND]: 'Petabits/sec',
|
||||
[UniversalYAxisUnit.EXABITS_SECOND]: 'Exabits/sec',
|
||||
[UniversalYAxisUnit.ZETTABITS_SECOND]: 'Zettabits/sec',
|
||||
[UniversalYAxisUnit.YOTTABITS_SECOND]: 'Yottabits/sec',
|
||||
[UniversalYAxisUnit.COUNT]: 'Count',
|
||||
[UniversalYAxisUnit.COUNT_SECOND]: 'Count/sec',
|
||||
[UniversalYAxisUnit.PERCENT]: 'Percent (0 - 100)',
|
||||
[UniversalYAxisUnit.NONE]: 'None',
|
||||
[UniversalYAxisUnit.WEEKS]: 'Weeks',
|
||||
[UniversalYAxisUnit.DAYS]: 'Days',
|
||||
[UniversalYAxisUnit.HOURS]: 'Hours',
|
||||
[UniversalYAxisUnit.MINUTES]: 'Minutes',
|
||||
[UniversalYAxisUnit.NANOSECONDS]: 'Nanoseconds',
|
||||
[UniversalYAxisUnit.COUNT_MINUTE]: 'Count/min',
|
||||
[UniversalYAxisUnit.OPS_SECOND]: 'Ops/sec',
|
||||
[UniversalYAxisUnit.OPS_MINUTE]: 'Ops/min',
|
||||
[UniversalYAxisUnit.REQUESTS_SECOND]: 'Requests/sec',
|
||||
[UniversalYAxisUnit.REQUESTS_MINUTE]: 'Requests/min',
|
||||
[UniversalYAxisUnit.READS_SECOND]: 'Reads/sec',
|
||||
[UniversalYAxisUnit.WRITES_SECOND]: 'Writes/sec',
|
||||
[UniversalYAxisUnit.READS_MINUTE]: 'Reads/min',
|
||||
[UniversalYAxisUnit.WRITES_MINUTE]: 'Writes/min',
|
||||
[UniversalYAxisUnit.IOOPS_SECOND]: 'IOPS/sec',
|
||||
[UniversalYAxisUnit.PERCENT_UNIT]: 'Percent (0.0 - 1.0)',
|
||||
};
|
||||
|
||||
// Splitting the universal y-axis units into categories
|
||||
export const Y_AXIS_CATEGORIES = [
|
||||
{
|
||||
name: 'Time',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.SECONDS],
|
||||
id: UniversalYAxisUnit.SECONDS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MILLISECONDS],
|
||||
id: UniversalYAxisUnit.MILLISECONDS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MICROSECONDS],
|
||||
id: UniversalYAxisUnit.MICROSECONDS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.NANOSECONDS],
|
||||
id: UniversalYAxisUnit.NANOSECONDS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MINUTES],
|
||||
id: UniversalYAxisUnit.MINUTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.HOURS],
|
||||
id: UniversalYAxisUnit.HOURS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.DAYS],
|
||||
id: UniversalYAxisUnit.DAYS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WEEKS],
|
||||
id: UniversalYAxisUnit.WEEKS,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES],
|
||||
id: UniversalYAxisUnit.BYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES],
|
||||
id: UniversalYAxisUnit.KILOBYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES],
|
||||
id: UniversalYAxisUnit.MEGABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES],
|
||||
id: UniversalYAxisUnit.GIGABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES],
|
||||
id: UniversalYAxisUnit.TERABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES],
|
||||
id: UniversalYAxisUnit.PETABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.EXABYTES],
|
||||
id: UniversalYAxisUnit.EXABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.ZETTABYTES],
|
||||
id: UniversalYAxisUnit.ZETTABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.YOTTABYTES],
|
||||
id: UniversalYAxisUnit.YOTTABYTES,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS],
|
||||
id: UniversalYAxisUnit.BITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS],
|
||||
id: UniversalYAxisUnit.KILOBITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS],
|
||||
id: UniversalYAxisUnit.MEGABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS],
|
||||
id: UniversalYAxisUnit.GIGABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS],
|
||||
id: UniversalYAxisUnit.TERABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS],
|
||||
id: UniversalYAxisUnit.PETABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.EXABITS],
|
||||
id: UniversalYAxisUnit.EXABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.ZETTABITS],
|
||||
id: UniversalYAxisUnit.ZETTABITS,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.YOTTABITS],
|
||||
id: UniversalYAxisUnit.YOTTABITS,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Data Rate',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BYTES_SECOND],
|
||||
id: UniversalYAxisUnit.BYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBYTES_SECOND],
|
||||
id: UniversalYAxisUnit.KILOBYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.MEGABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.GIGABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.TERABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.PETABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.EXABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.EXABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.ZETTABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.ZETTABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.YOTTABYTES_SECOND],
|
||||
id: UniversalYAxisUnit.YOTTABYTES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.BITS_SECOND],
|
||||
id: UniversalYAxisUnit.BITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.KILOBITS_SECOND],
|
||||
id: UniversalYAxisUnit.KILOBITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.MEGABITS_SECOND],
|
||||
id: UniversalYAxisUnit.MEGABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.GIGABITS_SECOND],
|
||||
id: UniversalYAxisUnit.GIGABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.TERABITS_SECOND],
|
||||
id: UniversalYAxisUnit.TERABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PETABITS_SECOND],
|
||||
id: UniversalYAxisUnit.PETABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.EXABITS_SECOND],
|
||||
id: UniversalYAxisUnit.EXABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.ZETTABITS_SECOND],
|
||||
id: UniversalYAxisUnit.ZETTABITS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.YOTTABITS_SECOND],
|
||||
id: UniversalYAxisUnit.YOTTABITS_SECOND,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Count',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT],
|
||||
id: UniversalYAxisUnit.COUNT,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_SECOND],
|
||||
id: UniversalYAxisUnit.COUNT_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.COUNT_MINUTE],
|
||||
id: UniversalYAxisUnit.COUNT_MINUTE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Operations',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_SECOND],
|
||||
id: UniversalYAxisUnit.OPS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.OPS_MINUTE],
|
||||
id: UniversalYAxisUnit.OPS_MINUTE,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.REQUESTS_SECOND],
|
||||
id: UniversalYAxisUnit.REQUESTS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.REQUESTS_MINUTE],
|
||||
id: UniversalYAxisUnit.REQUESTS_MINUTE,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_SECOND],
|
||||
id: UniversalYAxisUnit.READS_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_SECOND],
|
||||
id: UniversalYAxisUnit.WRITES_SECOND,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.READS_MINUTE],
|
||||
id: UniversalYAxisUnit.READS_MINUTE,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.WRITES_MINUTE],
|
||||
id: UniversalYAxisUnit.WRITES_MINUTE,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.IOOPS_SECOND],
|
||||
id: UniversalYAxisUnit.IOOPS_SECOND,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Percentage',
|
||||
units: [
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PERCENT],
|
||||
id: UniversalYAxisUnit.PERCENT,
|
||||
},
|
||||
{
|
||||
name: Y_AXIS_UNIT_NAMES[UniversalYAxisUnit.PERCENT_UNIT],
|
||||
id: UniversalYAxisUnit.PERCENT_UNIT,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
3
frontend/src/components/YAxisUnitSelector/index.ts
Normal file
3
frontend/src/components/YAxisUnitSelector/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import YAxisUnitSelector from './YAxisUnitSelector';
|
||||
|
||||
export default YAxisUnitSelector;
|
||||
5
frontend/src/components/YAxisUnitSelector/styles.scss
Normal file
5
frontend/src/components/YAxisUnitSelector/styles.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.y-axis-unit-selector-component {
|
||||
.ant-select {
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
365
frontend/src/components/YAxisUnitSelector/types.ts
Normal file
365
frontend/src/components/YAxisUnitSelector/types.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
export interface YAxisUnitSelectorProps {
|
||||
value: string | undefined;
|
||||
onChange: (value: UniversalYAxisUnit) => void;
|
||||
placeholder?: string;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export enum UniversalYAxisUnit {
|
||||
// Time
|
||||
WEEKS = 'wk',
|
||||
DAYS = 'd',
|
||||
HOURS = 'h',
|
||||
MINUTES = 'min',
|
||||
SECONDS = 's',
|
||||
MICROSECONDS = 'us',
|
||||
MILLISECONDS = 'ms',
|
||||
NANOSECONDS = 'ns',
|
||||
|
||||
// Data
|
||||
BYTES = 'By',
|
||||
KILOBYTES = 'kBy',
|
||||
MEGABYTES = 'MBy',
|
||||
GIGABYTES = 'GBy',
|
||||
TERABYTES = 'TBy',
|
||||
PETABYTES = 'PBy',
|
||||
EXABYTES = 'EBy',
|
||||
ZETTABYTES = 'ZBy',
|
||||
YOTTABYTES = 'YBy',
|
||||
|
||||
// Data Rate
|
||||
BYTES_SECOND = 'By/s',
|
||||
KILOBYTES_SECOND = 'kBy/s',
|
||||
MEGABYTES_SECOND = 'MBy/s',
|
||||
GIGABYTES_SECOND = 'GBy/s',
|
||||
TERABYTES_SECOND = 'TBy/s',
|
||||
PETABYTES_SECOND = 'PBy/s',
|
||||
EXABYTES_SECOND = 'EBy/s',
|
||||
ZETTABYTES_SECOND = 'ZBy/s',
|
||||
YOTTABYTES_SECOND = 'YBy/s',
|
||||
|
||||
// Bits
|
||||
BITS = 'bit',
|
||||
KILOBITS = 'kbit',
|
||||
MEGABITS = 'Mbit',
|
||||
GIGABITS = 'Gbit',
|
||||
TERABITS = 'Tbit',
|
||||
PETABITS = 'Pbit',
|
||||
EXABITS = 'Ebit',
|
||||
ZETTABITS = 'Zbit',
|
||||
YOTTABITS = 'Ybit',
|
||||
|
||||
// Bit Rate
|
||||
BITS_SECOND = 'bit/s',
|
||||
KILOBITS_SECOND = 'kbit/s',
|
||||
MEGABITS_SECOND = 'Mbit/s',
|
||||
GIGABITS_SECOND = 'Gbit/s',
|
||||
TERABITS_SECOND = 'Tbit/s',
|
||||
PETABITS_SECOND = 'Pbit/s',
|
||||
EXABITS_SECOND = 'Ebit/s',
|
||||
ZETTABITS_SECOND = 'Zbit/s',
|
||||
YOTTABITS_SECOND = 'Ybit/s',
|
||||
|
||||
// Count
|
||||
COUNT = '{count}',
|
||||
COUNT_SECOND = '{count}/s',
|
||||
COUNT_MINUTE = '{count}/min',
|
||||
|
||||
// Operations
|
||||
OPS_SECOND = '{ops}/s',
|
||||
OPS_MINUTE = '{ops}/min',
|
||||
|
||||
// Requests
|
||||
REQUESTS_SECOND = '{req}/s',
|
||||
REQUESTS_MINUTE = '{req}/min',
|
||||
|
||||
// Reads/Writes
|
||||
READS_SECOND = '{read}/s',
|
||||
WRITES_SECOND = '{write}/s',
|
||||
READS_MINUTE = '{read}/min',
|
||||
WRITES_MINUTE = '{write}/min',
|
||||
|
||||
// IO Operations
|
||||
IOOPS_SECOND = '{iops}/s',
|
||||
|
||||
// Percent
|
||||
PERCENT = '%',
|
||||
PERCENT_UNIT = 'percentunit',
|
||||
NONE = '1',
|
||||
}
|
||||
|
||||
export enum YAxisUnit {
|
||||
AWS_SECONDS = 'Seconds',
|
||||
UCUM_SECONDS = 's',
|
||||
OPEN_METRICS_SECONDS = 'seconds',
|
||||
|
||||
AWS_MICROSECONDS = 'Microseconds',
|
||||
UCUM_MICROSECONDS = 'us',
|
||||
OPEN_METRICS_MICROSECONDS = 'microseconds',
|
||||
|
||||
AWS_MILLISECONDS = 'Milliseconds',
|
||||
UCUM_MILLISECONDS = 'ms',
|
||||
OPEN_METRICS_MILLISECONDS = 'milliseconds',
|
||||
|
||||
AWS_BYTES = 'Bytes',
|
||||
UCUM_BYTES = 'By',
|
||||
OPEN_METRICS_BYTES = 'bytes',
|
||||
|
||||
AWS_KILOBYTES = 'Kilobytes',
|
||||
UCUM_KILOBYTES = 'kBy',
|
||||
OPEN_METRICS_KILOBYTES = 'kilobytes',
|
||||
|
||||
AWS_MEGABYTES = 'Megabytes',
|
||||
UCUM_MEGABYTES = 'MBy',
|
||||
OPEN_METRICS_MEGABYTES = 'megabytes',
|
||||
|
||||
AWS_GIGABYTES = 'Gigabytes',
|
||||
UCUM_GIGABYTES = 'GBy',
|
||||
OPEN_METRICS_GIGABYTES = 'gigabytes',
|
||||
|
||||
AWS_TERABYTES = 'Terabytes',
|
||||
UCUM_TERABYTES = 'TBy',
|
||||
OPEN_METRICS_TERABYTES = 'terabytes',
|
||||
|
||||
AWS_PETABYTES = 'Petabytes',
|
||||
UCUM_PETABYTES = 'PBy',
|
||||
OPEN_METRICS_PETABYTES = 'petabytes',
|
||||
|
||||
AWS_EXABYTES = 'Exabytes',
|
||||
UCUM_EXABYTES = 'EBy',
|
||||
OPEN_METRICS_EXABYTES = 'exabytes',
|
||||
|
||||
AWS_ZETTABYTES = 'Zettabytes',
|
||||
UCUM_ZETTABYTES = 'ZBy',
|
||||
OPEN_METRICS_ZETTABYTES = 'zettabytes',
|
||||
|
||||
AWS_YOTTABYTES = 'Yottabytes',
|
||||
UCUM_YOTTABYTES = 'YBy',
|
||||
OPEN_METRICS_YOTTABYTES = 'yottabytes',
|
||||
|
||||
AWS_BYTES_SECOND = 'Bytes/Second',
|
||||
UCUM_BYTES_SECOND = 'By/s',
|
||||
OPEN_METRICS_BYTES_SECOND = 'bytes_per_second',
|
||||
|
||||
AWS_KILOBYTES_SECOND = 'Kilobytes/Second',
|
||||
UCUM_KILOBYTES_SECOND = 'kBy/s',
|
||||
OPEN_METRICS_KILOBYTES_SECOND = 'kilobytes_per_second',
|
||||
|
||||
AWS_MEGABYTES_SECOND = 'Megabytes/Second',
|
||||
UCUM_MEGABYTES_SECOND = 'MBy/s',
|
||||
OPEN_METRICS_MEGABYTES_SECOND = 'megabytes_per_second',
|
||||
|
||||
AWS_GIGABYTES_SECOND = 'Gigabytes/Second',
|
||||
UCUM_GIGABYTES_SECOND = 'GBy/s',
|
||||
OPEN_METRICS_GIGABYTES_SECOND = 'gigabytes_per_second',
|
||||
|
||||
AWS_TERABYTES_SECOND = 'Terabytes/Second',
|
||||
UCUM_TERABYTES_SECOND = 'TBy/s',
|
||||
OPEN_METRICS_TERABYTES_SECOND = 'terabytes_per_second',
|
||||
|
||||
AWS_PETABYTES_SECOND = 'Petabytes/Second',
|
||||
UCUM_PETABYTES_SECOND = 'PBy/s',
|
||||
OPEN_METRICS_PETABYTES_SECOND = 'petabytes_per_second',
|
||||
|
||||
AWS_EXABYTES_SECOND = 'Exabytes/Second',
|
||||
UCUM_EXABYTES_SECOND = 'EBy/s',
|
||||
OPEN_METRICS_EXABYTES_SECOND = 'exabytes_per_second',
|
||||
|
||||
AWS_ZETTABYTES_SECOND = 'Zettabytes/Second',
|
||||
UCUM_ZETTABYTES_SECOND = 'ZBy/s',
|
||||
OPEN_METRICS_ZETTABYTES_SECOND = 'zettabytes_per_second',
|
||||
|
||||
AWS_YOTTABYTES_SECOND = 'Yottabytes/Second',
|
||||
UCUM_YOTTABYTES_SECOND = 'YBy/s',
|
||||
OPEN_METRICS_YOTTABYTES_SECOND = 'yottabytes_per_second',
|
||||
|
||||
AWS_BITS = 'Bits',
|
||||
UCUM_BITS = 'bit',
|
||||
OPEN_METRICS_BITS = 'bits',
|
||||
|
||||
AWS_KILOBITS = 'Kilobits',
|
||||
UCUM_KILOBITS = 'kbit',
|
||||
OPEN_METRICS_KILOBITS = 'kilobits',
|
||||
|
||||
AWS_MEGABITS = 'Megabits',
|
||||
UCUM_MEGABITS = 'Mbit',
|
||||
OPEN_METRICS_MEGABITS = 'megabits',
|
||||
|
||||
AWS_GIGABITS = 'Gigabits',
|
||||
UCUM_GIGABITS = 'Gbit',
|
||||
OPEN_METRICS_GIGABITS = 'gigabits',
|
||||
|
||||
AWS_TERABITS = 'Terabits',
|
||||
UCUM_TERABITS = 'Tbit',
|
||||
OPEN_METRICS_TERABITS = 'terabits',
|
||||
|
||||
AWS_PETABITS = 'Petabits',
|
||||
UCUM_PETABITS = 'Pbit',
|
||||
OPEN_METRICS_PETABITS = 'petabits',
|
||||
|
||||
AWS_EXABITS = 'Exabits',
|
||||
UCUM_EXABITS = 'Ebit',
|
||||
OPEN_METRICS_EXABITS = 'exabits',
|
||||
|
||||
AWS_ZETTABITS = 'Zettabits',
|
||||
UCUM_ZETTABITS = 'Zbit',
|
||||
OPEN_METRICS_ZETTABITS = 'zettabits',
|
||||
|
||||
AWS_YOTTABITS = 'Yottabits',
|
||||
UCUM_YOTTABITS = 'Ybit',
|
||||
OPEN_METRICS_YOTTABITS = 'yottabits',
|
||||
|
||||
AWS_BITS_SECOND = 'Bits/Second',
|
||||
UCUM_BITS_SECOND = 'bit/s',
|
||||
OPEN_METRICS_BITS_SECOND = 'bits_per_second',
|
||||
|
||||
AWS_KILOBITS_SECOND = 'Kilobits/Second',
|
||||
UCUM_KILOBITS_SECOND = 'kbit/s',
|
||||
OPEN_METRICS_KILOBITS_SECOND = 'kilobits_per_second',
|
||||
|
||||
AWS_MEGABITS_SECOND = 'Megabits/Second',
|
||||
UCUM_MEGABITS_SECOND = 'Mbit/s',
|
||||
OPEN_METRICS_MEGABITS_SECOND = 'megabits_per_second',
|
||||
|
||||
AWS_GIGABITS_SECOND = 'Gigabits/Second',
|
||||
UCUM_GIGABITS_SECOND = 'Gbit/s',
|
||||
OPEN_METRICS_GIGABITS_SECOND = 'gigabits_per_second',
|
||||
|
||||
AWS_TERABITS_SECOND = 'Terabits/Second',
|
||||
UCUM_TERABITS_SECOND = 'Tbit/s',
|
||||
OPEN_METRICS_TERABITS_SECOND = 'terabits_per_second',
|
||||
|
||||
AWS_PETABITS_SECOND = 'Petabits/Second',
|
||||
UCUM_PETABITS_SECOND = 'Pbit/s',
|
||||
OPEN_METRICS_PETABITS_SECOND = 'petabits_per_second',
|
||||
|
||||
AWS_EXABITS_SECOND = 'Exabits/Second',
|
||||
UCUM_EXABITS_SECOND = 'Ebit/s',
|
||||
OPEN_METRICS_EXABITS_SECOND = 'exabits_per_second',
|
||||
|
||||
AWS_ZETTABITS_SECOND = 'Zettabits/Second',
|
||||
UCUM_ZETTABITS_SECOND = 'Zbit/s',
|
||||
OPEN_METRICS_ZETTABITS_SECOND = 'zettabits_per_second',
|
||||
|
||||
AWS_YOTTABITS_SECOND = 'Yottabits/Second',
|
||||
UCUM_YOTTABITS_SECOND = 'Ybit/s',
|
||||
OPEN_METRICS_YOTTABITS_SECOND = 'yottabits_per_second',
|
||||
|
||||
AWS_COUNT = 'Count',
|
||||
UCUM_COUNT = '{count}',
|
||||
OPEN_METRICS_COUNT = 'count',
|
||||
|
||||
AWS_COUNT_SECOND = 'Count/Second',
|
||||
UCUM_COUNT_SECOND = '{count}/s',
|
||||
OPEN_METRICS_COUNT_SECOND = 'count_per_second',
|
||||
|
||||
AWS_PERCENT = 'Percent',
|
||||
UCUM_PERCENT = '%',
|
||||
OPEN_METRICS_PERCENT = 'ratio',
|
||||
|
||||
AWS_NONE = 'None',
|
||||
UCUM_NONE = '1',
|
||||
OPEN_METRICS_NONE = 'none',
|
||||
|
||||
UCUM_NANOSECONDS = 'ns',
|
||||
OPEN_METRICS_NANOSECONDS = 'nanoseconds',
|
||||
|
||||
UCUM_MINUTES = 'min',
|
||||
OPEN_METRICS_MINUTES = 'minutes',
|
||||
|
||||
UCUM_HOURS = 'h',
|
||||
OPEN_METRICS_HOURS = 'hours',
|
||||
|
||||
UCUM_DAYS = 'd',
|
||||
OPEN_METRICS_DAYS = 'days',
|
||||
|
||||
UCUM_WEEKS = 'wk',
|
||||
OPEN_METRICS_WEEKS = 'weeks',
|
||||
|
||||
UCUM_KIBIBYTES = 'KiBy',
|
||||
OPEN_METRICS_KIBIBYTES = 'kibibytes',
|
||||
|
||||
UCUM_MEBIBYTES = 'MiBy',
|
||||
OPEN_METRICS_MEBIBYTES = 'mebibytes',
|
||||
|
||||
UCUM_GIBIBYTES = 'GiBy',
|
||||
OPEN_METRICS_GIBIBYTES = 'gibibytes',
|
||||
|
||||
UCUM_TEBIBYTES = 'TiBy',
|
||||
OPEN_METRICS_TEBIBYTES = 'tebibytes',
|
||||
|
||||
UCUM_PEBIBYTES = 'PiBy',
|
||||
OPEN_METRICS_PEBIBYTES = 'pebibytes',
|
||||
|
||||
UCUM_KIBIBYTES_SECOND = 'KiBy/s',
|
||||
OPEN_METRICS_KIBIBYTES_SECOND = 'kibibytes_per_second',
|
||||
|
||||
UCUM_KIBIBITS_SECOND = 'Kibit/s',
|
||||
OPEN_METRICS_KIBIBITS_SECOND = 'kibibits_per_second',
|
||||
|
||||
UCUM_MEBIBYTES_SECOND = 'MiBy/s',
|
||||
OPEN_METRICS_MEBIBYTES_SECOND = 'mebibytes_per_second',
|
||||
|
||||
UCUM_MEBIBITS_SECOND = 'Mibit/s',
|
||||
OPEN_METRICS_MEBIBITS_SECOND = 'mebibits_per_second',
|
||||
|
||||
UCUM_GIBIBYTES_SECOND = 'GiBy/s',
|
||||
OPEN_METRICS_GIBIBYTES_SECOND = 'gibibytes_per_second',
|
||||
|
||||
UCUM_GIBIBITS_SECOND = 'Gibit/s',
|
||||
OPEN_METRICS_GIBIBITS_SECOND = 'gibibits_per_second',
|
||||
|
||||
UCUM_TEBIBYTES_SECOND = 'TiBy/s',
|
||||
OPEN_METRICS_TEBIBYTES_SECOND = 'tebibytes_per_second',
|
||||
|
||||
UCUM_TEBIBITS_SECOND = 'Tibit/s',
|
||||
OPEN_METRICS_TEBIBITS_SECOND = 'tebibits_per_second',
|
||||
|
||||
UCUM_PEBIBYTES_SECOND = 'PiBy/s',
|
||||
OPEN_METRICS_PEBIBYTES_SECOND = 'pebibytes_per_second',
|
||||
|
||||
UCUM_PEBIBITS_SECOND = 'Pibit/s',
|
||||
OPEN_METRICS_PEBIBITS_SECOND = 'pebibits_per_second',
|
||||
|
||||
UCUM_TRUE_FALSE = '{bool}',
|
||||
OPEN_METRICS_TRUE_FALSE = 'boolean_true_false',
|
||||
|
||||
UCUM_YES_NO = '{bool}',
|
||||
OPEN_METRICS_YES_NO = 'boolean_yes_no',
|
||||
|
||||
UCUM_COUNTS_SECOND = '{count}/s',
|
||||
OPEN_METRICS_COUNTS_SECOND = 'counts_per_second',
|
||||
|
||||
UCUM_OPS_SECOND = '{ops}/s',
|
||||
OPEN_METRICS_OPS_SECOND = 'ops_per_second',
|
||||
|
||||
UCUM_REQUESTS_SECOND = '{requests}/s',
|
||||
OPEN_METRICS_REQUESTS_SECOND = 'requests_per_second',
|
||||
|
||||
UCUM_REQUESTS_MINUTE = '{requests}/min',
|
||||
OPEN_METRICS_REQUESTS_MINUTE = 'requests_per_minute',
|
||||
|
||||
UCUM_READS_SECOND = '{reads}/s',
|
||||
OPEN_METRICS_READS_SECOND = 'reads_per_second',
|
||||
|
||||
UCUM_WRITES_SECOND = '{writes}/s',
|
||||
OPEN_METRICS_WRITES_SECOND = 'writes_per_second',
|
||||
|
||||
UCUM_IOPS_SECOND = '{iops}/s',
|
||||
OPEN_METRICS_IOPS_SECOND = 'io_ops_per_second',
|
||||
|
||||
UCUM_COUNTS_MINUTE = '{count}/min',
|
||||
OPEN_METRICS_COUNTS_MINUTE = 'counts_per_minute',
|
||||
|
||||
UCUM_OPS_MINUTE = '{ops}/min',
|
||||
OPEN_METRICS_OPS_MINUTE = 'ops_per_minute',
|
||||
|
||||
UCUM_READS_MINUTE = '{reads}/min',
|
||||
OPEN_METRICS_READS_MINUTE = 'reads_per_minute',
|
||||
|
||||
UCUM_WRITES_MINUTE = '{writes}/min',
|
||||
OPEN_METRICS_WRITES_MINUTE = 'writes_per_minute',
|
||||
|
||||
OPEN_METRICS_PERCENT_UNIT = 'percentunit',
|
||||
}
|
||||
33
frontend/src/components/YAxisUnitSelector/utils.tsx
Normal file
33
frontend/src/components/YAxisUnitSelector/utils.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { UniversalYAxisUnitMappings, Y_AXIS_UNIT_NAMES } from './constants';
|
||||
import { UniversalYAxisUnit, YAxisUnit } from './types';
|
||||
|
||||
export const mapMetricUnitToUniversalUnit = (
|
||||
unit: string | undefined,
|
||||
): UniversalYAxisUnit | null => {
|
||||
if (!unit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const universalUnit = Object.values(UniversalYAxisUnit).find(
|
||||
(u) => UniversalYAxisUnitMappings[u].has(unit as YAxisUnit) || unit === u,
|
||||
);
|
||||
|
||||
return universalUnit || (unit as UniversalYAxisUnit) || null;
|
||||
};
|
||||
|
||||
export const getUniversalNameFromMetricUnit = (
|
||||
unit: string | undefined,
|
||||
): string => {
|
||||
if (!unit) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const universalUnit = mapMetricUnitToUniversalUnit(unit);
|
||||
if (!universalUnit) {
|
||||
return unit;
|
||||
}
|
||||
|
||||
const universalName = Y_AXIS_UNIT_NAMES[universalUnit];
|
||||
|
||||
return universalName || unit || '-';
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
export const OPERATORS = {
|
||||
IN: 'IN',
|
||||
LIKE: 'LIKE',
|
||||
@@ -21,6 +23,44 @@ export const QUERY_BUILDER_FUNCTIONS = {
|
||||
HASALL: 'hasAll',
|
||||
};
|
||||
|
||||
export function negateOperator(operatorOrFunction: string): string {
|
||||
// Special cases for equals/not equals
|
||||
if (operatorOrFunction === OPERATORS['=']) {
|
||||
return OPERATORS['!='];
|
||||
}
|
||||
if (operatorOrFunction === OPERATORS['!=']) {
|
||||
return OPERATORS['='];
|
||||
}
|
||||
// For all other operators and functions, add NOT in front
|
||||
return `${OPERATORS.NOT} ${operatorOrFunction}`;
|
||||
}
|
||||
|
||||
export enum DEPRECATED_OPERATORS {
|
||||
REGEX = 'regex',
|
||||
NIN = 'nin',
|
||||
NREGEX = 'nregex',
|
||||
NLIKE = 'nlike',
|
||||
NILIKE = 'nilike',
|
||||
NEXTISTS = 'nexists',
|
||||
NCONTAINS = 'ncontains',
|
||||
NHAS = 'nhas',
|
||||
NHASANY = 'nhasany',
|
||||
NHASALL = 'nhasall',
|
||||
}
|
||||
|
||||
export const DEPRECATED_OPERATORS_MAP = {
|
||||
[DEPRECATED_OPERATORS.REGEX]: OPERATORS.REGEXP,
|
||||
[DEPRECATED_OPERATORS.NIN]: negateOperator(OPERATORS.IN),
|
||||
[DEPRECATED_OPERATORS.NREGEX]: negateOperator(OPERATORS.REGEXP),
|
||||
[DEPRECATED_OPERATORS.NLIKE]: negateOperator(OPERATORS.LIKE),
|
||||
[DEPRECATED_OPERATORS.NILIKE]: negateOperator(OPERATORS.ILIKE),
|
||||
[DEPRECATED_OPERATORS.NEXTISTS]: negateOperator(OPERATORS.EXISTS),
|
||||
[DEPRECATED_OPERATORS.NCONTAINS]: negateOperator(OPERATORS.CONTAINS),
|
||||
[DEPRECATED_OPERATORS.NHAS]: negateOperator(QUERY_BUILDER_FUNCTIONS.HAS),
|
||||
[DEPRECATED_OPERATORS.NHASANY]: negateOperator(QUERY_BUILDER_FUNCTIONS.HASANY),
|
||||
[DEPRECATED_OPERATORS.NHASALL]: negateOperator(QUERY_BUILDER_FUNCTIONS.HASALL),
|
||||
};
|
||||
|
||||
export const NON_VALUE_OPERATORS = [OPERATORS.EXISTS];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@@ -82,15 +122,3 @@ export const queryOperatorSuggestions = [
|
||||
{ label: OPERATORS.NOT, type: 'operator', info: 'Not' },
|
||||
...negationQueryOperatorSuggestions,
|
||||
];
|
||||
|
||||
export function negateOperator(operatorOrFunction: string): string {
|
||||
// Special cases for equals/not equals
|
||||
if (operatorOrFunction === OPERATORS['=']) {
|
||||
return OPERATORS['!='];
|
||||
}
|
||||
if (operatorOrFunction === OPERATORS['!=']) {
|
||||
return OPERATORS['='];
|
||||
}
|
||||
// For all other operators and functions, add NOT in front
|
||||
return `${OPERATORS.NOT} ${operatorOrFunction}`;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ export const DATE_TIME_FORMATS = {
|
||||
MONTH_DATETIME_SECONDS: 'MMM DD YYYY HH:mm:ss',
|
||||
MONTH_DATETIME_FULL: 'MMMM DD, YYYY HH:mm',
|
||||
MONTH_DATETIME_FULL_SECONDS: 'MMM DD, YYYY, HH:mm:ss',
|
||||
DD_MMM_YYYY_HH_MM: 'DD MMM YYYY, HH:mm',
|
||||
DD_MMM_YYYY_HH_MM_SS: 'DD MMM YYYY, HH:mm:ss',
|
||||
|
||||
// Ordinal formats (1st, 2nd, 3rd, etc)
|
||||
ORDINAL_DATE: 'Do MMM YYYY',
|
||||
|
||||
@@ -32,4 +32,6 @@ export enum LOCALSTORAGE {
|
||||
BANNER_DISMISSED = 'BANNER_DISMISSED',
|
||||
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
|
||||
FUNNEL_STEPS = 'FUNNEL_STEPS',
|
||||
LAST_USED_CUSTOM_TIME_RANGES = 'LAST_USED_CUSTOM_TIME_RANGES',
|
||||
SHOW_FREQUENCY_CHART = 'SHOW_FREQUENCY_CHART',
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ export const selectValueDivider = '__';
|
||||
|
||||
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
||||
BaseAutocompleteData,
|
||||
'id' | 'isJSON' | 'isIndexed'
|
||||
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
||||
'id' | 'isIndexed'
|
||||
>)[] = ['key', 'dataType', 'type'];
|
||||
|
||||
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
||||
resource: 'resource',
|
||||
@@ -150,14 +150,12 @@ export const initialHavingValues: HavingForm = {
|
||||
|
||||
export const initialAutocompleteData: BaseAutocompleteData = {
|
||||
id: createIdFromObjectFields(
|
||||
{ dataType: null, key: '', isColumn: null, type: null },
|
||||
{ dataType: null, key: '', type: null },
|
||||
baseAutoCompleteIdKeysOrder,
|
||||
),
|
||||
dataType: DataTypes.EMPTY,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
};
|
||||
|
||||
export const initialFilters: TagFilter = {
|
||||
@@ -189,7 +187,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
|
||||
sourceNames: alphabet,
|
||||
}),
|
||||
disabled: false,
|
||||
stepInterval: undefined,
|
||||
stepInterval: null,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
@@ -237,7 +235,7 @@ export const initialQueryBuilderFormMeterValues: IBuilderQuery = {
|
||||
sourceNames: alphabet,
|
||||
}),
|
||||
disabled: false,
|
||||
stepInterval: undefined,
|
||||
stepInterval: null,
|
||||
having: [],
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
|
||||
@@ -77,9 +77,9 @@ const ROUTES = {
|
||||
API_MONITORING: '/api-monitoring/explorer',
|
||||
METRICS_EXPLORER_BASE: '/metrics-explorer',
|
||||
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',
|
||||
METER_EXPLORER_BASE: '/meter-explorer',
|
||||
METER_EXPLORER: '/meter-explorer',
|
||||
METER_EXPLORER_VIEWS: '/meter-explorer/views',
|
||||
METER: '/meter',
|
||||
METER_EXPLORER: '/meter/explorer',
|
||||
METER_EXPLORER_VIEWS: '/meter/explorer/views',
|
||||
HOME_PAGE: '/',
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ export const GlobalShortcuts = {
|
||||
NavigateToAlerts: 'a+shift',
|
||||
NavigateToExceptions: 'e+shift',
|
||||
NavigateToMessagingQueues: 'm+shift',
|
||||
ToggleSidebar: 'b+shift',
|
||||
NavigateToHome: 'h+shift',
|
||||
};
|
||||
|
||||
export const GlobalShortcutsName = {
|
||||
@@ -16,14 +18,18 @@ export const GlobalShortcutsName = {
|
||||
NavigateToAlerts: 'shift+a',
|
||||
NavigateToExceptions: 'shift+e',
|
||||
NavigateToMessagingQueues: 'shift+m',
|
||||
ToggleSidebar: 'shift+b',
|
||||
NavigateToHome: 'shift+h',
|
||||
};
|
||||
|
||||
export const GlobalShortcutsDescription = {
|
||||
NavigateToHome: 'Navigate to Home',
|
||||
NavigateToServices: 'Navigate to Services page',
|
||||
NavigateToTraces: 'Navigate to Traces page',
|
||||
NavigateToLogs: 'Navigate to logs page',
|
||||
NavigateToDashboards: 'Navigate to dashboards page',
|
||||
NavigateToAlerts: 'Navigate to alerts page',
|
||||
NavigateToExceptions: 'Navigate to Exceptions page',
|
||||
NavigateToMessagingQueues: 'Navigate to Messaging Queues page',
|
||||
NavigateToTraces: 'Navigate to Traces Explorer',
|
||||
NavigateToLogs: 'Navigate to Logs Explorer',
|
||||
NavigateToDashboards: 'Navigate to Dashboards List',
|
||||
NavigateToAlerts: 'Navigate to Alerts List',
|
||||
NavigateToExceptions: 'Navigate to Exceptions List',
|
||||
NavigateToMessagingQueues: 'Navigate to Messaging Queues',
|
||||
ToggleSidebar: 'Toggle sidebar visibility',
|
||||
};
|
||||
|
||||
@@ -10,9 +10,9 @@ export const LogsExplorerShortcuts = {
|
||||
export const LogsExplorerShortcutsName = {
|
||||
StageAndRunQuery: `${
|
||||
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
||||
}+enter`,
|
||||
} + enter`,
|
||||
FocusTheSearchBar: 's',
|
||||
ShowAllFilters: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+/`,
|
||||
ShowAllFilters: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'} + /`,
|
||||
};
|
||||
|
||||
export const LogsExplorerShortcutsDescription = {
|
||||
|
||||
@@ -56,8 +56,6 @@ describe('API Monitoring Utils', () => {
|
||||
const groupBy = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
key: 'http.method',
|
||||
type: '',
|
||||
@@ -72,8 +70,6 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'test-filter',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'test-key',
|
||||
type: '',
|
||||
},
|
||||
@@ -144,8 +140,6 @@ describe('API Monitoring Utils', () => {
|
||||
[
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'custom.field',
|
||||
type: '',
|
||||
},
|
||||
@@ -180,24 +174,18 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'group-by-1',
|
||||
key: 'http.method',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
type: '',
|
||||
},
|
||||
{
|
||||
id: 'group-by-2',
|
||||
key: 'http.status_code',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
type: '',
|
||||
},
|
||||
{
|
||||
id: 'group-by-3',
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
type: 'tag',
|
||||
},
|
||||
];
|
||||
@@ -258,8 +246,6 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'group-by-1',
|
||||
key: 'http.method',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
type: '',
|
||||
},
|
||||
];
|
||||
@@ -281,7 +267,6 @@ describe('API Monitoring Utils', () => {
|
||||
expect(knownField).toBeDefined();
|
||||
if (knownField && knownField.key) {
|
||||
expect(knownField.key.dataType).toBe(DataTypes.String);
|
||||
expect(knownField.key.isColumn).toBe(true);
|
||||
}
|
||||
|
||||
// Should include the unknown field
|
||||
@@ -430,8 +415,6 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'test-filter',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'test-key',
|
||||
type: '',
|
||||
},
|
||||
@@ -543,8 +526,6 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'test-filter',
|
||||
key: {
|
||||
dataType: 'string',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'test.key',
|
||||
type: '',
|
||||
},
|
||||
@@ -1401,8 +1382,6 @@ describe('API Monitoring Utils', () => {
|
||||
id: 'custom-filter',
|
||||
key: {
|
||||
dataType: 'string',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'custom.key',
|
||||
type: '',
|
||||
},
|
||||
|
||||
@@ -32,8 +32,6 @@ import { SPAN_ATTRIBUTES } from './constants';
|
||||
|
||||
const httpUrlKey = {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
type: 'tag',
|
||||
};
|
||||
|
||||
@@ -58,8 +58,6 @@ function TopErrors({
|
||||
id: '92b8a1c1',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
type: 'tag',
|
||||
},
|
||||
|
||||
@@ -99,8 +99,6 @@ function DomainList(): JSX.Element {
|
||||
key: 'kind_string',
|
||||
dataType: 'string',
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
|
||||
@@ -123,8 +123,6 @@ describe('AllEndPoints', () => {
|
||||
{
|
||||
key: 'http.status_code',
|
||||
dataType: 'string',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
type: '',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -76,8 +76,6 @@ jest.mock(
|
||||
key: 'test.key',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'test-value',
|
||||
@@ -335,8 +333,6 @@ describe('EndPointDetails Component', () => {
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'test-service',
|
||||
|
||||
@@ -172,8 +172,6 @@ describe('StatusCodeBarCharts', () => {
|
||||
key: {
|
||||
dataType: 'string',
|
||||
id: 'response_status_code--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -185,8 +183,6 @@ describe('StatusCodeBarCharts', () => {
|
||||
key: {
|
||||
dataType: 'string',
|
||||
id: 'response_status_code--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: 'tag',
|
||||
},
|
||||
|
||||
@@ -43,8 +43,6 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
@@ -56,8 +54,6 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
@@ -69,8 +65,6 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
key: 'rpc.method',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
@@ -255,22 +249,16 @@ export const hardcodedAttributeKeys: BaseAutocompleteData[] = [
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
{
|
||||
key: 'service.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
{
|
||||
key: 'rpc.method',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -352,8 +340,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
aggregateOperator: 'count',
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -366,8 +352,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
id: '4c57937c',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -394,8 +378,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
aggregateOperator: 'p99',
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -408,8 +390,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
id: '2cf675cd',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -437,7 +417,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -450,8 +429,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
id: '3db0f605',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -462,8 +439,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
id: '6096f745',
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
@@ -491,7 +466,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'timestamp------false',
|
||||
isColumn: false,
|
||||
key: 'timestamp',
|
||||
type: '',
|
||||
},
|
||||
@@ -504,8 +478,6 @@ export const getDomainMetricsQueryPayload = (
|
||||
id: '8ff8dea1',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -639,8 +611,6 @@ export const getEndPointsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -654,8 +624,6 @@ export const getEndPointsQueryPayload = (
|
||||
id: 'ec316e57',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -668,8 +636,6 @@ export const getEndPointsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -692,8 +658,6 @@ export const getEndPointsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -707,8 +671,6 @@ export const getEndPointsQueryPayload = (
|
||||
id: '46d57857',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -721,8 +683,6 @@ export const getEndPointsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -746,7 +706,6 @@ export const getEndPointsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'timestamp------false',
|
||||
isColumn: false,
|
||||
key: 'timestamp',
|
||||
type: '',
|
||||
},
|
||||
@@ -760,8 +719,6 @@ export const getEndPointsQueryPayload = (
|
||||
id: '4a237616',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -774,8 +731,6 @@ export const getEndPointsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -798,8 +753,6 @@ export const getEndPointsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -813,8 +766,6 @@ export const getEndPointsQueryPayload = (
|
||||
id: 'f162de1e',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -825,8 +776,6 @@ export const getEndPointsQueryPayload = (
|
||||
id: '3df0ac1d',
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
@@ -839,8 +788,6 @@ export const getEndPointsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -919,9 +866,7 @@ export const getTopErrorsQueryPayload = (
|
||||
id: '------false',
|
||||
dataType: DataTypes.String,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
@@ -935,8 +880,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -947,8 +890,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
@@ -961,8 +902,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
@@ -975,8 +914,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: domainName,
|
||||
@@ -987,8 +924,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: 'has_error',
|
||||
dataType: DataTypes.bool,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: true,
|
||||
@@ -1012,13 +947,9 @@ export const getTopErrorsQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
id: 'response_status_code--string----true',
|
||||
@@ -1027,8 +958,6 @@ export const getTopErrorsQueryPayload = (
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
],
|
||||
legend: '',
|
||||
@@ -1404,8 +1333,6 @@ export const getTopErrorsCoRelationQueryFilters = (
|
||||
key: 'http.url',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'http.url--string--tag--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -1417,8 +1344,6 @@ export const getTopErrorsCoRelationQueryFilters = (
|
||||
key: 'has_error',
|
||||
dataType: DataTypes.bool,
|
||||
type: '',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'true',
|
||||
@@ -1429,8 +1354,6 @@ export const getTopErrorsCoRelationQueryFilters = (
|
||||
key: 'net.peer.name',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: domainName,
|
||||
@@ -1441,8 +1364,6 @@ export const getTopErrorsCoRelationQueryFilters = (
|
||||
key: 'response_status_code',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'response_status_code--string----true',
|
||||
},
|
||||
op: '=',
|
||||
@@ -1551,8 +1472,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -1566,8 +1485,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '874562e1',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1580,8 +1497,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1605,8 +1520,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -1620,8 +1533,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '0c5564e0',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1634,8 +1545,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1659,8 +1568,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -1674,8 +1581,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '0d656701',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1686,8 +1591,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '83ef9a1b',
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
@@ -1700,8 +1603,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1726,7 +1627,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'timestamp------false',
|
||||
isColumn: false,
|
||||
key: 'timestamp',
|
||||
type: '',
|
||||
},
|
||||
@@ -1740,8 +1640,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '918f5b99',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1754,8 +1652,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1779,8 +1675,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -1794,8 +1688,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'b355d1aa',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1808,8 +1700,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1874,8 +1764,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -1889,8 +1777,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '23450eb8',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1903,8 +1789,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1917,8 +1801,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
},
|
||||
@@ -1936,8 +1818,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -1951,8 +1831,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '2687dc18',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1965,8 +1843,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -1979,8 +1855,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
},
|
||||
@@ -2002,7 +1876,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -2016,8 +1889,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'net.peer.name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'net.peer.name',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2030,8 +1901,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2049,8 +1918,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
id: 'response_status_code--string----true',
|
||||
@@ -2100,7 +1967,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
},
|
||||
timeAggregation: 'count',
|
||||
@@ -2114,8 +1980,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: domainName,
|
||||
@@ -2126,8 +1990,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2148,8 +2010,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
],
|
||||
reduceTo: 'avg',
|
||||
@@ -2191,8 +2051,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -2206,8 +2064,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'b78ff216',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2220,8 +2076,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2235,8 +2089,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'service.name--string--resource--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'service.name',
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -2254,8 +2106,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -2269,8 +2119,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'a9024472',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2283,8 +2131,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2298,8 +2144,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'service.name--string--resource--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'service.name',
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -2318,7 +2162,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -2332,8 +2175,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: '1b6c062d',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2346,8 +2187,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2361,8 +2200,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'service.name--string--resource--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'service.name',
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -2380,8 +2217,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -2395,8 +2230,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'd14792a8',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2406,9 +2239,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
id: '3212bf1a',
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
@@ -2421,8 +2251,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2436,8 +2264,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'service.name--string--resource--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'service.name',
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -2497,7 +2323,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -2511,8 +2336,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'c6724407',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2525,8 +2348,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2539,8 +2360,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
},
|
||||
@@ -2592,8 +2411,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -2607,8 +2424,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
id: 'aae93366',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -2621,8 +2436,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2635,8 +2448,6 @@ export const getEndPointDetailsQueryPayload = (
|
||||
groupBy: [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: '',
|
||||
},
|
||||
@@ -2699,7 +2510,6 @@ export const getEndPointZeroStateQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
},
|
||||
timeAggregation: 'count',
|
||||
@@ -2713,8 +2523,6 @@ export const getEndPointZeroStateQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: domainName,
|
||||
@@ -2725,8 +2533,6 @@ export const getEndPointZeroStateQueryPayload = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -2746,8 +2552,6 @@ export const getEndPointZeroStateQueryPayload = (
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
],
|
||||
reduceTo: 'avg',
|
||||
@@ -3280,7 +3084,6 @@ export const getStatusCodeBarChartWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -3294,8 +3097,6 @@ export const getStatusCodeBarChartWidgetData = (
|
||||
id: 'c6724407',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3308,8 +3109,6 @@ export const getStatusCodeBarChartWidgetData = (
|
||||
id: '8b1be6f0',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.URL_PATH,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3427,8 +3226,6 @@ export const getAllEndpointsWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -3442,8 +3239,6 @@ export const getAllEndpointsWidgetData = (
|
||||
id: 'ec316e57',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3456,8 +3251,6 @@ export const getAllEndpointsWidgetData = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -3483,8 +3276,6 @@ export const getAllEndpointsWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -3498,8 +3289,6 @@ export const getAllEndpointsWidgetData = (
|
||||
id: '46d57857',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3512,8 +3301,6 @@ export const getAllEndpointsWidgetData = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -3540,7 +3327,6 @@ export const getAllEndpointsWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'timestamp------false',
|
||||
isColumn: false,
|
||||
key: 'timestamp',
|
||||
type: '',
|
||||
},
|
||||
@@ -3554,8 +3340,6 @@ export const getAllEndpointsWidgetData = (
|
||||
id: '4a237616',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3568,8 +3352,6 @@ export const getAllEndpointsWidgetData = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -3595,8 +3377,6 @@ export const getAllEndpointsWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
@@ -3610,8 +3390,6 @@ export const getAllEndpointsWidgetData = (
|
||||
id: 'f162de1e',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3622,13 +3400,11 @@ export const getAllEndpointsWidgetData = (
|
||||
id: '3df0ac1d',
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
op: '=',
|
||||
value: 'true',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
id: '212678b9',
|
||||
@@ -3636,8 +3412,6 @@ export const getAllEndpointsWidgetData = (
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
@@ -3773,8 +3547,6 @@ export const getGroupByFiltersFromGroupByValues = (
|
||||
id: groupByAttribute?.id || v4(),
|
||||
key: {
|
||||
dataType: groupByAttribute?.dataType || DataTypes.String,
|
||||
isColumn: groupByAttribute?.isColumn || true,
|
||||
isJSON: groupByAttribute?.isJSON || false,
|
||||
key: groupByAttribute?.key || key,
|
||||
type: groupByAttribute?.type || '',
|
||||
},
|
||||
@@ -3810,7 +3582,6 @@ export const getRateOverTimeWidgetData = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
@@ -3824,8 +3595,6 @@ export const getRateOverTimeWidgetData = (
|
||||
id: '3c76fe0b',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -3874,8 +3643,6 @@ export const getLatencyOverTimeWidgetData = (
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
@@ -3889,8 +3656,6 @@ export const getLatencyOverTimeWidgetData = (
|
||||
id: '63adb3ff',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: SPAN_ATTRIBUTES.SERVER_NAME,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -4000,8 +3765,6 @@ export const getCustomFiltersForBarChart = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'response_status_code--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -4013,8 +3776,6 @@ export const getCustomFiltersForBarChart = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'response_status_code--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'response_status_code',
|
||||
type: 'tag',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { GlobalShortcuts } from 'constants/shortcuts/globalShortcuts';
|
||||
import { USER_PREFERENCES } from 'constants/userPreferences';
|
||||
import {
|
||||
KeyboardHotkeysProvider,
|
||||
useKeyboardHotkeys,
|
||||
} from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('api/common/logEvent', () => jest.fn());
|
||||
|
||||
// Mock the AppContext
|
||||
const mockUpdateUserPreferenceInContext = jest.fn();
|
||||
|
||||
const SHIFT_B_KEYBOARD_SHORTCUT = '{Shift>}b{/Shift}';
|
||||
|
||||
jest.mock('providers/App/App', () => ({
|
||||
useAppContext: jest.fn(() => ({
|
||||
userPreferences: [
|
||||
{
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
updateUserPreferenceInContext: mockUpdateUserPreferenceInContext,
|
||||
})),
|
||||
}));
|
||||
|
||||
function TestComponent({
|
||||
mockHandleShortcut,
|
||||
}: {
|
||||
mockHandleShortcut: () => void;
|
||||
}): JSX.Element {
|
||||
const { registerShortcut } = useKeyboardHotkeys();
|
||||
registerShortcut(GlobalShortcuts.ToggleSidebar, mockHandleShortcut);
|
||||
return <div data-testid="test">Test</div>;
|
||||
}
|
||||
|
||||
describe('Sidebar Toggle Shortcut', () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Global Shortcuts Constants', () => {
|
||||
it('should have the correct shortcut key combination', () => {
|
||||
expect(GlobalShortcuts.ToggleSidebar).toBe('b+shift');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Keyboard Shortcut Registration', () => {
|
||||
it('should register the sidebar toggle shortcut correctly', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockHandleShortcut = jest.fn();
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KeyboardHotkeysProvider>
|
||||
<TestComponent mockHandleShortcut={mockHandleShortcut} />
|
||||
</KeyboardHotkeysProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
// Trigger the shortcut
|
||||
await user.keyboard(SHIFT_B_KEYBOARD_SHORTCUT);
|
||||
|
||||
expect(mockHandleShortcut).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not trigger shortcut in input fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockHandleShortcut = jest.fn();
|
||||
|
||||
function TestComponent(): JSX.Element {
|
||||
const { registerShortcut } = useKeyboardHotkeys();
|
||||
registerShortcut(GlobalShortcuts.ToggleSidebar, mockHandleShortcut);
|
||||
return (
|
||||
<div>
|
||||
<input data-testid="input-field" />
|
||||
<div data-testid="test">Test</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KeyboardHotkeysProvider>
|
||||
<TestComponent />
|
||||
</KeyboardHotkeysProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
// Focus on input field
|
||||
const inputField = screen.getByTestId('input-field');
|
||||
await user.click(inputField);
|
||||
|
||||
// Try to trigger shortcut while focused on input
|
||||
await user.keyboard('{Shift>}b{/Shift}');
|
||||
|
||||
// Should not trigger the shortcut
|
||||
expect(mockHandleShortcut).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sidebar Toggle Functionality', () => {
|
||||
it('should log the toggle event with correct parameters', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockHandleShortcut = jest.fn(() => {
|
||||
logEvent('Global Shortcut: Sidebar Toggle', {
|
||||
previousState: false,
|
||||
newState: true,
|
||||
});
|
||||
});
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KeyboardHotkeysProvider>
|
||||
<TestComponent mockHandleShortcut={mockHandleShortcut} />
|
||||
</KeyboardHotkeysProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await user.keyboard(SHIFT_B_KEYBOARD_SHORTCUT);
|
||||
|
||||
expect(logEvent).toHaveBeenCalledWith('Global Shortcut: Sidebar Toggle', {
|
||||
previousState: false,
|
||||
newState: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update user preference in context', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockHandleShortcut = jest.fn(() => {
|
||||
const save = {
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: true,
|
||||
};
|
||||
mockUpdateUserPreferenceInContext(save);
|
||||
});
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KeyboardHotkeysProvider>
|
||||
<TestComponent mockHandleShortcut={mockHandleShortcut} />
|
||||
</KeyboardHotkeysProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
await user.keyboard(SHIFT_B_KEYBOARD_SHORTCUT);
|
||||
|
||||
expect(mockUpdateUserPreferenceInContext).toHaveBeenCalledWith({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,14 +4,17 @@
|
||||
import './AppLayout.styles.scss';
|
||||
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Toaster } from '@signozhq/sonner';
|
||||
import { Flex } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import getChangelogByVersion from 'api/changelog/getChangelogByVersion';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import manageCreditCardApi from 'api/v1/portal/create';
|
||||
import updateUserPreference from 'api/v1/user/preferences/name/update';
|
||||
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
|
||||
import getUserVersion from 'api/v1/version/getVersion';
|
||||
import { AxiosError } from 'axios';
|
||||
import cx from 'classnames';
|
||||
import ChangelogModal from 'components/ChangelogModal/ChangelogModal';
|
||||
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
|
||||
@@ -22,10 +25,12 @@ import { Events } from 'constants/events';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { GlobalShortcuts } from 'constants/shortcuts/globalShortcuts';
|
||||
import { USER_PREFERENCES } from 'constants/userPreferences';
|
||||
import SideNav from 'container/SideNav';
|
||||
import TopNav from 'container/TopNav';
|
||||
import dayjs from 'dayjs';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@@ -68,8 +73,10 @@ import {
|
||||
LicensePlatform,
|
||||
LicenseState,
|
||||
} from 'types/api/licensesV3/getActive';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import {
|
||||
getFormattedDate,
|
||||
@@ -662,10 +669,85 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
</div>
|
||||
);
|
||||
|
||||
const sideNavPinned = userPreferences?.find(
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
const { updateUserPreferenceInContext } = useAppContext();
|
||||
|
||||
const { mutate: updateUserPreferenceMutation } = useMutation(
|
||||
updateUserPreference,
|
||||
{
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const sideNavPinnedPreference = userPreferences?.find(
|
||||
(preference) => preference.name === USER_PREFERENCES.SIDENAV_PINNED,
|
||||
)?.value as boolean;
|
||||
|
||||
// Add loading state to prevent layout shift during initial load
|
||||
const [isSidebarLoaded, setIsSidebarLoaded] = useState(false);
|
||||
|
||||
// Get sidebar state from localStorage as fallback until preferences are loaded
|
||||
const getSidebarStateFromLocalStorage = useCallback((): boolean => {
|
||||
try {
|
||||
const storedValue = getLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED);
|
||||
return storedValue === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Set sidebar as loaded after user preferences are fetched
|
||||
useEffect(() => {
|
||||
if (userPreferences !== null) {
|
||||
setIsSidebarLoaded(true);
|
||||
}
|
||||
}, [userPreferences]);
|
||||
|
||||
// Use localStorage value as fallback until preferences are loaded
|
||||
const isSideNavPinned = isSidebarLoaded
|
||||
? sideNavPinnedPreference
|
||||
: getSidebarStateFromLocalStorage();
|
||||
|
||||
const handleToggleSidebar = useCallback((): void => {
|
||||
const newState = !isSideNavPinned;
|
||||
|
||||
logEvent('Global Shortcut: Sidebar Toggle', {
|
||||
previousState: isSideNavPinned,
|
||||
newState,
|
||||
});
|
||||
|
||||
// Save to localStorage immediately for instant feedback
|
||||
setLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED, newState.toString());
|
||||
|
||||
// Update the context immediately
|
||||
const save = {
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: newState,
|
||||
};
|
||||
updateUserPreferenceInContext(save as UserPreference);
|
||||
|
||||
// Make the API call in the background
|
||||
updateUserPreferenceMutation({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: newState,
|
||||
});
|
||||
}, [
|
||||
isSideNavPinned,
|
||||
updateUserPreferenceInContext,
|
||||
updateUserPreferenceMutation,
|
||||
]);
|
||||
|
||||
// Register the sidebar toggle shortcut
|
||||
useEffect(() => {
|
||||
registerShortcut(GlobalShortcuts.ToggleSidebar, handleToggleSidebar);
|
||||
|
||||
return (): void => {
|
||||
deregisterShortcut(GlobalShortcuts.ToggleSidebar);
|
||||
};
|
||||
}, [registerShortcut, deregisterShortcut, handleToggleSidebar]);
|
||||
|
||||
const SHOW_TRIAL_EXPIRY_BANNER =
|
||||
showTrialExpiryBanner && !showPaymentFailedWarning;
|
||||
const SHOW_WORKSPACE_RESTRICTED_BANNER = showWorkspaceRestricted;
|
||||
@@ -739,14 +821,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
className={cx(
|
||||
'app-layout',
|
||||
isDarkMode ? 'darkMode dark' : 'lightMode',
|
||||
sideNavPinned ? 'side-nav-pinned' : '',
|
||||
isSideNavPinned ? 'side-nav-pinned' : '',
|
||||
SHOW_WORKSPACE_RESTRICTED_BANNER ? 'isWorkspaceRestricted' : '',
|
||||
SHOW_TRIAL_EXPIRY_BANNER ? 'isTrialExpired' : '',
|
||||
SHOW_PAYMENT_FAILED_BANNER ? 'isPaymentFailed' : '',
|
||||
)}
|
||||
>
|
||||
{isToDisplayLayout && !renderFullScreen && (
|
||||
<SideNav isPinned={sideNavPinned} />
|
||||
<SideNav isPinned={isSideNavPinned} />
|
||||
)}
|
||||
<div
|
||||
className={cx('app-content', {
|
||||
@@ -771,6 +853,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
{showChangelogModal && changelog && (
|
||||
<ChangelogModal changelog={changelog} onClose={toggleChangelogModal} />
|
||||
)}
|
||||
|
||||
<Toaster />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.explorer-options-container {
|
||||
position: fixed;
|
||||
bottom: 8px;
|
||||
bottom: 0px;
|
||||
left: calc(50% + 240px);
|
||||
transform: translate(calc(-50% - 120px), 0);
|
||||
transition: left 0.2s linear;
|
||||
@@ -74,6 +74,7 @@
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
z-index: 1;
|
||||
|
||||
.ant-select-selector {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
.explorer-show-btn {
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
border-radius: 6px 6px 0px 0px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: rgba(22, 24, 29, 0.4);
|
||||
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
|
||||
|
||||
@@ -34,7 +34,7 @@ function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
|
||||
const keysOptions = createOptions(data?.payload?.attributeKeys || []);
|
||||
|
||||
const customOptions = createOptions([
|
||||
{ key: 'timestamp', isColumn: true, type: '', dataType: DataTypes.EMPTY },
|
||||
{ key: 'timestamp', type: '', dataType: DataTypes.EMPTY },
|
||||
]);
|
||||
|
||||
const baseOptions = [
|
||||
|
||||
@@ -28,6 +28,7 @@ import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@@ -141,7 +142,6 @@ function ChartPreview({
|
||||
return false;
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
const queryResponse = useGetQueryRange(
|
||||
{
|
||||
query: query || initialQueriesMap.metrics,
|
||||
@@ -313,7 +313,7 @@ function ChartPreview({
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.ANOMALY_DETECTION)
|
||||
?.active || false;
|
||||
|
||||
const isWarning = !!queryResponse.data?.warning?.message;
|
||||
const isWarning = !isEmpty(queryResponse.data?.warning);
|
||||
return (
|
||||
<div className="alert-chart-container" ref={graphRef}>
|
||||
<ChartContainer>
|
||||
|
||||
@@ -240,8 +240,18 @@ function FormAlertRules({
|
||||
const queryData = currentQuery.builder.queryData[index];
|
||||
|
||||
const updatedFunctions = updateFunctions(queryData);
|
||||
queryData.functions = updatedFunctions;
|
||||
handleSetQueryData(index, queryData);
|
||||
|
||||
// Only update if functions actually changed to avoid resetting aggregateAttribute
|
||||
const currentFunctions = queryData.functions || [];
|
||||
const functionsChanged = !isEqual(currentFunctions, updatedFunctions);
|
||||
|
||||
if (functionsChanged) {
|
||||
const updatedQueryData = {
|
||||
...queryData,
|
||||
functions: updatedFunctions,
|
||||
};
|
||||
handleSetQueryData(index, updatedQueryData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -854,7 +864,7 @@ function FormAlertRules({
|
||||
queryCategory={currentQuery.queryType}
|
||||
setQueryCategory={onQueryCategoryChange}
|
||||
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
|
||||
runQuery={(): void => handleRunQuery(true, true)}
|
||||
runQuery={(): void => handleRunQuery()}
|
||||
alertDef={alertDef}
|
||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||
key={currentQuery.queryType}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const flattenLabels = (labels: Labels): ILabelRecord[] => {
|
||||
if (!hiddenLabels.includes(key)) {
|
||||
recs.push({
|
||||
key,
|
||||
value: labels[key],
|
||||
value: labels[key] || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -65,8 +65,6 @@ const mockProps: WidgetGraphComponentProps = {
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
|
||||
@@ -301,7 +301,7 @@ function GridCardGraph({
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
isWarning={!!queryResponse.data?.warning?.message}
|
||||
isWarning={!isEmpty(queryResponse.data?.warning)}
|
||||
version={version}
|
||||
threshold={threshold}
|
||||
headerMenuList={menuList}
|
||||
|
||||
@@ -2,16 +2,16 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { getStepIntervalPoints, updateStepInterval } from '../utils';
|
||||
import { getBarStepIntervalPoints, updateBarStepInterval } from '../utils';
|
||||
|
||||
describe('GridCardLayout Utils', () => {
|
||||
describe('getStepIntervalPoints', () => {
|
||||
describe('getBarStepIntervalPoints', () => {
|
||||
it('should return 60 points for duration <= 1 hour', () => {
|
||||
// 30 minutes in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 30 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should return 60 points for exactly 1 hour', () => {
|
||||
@@ -19,7 +19,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should return 120 points for duration <= 3 hours', () => {
|
||||
@@ -27,7 +27,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 2 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(120);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(120);
|
||||
});
|
||||
|
||||
it('should return 120 points for exactly 3 hours', () => {
|
||||
@@ -35,7 +35,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 3 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(120);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(120);
|
||||
});
|
||||
|
||||
it('should return 180 points for duration <= 5 hours', () => {
|
||||
@@ -43,7 +43,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 4 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(180);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(180);
|
||||
});
|
||||
|
||||
it('should return 180 points for exactly 5 hours', () => {
|
||||
@@ -51,7 +51,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 5 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(180);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(180);
|
||||
});
|
||||
|
||||
it('should calculate dynamic interval for duration > 5 hours', () => {
|
||||
@@ -59,7 +59,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 10 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
const result = getBarStepIntervalPoints(start, end);
|
||||
|
||||
// For 10 hours (600 minutes), interval should be ceil(600/80) = 8, rounded to 10, then * 60 = 600
|
||||
expect(result).toBe(600);
|
||||
@@ -70,7 +70,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
const result = getBarStepIntervalPoints(start, end);
|
||||
|
||||
// For 7 days (10080 minutes), interval should be ceil(10080/80) = 126, rounded to 130, then * 60 = 7800
|
||||
expect(result).toBe(7800);
|
||||
@@ -81,7 +81,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 12 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
const result = getBarStepIntervalPoints(start, end);
|
||||
|
||||
// For 12 hours (720 minutes), interval should be ceil(720/80) = 9, rounded to 10, then * 60 = 600
|
||||
expect(result).toBe(600);
|
||||
@@ -92,24 +92,24 @@ describe('GridCardLayout Utils', () => {
|
||||
const start = Date.now();
|
||||
const end = start + 1 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle zero duration', () => {
|
||||
const start = Date.now();
|
||||
const end = start;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
expect(getBarStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStepInterval', () => {
|
||||
describe('updateBarStepInterval', () => {
|
||||
const mockQuery: Query = {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
stepInterval: 60,
|
||||
stepInterval: null,
|
||||
aggregateOperator: 'avg',
|
||||
dataSource: DataSource.METRICS,
|
||||
queryName: 'A',
|
||||
@@ -142,7 +142,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 2 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
const result = updateBarStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(120);
|
||||
});
|
||||
@@ -151,7 +151,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
const result = updateBarStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].aggregateOperator).toBe('avg');
|
||||
expect(result.builder.queryData[0].queryName).toBe('A');
|
||||
@@ -177,7 +177,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 4 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(multiQueryMock, minTime, maxTime);
|
||||
const result = updateBarStepInterval(multiQueryMock, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData).toHaveLength(2);
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(180);
|
||||
@@ -201,7 +201,11 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(queryWithUndefinedStep, minTime, maxTime);
|
||||
const result = updateBarStepInterval(
|
||||
queryWithUndefinedStep,
|
||||
minTime,
|
||||
maxTime,
|
||||
);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(60);
|
||||
});
|
||||
@@ -210,7 +214,7 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime; // Same time = 0 duration
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
const result = updateBarStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(60);
|
||||
});
|
||||
@@ -219,10 +223,66 @@ describe('GridCardLayout Utils', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
const result = updateBarStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
// Should calculate appropriate interval for 30 days
|
||||
expect(result.builder.queryData[0].stepInterval).toBeGreaterThan(180);
|
||||
});
|
||||
|
||||
it('should handle stepInterval as 0', () => {
|
||||
const queryWithZeroStep: Query = {
|
||||
...mockQuery,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
...mockQuery.builder.queryData[0],
|
||||
stepInterval: 0,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
};
|
||||
|
||||
const minTime = Date.now();
|
||||
let maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateBarStepInterval(queryWithZeroStep, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(60);
|
||||
|
||||
maxTime = minTime + 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
|
||||
const result1 = updateBarStepInterval(queryWithZeroStep, minTime, maxTime);
|
||||
|
||||
expect(result1.builder.queryData[0].stepInterval).toBe(32400);
|
||||
});
|
||||
|
||||
it('should respect user entered inputs', () => {
|
||||
const queryWithUserStep: Query = {
|
||||
...mockQuery,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
...mockQuery.builder.queryData[0],
|
||||
stepInterval: 120,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
};
|
||||
|
||||
const minTime = Date.now();
|
||||
let maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateBarStepInterval(queryWithUserStep, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(120); // not 60
|
||||
|
||||
maxTime = minTime + 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
|
||||
const result1 = updateBarStepInterval(queryWithUserStep, minTime, maxTime);
|
||||
|
||||
expect(result1.builder.queryData[0].stepInterval).toBe(120); // not 32400
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,10 +53,7 @@ function useUpdatedQuery(): UseUpdatedQueryResult {
|
||||
const queryResult = await queryRangeMutation.mutateAsync(queryPayload);
|
||||
|
||||
// Map query data from API response
|
||||
return mapQueryDataFromApi(
|
||||
queryResult.data.compositeQuery,
|
||||
widgetConfig?.query,
|
||||
);
|
||||
return mapQueryDataFromApi(queryResult.data.compositeQuery);
|
||||
},
|
||||
[globalSelectedInterval, queryRangeMutation],
|
||||
);
|
||||
|
||||
@@ -57,6 +57,8 @@ export const hasColumnWidthsChanged = (
|
||||
* Calculates the step interval in uPlot points (1 minute = 60 points)
|
||||
* based on the time duration between two timestamps in nanoseconds.
|
||||
*
|
||||
* NOTE: This function is specifically designed for BAR visualization panels only.
|
||||
*
|
||||
* Conversion logic:
|
||||
* - <= 1 hr → 1 min (60 points)
|
||||
* - <= 3 hr → 2 min (120 points)
|
||||
@@ -67,7 +69,7 @@ export const hasColumnWidthsChanged = (
|
||||
* @param endNano - end time in nanoseconds
|
||||
* @returns stepInterval in uPlot points
|
||||
*/
|
||||
export function getStepIntervalPoints(
|
||||
export function getBarStepIntervalPoints(
|
||||
startNano: number,
|
||||
endNano: number,
|
||||
): number {
|
||||
@@ -92,18 +94,18 @@ export function getStepIntervalPoints(
|
||||
return roundedInterval * 60; // convert min to points
|
||||
}
|
||||
|
||||
export function updateStepInterval(
|
||||
export function updateBarStepInterval(
|
||||
query: Query,
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
): Query {
|
||||
const stepIntervalPoints = getStepIntervalPoints(minTime, maxTime);
|
||||
const stepIntervalPoints = getBarStepIntervalPoints(minTime, maxTime);
|
||||
|
||||
// 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
|
||||
? stepIntervalPoints || 60
|
||||
: queryData?.stepInterval || 60;
|
||||
const getBarSteps = (queryData: IBuilderQuery): number | null =>
|
||||
!queryData.stepInterval
|
||||
? stepIntervalPoints || null
|
||||
: queryData?.stepInterval;
|
||||
|
||||
return {
|
||||
...query,
|
||||
@@ -112,7 +114,7 @@ export function updateStepInterval(
|
||||
queryData: [
|
||||
...(query?.builder?.queryData ?? []).map((queryData) => ({
|
||||
...queryData,
|
||||
stepInterval: getSteps(queryData),
|
||||
stepInterval: getBarSteps(queryData),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -105,8 +105,6 @@ export const widgetQueryWithLegend = {
|
||||
aggregateAttribute: {
|
||||
dataType: 'float64',
|
||||
id: 'signoz_latency--float64--ExponentialHistogram--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'signoz_latency',
|
||||
type: 'ExponentialHistogram',
|
||||
},
|
||||
@@ -126,8 +124,6 @@ export const widgetQueryWithLegend = {
|
||||
groupBy: [
|
||||
{
|
||||
dataType: 'string',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'service_name',
|
||||
type: 'tag',
|
||||
id: 'service_name--string--tag--false',
|
||||
@@ -143,8 +139,6 @@ export const widgetQueryWithLegend = {
|
||||
aggregateAttribute: {
|
||||
dataType: 'float64',
|
||||
id: 'system_disk_operations--float64--Sum--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'system_disk_operations',
|
||||
type: 'Sum',
|
||||
},
|
||||
|
||||
@@ -220,18 +220,10 @@ function ServiceMetrics({
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
minTime: timeRange.startTime * 1e6,
|
||||
maxTime: timeRange.endTime * 1e6,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[
|
||||
globalSelectedInterval,
|
||||
timeRange.endTime,
|
||||
timeRange.startTime,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const dataQueries = useGetQueriesRange(
|
||||
|
||||
@@ -182,8 +182,6 @@ export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
key: 'host_name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
@@ -197,8 +195,6 @@ export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
key: 'os_type',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system_cpu_load_average_15m',
|
||||
@@ -230,8 +226,6 @@ export function GetHostsQuickFiltersConfig(
|
||||
key: hostNameKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
@@ -245,8 +239,6 @@ export function GetHostsQuickFiltersConfig(
|
||||
key: osTypeKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
@@ -260,8 +252,6 @@ export function GetHostsQuickFiltersConfig(
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
|
||||
@@ -122,8 +122,6 @@ function ClusterDetails({
|
||||
key: QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s_cluster_name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -150,8 +148,6 @@ function ClusterDetails({
|
||||
key: QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s.object.kind--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -163,8 +159,6 @@ function ClusterDetails({
|
||||
key: QUERY_KEYS.K8S_OBJECT_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s.object.name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -520,12 +514,6 @@ function ClusterDetails({
|
||||
>
|
||||
Cluster Name
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
className="entity-details-metadata-label"
|
||||
>
|
||||
Cluster Name
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="values-row">
|
||||
<Typography.Text className="entity-details-metadata-value">
|
||||
@@ -533,9 +521,6 @@ function ClusterDetails({
|
||||
{cluster.meta.k8s_cluster_name}
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
<Typography.Text className="entity-details-metadata-value">
|
||||
<Tooltip title="Cluster name">{cluster.meta.k8s_cluster_name}</Tooltip>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -147,8 +147,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodCpuUtilizationKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -163,8 +161,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -190,8 +186,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodCpuUtilizationKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -206,8 +200,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -233,8 +225,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodCpuUtilizationKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -249,8 +239,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -276,8 +264,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_node_allocatable_cpu--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sNodeAllocatableCpuKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -292,8 +278,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -352,8 +336,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_memory_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodMemoryUsageKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -368,8 +350,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -395,8 +375,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_memory_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodMemoryUsageKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -411,8 +389,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -438,8 +414,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_pod_memory_usage--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sPodMemoryUsageKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -454,8 +428,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -481,8 +453,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_node_allocatable_memory--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sNodeAllocatableMemoryKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -497,8 +467,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -557,8 +525,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_node_condition_ready--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sNodeConditionReadyKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -573,8 +539,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -589,8 +553,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_node_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNodeNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -648,8 +610,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_node_condition_ready--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sNodeConditionReadyKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -664,8 +624,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -680,8 +638,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_node_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNodeNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -739,8 +695,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_deployment_available--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sDeploymentAvailableKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -755,8 +709,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -771,16 +723,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_deployment_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sDeploymentNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -799,8 +747,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_deployment_desired--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sDeploymentDesiredKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -815,8 +761,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -831,16 +775,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_deployment_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sDeploymentNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -904,8 +844,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_statefulset_current_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetCurrentPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -920,8 +858,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -936,16 +872,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_statefulset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -964,8 +896,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_statefulset_desired_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetDesiredPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -980,8 +910,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -996,16 +924,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_statefulset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1024,8 +948,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_statefulset_ready_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetReadyPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1040,8 +962,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1056,16 +976,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_statefulset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1084,8 +1000,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_statefulset_updated_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetUpdatedPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1100,8 +1014,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1116,16 +1028,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_statefulset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sStatefulsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_namespace_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sNamespaceNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1213,8 +1121,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_daemonset_current_scheduled_nodes--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetCurrentScheduledNodesKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1229,8 +1135,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1245,8 +1149,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_daemonset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1265,8 +1167,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_daemonset_desired_scheduled_nodes--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetDesiredScheduledNodesKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1281,8 +1181,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1297,8 +1195,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_daemonset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1317,8 +1213,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_daemonset_ready_nodes--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetReadyNodesKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1333,8 +1227,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1349,8 +1241,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_daemonset_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sDaemonsetNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1426,8 +1316,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_job_active_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sJobActivePodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1442,8 +1330,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1458,16 +1344,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
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',
|
||||
},
|
||||
@@ -1486,8 +1368,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_job_successful_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sJobSuccessfulPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1502,8 +1382,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1518,16 +1396,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
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',
|
||||
},
|
||||
@@ -1546,8 +1420,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_job_failed_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sJobFailedPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1562,8 +1434,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1578,16 +1448,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
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',
|
||||
},
|
||||
@@ -1606,8 +1472,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'k8s_job_desired_successful_pods--float64--Gauge--true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: k8sJobDesiredSuccessfulPodsKey,
|
||||
type: 'Gauge',
|
||||
},
|
||||
@@ -1622,8 +1486,6 @@ export const getClusterMetricsQueryPayload = (
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'k8s_cluster_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: k8sClusterNameKey,
|
||||
type: 'tag',
|
||||
},
|
||||
@@ -1638,16 +1500,12 @@ export const getClusterMetricsQueryPayload = (
|
||||
{
|
||||
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',
|
||||
},
|
||||
|
||||
@@ -121,8 +121,6 @@ function DaemonSetDetails({
|
||||
key: QUERY_KEYS.K8S_DAEMON_SET_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s_daemonSet_name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -134,8 +132,6 @@ function DaemonSetDetails({
|
||||
key: QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s_daemonSet_name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -166,8 +162,6 @@ function DaemonSetDetails({
|
||||
key: QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s.object.kind--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
@@ -179,8 +173,6 @@ function DaemonSetDetails({
|
||||
key: QUERY_KEYS.K8S_OBJECT_NAME,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'k8s.object.name--string--resource--false',
|
||||
},
|
||||
op: '=',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user