diff --git a/.github/workflows/e2e-k3s.yaml b/.github/workflows/e2e-k3s.yaml
index 6b14a9c975..da3db01917 100644
--- a/.github/workflows/e2e-k3s.yaml
+++ b/.github/workflows/e2e-k3s.yaml
@@ -16,6 +16,8 @@ jobs:
uses: actions/checkout@v2
- name: Build query-service image
+ env:
+ DEV_BUILD: 1
run: make build-ee-query-service-amd64
- name: Build frontend image
diff --git a/Makefile b/Makefile
index 17a4b32fb6..c46effa9a9 100644
--- a/Makefile
+++ b/Makefile
@@ -120,14 +120,10 @@ down-local:
down -v
run-x86:
- @docker-compose -f \
- $(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-prod.yaml \
- up --build -d
+ @docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml up --build -d
down-x86:
- @docker-compose -f \
- $(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-prod.yaml \
- down -v
+ @docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml down -v
clear-standalone-data:
@docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \
diff --git a/README.md b/README.md
index 6b45706c44..d00adedcc8 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,12 @@ We support [OpenTelemetry](https://opentelemetry.io) as the library which you ca
- Python
- NodeJS
- Go
+- PHP
+- .NET
+- Ruby
+- Elixir
+- Rust
+
You can find the complete list of languages here - https://opentelemetry.io/docs/
diff --git a/README.zh-cn.md b/README.zh-cn.md
index d584d10f4e..3658eeb520 100644
--- a/README.zh-cn.md
+++ b/README.zh-cn.md
@@ -13,14 +13,19 @@
##
-SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式跟踪来增加软件技术栈的可见性。
+SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式追踪来增加软件技术栈的可见性。
-👉 你能看到一些性能矩阵,服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。
+👉 你能看到一些性能指标,服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。
-👉 通过准确的跟踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因。
+👉 通过准确的追踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因。
+👉 聚合trace数据来获得业务相关指标。
-
+
+
+
+
+
@@ -36,12 +41,12 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
## 功能:
-- 应用总览矩阵(matrix),如RPS, 50/90/99百分比延迟率,错误率
+- 应用概览指标(metrics),如RPS, p50/p90/p99延迟率分位值,错误率等。
- 应用中最慢的终端(endpoint)
-- 查看准确的网络请求跟踪来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题
-- 通过服务名称、操作、延迟、错误、标签来过滤跟踪
-- 对过滤后的跟踪数据做矩阵聚合。比如,获得过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal`的错误率和p99延迟
-- 整合的矩阵和跟踪用户界面。不需要像从Prometheus切换到Jaeger才能调试问题
+- 查看特定请求的trace数据来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题
+- 通过服务名称、操作、延迟、错误、标签来过滤traces。
+- 聚合trace数据(events/spans)来得到业务相关指标。比如,你可以通过过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal` 来获取指定业务的错误率和p99延迟
+- 为metrics和trace提供统一的UI。排查问题不需要在Prometheus和Jaeger之间切换。
@@ -53,7 +58,7 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
我们想做一个自服务的开源版本的工具,类似于DataDog和NewRelic,用于那些对客户数据流入第三方有隐私和安全担忧的厂商。
-开源也让你对配置、采样和上线率有完整的控制,你可以在SigNoz基础上构建模块来满足特定的商业需求。
+开源也让你对配置、采样和正常运行时间有完整的控制,你可以在SigNoz基础上构建模块来满足特定的商业需求。
### 语言支持
@@ -71,8 +76,8 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
## 入门
-
-
+
+
### 使用Docker部署
请按照[这里](https://signoz.io/docs/deployment/docker/)列出的步骤使用Docker来安装
@@ -80,35 +85,34 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/deployment/troubleshooting)会对你有帮助。
 
-
-
+
+
### 使用Helm在Kubernetes上部署
请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装
-
-## Comparisons to Familiar Tools
+## 与其他方案的比较
### SigNoz vs Prometheus
-如果你只是需要矩阵,那Prometheus是不错的,但如果你要无缝的在矩阵和跟踪之间切换,那目前把Prometheus & Jaeger串起来的体验并不好。
+如果你只是需要监控指标(metrics),那Prometheus是不错的,但如果你要无缝的在metrics和traces之间切换,那目前把Prometheus & Jaeger串起来的体验并不好。
-我们的目标是在矩阵和跟踪之间提供整合的UI - 类似于Datadog这样的Saas厂提供的方案,能够对跟踪进行过滤和聚合,这是目前Jaeger缺失的功能。
+我们的目标是为metrics和traces提供统一的UI - 类似于Datadog这样的Saas厂提供的方案。并且能够对trace进行过滤和聚合,这是目前Jaeger缺失的功能。
 
### SigNoz vs Jaeger
-Jaeger只做分布式跟踪,SigNoz则是做了矩阵和跟踪两块,我们在计划中也有日志管理功能。
+Jaeger只做分布式追踪(distributed tracing),SigNoz则支持metrics,traces,logs ,即可视化的三大支柱。
并且SigNoz有一些Jaeger没有的高级功能:
-- Jaegar UI无法在跟踪或过滤的跟踪基础上展示矩阵。
-- Jaeger不能在过滤的跟踪上进行聚合操作。例如,拥有tag为customer_type='premium'的所有请求的p99延迟,在SigNoz里这很容易实现。
+- Jaegar UI无法在traces或过滤的traces上展示metrics。
+- Jaeger不能对过滤的traces做聚合操作。例如,拥有tag为customer_type='premium'的所有请求的p99延迟。而这个功能在SigNoz这儿是很容易实现。
@@ -121,6 +125,23 @@ Jaeger只做分布式跟踪,SigNoz则是做了矩阵和跟踪两块,我们
还不清楚怎么开始? 只需在[slack社区](https://signoz.io/slack)的`#contributing`频道里ping我们。
+### Project maintainers
+
+#### Backend
+
+- [Ankit Nayan](https://github.com/ankitnayan)
+- [Nityananda Gohain](https://github.com/nityanandagohain)
+- [Srikanth Chekuri](https://github.com/srikanthccv)
+- [Vishal Sharma](https://github.com/makeavish)
+
+#### Frontend
+
+- [Palash Gupta](https://github.com/palashgdev)
+
+#### DevOps
+
+- [Prashant Shahi](https://github.com/prashant-shahi)
+
diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
index 5bc37de791..88380538d7 100644
--- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
+++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
@@ -2,7 +2,7 @@ version: "3.9"
services:
clickhouse:
- image: clickhouse/clickhouse-server:22.4.5-alpine
+ image: clickhouse/clickhouse-server:22.8.8-alpine
# ports:
# - "9000:9000"
# - "8123:8123"
@@ -40,7 +40,7 @@ services:
condition: on-failure
query-service:
- image: signoz/query-service:0.11.2
+ image: signoz/query-service:0.11.3
command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
@@ -70,7 +70,7 @@ services:
- clickhouse
frontend:
- image: signoz/frontend:0.11.2
+ image: signoz/frontend:0.11.3
deploy:
restart_policy:
condition: on-failure
@@ -83,7 +83,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs
volumes:
@@ -111,7 +111,7 @@ services:
- clickhouse
otel-collector-metrics:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
index 0a54841d0c..1c3b59c1c5 100644
--- a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
+++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
@@ -47,7 +47,7 @@ receivers:
# thrift_binary:
# endpoint: 0.0.0.0:6832
hostmetrics:
- collection_interval: 60s
+ collection_interval: 30s
scrapers:
cpu: {}
load: {}
@@ -55,6 +55,16 @@ receivers:
disk: {}
filesystem: {}
network: {}
+ prometheus:
+ config:
+ global:
+ scrape_interval: 60s
+ scrape_configs:
+ # otel-collector internal metrics
+ - job_name: otel-collector
+ static_configs:
+ - targets:
+ - localhost:8888
processors:
batch:
@@ -65,7 +75,6 @@ processors:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
timeout: 2s
- override: false
signozspanmetrics/prometheus:
metrics_exporter: prometheus
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
@@ -134,8 +143,8 @@ service:
receivers: [otlp]
processors: [batch]
exporters: [clickhousemetricswrite]
- metrics/hostmetrics:
- receivers: [hostmetrics]
+ metrics/generic:
+ receivers: [hostmetrics, prometheus]
processors: [resourcedetection, batch]
exporters: [clickhousemetricswrite]
metrics/spanmetrics:
diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml
index ecaee5977a..1786eb42e3 100644
--- a/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml
+++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml
@@ -2,27 +2,19 @@ receivers:
prometheus:
config:
scrape_configs:
- # otel-collector internal metrics
- - job_name: "otel-collector"
- scrape_interval: 60s
- dns_sd_configs:
- - names:
- - 'tasks.otel-collector'
- type: 'A'
- port: 8888
# otel-collector-metrics internal metrics
- - job_name: "otel-collector-metrics"
+ - job_name: otel-collector-metrics
scrape_interval: 60s
static_configs:
- targets:
- localhost:8888
# SigNoz span metrics
- - job_name: "signozspanmetrics-collector"
+ - job_name: signozspanmetrics-collector
scrape_interval: 60s
dns_sd_configs:
- names:
- - 'tasks.otel-collector'
- type: 'A'
+ - tasks.otel-collector
+ type: A
port: 8889
processors:
diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml
index a7d265d3f5..45a7043a0a 100644
--- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml
@@ -2,7 +2,7 @@ version: "2.4"
services:
clickhouse:
- image: clickhouse/clickhouse-server:22.4.5-alpine
+ image: clickhouse/clickhouse-server:22.8.8-alpine
container_name: clickhouse
# ports:
# - "9000:9000"
@@ -41,7 +41,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: otel-collector
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-config.yaml"]
# user: root # required for reading docker container logs
volumes:
@@ -67,7 +67,7 @@ services:
otel-collector-metrics:
container_name: otel-collector-metrics
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/deploy/docker/clickhouse-setup/docker-compose-prod.yaml b/deploy/docker/clickhouse-setup/docker-compose-prod.yaml
deleted file mode 100644
index 2aa522026e..0000000000
--- a/deploy/docker/clickhouse-setup/docker-compose-prod.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-version: "2.4"
-
-services:
- query-service:
- image: signoz/query-service:0.11.2
- container_name: query-service
- command: ["-config=/root/config/prometheus.yml"]
- # ports:
- # - "6060:6060" # pprof port
- # - "8080:8080" # query-service port
- volumes:
- - ./prometheus.yml:/root/config/prometheus.yml
- - ../dashboards:/root/config/dashboards
- - ./data/signoz/:/var/lib/signoz/
- environment:
- - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces
- - ALERTMANAGER_API_PREFIX=http://alertmanager:9093/api/
- - SIGNOZ_LOCAL_DB_PATH=/var/lib/signoz/signoz.db
- - DASHBOARDS_PATH=/root/config/dashboards
- - STORAGE=clickhouse
- - GODEBUG=netdns=go
- - TELEMETRY_ENABLED=true
- - DEPLOYMENT_TYPE=docker-standalone-amd
- restart: on-failure
- healthcheck:
- test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
- interval: 30s
- timeout: 5s
- retries: 3
- depends_on:
- clickhouse:
- condition: service_healthy
-
- frontend:
- image: signoz/frontend:0.11.2
- container_name: frontend
- restart: on-failure
- depends_on:
- - alertmanager
- - query-service
- ports:
- - "3301:3301"
- volumes:
- - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml
index 964a835e26..1f877ffc78 100644
--- a/deploy/docker/clickhouse-setup/docker-compose.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose.yaml
@@ -2,7 +2,7 @@ version: "2.4"
services:
clickhouse:
- image: clickhouse/clickhouse-server:22.4.5-alpine
+ image: clickhouse/clickhouse-server:22.8.8-alpine
# ports:
# - "9000:9000"
# - "8123:8123"
@@ -39,7 +39,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
- image: signoz/query-service:0.11.2
+ image: signoz/query-service:0.11.3
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
# ports:
@@ -69,7 +69,7 @@ services:
condition: service_healthy
frontend:
- image: signoz/frontend:0.11.2
+ image: signoz/frontend:0.11.3
container_name: frontend
restart: on-failure
depends_on:
@@ -81,7 +81,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs
volumes:
@@ -107,7 +107,7 @@ services:
condition: service_healthy
otel-collector-metrics:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml
index b05b957e30..46bd7a87d4 100644
--- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml
+++ b/deploy/docker/clickhouse-setup/otel-collector-config.yaml
@@ -47,7 +47,7 @@ receivers:
# thrift_binary:
# endpoint: 0.0.0.0:6832
hostmetrics:
- collection_interval: 60s
+ collection_interval: 30s
scrapers:
cpu: {}
load: {}
@@ -55,6 +55,16 @@ receivers:
disk: {}
filesystem: {}
network: {}
+ prometheus:
+ config:
+ global:
+ scrape_interval: 60s
+ scrape_configs:
+ # otel-collector internal metrics
+ - job_name: otel-collector
+ static_configs:
+ - targets:
+ - localhost:8888
processors:
batch:
@@ -89,7 +99,6 @@ processors:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
timeout: 2s
- override: false
extensions:
health_check:
@@ -138,8 +147,8 @@ service:
receivers: [otlp]
processors: [batch]
exporters: [clickhousemetricswrite]
- metrics/hostmetrics:
- receivers: [hostmetrics]
+ metrics/generic:
+ receivers: [hostmetrics, prometheus]
processors: [resourcedetection, batch]
exporters: [clickhousemetricswrite]
metrics/spanmetrics:
diff --git a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml
index fdc5830f57..aecad4eaaf 100644
--- a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml
+++ b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml
@@ -6,20 +6,14 @@ receivers:
prometheus:
config:
scrape_configs:
- # otel-collector internal metrics
- - job_name: "otel-collector"
- scrape_interval: 60s
- static_configs:
- - targets:
- - otel-collector:8888
# otel-collector-metrics internal metrics
- - job_name: "otel-collector-metrics"
+ - job_name: otel-collector-metrics
scrape_interval: 60s
static_configs:
- targets:
- localhost:8888
# SigNoz span metrics
- - job_name: "signozspanmetrics-collector"
+ - job_name: signozspanmetrics-collector
scrape_interval: 60s
static_configs:
- targets:
diff --git a/ee/query-service/license/manager.go b/ee/query-service/license/manager.go
index 306fa5a8d1..a3e9ba0771 100644
--- a/ee/query-service/license/manager.go
+++ b/ee/query-service/license/manager.go
@@ -10,6 +10,8 @@ import (
"sync"
+ baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
+
validate "go.signoz.io/signoz/ee/query-service/integrations/signozio"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
@@ -92,6 +94,8 @@ func (lm *Manager) SetActive(l *model.License) {
lm.activeLicense = l
lm.activeFeatures = l.FeatureSet
+ // set default features
+ setDefaultFeatures(lm)
if !lm.validatorRunning {
// we want to make sure only one validator runs,
// we already have lock() so good to go
@@ -101,7 +105,13 @@ func (lm *Manager) SetActive(l *model.License) {
}
-// LoadActiveLicense loads the most recent active licenseex
+func setDefaultFeatures(lm *Manager) {
+ for k, v := range baseconstants.DEFAULT_FEATURE_SET {
+ lm.activeFeatures[k] = v
+ }
+}
+
+// LoadActiveLicense loads the most recent active license
func (lm *Manager) LoadActiveLicense() error {
var err error
active, err := lm.repo.GetActiveLicense(context.Background())
@@ -111,7 +121,10 @@ func (lm *Manager) LoadActiveLicense() error {
if active != nil {
lm.SetActive(active)
} else {
- zap.S().Info("No active license found.")
+ zap.S().Info("No active license found, defaulting to basic plan")
+ // if no active license is found, we default to basic(free) plan with all default features
+ lm.activeFeatures = basemodel.BasicPlan
+ setDefaultFeatures(lm)
}
return nil
@@ -278,8 +291,11 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
// CheckFeature will be internally used by backend routines
// for feature gating
func (lm *Manager) CheckFeature(featureKey string) error {
- if _, ok := lm.activeFeatures[featureKey]; ok {
- return nil
+ if value, ok := lm.activeFeatures[featureKey]; ok {
+ if value {
+ return nil
+ }
+ return basemodel.ErrFeatureUnavailable{Key: featureKey}
}
return basemodel.ErrFeatureUnavailable{Key: featureKey}
}
diff --git a/frontend/public/locales/en-GB/dashboard.json b/frontend/public/locales/en-GB/dashboard.json
index 7f21149511..b643f4727d 100644
--- a/frontend/public/locales/en-GB/dashboard.json
+++ b/frontend/public/locales/en-GB/dashboard.json
@@ -1,6 +1,7 @@
{
"create_dashboard": "Create Dashboard",
"import_json": "Import JSON",
+ "import_grafana_json": "Import Grafana JSON",
"copy_to_clipboard": "Copy To ClipBoard",
"download_json": "Download JSON",
"view_json": "View JSON",
diff --git a/frontend/public/locales/en/dashboard.json b/frontend/public/locales/en/dashboard.json
index 7f21149511..b643f4727d 100644
--- a/frontend/public/locales/en/dashboard.json
+++ b/frontend/public/locales/en/dashboard.json
@@ -1,6 +1,7 @@
{
"create_dashboard": "Create Dashboard",
"import_json": "Import JSON",
+ "import_grafana_json": "Import Grafana JSON",
"copy_to_clipboard": "Copy To ClipBoard",
"download_json": "Download JSON",
"view_json": "View JSON",
diff --git a/frontend/src/api/dashboard/create.ts b/frontend/src/api/dashboard/create.ts
index ab2ace2144..3796eb685e 100644
--- a/frontend/src/api/dashboard/create.ts
+++ b/frontend/src/api/dashboard/create.ts
@@ -7,8 +7,9 @@ import { PayloadProps, Props } from 'types/api/dashboard/create';
const create = async (
props: Props,
): Promise | ErrorResponse> => {
+ const url = props.uploadedGrafana ? '/dashboards/grafana' : '/dashboards';
try {
- const response = await axios.post('/dashboards', {
+ const response = await axios.post(url, {
...props,
});
diff --git a/frontend/src/api/dynamicConfigs/getDynamicConfigs.ts b/frontend/src/api/dynamicConfigs/getDynamicConfigs.ts
new file mode 100644
index 0000000000..149c113119
--- /dev/null
+++ b/frontend/src/api/dynamicConfigs/getDynamicConfigs.ts
@@ -0,0 +1,24 @@
+import axios from 'api';
+import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
+import { AxiosError } from 'axios';
+import { ErrorResponse, SuccessResponse } from 'types/api';
+import { PayloadProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
+
+const getDynamicConfigs = async (): Promise<
+ SuccessResponse | ErrorResponse
+> => {
+ try {
+ const response = await axios.get(`/configs`);
+
+ return {
+ statusCode: 200,
+ error: null,
+ message: response.data.status,
+ payload: response.data.data,
+ };
+ } catch (error) {
+ return ErrorResponseHandler(error as AxiosError);
+ }
+};
+
+export default getDynamicConfigs;
diff --git a/frontend/src/constants/app.ts b/frontend/src/constants/app.ts
index 68bfe983db..8529db4e4d 100644
--- a/frontend/src/constants/app.ts
+++ b/frontend/src/constants/app.ts
@@ -11,3 +11,5 @@ export const INVITE_MEMBERS_HASH = '#invite-team-members';
export const SIGNOZ_UPGRADE_PLAN_URL =
'https://upgrade.signoz.io/upgrade-from-app';
+
+export const DASHBOARD_TIME_IN_DURATION = 'refreshInterval';
diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx
index 9d3dbb88f7..3ff1cfe6c4 100644
--- a/frontend/src/container/AppLayout/index.tsx
+++ b/frontend/src/container/AppLayout/index.tsx
@@ -1,4 +1,5 @@
import { notification } from 'antd';
+import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getFeaturesFlags from 'api/features/getFeatureFlags';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
@@ -14,6 +15,7 @@ import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
+ UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_FEATURE_FLAGS,
@@ -33,6 +35,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserVersionResponse,
getUserLatestVersionResponse,
getFeaturesResponse,
+ getDynamicConfigsResponse,
] = useQueries([
{
queryFn: getUserVersion,
@@ -48,6 +51,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryFn: getFeaturesFlags,
queryKey: 'getFeatureFlags',
},
+ {
+ queryFn: getDynamicConfigs,
+ queryKey: 'getDynamicConfigs',
+ },
]);
useEffect(() => {
@@ -65,11 +72,15 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (getFeaturesResponse.status === 'idle') {
getFeaturesResponse.refetch();
}
+ if (getDynamicConfigsResponse.status === 'idle') {
+ getDynamicConfigsResponse.refetch();
+ }
}, [
getFeaturesResponse,
getUserLatestVersionResponse,
getUserVersionResponse,
isLoggedIn,
+ getDynamicConfigsResponse,
]);
const { children } = props;
@@ -78,6 +89,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0);
+ const latestConfigCounter = useRef(0);
useEffect(() => {
if (
@@ -170,6 +182,23 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
},
});
}
+
+ if (
+ getDynamicConfigsResponse.isFetched &&
+ getDynamicConfigsResponse.isSuccess &&
+ getDynamicConfigsResponse.data &&
+ getDynamicConfigsResponse.data.payload &&
+ latestConfigCounter.current === 0
+ ) {
+ latestConfigCounter.current = 1;
+
+ dispatch({
+ type: UPDATE_CONFIGS,
+ payload: {
+ configs: getDynamicConfigsResponse.data.payload,
+ },
+ });
+ }
}, [
dispatch,
isLoggedIn,
@@ -187,6 +216,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getFeaturesResponse.isFetched,
getFeaturesResponse.isSuccess,
getFeaturesResponse.data,
+ getDynamicConfigsResponse.data,
+ getDynamicConfigsResponse.isFetched,
+ getDynamicConfigsResponse.isSuccess,
]);
const isToDisplayLayout = isLoggedIn;
diff --git a/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx b/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
new file mode 100644
index 0000000000..84ac44e60e
--- /dev/null
+++ b/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
@@ -0,0 +1,33 @@
+import React, { PureComponent } from 'react';
+
+interface State {
+ hasError: boolean;
+}
+
+interface Props {
+ children: JSX.Element;
+}
+
+class ErrorLink extends PureComponent {
+ constructor(props: Props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(): State {
+ return { hasError: true };
+ }
+
+ render(): JSX.Element {
+ const { children } = this.props;
+ const { hasError } = this.state;
+
+ if (hasError) {
+ return ;
+ }
+
+ return children;
+ }
+}
+
+export default ErrorLink;
diff --git a/frontend/src/container/ConfigDropdown/Config/Link.tsx b/frontend/src/container/ConfigDropdown/Config/Link.tsx
new file mode 100644
index 0000000000..2cc39b7779
--- /dev/null
+++ b/frontend/src/container/ConfigDropdown/Config/Link.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
+ const isInternalLink = href.startsWith('/');
+
+ if (isInternalLink) {
+ return {children};
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+interface LinkContainerProps {
+ children: React.ReactNode;
+ href: string;
+}
+
+export default LinkContainer;
diff --git a/frontend/src/container/ConfigDropdown/Config/index.tsx b/frontend/src/container/ConfigDropdown/Config/index.tsx
new file mode 100644
index 0000000000..956ec5aa00
--- /dev/null
+++ b/frontend/src/container/ConfigDropdown/Config/index.tsx
@@ -0,0 +1,51 @@
+import { Menu, Space } from 'antd';
+import Spinner from 'components/Spinner';
+import React, { Suspense, useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
+import AppReducer from 'types/reducer/app';
+
+import ErrorLink from './ErrorLink';
+import LinkContainer from './Link';
+
+function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
+ const sortedConfig = useMemo(
+ () => config.components.sort((a, b) => a.position - b.position),
+ [config.components],
+ );
+
+ const { isDarkMode } = useSelector((state) => state.app);
+
+ return (
+
+ {sortedConfig.map((item) => {
+ const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
+
+ const Component = React.lazy(
+ () => import(`@ant-design/icons/es/icons/${iconName}.js`),
+ );
+ return (
+
+ }>
+
+
+
+
+ {item.text}
+
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+interface HelpToolTipProps {
+ config: ConfigProps;
+}
+
+export default HelpToolTip;
diff --git a/frontend/src/container/ConfigDropdown/index.tsx b/frontend/src/container/ConfigDropdown/index.tsx
new file mode 100644
index 0000000000..12992bf1a6
--- /dev/null
+++ b/frontend/src/container/ConfigDropdown/index.tsx
@@ -0,0 +1,67 @@
+import {
+ CaretDownFilled,
+ CaretUpFilled,
+ QuestionCircleFilled,
+ QuestionCircleOutlined,
+} from '@ant-design/icons';
+import { Dropdown, Menu, Space } from 'antd';
+import React, { useMemo, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import AppReducer from 'types/reducer/app';
+
+import HelpToolTip from './Config';
+
+function DynamicConfigDropdown({
+ frontendId,
+}: DynamicConfigDropdownProps): JSX.Element {
+ const { configs, isDarkMode } = useSelector(
+ (state) => state.app,
+ );
+ const [isHelpDropDownOpen, setIsHelpDropDownOpen] = useState(false);
+
+ const config = useMemo(
+ () =>
+ Object.values(configs).find(
+ (config) => config.frontendPositionId === frontendId,
+ ),
+ [frontendId, configs],
+ );
+
+ const onToggleHandler = (): void => {
+ setIsHelpDropDownOpen(!isHelpDropDownOpen);
+ };
+
+ if (!config) {
+ return ;
+ }
+
+ const Icon = isDarkMode ? QuestionCircleOutlined : QuestionCircleFilled;
+ const DropDownIcon = isHelpDropDownOpen ? CaretUpFilled : CaretDownFilled;
+
+ return (
+
+
+
+ }
+ visible={isHelpDropDownOpen}
+ >
+
+
+
+
+
+ );
+}
+
+interface DynamicConfigDropdownProps {
+ frontendId: string;
+}
+
+export default DynamicConfigDropdown;
diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx
index d6e4f79ae5..5deaa07bd2 100644
--- a/frontend/src/container/Header/index.tsx
+++ b/frontend/src/container/Header/index.tsx
@@ -14,8 +14,9 @@ import {
} from 'antd';
import { Logout } from 'api/utils';
import ROUTES from 'constants/routes';
+import Config from 'container/ConfigDropdown';
import setTheme, { AppMode } from 'lib/theme/setTheme';
-import React, { useCallback, useState } from 'react';
+import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { bindActionCreators } from 'redux';
@@ -28,13 +29,19 @@ import AppReducer from 'types/reducer/app';
import CurrentOrganization from './CurrentOrganization';
import ManageLicense from './ManageLicense';
import SignedInAS from './SignedInAs';
-import { Container, LogoutContainer, ToggleButton } from './styles';
+import {
+ Container,
+ IconContainer,
+ LogoutContainer,
+ ToggleButton,
+} from './styles';
function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
const { isDarkMode, user, currentVersion } = useSelector(
(state) => state.app,
);
- const [isUserDropDownOpen, setIsUserDropDownOpen] = useState();
+
+ const [isUserDropDownOpen, setIsUserDropDownOpen] = useState(false);
const onToggleThemeHandler = useCallback(() => {
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
@@ -57,22 +64,21 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
};
}, [toggleDarkMode, isDarkMode]);
- const onArrowClickHandler: VoidFunction = () => {
- setIsUserDropDownOpen((state) => !state);
- };
-
- const onClickLogoutHandler = (): void => {
- Logout();
- };
+ const onToggleHandler = useCallback(
+ (functionToExecute: Dispatch>) => (): void => {
+ functionToExecute((state) => !state);
+ },
+ [],
+ );
const menu = (
),
[createNewDashboard, loading, onNewDashboardHandler, t],
@@ -256,7 +268,8 @@ function ListOfAllDashboard(): JSX.Element {
onModalHandler(false)}
/>
{
- return new URLSearchParams(search);
- }, [search]);
+ const urlQuery = useUrlQuery();
const dispatch = useDispatch();
@@ -58,6 +51,8 @@ function Logs({ getLogsFields }: LogsProps): JSX.Element {
);
}
+type LogsProps = DispatchProps;
+
interface DispatchProps {
getLogsFields: () => (dispatch: Dispatch) => void;
}
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
index 1014fa7945..0723d2378e 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
@@ -22,10 +22,11 @@ import { v4 } from 'uuid';
import FieldKey from '../FieldKey';
import { QueryConditionContainer, QueryFieldContainer } from '../styles';
import { createParsedQueryStructure } from '../utils';
+import { hashCode, parseQuery } from './utils';
const { Option } = Select;
interface QueryFieldProps {
- query: { value: string | string[]; type: string }[];
+ query: Query;
queryIndex: number;
onUpdate: (query: unknown, queryIndex: number) => void;
onDelete: (queryIndex: number) => void;
@@ -49,12 +50,12 @@ function QueryField({
}
return '';
};
+
const fieldType = useMemo(() => getFieldType(query[0].value as string), [
query,
]);
const handleChange = (qIdx: number, value: string): void => {
query[qIdx].value = value || '';
-
if (qIdx === 1) {
if (Object.values(QueryOperatorsMultiVal).includes(value)) {
if (!Array.isArray(query[2].value)) {
@@ -166,17 +167,8 @@ function QueryConditionField({
);
}
-const hashCode = (s: string): string => {
- if (!s) {
- return '0';
- }
- return `${Math.abs(
- s.split('').reduce((a, b) => {
- a = (a << 5) - a + b.charCodeAt(0);
- return a & a;
- }, 0),
- )}`;
-};
+
+export type Query = { value: string | string[]; type: string }[];
function QueryBuilder({
updateParsedQuery,
@@ -201,12 +193,9 @@ function QueryBuilder({
}
}, [parsedQuery]);
- const handleUpdate = (
- query: { value: string | string[]; type: string }[],
- queryIndex: number,
- ): void => {
+ const handleUpdate = (query: Query, queryIndex: number): void => {
const updatedParsedQuery = generatedQueryStructure;
- updatedParsedQuery[queryIndex] = query as never;
+ updatedParsedQuery[queryIndex] = parseQuery(query) as never;
const flatParsedQuery = flatten(updatedParsedQuery).filter((q) => q.value);
keyPrefixRef.current = hashCode(JSON.stringify(flatParsedQuery));
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/utils.ts b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/utils.ts
new file mode 100644
index 0000000000..2641d8af35
--- /dev/null
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/utils.ts
@@ -0,0 +1,37 @@
+import _set from 'lodash-es/set';
+
+import { Query } from './QueryBuilder';
+
+export const parseQuery = (queries: Query): Query => {
+ if (Array.isArray(queries)) {
+ const isContainsPresent = queries.find((e) => e.value === 'CONTAINS');
+ if (isContainsPresent) {
+ // find the index of VALUE to update
+ const valueIndex = queries.findIndex((e) => e.type === 'QUERY_VALUE');
+ if (valueIndex > -1) {
+ // update the value to wrap with ""
+ _set(
+ queries,
+ [valueIndex, 'value'],
+ `'${queries[valueIndex].value || ''}'`,
+ );
+ }
+ return queries;
+ }
+ }
+ return queries;
+};
+
+export const hashCode = (s: string): string => {
+ if (!s) {
+ return '0';
+ }
+ return `${Math.abs(
+ s.split('').reduce((a, b) => {
+ // eslint-disable-next-line no-bitwise, no-param-reassign
+ a = (a << 5) - a + b.charCodeAt(0);
+ // eslint-disable-next-line no-bitwise
+ return a & a;
+ }, 0),
+ )}`;
+};
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/utils.ts b/frontend/src/container/LogsSearchFilter/SearchFields/utils.ts
index 059392f36d..ae091dc061 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/utils.ts
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/utils.ts
@@ -2,9 +2,14 @@
// @ts-ignore
// @ts-nocheck
-import { QueryTypes } from 'lib/logql/tokens';
+import { QueryTypes, QueryOperatorsSingleVal } from 'lib/logql/tokens';
-export const queryKOVPair = () => [
+export interface QueryFields {
+ type: keyof typeof QueryTypes;
+ value: string;
+}
+
+export const queryKOVPair = (): QueryFields[] => [
{
type: QueryTypes.QUERY_KEY,
value: null,
diff --git a/frontend/src/container/LogsTable/index.tsx b/frontend/src/container/LogsTable/index.tsx
index 974e77f7fe..7997fac91f 100644
--- a/frontend/src/container/LogsTable/index.tsx
+++ b/frontend/src/container/LogsTable/index.tsx
@@ -15,9 +15,6 @@ import { ILogsReducer } from 'types/reducer/logs';
import { Container, Heading } from './styles';
-interface LogsTableProps {
- getLogs: (props: Parameters[0]) => ReturnType;
-}
function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
const {
searchFilter: { queryString },
@@ -51,6 +48,7 @@ function LogsTable({ getLogs }: LogsTableProps): JSX.Element {
if (isLoading) {
return ;
}
+
return (
@@ -86,4 +84,8 @@ const mapDispatchToProps = (
getLogs: bindActionCreators(getLogs, dispatch),
});
+interface LogsTableProps {
+ getLogs: (props: Parameters[0]) => ReturnType;
+}
+
export default connect(null, mapDispatchToProps)(memo(LogsTable));
diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
index 7197887be1..082e6514e6 100644
--- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
+++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
@@ -51,7 +51,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
history.replace(
`${
ROUTES.TRACE
- }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
+ }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1`,
);
};
@@ -103,7 +103,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
history.replace(
`${
ROUTES.TRACE
- }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
+ }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1`,
);
};
diff --git a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
index 4f91a97781..0048482837 100644
--- a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
+++ b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
@@ -42,7 +42,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
history.push(
`${
ROUTES.TRACE
- }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`,
+ }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
);
};
diff --git a/frontend/src/container/TopNav/AutoRefresh/config.ts b/frontend/src/container/TopNav/AutoRefresh/config.ts
new file mode 100644
index 0000000000..cefd0c8bf1
--- /dev/null
+++ b/frontend/src/container/TopNav/AutoRefresh/config.ts
@@ -0,0 +1,63 @@
+export const options: IOptions[] = [
+ {
+ label: 'off',
+ key: 'off',
+ value: 0,
+ },
+ {
+ label: '5s',
+ key: '5s',
+ value: 5000,
+ },
+ {
+ label: '10s',
+ key: '10s',
+ value: 10000,
+ },
+ {
+ label: '30s',
+ key: '30s',
+ value: 30000,
+ },
+ {
+ label: '1m',
+ key: '1m',
+ value: 60000,
+ },
+ {
+ label: '5m',
+ key: '5m',
+ value: 300000,
+ },
+ {
+ label: '10m',
+ key: '10m',
+ value: 600000,
+ },
+ {
+ label: '30m',
+ key: '30m',
+ value: 1800000,
+ },
+ {
+ label: '1h',
+ key: '1h',
+ value: 3600000,
+ },
+ {
+ label: '2h',
+ key: '2h',
+ value: 7200000,
+ },
+ {
+ label: '1d',
+ key: '1d',
+ value: 86400000,
+ },
+];
+
+export interface IOptions {
+ label: string;
+ key: string;
+ value: number;
+}
diff --git a/frontend/src/container/TopNav/AutoRefresh/index.tsx b/frontend/src/container/TopNav/AutoRefresh/index.tsx
new file mode 100644
index 0000000000..bc52e2cf86
--- /dev/null
+++ b/frontend/src/container/TopNav/AutoRefresh/index.tsx
@@ -0,0 +1,166 @@
+import { CaretDownFilled } from '@ant-design/icons';
+import {
+ Checkbox,
+ Divider,
+ Popover,
+ Radio,
+ RadioChangeEvent,
+ Space,
+ Typography,
+} from 'antd';
+import { CheckboxChangeEvent } from 'antd/lib/checkbox';
+import get from 'api/browser/localstorage/get';
+import set from 'api/browser/localstorage/set';
+import { DASHBOARD_TIME_IN_DURATION } from 'constants/app';
+import dayjs from 'dayjs';
+import useUrlQuery from 'hooks/useUrlQuery';
+import _omit from 'lodash-es/omit';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useLocation } from 'react-router-dom';
+import { useInterval } from 'react-use';
+import { Dispatch } from 'redux';
+import { AppState } from 'store/reducers';
+import AppActions from 'types/actions';
+import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
+import { GlobalReducer } from 'types/reducer/globalTime';
+
+import { options } from './config';
+import { ButtonContainer, Container } from './styles';
+
+function AutoRefresh({ disabled = false }: AutoRefreshProps): JSX.Element {
+ const { minTime: initialMinTime, selectedTime } = useSelector<
+ AppState,
+ GlobalReducer
+ >((state) => state.globalTime);
+ const { pathname } = useLocation();
+
+ const localStorageData = JSON.parse(get(DASHBOARD_TIME_IN_DURATION) || '{}');
+
+ const localStorageValue = useMemo(() => localStorageData[pathname], [
+ pathname,
+ localStorageData,
+ ]);
+
+ const [isAutoRefreshEnabled, setIsAutoRefreshfreshEnabled] = useState(
+ Boolean(localStorageValue),
+ );
+
+ useEffect(() => {
+ setIsAutoRefreshfreshEnabled(Boolean(localStorageValue));
+ }, [localStorageValue]);
+
+ const params = useUrlQuery();
+
+ const dispatch = useDispatch>();
+ const [selectedOption, setSelectedOption] = useState(
+ localStorageValue || options[0].key,
+ );
+
+ useEffect(() => {
+ setSelectedOption(localStorageValue || options[0].key);
+ }, [localStorageValue, params]);
+
+ const getOption = useMemo(
+ () => options.find((option) => option.key === selectedOption),
+ [selectedOption],
+ );
+
+ useInterval(() => {
+ const selectedValue = getOption?.value;
+
+ if (disabled || !isAutoRefreshEnabled) {
+ return;
+ }
+
+ if (selectedOption !== 'off' && selectedValue) {
+ const min = initialMinTime / 1000000;
+
+ dispatch({
+ type: UPDATE_TIME_INTERVAL,
+ payload: {
+ maxTime: dayjs().valueOf() * 1000000,
+ minTime: dayjs(min).subtract(selectedValue, 'second').valueOf() * 1000000,
+ selectedTime,
+ },
+ });
+ }
+ }, getOption?.value || 0);
+
+ const onChangeHandler = useCallback(
+ (event: RadioChangeEvent) => {
+ const selectedValue = event.target.value;
+ setSelectedOption(selectedValue);
+ params.set(DASHBOARD_TIME_IN_DURATION, selectedValue);
+ set(
+ DASHBOARD_TIME_IN_DURATION,
+ JSON.stringify({ ...localStorageData, [pathname]: selectedValue }),
+ );
+ setIsAutoRefreshfreshEnabled(true);
+ },
+ [params, pathname, localStorageData],
+ );
+
+ const onChangeAutoRefreshHandler = useCallback(
+ (event: CheckboxChangeEvent) => {
+ const { checked } = event.target;
+ if (!checked) {
+ // remove the path from localstorage
+ set(
+ DASHBOARD_TIME_IN_DURATION,
+ JSON.stringify(_omit(localStorageData, pathname)),
+ );
+ }
+ setIsAutoRefreshfreshEnabled(checked);
+ },
+ [localStorageData, pathname],
+ );
+
+ return (
+
+
+ Auto Refresh
+
+
+
+
+ Refresh Interval
+
+
+
+ {options
+ .filter((e) => e.label !== 'off')
+ .map((option) => (
+
+ {option.label}
+
+ ))}
+
+
+
+ }
+ >
+
+
+
+
+ );
+}
+
+interface AutoRefreshProps {
+ disabled?: boolean;
+}
+
+AutoRefresh.defaultProps = {
+ disabled: false,
+};
+
+export default AutoRefresh;
diff --git a/frontend/src/container/TopNav/AutoRefresh/styles.ts b/frontend/src/container/TopNav/AutoRefresh/styles.ts
new file mode 100644
index 0000000000..9672a346e7
--- /dev/null
+++ b/frontend/src/container/TopNav/AutoRefresh/styles.ts
@@ -0,0 +1,13 @@
+import { Button } from 'antd';
+import styled from 'styled-components';
+
+export const Container = styled.div`
+ min-width: 8rem;
+`;
+
+export const ButtonContainer = styled(Button)`
+ &&& {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ }
+`;
diff --git a/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx b/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
index 36b1163dda..f4597e5727 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
@@ -2,7 +2,10 @@ import React, { useEffect, useState } from 'react';
import { RefreshTextContainer, Typography } from './styles';
-function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
+function RefreshText({
+ onLastRefreshHandler,
+ refreshButtonHidden,
+}: RefreshTextProps): JSX.Element {
const [refreshText, setRefreshText] = useState('');
// this is to update the refresh text
@@ -19,7 +22,7 @@ function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
}, [onLastRefreshHandler, refreshText]);
return (
-
+
{refreshText}
);
@@ -27,6 +30,7 @@ function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
interface RefreshTextProps {
onLastRefreshHandler: () => string;
+ refreshButtonHidden: boolean;
}
export default RefreshText;
diff --git a/frontend/src/container/TopNav/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts
index 1654def09f..d327476e08 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/config.ts
+++ b/frontend/src/container/TopNav/DateTimeSelection/config.ts
@@ -67,3 +67,19 @@ export const getOptions = (routes: string): Option[] => {
}
return Options;
};
+
+export const routesToSkip = [
+ ROUTES.SETTINGS,
+ ROUTES.LIST_ALL_ALERT,
+ ROUTES.TRACE_DETAIL,
+ ROUTES.ALL_CHANNELS,
+ ROUTES.USAGE_EXPLORER,
+ ROUTES.INSTRUMENTATION,
+ ROUTES.VERSION,
+ ROUTES.ALL_DASHBOARD,
+ ROUTES.ORG_SETTINGS,
+ ROUTES.ERROR_DETAIL,
+ ROUTES.ALERTS_NEW,
+ ROUTES.EDIT_ALERTS,
+ ROUTES.LIST_ALL_ALERT,
+];
diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
index c170a7cef3..18e8dce4c8 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
@@ -1,3 +1,4 @@
+import { SyncOutlined } from '@ant-design/icons';
import { Button, Select as DefaultSelect } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
@@ -14,10 +15,11 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { GlobalReducer } from 'types/reducer/globalTime';
+import AutoRefresh from '../AutoRefresh';
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
import { getDefaultOption, getOptions, Time } from './config';
import RefreshText from './Refresh';
-import { Container, Form, FormItem } from './styles';
+import { Form, FormContainer, FormItem } from './styles';
const { Option } = DefaultSelect;
@@ -240,6 +242,8 @@ function DateTimeSelection({
setStartTime(dayjs(preStartTime));
setEndTime(dayjs(preEndTime));
+ setRefreshButtonHidden(updatedTime === 'custom');
+
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
}, [
location.pathname,
@@ -253,35 +257,44 @@ function DateTimeSelection({
]);
return (
-
+ <>
-
+ >
);
}
diff --git a/frontend/src/container/TopNav/DateTimeSelection/styles.ts b/frontend/src/container/TopNav/DateTimeSelection/styles.ts
index 7993831894..e87f2ff92a 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/styles.ts
+++ b/frontend/src/container/TopNav/DateTimeSelection/styles.ts
@@ -1,10 +1,6 @@
import { Form as FormComponent, Typography as TypographyComponent } from 'antd';
import styled from 'styled-components';
-export const Container = styled.div`
- justify-content: flex-end;
-`;
-
export const Form = styled(FormComponent)`
&&& {
justify-content: flex-end;
@@ -23,6 +19,17 @@ export const FormItem = styled(Form.Item)`
}
`;
-export const RefreshTextContainer = styled.div`
+interface Props {
+ refreshButtonHidden: boolean;
+}
+
+export const RefreshTextContainer = styled.div`
min-height: 2rem;
+ visibility: ${({ refreshButtonHidden }): string =>
+ refreshButtonHidden ? 'hidden' : 'visible'};
+`;
+
+export const FormContainer = styled.div`
+ display: flex;
+ gap: 0.1rem;
`;
diff --git a/frontend/src/container/TopNav/index.tsx b/frontend/src/container/TopNav/index.tsx
index ddf10023a3..1cfbf19dbc 100644
--- a/frontend/src/container/TopNav/index.tsx
+++ b/frontend/src/container/TopNav/index.tsx
@@ -1,52 +1,40 @@
import { Col } from 'antd';
import ROUTES from 'constants/routes';
-import history from 'lib/history';
-import React from 'react';
-import { matchPath } from 'react-router-dom';
+import React, { useMemo } from 'react';
+import { matchPath, useHistory } from 'react-router-dom';
import ShowBreadcrumbs from './Breadcrumbs';
import DateTimeSelector from './DateTimeSelection';
+import { routesToSkip } from './DateTimeSelection/config';
import { Container } from './styles';
-const routesToSkip = [
- ROUTES.SETTINGS,
- ROUTES.LIST_ALL_ALERT,
- ROUTES.TRACE_DETAIL,
- ROUTES.ALL_CHANNELS,
- ROUTES.USAGE_EXPLORER,
- ROUTES.INSTRUMENTATION,
- ROUTES.VERSION,
- ROUTES.ALL_DASHBOARD,
- ROUTES.ORG_SETTINGS,
- ROUTES.ERROR_DETAIL,
- ROUTES.ALERTS_NEW,
- ROUTES.EDIT_ALERTS,
- ROUTES.LIST_ALL_ALERT,
-];
-
function TopNav(): JSX.Element | null {
- if (history.location.pathname === ROUTES.SIGN_UP) {
+ const { location } = useHistory();
+
+ const isRouteToSkip = useMemo(
+ () =>
+ routesToSkip.some((route) =>
+ matchPath(location.pathname, { path: route, exact: true }),
+ ),
+ [location.pathname],
+ );
+
+ const isSignUpPage = useMemo(
+ () => matchPath(location.pathname, { path: ROUTES.SIGN_UP, exact: true }),
+ [location.pathname],
+ );
+
+ if (isSignUpPage) {
return null;
}
- const checkRouteExists = (currentPath: string): boolean => {
- for (let i = 0; i < routesToSkip.length; i += 1) {
- if (
- matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
- ) {
- return true;
- }
- }
- return false;
- };
-
return (
- {!checkRouteExists(history.location.pathname) && (
+ {!isRouteToSkip && (
diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx
index 96fece7ba4..c532ff2683 100644
--- a/frontend/src/container/Trace/TraceTable/index.tsx
+++ b/frontend/src/container/Trace/TraceTable/index.tsx
@@ -3,7 +3,6 @@ import Table, { ColumnsType } from 'antd/lib/table';
import ROUTES from 'constants/routes';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
-import history from 'lib/history';
import omit from 'lodash-es/omit';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -195,7 +194,7 @@ function TraceTable(): JSX.Element {
onClick: (event): void => {
event.preventDefault();
event.stopPropagation();
- history.push(getLink(record));
+ window.open(getLink(record));
},
})}
pagination={{
diff --git a/frontend/src/store/actions/global.ts b/frontend/src/store/actions/global.ts
index c2b88919da..0e7b2e172f 100644
--- a/frontend/src/store/actions/global.ts
+++ b/frontend/src/store/actions/global.ts
@@ -2,6 +2,7 @@ import { Time } from 'container/TopNav/DateTimeSelection/config';
import GetMinMax from 'lib/getMinMax';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
+import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
export const UpdateTimeInterval = (
interval: Time,
@@ -11,7 +12,7 @@ export const UpdateTimeInterval = (
const { maxTime, minTime } = GetMinMax(interval, dateTimeRange);
dispatch({
- type: 'UPDATE_TIME_INTERVAL',
+ type: UPDATE_TIME_INTERVAL,
payload: {
maxTime,
minTime,
diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts
index 3e18a4c957..6fbc48049d 100644
--- a/frontend/src/store/reducers/app.ts
+++ b/frontend/src/store/reducers/app.ts
@@ -8,6 +8,7 @@ import {
LOGGED_IN,
SIDEBAR_COLLAPSE,
SWITCH_DARK_MODE,
+ UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_FEATURE_FLAGS,
@@ -56,6 +57,7 @@ const InitialValue: InitialValueTypes = {
isUserFetchingError: false,
org: null,
role: null,
+ configs: {},
};
const appReducer = (
@@ -210,6 +212,13 @@ const appReducer = (
};
}
+ case UPDATE_CONFIGS: {
+ return {
+ ...state,
+ configs: action.payload.configs,
+ };
+ }
+
default:
return state;
}
diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts
index 65264f5ca3..a2a4b90f39 100644
--- a/frontend/src/types/actions/app.ts
+++ b/frontend/src/types/actions/app.ts
@@ -23,6 +23,7 @@ export const UPDATE_USER = 'UPDATE_USER';
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
export const UPDATE_ORG = 'UPDATE_ORG';
export const UPDATE_FEATURE_FLAGS = 'UPDATE_FEATURE_FLAGS';
+export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
export interface SwitchDarkMode {
type: typeof SWITCH_DARK_MODE;
@@ -115,6 +116,12 @@ export interface UpdateOrg {
org: AppReducer['org'];
};
}
+export interface UpdateConfigs {
+ type: typeof UPDATE_CONFIGS;
+ payload: {
+ configs: AppReducer['configs'];
+ };
+}
export type AppAction =
| SwitchDarkMode
@@ -129,4 +136,5 @@ export type AppAction =
| UpdateUser
| UpdateOrgName
| UpdateOrg
- | UpdateFeatureFlags;
+ | UpdateFeatureFlags
+ | UpdateConfigs;
diff --git a/frontend/src/types/api/dashboard/create.ts b/frontend/src/types/api/dashboard/create.ts
index 3c859edc6c..9b0e26457d 100644
--- a/frontend/src/types/api/dashboard/create.ts
+++ b/frontend/src/types/api/dashboard/create.ts
@@ -3,7 +3,8 @@ import { Dashboard, DashboardData } from './getAll';
export type Props =
| {
title: Dashboard['data']['title'];
+ uploadedGrafana: boolean;
}
- | DashboardData;
+ | { DashboardData: DashboardData; uploadedGrafana: boolean };
export type PayloadProps = Dashboard;
diff --git a/frontend/src/types/api/dynamicConfigs/getDynamicConfigs.ts b/frontend/src/types/api/dynamicConfigs/getDynamicConfigs.ts
new file mode 100644
index 0000000000..69e55e008d
--- /dev/null
+++ b/frontend/src/types/api/dynamicConfigs/getDynamicConfigs.ts
@@ -0,0 +1,14 @@
+export interface ConfigProps {
+ enabled: boolean;
+ frontendPositionId: string;
+ components: Array<{
+ href: string;
+ darkIcon: string;
+ lightIcon: string;
+ position: 1;
+ text: string;
+ }>;
+}
+export interface PayloadProps {
+ [key: string]: ConfigProps;
+}
diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts
index 5c10f31a83..d95fd3a77b 100644
--- a/frontend/src/types/reducer/app.ts
+++ b/frontend/src/types/reducer/app.ts
@@ -1,3 +1,4 @@
+import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
import { PayloadProps as OrgPayload } from 'types/api/user/getOrganization';
import { PayloadProps as UserPayload } from 'types/api/user/getUser';
@@ -26,4 +27,5 @@ export default interface AppReducer {
role: ROLES | null;
org: OrgPayload | null;
featureFlags: null | FeatureFlagPayload;
+ configs: ConfigPayload;
}
diff --git a/go.mod b/go.mod
index 74fca9afe3..8666887ad6 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/mattn/go-sqlite3 v1.14.8
github.com/minio/minio-go/v6 v6.0.57
+ github.com/mitchellh/mapstructure v1.5.0
github.com/oklog/oklog v0.3.2
github.com/pkg/errors v0.9.1
github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f
@@ -145,4 +146,4 @@ require (
k8s.io/client-go v8.0.0+incompatible // indirect
)
-replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.74
+replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.76
diff --git a/go.sum b/go.sum
index 6babcdcb67..ba38dc1069 100644
--- a/go.sum
+++ b/go.sum
@@ -57,8 +57,8 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFmafKx32bynV6QrzViL/s+ZDvQxH1E4=
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb/go.mod h1:JznGDNg9x1cujDKa22RaQOimOvvEfy3nxzDGd8XDgmA=
-github.com/SigNoz/prometheus v1.9.74 h1:/AcKVZ80Cg4FQ/quMDLO4Ejyeb3KDjgUKveiN+OXVL8=
-github.com/SigNoz/prometheus v1.9.74/go.mod h1:Y4J9tGDmacMC+EcOTp+EIAn2C1sN+9kE+idyVKadiVM=
+github.com/SigNoz/prometheus v1.9.76 h1:YQOHezj4Yyu6PHV7/bVR297FQgUMQAAJtCVZ+NslwYk=
+github.com/SigNoz/prometheus v1.9.76/go.mod h1:Y4J9tGDmacMC+EcOTp+EIAn2C1sN+9kE+idyVKadiVM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -345,6 +345,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go
index 009a372add..0aa037490e 100644
--- a/pkg/query-service/app/clickhouseReader/reader.go
+++ b/pkg/query-service/app/clickhouseReader/reader.go
@@ -2729,7 +2729,7 @@ func (r *ClickHouseReader) GetMetricAutocompleteTagKey(ctx context.Context, para
tagsWhereClause := ""
for key, val := range params.MetricTags {
- tagsWhereClause += fmt.Sprintf(" AND labels_object.%s = '%s' ", key, val)
+ tagsWhereClause += fmt.Sprintf(" AND JSONExtractString(labels, '%s') = '%s' ", key, val)
}
// "select distinctTagKeys from (SELECT DISTINCT arrayJoin(tagKeys) distinctTagKeys from (SELECT DISTINCT(JSONExtractKeys(labels)) tagKeys from signoz_metrics.time_series WHERE JSONExtractString(labels,'__name__')='node_udp_queues')) WHERE distinctTagKeys ILIKE '%host%';"
if len(params.Match) != 0 {
@@ -2768,16 +2768,16 @@ func (r *ClickHouseReader) GetMetricAutocompleteTagValue(ctx context.Context, pa
tagsWhereClause := ""
for key, val := range params.MetricTags {
- tagsWhereClause += fmt.Sprintf(" AND labels_object.%s = '%s' ", key, val)
+ tagsWhereClause += fmt.Sprintf(" AND JSONExtractString(labels, '%s') = '%s' ", key, val)
}
if len(params.Match) != 0 {
- query = fmt.Sprintf("SELECT DISTINCT(labels_object.%s) from %s.%s WHERE metric_name=$1 %s AND labels_object.%s ILIKE $2;", params.TagKey, signozMetricDBName, signozTSTableName, tagsWhereClause, params.TagKey)
+ query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels, '%s')) from %s.%s WHERE metric_name=$1 %s AND JSONExtractString(labels, '%s') ILIKE $2;", params.TagKey, signozMetricDBName, signozTSTableName, tagsWhereClause, params.TagKey)
rows, err = r.db.Query(ctx, query, params.TagKey, params.MetricName, fmt.Sprintf("%%%s%%", params.Match))
} else {
- query = fmt.Sprintf("SELECT DISTINCT(labels_object.%s) FROM %s.%s WHERE metric_name=$2 %s;", params.TagKey, signozMetricDBName, signozTSTableName, tagsWhereClause)
+ query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels, '%s')) FROM %s.%s WHERE metric_name=$2 %s;", params.TagKey, signozMetricDBName, signozTSTableName, tagsWhereClause)
rows, err = r.db.Query(ctx, query, params.TagKey, params.MetricName)
}
diff --git a/pkg/query-service/app/dashboards/model.go b/pkg/query-service/app/dashboards/model.go
index 4969a18728..56adf9aae7 100644
--- a/pkg/query-service/app/dashboards/model.go
+++ b/pkg/query-service/app/dashboards/model.go
@@ -4,12 +4,16 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
+ "reflect"
+ "regexp"
+ "strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/gosimple/slug"
"github.com/jmoiron/sqlx"
+ "github.com/mitchellh/mapstructure"
"go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -17,6 +21,14 @@ import (
// This time the global variable is unexported.
var db *sqlx.DB
+// User for mapping job,instance from grafana
+var instanceEQRE = regexp.MustCompile("instance(?s)=(?s)\\\"{{.instance}}\\\"")
+var nodeEQRE = regexp.MustCompile("instance(?s)=(?s)\\\"{{.node}}\\\"")
+var jobEQRE = regexp.MustCompile("job(?s)=(?s)\\\"{{.job}}\\\"")
+var instanceRERE = regexp.MustCompile("instance(?s)=~(?s)\\\"{{.instance}}\\\"")
+var nodeRERE = regexp.MustCompile("instance(?s)=~(?s)\\\"{{.node}}\\\"")
+var jobRERE = regexp.MustCompile("job(?s)=~(?s)\\\"{{.job}}\\\"")
+
// InitDB sets up setting up the connection pool global variable.
func InitDB(dataSourceName string) (*sqlx.DB, error) {
var err error
@@ -260,3 +272,236 @@ func SlugifyTitle(title string) string {
}
return s
}
+
+func widgetFromPanel(panel model.Panels, idx int, variables map[string]model.Variable) *model.Widget {
+ widget := model.Widget{
+ Description: panel.Description,
+ ID: strconv.Itoa(idx),
+ IsStacked: false,
+ NullZeroValues: "zero",
+ Opacity: "1",
+ PanelTypes: "TIME_SERIES", // TODO: Need to figure out how to get this
+ Query: model.Query{
+ ClickHouse: []model.ClickHouseQueryDashboard{
+ {
+ Disabled: false,
+ Legend: "",
+ Name: "A",
+ Query: "",
+ },
+ },
+ MetricsBuilder: model.MetricsBuilder{
+ Formulas: []string{},
+ QueryBuilder: []model.QueryBuilder{
+ {
+ AggregateOperator: 1,
+ Disabled: false,
+ GroupBy: []string{},
+ Legend: "",
+ MetricName: "",
+ Name: "A",
+ ReduceTo: 1,
+ },
+ },
+ },
+ PromQL: []model.PromQueryDashboard{},
+ QueryType: int(model.PROM),
+ },
+ QueryData: model.QueryDataDashboard{
+ Data: model.Data{
+ QueryData: []interface{}{},
+ },
+ },
+ Title: panel.Title,
+ YAxisUnit: panel.FieldConfig.Defaults.Unit,
+ QueryType: int(model.PROM), // TODO: Supprot for multiple query types
+ }
+ for _, target := range panel.Targets {
+ if target.Expr != "" {
+ for name := range variables {
+ target.Expr = strings.ReplaceAll(target.Expr, "$"+name, "{{"+"."+name+"}}")
+ target.Expr = strings.ReplaceAll(target.Expr, "$"+"__rate_interval", "5m")
+ }
+
+ // prometheus receiver in collector maps job,instance as service_name,service_instance_id
+ target.Expr = instanceEQRE.ReplaceAllString(target.Expr, "service_instance_id=\"{{.instance}}\"")
+ target.Expr = nodeEQRE.ReplaceAllString(target.Expr, "service_instance_id=\"{{.node}}\"")
+ target.Expr = jobEQRE.ReplaceAllString(target.Expr, "service_name=\"{{.job}}\"")
+ target.Expr = instanceRERE.ReplaceAllString(target.Expr, "service_instance_id=~\"{{.instance}}\"")
+ target.Expr = nodeRERE.ReplaceAllString(target.Expr, "service_instance_id=~\"{{.node}}\"")
+ target.Expr = jobRERE.ReplaceAllString(target.Expr, "service_name=~\"{{.job}}\"")
+
+ widget.Query.PromQL = append(
+ widget.Query.PromQL,
+ model.PromQueryDashboard{
+ Disabled: false,
+ Legend: target.LegendFormat,
+ Name: target.RefID,
+ Query: target.Expr,
+ },
+ )
+ }
+ }
+ return &widget
+}
+
+func TransformGrafanaJSONToSignoz(grafanaJSON model.GrafanaJSON) model.DashboardData {
+ var toReturn model.DashboardData
+ toReturn.Title = grafanaJSON.Title
+ toReturn.Tags = grafanaJSON.Tags
+ toReturn.Variables = make(map[string]model.Variable)
+
+ for templateIdx, template := range grafanaJSON.Templating.List {
+ var sort, typ, textboxValue, customValue, queryValue string
+ if template.Sort == 1 {
+ sort = "ASC"
+ } else if template.Sort == 2 {
+ sort = "DESC"
+ } else {
+ sort = "DISABLED"
+ }
+
+ if template.Type == "query" {
+ if template.Datasource == nil {
+ zap.S().Warnf("Skipping panel %d as it has no datasource", templateIdx)
+ continue
+ }
+ // Skip if the source is not prometheus
+ source, stringOk := template.Datasource.(string)
+ if stringOk && !strings.Contains(strings.ToLower(source), "prometheus") {
+ zap.S().Warnf("Skipping template %d as it is not prometheus", templateIdx)
+ continue
+ }
+ var result model.Datasource
+ var structOk bool
+ if reflect.TypeOf(template.Datasource).Kind() == reflect.Map {
+ err := mapstructure.Decode(template.Datasource, &result)
+ if err == nil {
+ structOk = true
+ }
+ }
+ if result.Type != "prometheus" && result.Type != "" {
+ zap.S().Warnf("Skipping template %d as it is not prometheus", templateIdx)
+ continue
+ }
+
+ if !stringOk && !structOk {
+ zap.S().Warnf("Didn't recognize source, skipping")
+ continue
+ }
+ typ = "QUERY"
+ } else if template.Type == "custom" {
+ typ = "CUSTOM"
+ } else if template.Type == "textbox" {
+ typ = "TEXTBOX"
+ text, ok := template.Current.Text.(string)
+ if ok {
+ textboxValue = text
+ }
+ array, ok := template.Current.Text.([]string)
+ if ok {
+ textboxValue = strings.Join(array, ",")
+ }
+ } else {
+ continue
+ }
+
+ var selectedValue string
+ text, ok := template.Current.Value.(string)
+ if ok {
+ selectedValue = text
+ }
+ array, ok := template.Current.Value.([]string)
+ if ok {
+ selectedValue = strings.Join(array, ",")
+ }
+
+ toReturn.Variables[template.Name] = model.Variable{
+ AllSelected: false,
+ CustomValue: customValue,
+ Description: template.Label,
+ MultiSelect: template.Multi,
+ QueryValue: queryValue,
+ SelectedValue: selectedValue,
+ ShowALLOption: template.IncludeAll,
+ Sort: sort,
+ TextboxValue: textboxValue,
+ Type: typ,
+ }
+ }
+
+ row := 0
+ idx := 0
+ for _, panel := range grafanaJSON.Panels {
+ if panel.Type == "row" {
+ if panel.Panels != nil && len(panel.Panels) > 0 {
+ for _, innerPanel := range panel.Panels {
+ if idx%3 == 0 {
+ row++
+ }
+ toReturn.Layout = append(
+ toReturn.Layout,
+ model.Layout{
+ X: idx % 3 * 4,
+ Y: row * 3,
+ W: 4,
+ H: 3,
+ I: strconv.Itoa(idx),
+ },
+ )
+
+ toReturn.Widgets = append(toReturn.Widgets, *widgetFromPanel(innerPanel, idx, toReturn.Variables))
+ idx++
+ }
+ }
+ continue
+ }
+ if panel.Datasource == nil {
+ zap.S().Warnf("Skipping panel %d as it has no datasource", idx)
+ continue
+ }
+ // Skip if the datasource is not prometheus
+ source, stringOk := panel.Datasource.(string)
+ if stringOk && !strings.Contains(strings.ToLower(source), "prometheus") {
+ zap.S().Warnf("Skipping panel %d as it is not prometheus", idx)
+ continue
+ }
+ var result model.Datasource
+ var structOk bool
+ if reflect.TypeOf(panel.Datasource).Kind() == reflect.Map {
+ err := mapstructure.Decode(panel.Datasource, &result)
+ if err == nil {
+ structOk = true
+ }
+ }
+ if result.Type != "prometheus" && result.Type != "" {
+ zap.S().Warnf("Skipping panel %d as it is not prometheus", idx)
+ continue
+ }
+
+ if !stringOk && !structOk {
+ zap.S().Warnf("Didn't recognize source, skipping")
+ continue
+ }
+
+ // Create a panel from "gridPos"
+
+ if idx%3 == 0 {
+ row++
+ }
+ toReturn.Layout = append(
+ toReturn.Layout,
+ model.Layout{
+ X: idx % 3 * 4,
+ Y: row * 3,
+ W: 4,
+ H: 3,
+ I: strconv.Itoa(idx),
+ },
+ )
+
+ toReturn.Widgets = append(toReturn.Widgets, *widgetFromPanel(panel, idx, toReturn.Variables))
+ idx++
+ }
+ return toReturn
+}
diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go
index 1d648f4651..18d2743924 100644
--- a/pkg/query-service/app/http_handler.go
+++ b/pkg/query-service/app/http_handler.go
@@ -27,6 +27,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/dao"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
+ signozio "go.signoz.io/signoz/pkg/query-service/integrations/signozio"
"go.signoz.io/signoz/pkg/query-service/interfaces"
"go.signoz.io/signoz/pkg/query-service/model"
"go.signoz.io/signoz/pkg/query-service/rules"
@@ -339,6 +340,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
router.HandleFunc("/api/v1/dashboards", ViewAccess(aH.getDashboards)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards", EditAccess(aH.createDashboards)).Methods(http.MethodPost)
+ router.HandleFunc("/api/v1/dashboards/grafana", EditAccess(aH.createDashboardsTransform)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/dashboards/{uuid}", ViewAccess(aH.getDashboard)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.updateDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.deleteDashboard)).Methods(http.MethodDelete)
@@ -358,6 +360,8 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet)
+ router.HandleFunc("/api/v1/featureFlags", OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
+ router.HandleFunc("/api/v1/configs", OpenAccess(aH.getConfigs)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost)
@@ -822,6 +826,40 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
}
+func (aH *APIHandler) saveAndReturn(w http.ResponseWriter, signozDashboard model.DashboardData) {
+ toSave := make(map[string]interface{})
+ toSave["title"] = signozDashboard.Title
+ toSave["description"] = signozDashboard.Description
+ toSave["tags"] = signozDashboard.Tags
+ toSave["layout"] = signozDashboard.Layout
+ toSave["widgets"] = signozDashboard.Widgets
+ toSave["variables"] = signozDashboard.Variables
+
+ dashboard, apiError := dashboards.CreateDashboard(toSave)
+ if apiError != nil {
+ RespondError(w, apiError, nil)
+ return
+ }
+ aH.Respond(w, dashboard)
+ return
+}
+
+func (aH *APIHandler) createDashboardsTransform(w http.ResponseWriter, r *http.Request) {
+
+ defer r.Body.Close()
+ b, err := ioutil.ReadAll(r.Body)
+
+ var importData model.GrafanaJSON
+
+ err = json.Unmarshal(b, &importData)
+ if err == nil {
+ signozDashboard := dashboards.TransformGrafanaJSONToSignoz(importData)
+ aH.saveAndReturn(w, signozDashboard)
+ return
+ }
+ RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error while creating dashboard from grafana json")
+}
+
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
var postData map[string]interface{}
@@ -1422,7 +1460,7 @@ func (aH *APIHandler) getSpanFilters(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getFilteredSpans(w http.ResponseWriter, r *http.Request) {
- query, err := parseFilteredSpansRequest(r)
+ query, err := parseFilteredSpansRequest(r, aH)
if aH.HandleError(w, err, http.StatusBadRequest) {
return
}
@@ -1533,6 +1571,30 @@ func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
aH.WriteJSON(w, r, map[string]string{"version": version, "ee": "N"})
}
+func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
+ featureSet := aH.FF().GetFeatureFlags()
+ aH.Respond(w, featureSet)
+}
+
+func (aH *APIHandler) FF() interfaces.FeatureLookup {
+ return aH.featureFlags
+}
+
+func (aH *APIHandler) CheckFeature(f string) bool {
+ err := aH.FF().CheckFeature(f)
+ return err == nil
+}
+
+func (aH *APIHandler) getConfigs(w http.ResponseWriter, r *http.Request) {
+
+ configs, err := signozio.FetchDynamicConfigs()
+ if err != nil {
+ aH.HandleError(w, err, http.StatusInternalServerError)
+ return
+ }
+ aH.Respond(w, configs)
+}
+
// inviteUser is used to invite a user. It is used by an admin api.
func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) {
req, err := parseInviteRequest(r)
diff --git a/pkg/query-service/app/logs/parser.go b/pkg/query-service/app/logs/parser.go
index 1ccb0dbc54..a1ab021e33 100644
--- a/pkg/query-service/app/logs/parser.go
+++ b/pkg/query-service/app/logs/parser.go
@@ -36,7 +36,7 @@ const (
DESC = "desc"
)
-var tokenRegex, _ = regexp.Compile(`(?i)(and( )*?|or( )*?)?(([\w.-]+ (in|nin) \([^(]+\))|([\w.]+ (gt|lt|gte|lte) (')?[\S]+(')?)|([\w.]+ (contains|ncontains)) '[^']+')`)
+var tokenRegex, _ = regexp.Compile(`(?i)(and( )*?|or( )*?)?(([\w.-]+ (in|nin) \([^(]+\))|([\w.]+ (gt|lt|gte|lte) (')?[\S]+(')?)|([\w.]+ (contains|ncontains)) [^\\]?'(.*?[^\\])')`)
var operatorRegex, _ = regexp.Compile(`(?i)(?: )(in|nin|gt|lt|gte|lte|contains|ncontains)(?: )`)
func ParseLogFilterParams(r *http.Request) (*model.LogsFilterParams, error) {
diff --git a/pkg/query-service/app/logs/parser_test.go b/pkg/query-service/app/logs/parser_test.go
index 439e323739..f4b75f41e0 100644
--- a/pkg/query-service/app/logs/parser_test.go
+++ b/pkg/query-service/app/logs/parser_test.go
@@ -29,8 +29,8 @@ var correctQueriesTest = []struct {
},
{
`contains search with a different attributes`,
- `resource contains 'Hello, "World"'`,
- []string{`resource ILIKE '%Hello, "World"%' `},
+ `resource contains 'Hello, "World" and user\'s'`,
+ []string{`resource ILIKE '%Hello, "World" and user\'s%' `},
},
{
`more than one continas`,
diff --git a/pkg/query-service/app/metrics/query_builder.go b/pkg/query-service/app/metrics/query_builder.go
index e0a696f509..071a32baa3 100644
--- a/pkg/query-service/app/metrics/query_builder.go
+++ b/pkg/query-service/app/metrics/query_builder.go
@@ -111,21 +111,21 @@ func BuildMetricsTimeSeriesFilterQuery(fs *model.FilterSet, groupTags []string,
fmtVal := FormattedValue(toFormat)
switch op {
case "eq":
- conditions = append(conditions, fmt.Sprintf("labels_object.%s = %s", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key, fmtVal))
case "neq":
- conditions = append(conditions, fmt.Sprintf("labels_object.%s != %s", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", item.Key, fmtVal))
case "in":
- conditions = append(conditions, fmt.Sprintf("labels_object.%s IN %s", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", item.Key, fmtVal))
case "nin":
- conditions = append(conditions, fmt.Sprintf("labels_object.%s NOT IN %s", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", item.Key, fmtVal))
case "like":
- conditions = append(conditions, fmt.Sprintf("like(labels_object.%s, %s)", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key, fmtVal))
case "nlike":
- conditions = append(conditions, fmt.Sprintf("notLike(labels_object.%s, %s)", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key, fmtVal))
case "match":
- conditions = append(conditions, fmt.Sprintf("match(labels_object.%s, %s)", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", item.Key, fmtVal))
case "nmatch":
- conditions = append(conditions, fmt.Sprintf("not match(labels_object.%s, %s)", item.Key, fmtVal))
+ conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", item.Key, fmtVal))
default:
return "", fmt.Errorf("unsupported operation")
}
@@ -138,7 +138,7 @@ func BuildMetricsTimeSeriesFilterQuery(fs *model.FilterSet, groupTags []string,
selectLabels = "labels,"
} else {
for _, tag := range groupTags {
- selectLabels += fmt.Sprintf(" labels_object.%s as %s,", tag, tag)
+ selectLabels += fmt.Sprintf(" JSONExtractString(labels, '%s') as %s,", tag, tag)
}
}
diff --git a/pkg/query-service/app/metrics/query_builder_test.go b/pkg/query-service/app/metrics/query_builder_test.go
index a9cf780ae4..3b15e1f464 100644
--- a/pkg/query-service/app/metrics/query_builder_test.go
+++ b/pkg/query-service/app/metrics/query_builder_test.go
@@ -55,9 +55,9 @@ func TestBuildQueryWithFilters(t *testing.T) {
queries := PrepareBuilderMetricQueries(q, "table").Queries
So(len(queries), ShouldEqual, 1)
- So(queries["a"], ShouldContainSubstring, "WHERE metric_name = 'name' AND labels_object.a != 'b'")
+ So(queries["a"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'a') != 'b'")
So(queries["a"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)")
- So(queries["a"], ShouldContainSubstring, "not match(labels_object.code, 'ERROR_*')")
+ So(queries["a"], ShouldContainSubstring, "not match(JSONExtractString(labels, 'code'), 'ERROR_*')")
})
}
@@ -89,7 +89,7 @@ func TestBuildQueryWithMultipleQueries(t *testing.T) {
}
queries := PrepareBuilderMetricQueries(q, "table").Queries
So(len(queries), ShouldEqual, 2)
- So(queries["a"], ShouldContainSubstring, "WHERE metric_name = 'name' AND labels_object.in IN ['a','b','c']")
+ So(queries["a"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'in') IN ['a','b','c']")
So(queries["a"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)")
})
}
@@ -126,7 +126,7 @@ func TestBuildQueryWithMultipleQueriesAndFormula(t *testing.T) {
queries := PrepareBuilderMetricQueries(q, "table").Queries
So(len(queries), ShouldEqual, 3)
So(queries["c"], ShouldContainSubstring, "SELECT ts, a.value / b.value")
- So(queries["c"], ShouldContainSubstring, "WHERE metric_name = 'name' AND labels_object.in IN ['a','b','c']")
+ So(queries["c"], ShouldContainSubstring, "WHERE metric_name = 'name' AND JSONExtractString(labels, 'in') IN ['a','b','c']")
So(queries["c"], ShouldContainSubstring, "runningDifference(value)/runningDifference(ts)")
})
}
diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go
index 2fed317973..bfd4042d22 100644
--- a/pkg/query-service/app/parser.go
+++ b/pkg/query-service/app/parser.go
@@ -255,7 +255,7 @@ func parseSpanFilterRequestBody(r *http.Request) (*model.SpanFilterParams, error
return postData, nil
}
-func parseFilteredSpansRequest(r *http.Request) (*model.GetFilteredSpansParams, error) {
+func parseFilteredSpansRequest(r *http.Request, aH *APIHandler) (*model.GetFilteredSpansParams, error) {
var postData *model.GetFilteredSpansParams
err := json.NewDecoder(r.Body).Decode(&postData)
@@ -277,6 +277,20 @@ func parseFilteredSpansRequest(r *http.Request) (*model.GetFilteredSpansParams,
postData.Limit = 10
}
+ if len(postData.Order) != 0 {
+ if postData.Order != constants.Ascending && postData.Order != constants.Descending {
+ return nil, errors.New("order param is not in correct format")
+ }
+ if postData.OrderParam != constants.Duration && postData.OrderParam != constants.Timestamp {
+ return nil, errors.New("order param is not in correct format")
+ }
+ if postData.OrderParam == constants.Duration && !aH.CheckFeature(constants.DurationSort) {
+ return nil, model.ErrFeatureUnavailable{Key: constants.DurationSort}
+ } else if postData.OrderParam == constants.Timestamp && !aH.CheckFeature(constants.TimestampSort) {
+ return nil, model.ErrFeatureUnavailable{Key: constants.TimestampSort}
+ }
+ }
+
return postData, nil
}
diff --git a/pkg/query-service/app/parser_test.go b/pkg/query-service/app/parser_test.go
index 3e78263696..1ce5ec488b 100644
--- a/pkg/query-service/app/parser_test.go
+++ b/pkg/query-service/app/parser_test.go
@@ -23,7 +23,7 @@ func TestParseFilterSingleFilter(t *testing.T) {
req, _ := http.NewRequest("POST", "", bytes.NewReader(postBody))
res, _ := parseFilterSet(req)
query, _ := metrics.BuildMetricsTimeSeriesFilterQuery(res, []string{}, "table", model.NOOP)
- So(query, ShouldContainSubstring, "signoz_metrics.time_series_v2 WHERE metric_name = 'table' AND labels_object.namespace = 'a'")
+ So(query, ShouldContainSubstring, "signoz_metrics.time_series_v2 WHERE metric_name = 'table' AND JSONExtractString(labels, 'namespace') = 'a'")
})
}
@@ -39,8 +39,8 @@ func TestParseFilterMultipleFilter(t *testing.T) {
req, _ := http.NewRequest("POST", "", bytes.NewReader(postBody))
res, _ := parseFilterSet(req)
query, _ := metrics.BuildMetricsTimeSeriesFilterQuery(res, []string{}, "table", model.NOOP)
- So(query, should.ContainSubstring, "labels_object.host IN ['host-1','host-2']")
- So(query, should.ContainSubstring, "labels_object.namespace = 'a'")
+ So(query, should.ContainSubstring, "JSONExtractString(labels, 'host') IN ['host-1','host-2']")
+ So(query, should.ContainSubstring, "JSONExtractString(labels, 'namespace') = 'a'")
})
}
diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go
index 2b46ae8fed..cbb8a807fa 100644
--- a/pkg/query-service/app/server.go
+++ b/pkg/query-service/app/server.go
@@ -19,6 +19,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao"
+ "go.signoz.io/signoz/pkg/query-service/featureManager"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/interfaces"
@@ -77,6 +78,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
localDB.SetMaxOpenConns(10)
+
+ // initiate feature manager
+ fm := featureManager.StartManager()
+
readerReady := make(chan bool)
var reader interfaces.Reader
@@ -98,9 +103,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
telemetry.GetInstance().SetReader(reader)
apiHandler, err := NewAPIHandler(APIHandlerOpts{
- Reader: reader,
- AppDao: dao.DB(),
- RuleManager: rm,
+ Reader: reader,
+ AppDao: dao.DB(),
+ RuleManager: rm,
+ FeatureFlags: fm,
})
if err != nil {
return nil, err
diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go
index 6e3af77a5e..d376b068e9 100644
--- a/pkg/query-service/constants/constants.go
+++ b/pkg/query-service/constants/constants.go
@@ -13,6 +13,8 @@ const (
DebugHttpPort = "0.0.0.0:6060" // Address to serve http (pprof)
)
+var ConfigSignozIo = "https://config.signoz.io/api/v1"
+
var DEFAULT_TELEMETRY_ANONYMOUS = false
func IsTelemetryEnabled() bool {
@@ -28,6 +30,9 @@ const TraceTTL = "traces"
const MetricsTTL = "metrics"
const LogsTTL = "logs"
+const DurationSort = "DurationSort"
+const TimestampSort = "TimestampSort"
+
func GetAlertManagerApiPrefix() string {
if os.Getenv("ALERTMANAGER_API_PREFIX") != "" {
return os.Getenv("ALERTMANAGER_API_PREFIX")
@@ -40,6 +45,33 @@ var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/rout
var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db")
+var DurationSortFeature = GetOrDefaultEnv("DURATION_SORT_FEATURE", "true")
+
+var TimestampSortFeature = GetOrDefaultEnv("TIMESTAMP_SORT_FEATURE", "true")
+
+func IsDurationSortFeatureEnabled() bool {
+ isDurationSortFeatureEnabledStr := DurationSortFeature
+ isDurationSortFeatureEnabledBool, err := strconv.ParseBool(isDurationSortFeatureEnabledStr)
+ if err != nil {
+ return false
+ }
+ return isDurationSortFeatureEnabledBool
+}
+
+func IsTimestampSortFeatureEnabled() bool {
+ isTimestampSortFeatureEnabledStr := TimestampSortFeature
+ isTimestampSortFeatureEnabledBool, err := strconv.ParseBool(isTimestampSortFeatureEnabledStr)
+ if err != nil {
+ return false
+ }
+ return isTimestampSortFeatureEnabledBool
+}
+
+var DEFAULT_FEATURE_SET = model.FeatureSet{
+ DurationSort: IsDurationSortFeatureEnabled(),
+ TimestampSort: IsTimestampSortFeatureEnabled(),
+}
+
const (
TraceID = "traceID"
ServiceName = "serviceName"
diff --git a/pkg/query-service/featureManager/manager.go b/pkg/query-service/featureManager/manager.go
new file mode 100644
index 0000000000..1c8c953982
--- /dev/null
+++ b/pkg/query-service/featureManager/manager.go
@@ -0,0 +1,34 @@
+package featureManager
+
+import (
+ "go.signoz.io/signoz/pkg/query-service/constants"
+ "go.signoz.io/signoz/pkg/query-service/model"
+)
+
+type FeatureManager struct {
+ activeFeatures model.FeatureSet
+}
+
+func StartManager() *FeatureManager {
+ fM := &FeatureManager{
+ activeFeatures: constants.DEFAULT_FEATURE_SET,
+ }
+ return fM
+}
+
+// CheckFeature will be internally used by backend routines
+// for feature gating
+func (fm *FeatureManager) CheckFeature(featureKey string) error {
+ if value, ok := fm.activeFeatures[featureKey]; ok {
+ if value {
+ return nil
+ }
+ return model.ErrFeatureUnavailable{Key: featureKey}
+ }
+ return model.ErrFeatureUnavailable{Key: featureKey}
+}
+
+// GetFeatureFlags returns current active features
+func (fm *FeatureManager) GetFeatureFlags() model.FeatureSet {
+ return fm.activeFeatures
+}
diff --git a/pkg/query-service/integrations/signozio/dynamic_config.go b/pkg/query-service/integrations/signozio/dynamic_config.go
new file mode 100644
index 0000000000..42827b73d5
--- /dev/null
+++ b/pkg/query-service/integrations/signozio/dynamic_config.go
@@ -0,0 +1,75 @@
+package signozio
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "go.signoz.io/signoz/ee/query-service/model"
+ "go.signoz.io/signoz/pkg/query-service/constants"
+)
+
+var C *Client
+
+const (
+ POST = "POST"
+ APPLICATION_JSON = "application/json"
+)
+
+type Client struct {
+ Prefix string
+}
+
+func New() *Client {
+ return &Client{
+ Prefix: constants.ConfigSignozIo,
+ }
+}
+
+func init() {
+ C = New()
+}
+
+// FetchDynamicConfigs fetches configs from config server
+func FetchDynamicConfigs() (map[string]Config, *model.ApiError) {
+
+ client := http.Client{Timeout: 5 * time.Second}
+ req, err := http.NewRequest(http.MethodGet, C.Prefix+"/configs", http.NoBody)
+ if err != nil {
+ return DefaultConfig, nil
+ }
+ req.SetBasicAuth("admin", "SigNoz@adm1n")
+ httpResponse, err := client.Do(req)
+ if err != nil {
+ return DefaultConfig, nil
+ }
+
+ defer httpResponse.Body.Close()
+
+ if err != nil {
+ return DefaultConfig, nil
+ }
+
+ httpBody, err := ioutil.ReadAll(httpResponse.Body)
+ if err != nil {
+ return DefaultConfig, nil
+ }
+
+ // read api request result
+ result := ConfigResult{}
+ err = json.Unmarshal(httpBody, &result)
+ if err != nil {
+ return DefaultConfig, nil
+ }
+
+ switch httpResponse.StatusCode {
+ case 200, 201:
+ return result.Data, nil
+ case 400, 401:
+ return DefaultConfig, nil
+ default:
+ return DefaultConfig, nil
+ }
+
+}
diff --git a/pkg/query-service/integrations/signozio/response.go b/pkg/query-service/integrations/signozio/response.go
new file mode 100644
index 0000000000..8440346ec4
--- /dev/null
+++ b/pkg/query-service/integrations/signozio/response.go
@@ -0,0 +1,54 @@
+package signozio
+
+type status string
+
+type ConfigResult struct {
+ Status status `json:"status"`
+ Data map[string]Config `json:"data,omitempty"`
+ ErrorType string `json:"errorType,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+type Config struct {
+ Enabled bool `json:"enabled"`
+ FrontendPositionId string `json:"frontendPositionId"`
+ Components []ComponentProps `json:"components"`
+}
+
+type ComponentProps struct {
+ Text string `json:"text"`
+ Position int `json:"position"`
+ DarkIcon string `json:"darkIcon"`
+ LightIcon string `json:"lightIcon"`
+ Href string `json:"href"`
+}
+
+var DefaultConfig = map[string]Config{
+ "helpConfig": {
+ Enabled: true,
+ FrontendPositionId: "tooltip",
+ Components: []ComponentProps{
+ {
+ Text: "How to use SigNoz in production",
+ Position: 1,
+ LightIcon: "RiseOutlined",
+ DarkIcon: "RiseOutlined",
+ Href: "https://signoz.io/docs/production-readiness",
+ },
+ {
+ Text: "Create an issue in GitHub",
+ Position: 2,
+ LightIcon: "GithubFilled",
+ DarkIcon: "GithubOutlined",
+ Href: "https://github.com/SigNoz/signoz/issues/new/choose",
+ },
+ {
+ Text: "Read the docs",
+ Position: 3,
+ LightIcon: "FileTextFilled",
+ DarkIcon: "FileTextOutlined",
+ Href: "https://signoz.io/docs",
+ },
+ },
+ },
+}
diff --git a/pkg/query-service/model/dashboards.go b/pkg/query-service/model/dashboards.go
new file mode 100644
index 0000000000..f897eeb7fb
--- /dev/null
+++ b/pkg/query-service/model/dashboards.go
@@ -0,0 +1,254 @@
+package model
+
+type Datasource struct {
+ Type string `json:"type"`
+ UID string `json:"uid"`
+}
+
+type Panels struct {
+ Datasource interface{} `json:"datasource"`
+ Description string `json:"description,omitempty"`
+ FieldConfig struct {
+ Defaults struct {
+ Color struct {
+ Mode string `json:"mode"`
+ } `json:"color"`
+ Max float64 `json:"max"`
+ Min float64 `json:"min"`
+ Thresholds struct {
+ Mode string `json:"mode"`
+ Steps []struct {
+ Color string `json:"color"`
+ Value interface{} `json:"value"`
+ } `json:"steps"`
+ } `json:"thresholds"`
+ Unit string `json:"unit"`
+ } `json:"defaults"`
+ Overrides []interface{} `json:"overrides"`
+ } `json:"fieldConfig,omitempty"`
+ GridPos struct {
+ H int `json:"h"`
+ W int `json:"w"`
+ X int `json:"x"`
+ Y int `json:"y"`
+ } `json:"gridPos"`
+ ID int `json:"id"`
+ Links []interface{} `json:"links,omitempty"`
+ Options struct {
+ Orientation string `json:"orientation"`
+ ReduceOptions struct {
+ Calcs []string `json:"calcs"`
+ Fields string `json:"fields"`
+ Values bool `json:"values"`
+ } `json:"reduceOptions"`
+ ShowThresholdLabels bool `json:"showThresholdLabels"`
+ ShowThresholdMarkers bool `json:"showThresholdMarkers"`
+ } `json:"options,omitempty"`
+ PluginVersion string `json:"pluginVersion,omitempty"`
+ Targets []struct {
+ Datasource interface{} `json:"datasource"`
+ EditorMode string `json:"editorMode"`
+ Expr string `json:"expr"`
+ Hide bool `json:"hide"`
+ IntervalFactor int `json:"intervalFactor"`
+ LegendFormat string `json:"legendFormat"`
+ Range bool `json:"range"`
+ RefID string `json:"refId"`
+ Step int `json:"step"`
+ } `json:"targets"`
+ Title string `json:"title"`
+ Type string `json:"type"`
+ HideTimeOverride bool `json:"hideTimeOverride,omitempty"`
+ MaxDataPoints int `json:"maxDataPoints,omitempty"`
+ Collapsed bool `json:"collapsed,omitempty"`
+ Panels []Panels `json:"panels,omitempty"`
+}
+
+type GrafanaJSON struct {
+ Inputs []struct {
+ Name string `json:"name"`
+ Label string `json:"label"`
+ Description string `json:"description"`
+ Type string `json:"type"`
+ PluginID string `json:"pluginId"`
+ PluginName string `json:"pluginName"`
+ } `json:"__inputs"`
+ Requires []struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Version string `json:"version"`
+ } `json:"__requires"`
+ Annotations struct {
+ List []struct {
+ HashKey string `json:"$$hashKey"`
+ BuiltIn int `json:"builtIn"`
+ Datasource interface{} `json:"datasource"`
+ Enable bool `json:"enable"`
+ Hide bool `json:"hide"`
+ IconColor string `json:"iconColor"`
+ Name string `json:"name"`
+ Target struct {
+ Limit int `json:"limit"`
+ MatchAny bool `json:"matchAny"`
+ Tags []interface{} `json:"tags"`
+ Type string `json:"type"`
+ } `json:"target"`
+ Type string `json:"type"`
+ } `json:"list"`
+ } `json:"annotations"`
+ Editable bool `json:"editable"`
+ FiscalYearStartMonth int `json:"fiscalYearStartMonth"`
+ GnetID int `json:"gnetId"`
+ GraphTooltip int `json:"graphTooltip"`
+ ID interface{} `json:"id"`
+ Links []struct {
+ Icon string `json:"icon"`
+ Tags []interface{} `json:"tags"`
+ TargetBlank bool `json:"targetBlank"`
+ Title string `json:"title"`
+ Type string `json:"type"`
+ URL string `json:"url"`
+ } `json:"links"`
+ LiveNow bool `json:"liveNow"`
+ Panels []Panels `json:"panels"`
+ SchemaVersion int `json:"schemaVersion"`
+ Style string `json:"style"`
+ Tags []string `json:"tags"`
+ Templating struct {
+ List []struct {
+ Current struct {
+ Selected bool `json:"selected"`
+ Text interface{} `json:"text"`
+ Value interface{} `json:"value"`
+ } `json:"current"`
+ Hide int `json:"hide"`
+ IncludeAll bool `json:"includeAll"`
+ Label string `json:"label,omitempty"`
+ Multi bool `json:"multi"`
+ Name string `json:"name"`
+ Options []interface{} `json:"options"`
+ Query interface{} `json:"query"`
+ Refresh int `json:"refresh,omitempty"`
+ Regex string `json:"regex,omitempty"`
+ SkipURLSync bool `json:"skipUrlSync"`
+ Type string `json:"type"`
+ Datasource interface{} `json:"datasource,omitempty"`
+ Definition string `json:"definition,omitempty"`
+ Sort int `json:"sort,omitempty"`
+ TagValuesQuery string `json:"tagValuesQuery,omitempty"`
+ TagsQuery string `json:"tagsQuery,omitempty"`
+ UseTags bool `json:"useTags,omitempty"`
+ } `json:"list"`
+ } `json:"templating"`
+ Time struct {
+ From string `json:"from"`
+ To string `json:"to"`
+ } `json:"time"`
+ Timepicker struct {
+ RefreshIntervals []string `json:"refresh_intervals"`
+ TimeOptions []string `json:"time_options"`
+ } `json:"timepicker"`
+ Timezone string `json:"timezone"`
+ Title string `json:"title"`
+ UID string `json:"uid"`
+ Version int `json:"version"`
+ WeekStart string `json:"weekStart"`
+}
+type Layout struct {
+ H int `json:"h"`
+ I string `json:"i"`
+ Moved bool `json:"moved"`
+ Static bool `json:"static"`
+ W int `json:"w"`
+ X int `json:"x"`
+ Y int `json:"y"`
+}
+
+type Variable struct {
+ AllSelected bool `json:"allSelected"`
+ CustomValue string `json:"customValue"`
+ Description string `json:"description"`
+ ModificationUUID string `json:"modificationUUID"`
+ MultiSelect bool `json:"multiSelect"`
+ QueryValue string `json:"queryValue"`
+ SelectedValue string `json:"selectedValue"`
+ ShowALLOption bool `json:"showALLOption"`
+ Sort string `json:"sort"`
+ TextboxValue string `json:"textboxValue"`
+ Type string `json:"type"`
+}
+
+type Data struct {
+ Legend string `json:"legend"`
+ Query string `json:"query"`
+ QueryData []interface{} `json:"queryData"`
+}
+
+type QueryDataDashboard struct {
+ Data Data `json:"data"`
+ Error bool `json:"error"`
+ ErrorMessage string `json:"errorMessage"`
+ Loading bool `json:"loading"`
+}
+
+type ClickHouseQueryDashboard struct {
+ Legend string `json:"legend"`
+ Name string `json:"name"`
+ Query string `json:"rawQuery"`
+ Disabled bool `json:"disabled"`
+}
+
+type QueryBuilder struct {
+ AggregateOperator interface{} `json:"aggregateOperator"`
+ Disabled bool `json:"disabled"`
+ GroupBy []string `json:"groupBy"`
+ Legend string `json:"legend"`
+ MetricName string `json:"metricName"`
+ Name string `json:"name"`
+ TagFilters TagFilters `json:"tagFilters"`
+ ReduceTo interface{} `json:"reduceTo"`
+}
+
+type MetricsBuilder struct {
+ Formulas []string `json:"formulas"`
+ QueryBuilder []QueryBuilder `json:"queryBuilder"`
+}
+
+type PromQueryDashboard struct {
+ Query string `json:"query"`
+ Disabled bool `json:"disabled"`
+ Name string `json:"name"`
+ Legend string `json:"legend"`
+}
+
+type Query struct {
+ ClickHouse []ClickHouseQueryDashboard `json:"clickHouse"`
+ PromQL []PromQueryDashboard `json:"promQL"`
+ MetricsBuilder MetricsBuilder `json:"metricsBuilder"`
+ QueryType int `json:"queryType"`
+}
+
+type Widget struct {
+ Description string `json:"description"`
+ ID string `json:"id"`
+ IsStacked bool `json:"isStacked"`
+ NullZeroValues string `json:"nullZeroValues"`
+ Opacity string `json:"opacity"`
+ PanelTypes string `json:"panelTypes"`
+ Query Query `json:"query"`
+ QueryData QueryDataDashboard `json:"queryData"`
+ TimePreferance string `json:"timePreferance"`
+ Title string `json:"title"`
+ YAxisUnit string `json:"yAxisUnit"`
+ QueryType int `json:"queryType"`
+}
+
+type DashboardData struct {
+ Description string `json:"description"`
+ Tags []string `json:"tags"`
+ Layout []Layout `json:"layout"`
+ Title string `json:"title"`
+ Widgets []Widget `json:"widgets"`
+ Variables map[string]Variable `json:"variables"`
+}
diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go
index 793c02b8ab..43f1652093 100644
--- a/pkg/query-service/telemetry/telemetry.go
+++ b/pkg/query-service/telemetry/telemetry.go
@@ -44,7 +44,7 @@ const HEART_BEAT_DURATION = 6 * time.Hour
// const HEART_BEAT_DURATION = 10 * time.Second
const RATE_LIMIT_CHECK_DURATION = 1 * time.Minute
-const RATE_LIMIT_VALUE = 60
+const RATE_LIMIT_VALUE = 10
// const RATE_LIMIT_CHECK_DURATION = 20 * time.Second
// const RATE_LIMIT_VALUE = 5
diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml
index 294fed787d..5944764e38 100644
--- a/pkg/query-service/tests/test-deploy/docker-compose.yaml
+++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml
@@ -2,7 +2,7 @@ version: "2.4"
services:
clickhouse:
- image: clickhouse/clickhouse-server:22.4.5-alpine
+ image: clickhouse/clickhouse-server:22.8.8-alpine
tty: true
volumes:
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
@@ -62,7 +62,7 @@ services:
condition: service_healthy
otel-collector:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-config.yaml"]
user: root # required for reading docker container logs
volumes:
@@ -78,7 +78,7 @@ services:
condition: service_healthy
otel-collector-metrics:
- image: signoz/signoz-otel-collector:0.55.3
+ image: signoz/signoz-otel-collector:0.63.0
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
diff --git a/pkg/query-service/tests/test-deploy/otel-collector-config.yaml b/pkg/query-service/tests/test-deploy/otel-collector-config.yaml
index d11e2793b8..33600b35e6 100644
--- a/pkg/query-service/tests/test-deploy/otel-collector-config.yaml
+++ b/pkg/query-service/tests/test-deploy/otel-collector-config.yaml
@@ -47,7 +47,7 @@ receivers:
# thrift_binary:
# endpoint: 0.0.0.0:6832
hostmetrics:
- collection_interval: 60s
+ collection_interval: 30s
scrapers:
cpu: {}
load: {}
@@ -55,6 +55,16 @@ receivers:
disk: {}
filesystem: {}
network: {}
+ prometheus:
+ config:
+ global:
+ scrape_interval: 60s
+ scrape_configs:
+ # otel-collector internal metrics
+ - job_name: otel-collector
+ static_configs:
+ - targets:
+ - otel-collector:8888
processors:
batch:
@@ -88,7 +98,6 @@ processors:
resourcedetection:
detectors: [env, system]
timeout: 2s
- override: false
extensions:
health_check:
@@ -126,8 +135,8 @@ service:
receivers: [otlp]
processors: [batch]
exporters: [clickhousemetricswrite]
- metrics/hostmetrics:
- receivers: [hostmetrics]
+ metrics/generic:
+ receivers: [hostmetrics, prometheus]
processors: [resourcedetection, batch]
exporters: [clickhousemetricswrite]
metrics/spanmetrics:
diff --git a/pkg/query-service/tests/test-deploy/otel-collector-metrics-config.yaml b/pkg/query-service/tests/test-deploy/otel-collector-metrics-config.yaml
index fdc5830f57..aecad4eaaf 100644
--- a/pkg/query-service/tests/test-deploy/otel-collector-metrics-config.yaml
+++ b/pkg/query-service/tests/test-deploy/otel-collector-metrics-config.yaml
@@ -6,20 +6,14 @@ receivers:
prometheus:
config:
scrape_configs:
- # otel-collector internal metrics
- - job_name: "otel-collector"
- scrape_interval: 60s
- static_configs:
- - targets:
- - otel-collector:8888
# otel-collector-metrics internal metrics
- - job_name: "otel-collector-metrics"
+ - job_name: otel-collector-metrics
scrape_interval: 60s
static_configs:
- targets:
- localhost:8888
# SigNoz span metrics
- - job_name: "signozspanmetrics-collector"
+ - job_name: signozspanmetrics-collector
scrape_interval: 60s
static_configs:
- targets: