diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c1aa885b6a..fd42658745 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,6 +2,6 @@
# Owners are automatically requested for review for PRs that changes code
# that they own.
* @ankitnayan
-/frontend/ @palashgdev @pranshuchittora
+/frontend/ @palashgdev
/deploy/ @prashant-shahi
**/query-service/ @srikanthccv
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
index 0f4a4b94db..b624a90b9f 100644
--- a/.github/workflows/commitlint.yml
+++ b/.github/workflows/commitlint.yml
@@ -7,12 +7,7 @@ jobs:
lint-commits:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2.3.1
+ - uses: actions/checkout@v3
with:
- # we actually need "github.event.pull_request.commits + 1" commit
fetch-depth: 0
- - uses: actions/setup-node@v2.1.0
- # or just "yarn" if you depend on "@commitlint/cli" already
- - run: yarn add @commitlint/cli
- - run: yarn add @commitlint/config-conventional
- - run: yarn run commitlint --config ./node_modules/@commitlint/config-conventional/index.js --from HEAD~${{ github.event.pull_request.commits }} --to HEAD
+ - uses: wagoid/commitlint-github-action@v5
diff --git a/.github/workflows/pr_verify_linked_issue.yml b/.github/workflows/pr_verify_linked_issue.yml
index 3fd5abd2ec..a2442cc3a4 100644
--- a/.github/workflows/pr_verify_linked_issue.yml
+++ b/.github/workflows/pr_verify_linked_issue.yml
@@ -5,7 +5,7 @@ name: VerifyIssue
on:
pull_request:
- types: [edited, synchronize, opened, reopened]
+ types: [edited, opened]
check_run:
jobs:
@@ -14,6 +14,6 @@ jobs:
name: Ensure Pull Request has a linked issue.
steps:
- name: Verify Linked Issue
- uses: hattan/verify-linked-issue-action@v1.1.0
+ uses: srikanthccv/verify-linked-issue-action@v0.70
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index d52cb94568..3627f21481 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,5 @@
node_modules
-yarn.lock
-package.json
deploy/docker/environment_tiny/common_test
frontend/node_modules
diff --git a/README.de-de.md b/README.de-de.md
index 55dd7f4c22..6587756b9b 100644
--- a/README.de-de.md
+++ b/README.de-de.md
@@ -85,9 +85,9 @@ Hier findest du die vollständige Liste von unterstützten Programmiersprachen -
### Bereitstellung mit Docker
-Bitte folge den [hier](https://signoz.io/docs/deployment/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen.
+Bitte folge den [hier](https://signoz.io/docs/install/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen.
-Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/deployment/troubleshooting) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt.
+Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/install/troubleshooting/) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt.
 
diff --git a/README.md b/README.md
index 70779f3de5..feae818ce6 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,6 @@ SigNoz helps developers monitor applications and troubleshoot problems in their
-
## Join our Slack community
@@ -78,7 +77,6 @@ Come say Hi to us on [Slack](https://signoz.io/slack) 👋
-
## Features:
@@ -89,13 +87,12 @@ Come say Hi to us on [Slack](https://signoz.io/slack) 👋
- Filter traces by service name, operation, latency, error, tags/annotations.
- Run aggregates on trace data (events/spans) to get business relevant metrics. e.g. You can get error rate and 99th percentile latency of `customer_type: gold` or `deployment_version: v2` or `external_call: paypal`
- Native support for OpenTelemetry Logs, advanced log query builder, and automatic log collection from k8s cluster
-- Lightening quick log analytics ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
+- Lightning quick log analytics ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
- End-to-End visibility into infrastructure performance, ingest metrics from all kinds of host environments
- Easy to set alerts with DIY query builder
-
## Why SigNoz?
@@ -124,15 +121,14 @@ You can find the complete list of languages here - https://opentelemetry.io/docs
-
## Getting Started
### Deploy using Docker
-Please follow the steps listed [here](https://signoz.io/docs/deployment/docker/) to install using docker
+Please follow the steps listed [here](https://signoz.io/docs/install/docker/) to install using docker
-The [troubleshooting instructions](https://signoz.io/docs/deployment/troubleshooting) may be helpful if you face any issues.
+The [troubleshooting instructions](https://signoz.io/docs/install/troubleshooting/) may be helpful if you face any issues.
 
@@ -143,7 +139,6 @@ Please follow the steps listed [here](https://signoz.io/docs/deployment/helm_cha
-
## Comparisons to Familiar Tools
@@ -185,7 +180,6 @@ We have published benchmarks comparing Loki with SigNoz. Check it out [here](htt
-
## Contributing
@@ -212,7 +206,6 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
-
## Documentation
@@ -220,7 +213,6 @@ You can find docs at https://signoz.io/docs/. If you need any clarification or f
-
## Community
diff --git a/README.pt-br.md b/README.pt-br.md
index ce168b4101..c817e8afb9 100644
--- a/README.pt-br.md
+++ b/README.pt-br.md
@@ -84,9 +84,9 @@ Você pode encontrar a lista completa de linguagens aqui - https://opentelemetry
### Implantar usando Docker
-Siga as etapas listadas [aqui](https://signoz.io/docs/deployment/docker/) para instalar usando o Docker.
+Siga as etapas listadas [aqui](https://signoz.io/docs/install/docker/) para instalar usando o Docker.
-Esse [guia para solução de problemas](https://signoz.io/docs/deployment/troubleshooting) pode ser útil se você enfrentar quaisquer problemas.
+Esse [guia para solução de problemas](https://signoz.io/docs/install/troubleshooting/) pode ser útil se você enfrentar quaisquer problemas.
 
diff --git a/README.zh-cn.md b/README.zh-cn.md
index 3658eeb520..aaa89551bf 100644
--- a/README.zh-cn.md
+++ b/README.zh-cn.md
@@ -80,9 +80,9 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
### 使用Docker部署
-请按照[这里](https://signoz.io/docs/deployment/docker/)列出的步骤使用Docker来安装
+请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装
-如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/deployment/troubleshooting)会对你有帮助。
+如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。
 
diff --git a/deploy/docker-swarm/clickhouse-setup/clickhouse-storage.xml b/deploy/docker-swarm/clickhouse-setup/clickhouse-storage.xml
index 2b2f4010ac..54ec4976f5 100644
--- a/deploy/docker-swarm/clickhouse-setup/clickhouse-storage.xml
+++ b/deploy/docker-swarm/clickhouse-setup/clickhouse-storage.xml
@@ -7,9 +7,21 @@
s3
- https://BUCKET-NAME.s3.amazonaws.com/data/
+
+ https://BUCKET-NAME.s3-REGION-NAME.amazonaws.com/data/
ACCESS-KEY-ID
SECRET-ACCESS-KEY
+
+
+
+
diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
index 26b87d18a6..5c34f979ad 100644
--- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
+++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml
@@ -34,7 +34,7 @@ x-clickhouse-depend: &clickhouse-depend
services:
zookeeper-1:
- image: bitnami/zookeeper:3.7.0
+ image: bitnami/zookeeper:3.7.1
hostname: zookeeper-1
user: root
ports:
@@ -124,7 +124,7 @@ services:
# - ./data/clickhouse-3/:/var/lib/clickhouse/
alertmanager:
- image: signoz/alertmanager:0.23.0-0.2
+ image: signoz/alertmanager:0.23.1
volumes:
- ./data/alertmanager:/data
command:
@@ -137,7 +137,7 @@ services:
condition: on-failure
query-service:
- image: signoz/query-service:0.18.3
+ image: signoz/query-service:0.19.0
command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
@@ -166,7 +166,7 @@ services:
<<: *clickhouse-depend
frontend:
- image: signoz/frontend:0.18.3
+ image: signoz/frontend:0.19.0
deploy:
restart_policy:
condition: on-failure
@@ -179,8 +179,8 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/signoz-otel-collector:0.66.7
- command: ["--config=/etc/otel-collector-config.yaml"]
+ image: signoz/signoz-otel-collector:0.76.1
+ command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@@ -208,8 +208,8 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
- image: signoz/signoz-otel-collector:0.66.7
- command: ["--config=/etc/otel-collector-metrics-config.yaml"]
+ image: signoz/signoz-otel-collector:0.76.1
+ command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
index 61b937ea4b..e755936e8b 100644
--- a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
+++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml
@@ -75,7 +75,7 @@ processors:
timeout: 10s
resourcedetection:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
- detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
+ detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
signozspanmetrics/prometheus:
metrics_exporter: prometheus
diff --git a/deploy/docker/clickhouse-setup/clickhouse-storage.xml b/deploy/docker/clickhouse-setup/clickhouse-storage.xml
index 2b2f4010ac..54ec4976f5 100644
--- a/deploy/docker/clickhouse-setup/clickhouse-storage.xml
+++ b/deploy/docker/clickhouse-setup/clickhouse-storage.xml
@@ -7,9 +7,21 @@
s3
- https://BUCKET-NAME.s3.amazonaws.com/data/
+
+ https://BUCKET-NAME.s3-REGION-NAME.amazonaws.com/data/
ACCESS-KEY-ID
SECRET-ACCESS-KEY
+
+
+
+
diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml
index 80bd4a9890..e7b598ae38 100644
--- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml
@@ -27,7 +27,7 @@ services:
alertmanager:
container_name: alertmanager
- image: signoz/alertmanager:0.23.0-0.2
+ image: signoz/alertmanager:0.23.1
volumes:
- ./data/alertmanager:/data
depends_on:
@@ -41,8 +41,8 @@ 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.66.7
- command: ["--config=/etc/otel-collector-config.yaml"]
+ image: signoz/signoz-otel-collector:0.76.1
+ command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
# user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@@ -67,8 +67,8 @@ services:
otel-collector-metrics:
container_name: otel-collector-metrics
- image: signoz/signoz-otel-collector:0.66.7
- command: ["--config=/etc/otel-collector-metrics-config.yaml"]
+ image: signoz/signoz-otel-collector:0.76.1
+ command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml
index 88923daac6..6b9006f876 100644
--- a/deploy/docker/clickhouse-setup/docker-compose.yaml
+++ b/deploy/docker/clickhouse-setup/docker-compose.yaml
@@ -34,9 +34,9 @@ x-clickhouse-depend: &clickhouse-depend
# condition: service_healthy
services:
-
+
zookeeper-1:
- image: bitnami/zookeeper:3.7.0
+ image: bitnami/zookeeper:3.7.1
container_name: zookeeper-1
hostname: zookeeper-1
user: root
@@ -120,7 +120,7 @@ services:
# - ./data/clickhouse-2/:/var/lib/clickhouse/
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
-
+
# clickhouse-3:
# <<: *clickhouse-defaults
# container_name: clickhouse-3
@@ -139,7 +139,7 @@ services:
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
alertmanager:
- image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.0-0.2}
+ image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.1}
volumes:
- ./data/alertmanager:/data
depends_on:
@@ -150,10 +150,10 @@ services:
- --queryService.url=http://query-service:8085
- --storage.path=/data
-# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
+ # 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:${DOCKER_TAG:-0.18.3}
+ image: signoz/query-service:${DOCKER_TAG:-0.19.0}
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
# ports:
@@ -181,7 +181,7 @@ services:
<<: *clickhouse-depend
frontend:
- image: signoz/frontend:${DOCKER_TAG:-0.18.3}
+ image: signoz/frontend:${DOCKER_TAG:-0.19.0}
container_name: frontend
restart: on-failure
depends_on:
@@ -193,8 +193,8 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
- image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
- command: ["--config=/etc/otel-collector-config.yaml"]
+ image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.76.1}
+ command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@@ -219,8 +219,8 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
- image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
- command: ["--config=/etc/otel-collector-metrics-config.yaml"]
+ image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.76.1}
+ command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:
diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml
index 409580696a..c331f3a032 100644
--- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml
+++ b/deploy/docker/clickhouse-setup/otel-collector-config.yaml
@@ -70,6 +70,40 @@ receivers:
processors:
+ logstransform/internal:
+ operators:
+ - type: trace_parser
+ if: '"trace_id" in attributes or "span_id" in attributes'
+ trace_id:
+ parse_from: attributes.trace_id
+ span_id:
+ parse_from: attributes.span_id
+ output: remove_trace_id
+ - type: trace_parser
+ if: '"traceId" in attributes or "spanId" in attributes'
+ trace_id:
+ parse_from: attributes.traceId
+ span_id:
+ parse_from: attributes.spanId
+ output: remove_traceId
+ - id: remove_traceId
+ type: remove
+ if: '"traceId" in attributes'
+ field: attributes.traceId
+ output: remove_spanId
+ - id: remove_spanId
+ type: remove
+ if: '"spanId" in attributes'
+ field: attributes.spanId
+ - id: remove_trace_id
+ type: remove
+ if: '"trace_id" in attributes'
+ field: attributes.trace_id
+ output: remove_span_id
+ - id: remove_span_id
+ type: remove
+ if: '"span_id" in attributes'
+ field: attributes.span_id
batch:
send_batch_size: 10000
send_batch_max_size: 11000
@@ -104,7 +138,7 @@ processors:
# retry_on_failure: true
resourcedetection:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
- detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
+ detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
extensions:
@@ -172,5 +206,5 @@ service:
exporters: [prometheus]
logs:
receivers: [otlp, filelog/dockercontainers]
- processors: [batch]
+ processors: [logstransform/internal, batch]
exporters: [clickhouselogsexporter]
\ No newline at end of file
diff --git a/deploy/install.sh b/deploy/install.sh
index e8a14a5821..e908dd8952 100755
--- a/deploy/install.sh
+++ b/deploy/install.sh
@@ -125,7 +125,7 @@ check_ports_occupied() {
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "SigNoz requires ports 3301 & 4317 to be open. Please shut down any other service(s) that may be running on these ports."
- echo "You can run SigNoz on another port following this guide https://signoz.io/docs/deployment/docker#troubleshooting"
+ echo "You can run SigNoz on another port following this guide https://signoz.io/docs/install/troubleshooting/"
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
exit 1
@@ -249,7 +249,7 @@ bye() { # Prints a friendly good bye message and exits the script.
echo ""
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
- # echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker#troubleshooting"
+ echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
echo "++++++++++++++++++++++++++++++++++++++++"
@@ -500,7 +500,7 @@ if [[ $status_code -ne 200 ]]; then
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
- echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker/#troubleshooting-of-common-issues"
+ echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
echo "or reach us on SigNoz for support https://signoz.io/slack"
echo "++++++++++++++++++++++++++++++++++++++++"
diff --git a/ee/query-service/app/api/featureFlags.go b/ee/query-service/app/api/featureFlags.go
index 9c979d17ba..63b36d45c4 100644
--- a/ee/query-service/app/api/featureFlags.go
+++ b/ee/query-service/app/api/featureFlags.go
@@ -5,6 +5,10 @@ import (
)
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
- featureSet := ah.FF().GetFeatureFlags()
+ featureSet, err := ah.FF().GetFeatureFlags()
+ if err != nil {
+ ah.HandleError(w, err, http.StatusInternalServerError)
+ return
+ }
ah.Respond(w, featureSet)
}
diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go
index 72091cc1e3..315a211f9f 100644
--- a/ee/query-service/app/server.go
+++ b/ee/query-service/app/server.go
@@ -22,6 +22,8 @@ import (
"go.signoz.io/signoz/ee/query-service/app/db"
"go.signoz.io/signoz/ee/query-service/dao"
"go.signoz.io/signoz/ee/query-service/interfaces"
+ baseInterface "go.signoz.io/signoz/pkg/query-service/interfaces"
+
licensepkg "go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
@@ -126,7 +128,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
serverOptions.RuleRepoURL,
localDB,
reader,
- serverOptions.DisableRules)
+ serverOptions.DisableRules,
+ lm)
if err != nil {
return nil, err
@@ -402,7 +405,7 @@ func setTimeoutMiddleware(next http.Handler) http.Handler {
// check if route is not excluded
url := r.URL.Path
if _, ok := baseconst.TimeoutExcludedRoutes[url]; !ok {
- ctx, cancel = context.WithTimeout(r.Context(), baseconst.ContextTimeout*time.Second)
+ ctx, cancel = context.WithTimeout(r.Context(), baseconst.ContextTimeout)
defer cancel()
}
@@ -544,7 +547,8 @@ func makeRulesManager(
ruleRepoURL string,
db *sqlx.DB,
ch baseint.Reader,
- disableRules bool) (*rules.Manager, error) {
+ disableRules bool,
+ fm baseInterface.FeatureLookup) (*rules.Manager, error) {
// create engine
pqle, err := pqle.FromConfigPath(promConfigPath)
@@ -571,6 +575,7 @@ func makeRulesManager(
Context: context.Background(),
Logger: nil,
DisableRules: disableRules,
+ FeatureFlags: fm,
}
// create Manager
diff --git a/ee/query-service/license/db.go b/ee/query-service/license/db.go
index a82f0377e2..8d2f7065ff 100644
--- a/ee/query-service/license/db.go
+++ b/ee/query-service/license/db.go
@@ -2,6 +2,7 @@ package license
import (
"context"
+ "database/sql"
"fmt"
"time"
@@ -9,6 +10,7 @@ import (
"go.signoz.io/signoz/ee/query-service/license/sqlite"
"go.signoz.io/signoz/ee/query-service/model"
+ basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -125,3 +127,79 @@ func (r *Repo) UpdatePlanDetails(ctx context.Context,
return nil
}
+
+func (r *Repo) CreateFeature(req *basemodel.Feature) *basemodel.ApiError {
+
+ _, err := r.db.Exec(
+ `INSERT INTO feature_status (name, active, usage, usage_limit, route)
+ VALUES (?, ?, ?, ?, ?);`,
+ req.Name, req.Active, req.Usage, req.UsageLimit, req.Route)
+ if err != nil {
+ return &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}
+ }
+ return nil
+}
+
+func (r *Repo) GetFeature(featureName string) (basemodel.Feature, error) {
+
+ var feature basemodel.Feature
+
+ err := r.db.Get(&feature,
+ `SELECT * FROM feature_status WHERE name = ?;`, featureName)
+ if err != nil {
+ return feature, err
+ }
+ if feature.Name == "" {
+ return feature, basemodel.ErrFeatureUnavailable{Key: featureName}
+ }
+ return feature, nil
+}
+
+func (r *Repo) GetAllFeatures() ([]basemodel.Feature, error) {
+
+ var feature []basemodel.Feature
+
+ err := r.db.Select(&feature,
+ `SELECT * FROM feature_status;`)
+ if err != nil {
+ return feature, err
+ }
+
+ return feature, nil
+}
+
+func (r *Repo) UpdateFeature(req basemodel.Feature) error {
+
+ _, err := r.db.Exec(
+ `UPDATE feature_status SET active = ?, usage = ?, usage_limit = ?, route = ? WHERE name = ?;`,
+ req.Active, req.Usage, req.UsageLimit, req.Route, req.Name)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
+ // get a feature by name, if it doesn't exist, create it. If it does exist, update it.
+ for _, feature := range req {
+ currentFeature, err := r.GetFeature(feature.Name)
+ if err != nil && err == sql.ErrNoRows {
+ err := r.CreateFeature(&feature)
+ if err != nil {
+ return err
+ }
+ continue
+ } else if err != nil {
+ return err
+ }
+ feature.Usage = currentFeature.Usage
+ if feature.Usage >= feature.UsageLimit && feature.UsageLimit != -1 {
+ feature.Active = false
+ }
+ err = r.UpdateFeature(feature)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/ee/query-service/license/manager.go b/ee/query-service/license/manager.go
index a3e9ba0771..7a1be5118f 100644
--- a/ee/query-service/license/manager.go
+++ b/ee/query-service/license/manager.go
@@ -96,6 +96,11 @@ func (lm *Manager) SetActive(l *model.License) {
lm.activeFeatures = l.FeatureSet
// set default features
setDefaultFeatures(lm)
+
+ err := lm.InitFeatures(lm.activeFeatures)
+ if err != nil {
+ zap.S().Panicf("Couldn't activate features: %v", err)
+ }
if !lm.validatorRunning {
// we want to make sure only one validator runs,
// we already have lock() so good to go
@@ -106,9 +111,7 @@ func (lm *Manager) SetActive(l *model.License) {
}
func setDefaultFeatures(lm *Manager) {
- for k, v := range baseconstants.DEFAULT_FEATURE_SET {
- lm.activeFeatures[k] = v
- }
+ lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
}
// LoadActiveLicense loads the most recent active license
@@ -123,8 +126,13 @@ func (lm *Manager) LoadActiveLicense() error {
} else {
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
+ lm.activeFeatures = model.BasicPlan
setDefaultFeatures(lm)
+ err := lm.InitFeatures(lm.activeFeatures)
+ if err != nil {
+ zap.S().Error("Couldn't initialize features: ", err)
+ return err
+ }
}
return nil
@@ -291,18 +299,31 @@ 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 value, ok := lm.activeFeatures[featureKey]; ok {
- if value {
- return nil
- }
- return basemodel.ErrFeatureUnavailable{Key: featureKey}
+ feature, err := lm.repo.GetFeature(featureKey)
+ if err != nil {
+ return err
+ }
+ if feature.Active {
+ return nil
}
return basemodel.ErrFeatureUnavailable{Key: featureKey}
}
// GetFeatureFlags returns current active features
-func (lm *Manager) GetFeatureFlags() basemodel.FeatureSet {
- return lm.activeFeatures
+func (lm *Manager) GetFeatureFlags() (basemodel.FeatureSet, error) {
+ return lm.repo.GetAllFeatures()
+}
+
+func (lm *Manager) InitFeatures(features basemodel.FeatureSet) error {
+ return lm.repo.InitFeatures(features)
+}
+
+func (lm *Manager) UpdateFeatureFlag(feature basemodel.Feature) error {
+ return lm.repo.UpdateFeature(feature)
+}
+
+func (lm *Manager) GetFeatureFlag(key string) (basemodel.Feature, error) {
+ return lm.repo.GetFeature(key)
}
// GetRepo return the license repo
diff --git a/ee/query-service/license/sqlite/init.go b/ee/query-service/license/sqlite/init.go
index a03153659c..e500ddb4aa 100644
--- a/ee/query-service/license/sqlite/init.go
+++ b/ee/query-service/license/sqlite/init.go
@@ -2,6 +2,7 @@ package sqlite
import (
"fmt"
+
"github.com/jmoiron/sqlx"
)
@@ -33,5 +34,19 @@ func InitDB(db *sqlx.DB) error {
if err != nil {
return fmt.Errorf("Error in creating licenses table: %s", err.Error())
}
+
+ table_schema = `CREATE TABLE IF NOT EXISTS feature_status (
+ name TEXT PRIMARY KEY,
+ active bool,
+ usage INTEGER DEFAULT 0,
+ usage_limit INTEGER DEFAULT 0,
+ route TEXT
+ );`
+
+ _, err = db.Exec(table_schema)
+ if err != nil {
+ return fmt.Errorf("Error in creating feature_status table: %s", err.Error())
+ }
+
return nil
}
diff --git a/ee/query-service/model/plans.go b/ee/query-service/model/plans.go
index c42712f693..52ebd5c5b5 100644
--- a/ee/query-service/model/plans.go
+++ b/ee/query-service/model/plans.go
@@ -11,21 +11,143 @@ const Enterprise = "ENTERPRISE_PLAN"
const DisableUpsell = "DISABLE_UPSELL"
var BasicPlan = basemodel.FeatureSet{
- Basic: true,
- SSO: false,
- DisableUpsell: false,
+ basemodel.Feature{
+ Name: SSO,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.OSS,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: DisableUpsell,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.SmartTraceDetail,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.CustomMetricsFunction,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderPanels,
+ Active: true,
+ Usage: 0,
+ UsageLimit: 5,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderAlerts,
+ Active: true,
+ Usage: 0,
+ UsageLimit: 5,
+ Route: "",
+ },
}
var ProPlan = basemodel.FeatureSet{
- Pro: true,
- SSO: true,
- basemodel.SmartTraceDetail: true,
- basemodel.CustomMetricsFunction: true,
+ basemodel.Feature{
+ Name: SSO,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.OSS,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.SmartTraceDetail,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.CustomMetricsFunction,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderPanels,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderAlerts,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
}
var EnterprisePlan = basemodel.FeatureSet{
- Enterprise: true,
- SSO: true,
- basemodel.SmartTraceDetail: true,
- basemodel.CustomMetricsFunction: true,
+ basemodel.Feature{
+ Name: SSO,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.OSS,
+ Active: false,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.SmartTraceDetail,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.CustomMetricsFunction,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderPanels,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
+ basemodel.Feature{
+ Name: basemodel.QueryBuilderAlerts,
+ Active: true,
+ Usage: 0,
+ UsageLimit: -1,
+ Route: "",
+ },
}
diff --git a/frontend/.babelrc b/frontend/.babelrc
index aa9ef302a0..9efe6ca907 100644
--- a/frontend/.babelrc
+++ b/frontend/.babelrc
@@ -1,7 +1,7 @@
{
"presets": [
"@babel/preset-env",
- "@babel/preset-react",
+ ["@babel/preset-react", { "runtime": "automatic" }],
"@babel/preset-typescript"
],
"plugins": [
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js
index 37c7b17c45..540b1ded70 100644
--- a/frontend/.eslintrc.js
+++ b/frontend/.eslintrc.js
@@ -16,6 +16,7 @@ module.exports = {
'plugin:sonarjs/recommended',
'plugin:import/errors',
'plugin:import/warnings',
+ 'plugin:react/jsx-runtime',
],
parser: '@typescript-eslint/parser',
parserOptions: {
diff --git a/frontend/.yarnrc b/frontend/.yarnrc
index 788570fcd5..843c88f028 100644
--- a/frontend/.yarnrc
+++ b/frontend/.yarnrc
@@ -1 +1,2 @@
network-timeout 600000
+save-prefix ""
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 145f205a9d..cf9b792cf1 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,5 +1,5 @@
# Builder stage
-FROM node:16.15.0-slim as builder
+FROM node:16.15.0 as builder
# Add Maintainer Info
LABEL maintainer="signoz"
@@ -11,6 +11,8 @@ WORKDIR /frontend
# Copy the package.json and .yarnrc files prior to install dependencies
COPY package.json ./
+# Copy lock file
+COPY yarn.lock ./
COPY .yarnrc ./
# Install the dependencies and make the folder
diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts
index b3b8061422..c9441402d9 100644
--- a/frontend/jest.setup.ts
+++ b/frontend/jest.setup.ts
@@ -1,5 +1,20 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+/* eslint-disable object-shorthand */
+/* eslint-disable func-names */
+
/**
* Adds custom matchers from the react testing library to all tests
*/
import '@testing-library/jest-dom';
import 'jest-styled-components';
+
+// Mock window.matchMedia
+window.matchMedia =
+ window.matchMedia ||
+ function (): any {
+ return {
+ matches: false,
+ addListener: function () {},
+ removeListener: function () {},
+ };
+ };
diff --git a/frontend/package.json b/frontend/package.json
index 24e883bb8b..f26a92c9be 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -48,15 +48,11 @@
"cross-env": "^7.0.3",
"css-loader": "4.3.0",
"css-minimizer-webpack-plugin": "^3.2.0",
- "d3": "^6.2.0",
- "d3-flame-graph": "^3.1.1",
- "d3-tip": "^0.9.1",
"dayjs": "^1.10.7",
"dompurify": "3.0.0",
"dotenv": "8.2.0",
"event-source-polyfill": "1.0.31",
"file-loader": "6.1.1",
- "flat": "^5.0.2",
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.1.0",
@@ -69,10 +65,10 @@
"less-loader": "^10.2.0",
"lodash-es": "^4.17.21",
"mini-css-extract-plugin": "2.4.5",
+ "papaparse": "5.4.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-force-graph": "^1.41.0",
- "react-graph-vis": "^1.0.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.1",
"react-intersection-observer": "9.4.1",
@@ -126,16 +122,14 @@
"@types/color": "^3.0.3",
"@types/compression-webpack-plugin": "^9.0.0",
"@types/copy-webpack-plugin": "^8.0.1",
- "@types/d3": "^6.2.0",
- "@types/d3-tip": "^3.5.5",
"@types/dompurify": "^2.4.0",
"@types/event-source-polyfill": "^1.0.0",
- "@types/flat": "^5.0.2",
"@types/fontfaceobserver": "2.1.0",
"@types/jest": "^27.5.1",
"@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1",
"@types/node": "^16.10.3",
+ "@types/papaparse": "5.3.7",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
@@ -144,7 +138,6 @@
"@types/react-router-dom": "^5.1.6",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
- "@types/vis": "^4.21.21",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^4.28.2",
@@ -174,7 +167,6 @@
"is-ci": "^3.0.1",
"jest-playwright-preset": "^1.7.0",
"jest-styled-components": "^7.0.8",
- "less-plugin-npm-import": "^2.1.0",
"lint-staged": "^12.3.7",
"portfinder-sync": "^0.0.2",
"prettier": "2.2.1",
diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx
index 82d6be2620..ddfb072d02 100644
--- a/frontend/src/AppRoutes/Private.tsx
+++ b/frontend/src/AppRoutes/Private.tsx
@@ -7,7 +7,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useEffect, useMemo } from 'react';
+import { ReactChild, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { matchPath, Redirect, useLocation } from 'react-router-dom';
@@ -161,7 +161,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
interface PrivateRouteProps {
- children: React.ReactChild;
+ children: ReactChild;
}
export default PrivateRoute;
diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx
index edfe843882..a4ccb0d0f0 100644
--- a/frontend/src/AppRoutes/index.tsx
+++ b/frontend/src/AppRoutes/index.tsx
@@ -7,7 +7,7 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
-import React, { Suspense } from 'react';
+import { Suspense } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import PrivateRoute from './Private';
diff --git a/frontend/src/api/features/getFeatures.ts b/frontend/src/api/features/getFeatures.ts
deleted file mode 100644
index ca6bf30ca7..0000000000
--- a/frontend/src/api/features/getFeatures.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import axios from 'api';
-import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
-import { AxiosError } from 'axios';
-import { ErrorResponse, SuccessResponse } from 'types/api';
-import { PayloadProps } from 'types/api/features/getFeatures';
-
-const getFeaturesFlags = async (): Promise<
- SuccessResponse | ErrorResponse
-> => {
- try {
- const response = await axios.get(`/featureFlags`);
- return {
- statusCode: 200,
- error: null,
- message: response.data.status,
- payload: response.data.data,
- };
- } catch (error) {
- return ErrorResponseHandler(error as AxiosError);
- }
-};
-
-export default getFeaturesFlags;
diff --git a/frontend/src/api/metrics/getQueryRange.ts b/frontend/src/api/metrics/getQueryRange.ts
index b6715f85e6..9a0a22bda7 100644
--- a/frontend/src/api/metrics/getQueryRange.ts
+++ b/frontend/src/api/metrics/getQueryRange.ts
@@ -1,17 +1,17 @@
-import { ApiV2Instance as axios } from 'api';
+import { ApiV3Instance as axios } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
- MetricRangePayloadProps,
+ MetricRangePayloadV3,
MetricsRangeProps,
} from 'types/api/metrics/getQueryRange';
export const getMetricsQueryRange = async (
props: MetricsRangeProps,
-): Promise | ErrorResponse> => {
+): Promise | ErrorResponse> => {
try {
- const response = await axios.post(`/metrics/query_range`, props);
+ const response = await axios.post('/query_range', props);
return {
statusCode: 200,
diff --git a/frontend/src/api/queryBuilder/getAggregateAttribute.ts b/frontend/src/api/queryBuilder/getAggregateAttribute.ts
index 15e221d975..cbd6740f31 100644
--- a/frontend/src/api/queryBuilder/getAggregateAttribute.ts
+++ b/frontend/src/api/queryBuilder/getAggregateAttribute.ts
@@ -1,11 +1,16 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
+import createQueryParams from 'lib/createQueryParams';
// ** Helpers
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute';
-import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import {
+ BaseAutocompleteData,
+ IQueryAutocompleteResponse,
+} from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { v4 as uuid } from 'uuid';
export const getAggregateAttribute = async ({
aggregateOperator,
@@ -18,14 +23,22 @@ export const getAggregateAttribute = async ({
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
- `autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`,
+ `autocomplete/aggregate_attributes?${createQueryParams({
+ aggregateOperator,
+ searchText,
+ dataSource,
+ })}`,
);
+ const payload: BaseAutocompleteData[] =
+ response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) ||
+ [];
+
return {
statusCode: 200,
error: null,
message: response.statusText,
- payload: response.data.data,
+ payload: { attributeKeys: payload },
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);
diff --git a/frontend/src/api/queryBuilder/getAttributeKeys.ts b/frontend/src/api/queryBuilder/getAttributeKeys.ts
index b42566a75e..b47509de0a 100644
--- a/frontend/src/api/queryBuilder/getAttributeKeys.ts
+++ b/frontend/src/api/queryBuilder/getAttributeKeys.ts
@@ -1,16 +1,22 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
+import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
-import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import {
+ BaseAutocompleteData,
+ IQueryAutocompleteResponse,
+} from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { v4 as uuid } from 'uuid';
export const getAggregateKeys = async ({
aggregateOperator,
searchText,
dataSource,
aggregateAttribute,
+ tagType,
}: IGetAttributeKeysPayload): Promise<
SuccessResponse | ErrorResponse
> => {
@@ -18,14 +24,23 @@ export const getAggregateKeys = async ({
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
- `autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`,
+ `autocomplete/attribute_keys?${createQueryParams({
+ aggregateOperator,
+ searchText,
+ dataSource,
+ aggregateAttribute,
+ })}&tagType=${tagType}`,
);
+ const payload: BaseAutocompleteData[] =
+ response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) ||
+ [];
+
return {
statusCode: 200,
error: null,
message: response.statusText,
- payload: response.data.data,
+ payload: { attributeKeys: payload },
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);
diff --git a/frontend/src/api/queryBuilder/getAttributesValues.ts b/frontend/src/api/queryBuilder/getAttributesValues.ts
new file mode 100644
index 0000000000..216da1e451
--- /dev/null
+++ b/frontend/src/api/queryBuilder/getAttributesValues.ts
@@ -0,0 +1,42 @@
+import { ApiV3Instance } from 'api';
+import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
+import { AxiosError } from 'axios';
+import createQueryParams from 'lib/createQueryParams';
+import { ErrorResponse, SuccessResponse } from 'types/api';
+import {
+ IAttributeValuesResponse,
+ IGetAttributeValuesPayload,
+} from 'types/api/queryBuilder/getAttributesValues';
+
+export const getAttributesValues = async ({
+ aggregateOperator,
+ dataSource,
+ aggregateAttribute,
+ attributeKey,
+ filterAttributeKeyDataType,
+ tagType,
+ searchText,
+}: IGetAttributeValuesPayload): Promise<
+ SuccessResponse | ErrorResponse
+> => {
+ try {
+ const response = await ApiV3Instance.get(
+ `/autocomplete/attribute_values?${createQueryParams({
+ aggregateOperator,
+ dataSource,
+ aggregateAttribute,
+ attributeKey,
+ searchText,
+ })}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
+ );
+
+ return {
+ statusCode: 200,
+ error: null,
+ message: response.data.status,
+ payload: response.data.data,
+ };
+ } catch (error) {
+ return ErrorResponseHandler(error as AxiosError);
+ }
+};
diff --git a/frontend/src/assets/Dashboard/TimeSeries.tsx b/frontend/src/assets/Dashboard/TimeSeries.tsx
index 46e30e29fc..54d8100a63 100644
--- a/frontend/src/assets/Dashboard/TimeSeries.tsx
+++ b/frontend/src/assets/Dashboard/TimeSeries.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
function TimeSeries(): JSX.Element {
return (
({
+ useIsDarkMode: jest.fn(),
+}));
+
+describe('Editor', () => {
+ it('renders correctly with default props', () => {
+ const { container } = render( );
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders correctly with custom props', () => {
+ const customProps = {
+ value: 'test',
+ language: 'javascript',
+ readOnly: true,
+ height: '50vh',
+ options: { minimap: { enabled: false } },
+ };
+ const { container } = render(
+ ,
+ );
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders with dark mode theme', () => {
+ (useIsDarkMode as jest.Mock).mockImplementation(() => true);
+
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('renders with light mode theme', () => {
+ (useIsDarkMode as jest.Mock).mockImplementation(() => false);
+
+ const { container } = render( );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('displays "Loading..." message initially', () => {
+ const { rerender } = render( );
+
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+
+ rerender( );
+ });
+});
diff --git a/frontend/src/components/Editor/__snapshots__/Editor.test.tsx.snap b/frontend/src/components/Editor/__snapshots__/Editor.test.tsx.snap
new file mode 100644
index 0000000000..1670cedb9c
--- /dev/null
+++ b/frontend/src/components/Editor/__snapshots__/Editor.test.tsx.snap
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Editor renders correctly with custom props 1`] = `
+
+`;
+
+exports[`Editor renders correctly with default props 1`] = `
+
+`;
+
+exports[`Editor renders with dark mode theme 1`] = `
+
+`;
+
+exports[`Editor renders with light mode theme 1`] = `
+
+`;
diff --git a/frontend/src/components/Editor/index.tsx b/frontend/src/components/Editor/index.tsx
index e24fab3c80..5f70d92c41 100644
--- a/frontend/src/components/Editor/index.tsx
+++ b/frontend/src/components/Editor/index.tsx
@@ -1,6 +1,6 @@
import MEditor, { EditorProps } from '@monaco-editor/react';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
function Editor({
value,
@@ -13,6 +13,8 @@ function Editor({
const isDarkMode = useIsDarkMode();
const onChangeHandler = (newValue?: string): void => {
+ if (readOnly) return;
+
if (typeof newValue === 'string' && onChange) onChange(newValue);
};
@@ -29,6 +31,7 @@ function Editor({
options={editorOptions}
height={height}
onChange={onChangeHandler}
+ data-testid="monaco-editor"
/>
);
}
diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx
index 527510f764..f4ea8b14b2 100644
--- a/frontend/src/components/Graph/index.tsx
+++ b/frontend/src/components/Graph/index.tsx
@@ -26,7 +26,7 @@ import annotationPlugin from 'chartjs-plugin-annotation';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import isEqual from 'lodash-es/isEqual';
-import React, { memo, useCallback, useEffect, useRef } from 'react';
+import { memo, useCallback, useEffect, useRef } from 'react';
import { hasData } from './hasData';
import { getAxisLabelColor } from './helpers';
diff --git a/frontend/src/components/Input/index.tsx b/frontend/src/components/Input/index.tsx
index 6516b6b209..18a84f611a 100644
--- a/frontend/src/components/Input/index.tsx
+++ b/frontend/src/components/Input/index.tsx
@@ -1,5 +1,12 @@
import { Form, Input, InputProps, InputRef } from 'antd';
-import React from 'react';
+import {
+ ChangeEventHandler,
+ FocusEventHandler,
+ KeyboardEventHandler,
+ LegacyRef,
+ ReactNode,
+ Ref,
+} from 'react';
function InputComponent({
value,
@@ -22,7 +29,7 @@ function InputComponent({
type={type}
onChange={onChangeHandler}
value={value}
- ref={ref as React.Ref}
+ ref={ref as Ref}
size={size}
addonBefore={addonBefore}
onBlur={onBlurHandler}
@@ -37,15 +44,15 @@ function InputComponent({
interface InputComponentProps extends InputProps {
value: InputProps['value'];
type?: InputProps['type'];
- onChangeHandler?: React.ChangeEventHandler;
+ onChangeHandler?: ChangeEventHandler;
placeholder?: InputProps['placeholder'];
- ref?: React.LegacyRef;
+ ref?: LegacyRef;
size?: InputProps['size'];
- onBlurHandler?: React.FocusEventHandler;
- onPressEnterHandler?: React.KeyboardEventHandler;
+ onBlurHandler?: FocusEventHandler;
+ onPressEnterHandler?: KeyboardEventHandler;
label?: string;
labelOnTop?: boolean;
- addonBefore?: React.ReactNode;
+ addonBefore?: ReactNode;
}
InputComponent.defaultProps = {
diff --git a/frontend/src/components/Loadable/Loadable.test.tsx b/frontend/src/components/Loadable/Loadable.test.tsx
new file mode 100644
index 0000000000..2d2a2173e5
--- /dev/null
+++ b/frontend/src/components/Loadable/Loadable.test.tsx
@@ -0,0 +1,49 @@
+import {
+ render,
+ screen,
+ waitForElementToBeRemoved,
+} from '@testing-library/react';
+import React, { ComponentType, Suspense } from 'react';
+
+import Loadable from './index';
+
+// Sample component to be loaded lazily
+function SampleComponent(): JSX.Element {
+ return Sample Component
;
+}
+
+const loadSampleComponent = (): Promise<{
+ default: ComponentType;
+}> =>
+ new Promise<{ default: ComponentType }>((resolve) => {
+ setTimeout(() => {
+ resolve({ default: SampleComponent });
+ }, 500);
+ });
+
+describe('Loadable', () => {
+ it('should render the lazily loaded component', async () => {
+ const LoadableSampleComponent = Loadable(loadSampleComponent);
+
+ const { container } = render(
+ Loading...}>
+
+ ,
+ );
+
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+ await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));
+
+ expect(container.querySelector('div')).toHaveTextContent('Sample Component');
+ });
+
+ it('should call lazy with the provided import path', () => {
+ const reactLazySpy = jest.spyOn(React, 'lazy');
+ Loadable(loadSampleComponent);
+
+ expect(reactLazySpy).toHaveBeenCalledTimes(1);
+ expect(reactLazySpy).toHaveBeenCalledWith(expect.any(Function));
+
+ reactLazySpy.mockRestore();
+ });
+});
diff --git a/frontend/src/components/Loadable/index.tsx b/frontend/src/components/Loadable/index.tsx
index 60c794f542..5cffc5793e 100644
--- a/frontend/src/components/Loadable/index.tsx
+++ b/frontend/src/components/Loadable/index.tsx
@@ -1,8 +1,8 @@
-import { ComponentType, lazy } from 'react';
+import { ComponentType, lazy, LazyExoticComponent } from 'react';
function Loadable(importPath: {
(): LoadableProps;
-}): React.LazyExoticComponent {
+}): LazyExoticComponent {
return lazy(() => importPath());
}
diff --git a/frontend/src/components/Logs/AddToQueryHOC.tsx b/frontend/src/components/Logs/AddToQueryHOC.tsx
index d8aee19708..874a9a0ec7 100644
--- a/frontend/src/components/Logs/AddToQueryHOC.tsx
+++ b/frontend/src/components/Logs/AddToQueryHOC.tsx
@@ -1,13 +1,14 @@
-import { Button, Popover } from 'antd';
+import { Popover } from 'antd';
+import ROUTES from 'constants/routes';
+import history from 'lib/history';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
-import React, { memo, useCallback, useMemo } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { Dispatch } from 'redux';
+import { memo, ReactNode, useCallback, useMemo } from 'react';
+import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
-import AppActions from 'types/actions';
-import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs';
+import { ButtonContainer } from './styles';
+
function AddToQueryHOC({
fieldKey,
fieldValue,
@@ -16,7 +17,6 @@ function AddToQueryHOC({
const {
searchFilter: { queryString },
} = useSelector((store) => store.logs);
- const dispatch = useDispatch>();
const generatedQuery = useMemo(
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
@@ -31,31 +31,27 @@ function AddToQueryHOC({
} else {
updatedQueryString += ` AND ${generatedQuery}`;
}
- dispatch({
- type: SET_SEARCH_QUERY_STRING,
- payload: {
- searchQueryString: updatedQueryString,
- },
- });
- }, [dispatch, generatedQuery, queryString]);
+
+ history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
+ }, [generatedQuery, queryString]);
const popOverContent = useMemo(() => Add to query: {fieldKey} , [
fieldKey,
]);
return (
-
+
{children}
-
+
);
}
interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
- children: React.ReactNode;
+ children: ReactNode;
}
export default memo(AddToQueryHOC);
diff --git a/frontend/src/components/Logs/CategoryHeading/index.tsx b/frontend/src/components/Logs/CategoryHeading/index.tsx
index f48160593a..b7d8ae7b50 100644
--- a/frontend/src/components/Logs/CategoryHeading/index.tsx
+++ b/frontend/src/components/Logs/CategoryHeading/index.tsx
@@ -1,9 +1,9 @@
-import React from 'react';
+import { ReactNode } from 'react';
import { CategoryHeadingText } from './styles';
interface ICategoryHeadingProps {
- children: React.ReactNode;
+ children: ReactNode;
}
function CategoryHeading({ children }: ICategoryHeadingProps): JSX.Element {
return {children} ;
diff --git a/frontend/src/components/Logs/CopyClipboardHOC.tsx b/frontend/src/components/Logs/CopyClipboardHOC.tsx
index 8296b8a05c..a12208bf77 100644
--- a/frontend/src/components/Logs/CopyClipboardHOC.tsx
+++ b/frontend/src/components/Logs/CopyClipboardHOC.tsx
@@ -1,6 +1,6 @@
import { Popover } from 'antd';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useEffect } from 'react';
+import { ReactNode, useCallback, useEffect } from 'react';
import { useCopyToClipboard } from 'react-use';
function CopyClipboardHOC({
@@ -22,7 +22,7 @@ function CopyClipboardHOC({
}, [setCopy, textToCopy]);
return (
-
+
Copy to clipboard }
@@ -35,7 +35,7 @@ function CopyClipboardHOC({
interface CopyClipboardHOCProps {
textToCopy: string;
- children: React.ReactNode;
+ children: ReactNode;
}
export default CopyClipboardHOC;
diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx
index 6095d343fc..ebee0899c7 100644
--- a/frontend/src/components/Logs/ListLogView/index.tsx
+++ b/frontend/src/components/Logs/ListLogView/index.tsx
@@ -2,13 +2,12 @@ import { blue, grey, orange } from '@ant-design/colors';
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
import Convert from 'ansi-to-html';
import { Button, Divider, Row, Typography } from 'antd';
-import { map } from 'd3';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useNotifications } from 'hooks/useNotifications';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
-import React, { useCallback, useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use';
// interfaces
@@ -127,7 +126,7 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
>
- {map(updatedSelecedFields, (field) =>
+ {updatedSelecedFields.map((field) =>
isValidLogField(flattenLogData[field.name] as never) ? (
`
font-size: 0.875rem;
line-height: 2rem;
-
- cursor: pointer;
`;
diff --git a/frontend/src/components/Logs/styles.ts b/frontend/src/components/Logs/styles.ts
new file mode 100644
index 0000000000..53f31192f9
--- /dev/null
+++ b/frontend/src/components/Logs/styles.ts
@@ -0,0 +1,8 @@
+import { Button } from 'antd';
+import styled from 'styled-components';
+
+export const ButtonContainer = styled(Button)`
+ &&& {
+ padding-left: 0;
+ }
+`;
diff --git a/frontend/src/components/MessageTip/MessageTip.test.tsx b/frontend/src/components/MessageTip/MessageTip.test.tsx
new file mode 100644
index 0000000000..1c050c01f6
--- /dev/null
+++ b/frontend/src/components/MessageTip/MessageTip.test.tsx
@@ -0,0 +1,47 @@
+import { render, screen } from '@testing-library/react';
+
+import MessageTip from './index';
+
+describe('MessageTip', () => {
+ it('should not render when show prop is false', () => {
+ render(
+ Close}
+ />,
+ );
+
+ const messageTip = screen.queryByRole('alert');
+
+ expect(messageTip).toBeNull();
+ });
+
+ it('should render with the provided message and action', () => {
+ const message = 'Test Message';
+ const action = Close ;
+
+ render( );
+
+ const messageTip = screen.getByRole('alert');
+ const messageText = screen.getByText(message);
+ const actionButton = screen.getByRole('button', { name: 'Close' });
+
+ expect(messageTip).toBeInTheDocument();
+ expect(messageText).toBeInTheDocument();
+ expect(actionButton).toBeInTheDocument();
+ });
+
+ // taken from antd docs
+ // https://github.com/ant-design/ant-design/blob/master/components/alert/__tests__/index.test.tsx
+ it('custom action', () => {
+ const { container } = render(
+ Close}
+ />,
+ );
+ expect(container.firstChild).toMatchSnapshot();
+ });
+});
diff --git a/frontend/src/components/MessageTip/__snapshots__/MessageTip.test.tsx.snap b/frontend/src/components/MessageTip/__snapshots__/MessageTip.test.tsx.snap
new file mode 100644
index 0000000000..044b288468
--- /dev/null
+++ b/frontend/src/components/MessageTip/__snapshots__/MessageTip.test.tsx.snap
@@ -0,0 +1,54 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MessageTip custom action 1`] = `
+.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+`;
diff --git a/frontend/src/components/MessageTip/index.tsx b/frontend/src/components/MessageTip/index.tsx
index 67cdb41703..23c740395e 100644
--- a/frontend/src/components/MessageTip/index.tsx
+++ b/frontend/src/components/MessageTip/index.tsx
@@ -1,11 +1,11 @@
-import React from 'react';
+import { ReactNode } from 'react';
import { StyledAlert } from './styles';
interface MessageTipProps {
show?: boolean;
- message: React.ReactNode | string;
- action: React.ReactNode | undefined;
+ message: ReactNode | string;
+ action: ReactNode | undefined;
}
function MessageTip({
diff --git a/frontend/src/components/Modal.tsx b/frontend/src/components/Modal.tsx
index 83e380a981..a8f8bb6532 100644
--- a/frontend/src/components/Modal.tsx
+++ b/frontend/src/components/Modal.tsx
@@ -1,5 +1,5 @@
import { Modal, ModalProps as Props } from 'antd';
-import React, { ReactElement } from 'react';
+import { ReactElement } from 'react';
function CustomModal({
title,
diff --git a/frontend/src/components/NotFound/NotFound.test.tsx b/frontend/src/components/NotFound/NotFound.test.tsx
index 0eed86262c..f72596e875 100644
--- a/frontend/src/components/NotFound/NotFound.test.tsx
+++ b/frontend/src/components/NotFound/NotFound.test.tsx
@@ -1,5 +1,4 @@
import { render } from '@testing-library/react';
-import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from 'store';
diff --git a/frontend/src/components/NotFound/index.tsx b/frontend/src/components/NotFound/index.tsx
index 04d140e34f..5af3c4640c 100644
--- a/frontend/src/components/NotFound/index.tsx
+++ b/frontend/src/components/NotFound/index.tsx
@@ -2,7 +2,7 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import NotFoundImage from 'assets/NotFound';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
diff --git a/frontend/src/components/ReleaseNote/Releases/ReleaseNote0120.tsx b/frontend/src/components/ReleaseNote/Releases/ReleaseNote0120.tsx
index 78b2800b61..249147fcde 100644
--- a/frontend/src/components/ReleaseNote/Releases/ReleaseNote0120.tsx
+++ b/frontend/src/components/ReleaseNote/Releases/ReleaseNote0120.tsx
@@ -1,7 +1,7 @@
import { Button, Space } from 'antd';
import setFlags from 'api/user/setFlags';
import MessageTip from 'components/MessageTip';
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/components/ReleaseNote/index.tsx b/frontend/src/components/ReleaseNote/index.tsx
index daede3a51f..bfabbd2637 100644
--- a/frontend/src/components/ReleaseNote/index.tsx
+++ b/frontend/src/components/ReleaseNote/index.tsx
@@ -1,7 +1,6 @@
import ReleaseNoteProps from 'components/ReleaseNote/ReleaseNoteProps';
import ReleaseNote0120 from 'components/ReleaseNote/Releases/ReleaseNote0120';
import ROUTES from 'constants/routes';
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { UserFlags } from 'types/api/user/setFlags';
diff --git a/frontend/src/components/ResizeTable/ResizableHeader.tsx b/frontend/src/components/ResizeTable/ResizableHeader.tsx
index b3119c0c1a..8611a45886 100644
--- a/frontend/src/components/ResizeTable/ResizableHeader.tsx
+++ b/frontend/src/components/ResizeTable/ResizableHeader.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import { SyntheticEvent, useMemo } from 'react';
import { Resizable, ResizeCallbackData } from 'react-resizable';
import { enableUserSelectHack } from './config';
@@ -37,7 +37,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
}
interface ResizableHeaderProps {
- onResize: (e: React.SyntheticEvent, data: ResizeCallbackData) => void;
+ onResize: (e: SyntheticEvent, data: ResizeCallbackData) => void;
width: number;
}
diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx
index b7aee770c9..681d8b8670 100644
--- a/frontend/src/components/ResizeTable/ResizeTable.tsx
+++ b/frontend/src/components/ResizeTable/ResizeTable.tsx
@@ -1,7 +1,7 @@
import { Table } from 'antd';
import type { TableProps } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table';
-import React, { useCallback, useMemo, useState } from 'react';
+import { SyntheticEvent, useCallback, useMemo, useState } from 'react';
import { ResizeCallbackData } from 'react-resizable';
import ResizableHeader from './ResizableHeader';
@@ -12,7 +12,7 @@ function ResizeTable({ columns, ...restprops }: TableProps): JSX.Element {
const handleResize = useCallback(
(index: number) => (
- _e: React.SyntheticEvent,
+ _e: SyntheticEvent,
{ size }: ResizeCallbackData,
): void => {
const newColumns = [...columnsData];
diff --git a/frontend/src/components/RouteTab/index.tsx b/frontend/src/components/RouteTab/index.tsx
index 0059f5b84c..07b9f70938 100644
--- a/frontend/src/components/RouteTab/index.tsx
+++ b/frontend/src/components/RouteTab/index.tsx
@@ -1,6 +1,5 @@
import { Tabs, TabsProps } from 'antd';
import history from 'lib/history';
-import React from 'react';
function RouteTab({
routes,
diff --git a/frontend/src/components/Spinner/index.tsx b/frontend/src/components/Spinner/index.tsx
index 819e552442..669b3f0cda 100644
--- a/frontend/src/components/Spinner/index.tsx
+++ b/frontend/src/components/Spinner/index.tsx
@@ -1,6 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, SpinProps } from 'antd';
-import React from 'react';
+import { CSSProperties } from 'react';
import { SpinerStyle } from './styles';
@@ -15,8 +15,8 @@ function Spinner({ size, tip, height, style }: SpinnerProps): JSX.Element {
interface SpinnerProps {
size?: SpinProps['size'];
tip?: SpinProps['tip'];
- height?: React.CSSProperties['height'];
- style?: React.CSSProperties;
+ height?: CSSProperties['height'];
+ style?: CSSProperties;
}
Spinner.defaultProps = {
size: undefined,
diff --git a/frontend/src/components/Spinner/styles.ts b/frontend/src/components/Spinner/styles.ts
index 53a77a8978..6763239c40 100644
--- a/frontend/src/components/Spinner/styles.ts
+++ b/frontend/src/components/Spinner/styles.ts
@@ -1,8 +1,8 @@
-import React from 'react';
+import { CSSProperties } from 'react';
import styled from 'styled-components';
interface Props {
- height: React.CSSProperties['height'];
+ height: CSSProperties['height'];
}
export const SpinerStyle = styled.div`
diff --git a/frontend/src/components/Styled/index.ts b/frontend/src/components/Styled/index.ts
index ffa3fbccc0..579e15a499 100644
--- a/frontend/src/components/Styled/index.ts
+++ b/frontend/src/components/Styled/index.ts
@@ -1,7 +1,7 @@
import * as AntD from 'antd';
import { TextProps } from 'antd/lib/typography/Text';
import { TitleProps } from 'antd/lib/typography/Title';
-import React from 'react';
+import { HTMLAttributes } from 'react';
import styled, { FlattenSimpleInterpolation } from 'styled-components';
import { IStyledClass } from './types';
@@ -51,7 +51,7 @@ const StyledTypographyTitle = styled(Title)`
${styledClass}
`;
-type TStyledDiv = React.HTMLAttributes & IStyledClass;
+type TStyledDiv = HTMLAttributes & IStyledClass;
const StyledDiv = styled.div`
${styledClass}
`;
diff --git a/frontend/src/components/TextToolTip/TextToolTip.test.tsx b/frontend/src/components/TextToolTip/TextToolTip.test.tsx
new file mode 100644
index 0000000000..65192cf001
--- /dev/null
+++ b/frontend/src/components/TextToolTip/TextToolTip.test.tsx
@@ -0,0 +1,53 @@
+import { fireEvent, render, waitFor } from '@testing-library/react';
+
+import TextToolTip from './index';
+
+describe('TextToolTip', () => {
+ const tooltipText = 'Tooltip Text';
+
+ it('displays the tooltip when hovering over the icon', async () => {
+ const { getByRole, getByText } = render( );
+ const icon = getByRole('img');
+ fireEvent.mouseOver(icon);
+
+ await waitFor(() => {
+ const tooltip = getByText(tooltipText);
+ expect(tooltip).toBeInTheDocument();
+ });
+ });
+
+ it('renders the tooltip content correctly', async () => {
+ const { getByRole, getByText } = render(
+ ,
+ );
+ const icon = getByRole('img');
+ fireEvent.mouseOver(icon);
+
+ await waitFor(() => {
+ const tooltip = getByText(tooltipText);
+ const link = getByText('here');
+ expect(tooltip).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(link).toHaveAttribute('target', '_blank');
+ });
+ });
+
+ it('does not display the tooltip by default', () => {
+ const { queryByText } = render( );
+ const tooltip = queryByText(tooltipText);
+ expect(tooltip).toBeNull();
+ });
+
+ it('opens the URL in a new tab when clicked', async () => {
+ const { getByRole } = render(
+ ,
+ );
+ const icon = getByRole('img');
+ fireEvent.mouseOver(icon);
+ await waitFor(() => {
+ const link = getByRole('link');
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(link).toHaveAttribute('target', '_blank');
+ });
+ });
+});
diff --git a/frontend/src/components/TextToolTip/index.tsx b/frontend/src/components/TextToolTip/index.tsx
index 64a9cd053a..f01a623988 100644
--- a/frontend/src/components/TextToolTip/index.tsx
+++ b/frontend/src/components/TextToolTip/index.tsx
@@ -3,7 +3,7 @@ import { QuestionCircleFilled } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { style } from './styles';
diff --git a/frontend/src/components/TimePreferenceDropDown/config.tsx b/frontend/src/components/TimePreferenceDropDown/config.tsx
index bf09498ec7..e7bdd793db 100644
--- a/frontend/src/components/TimePreferenceDropDown/config.tsx
+++ b/frontend/src/components/TimePreferenceDropDown/config.tsx
@@ -1,6 +1,5 @@
import { Typography } from 'antd';
import { timeItems } from 'container/NewWidget/RightContainer/timeItems';
-import React from 'react';
export const menuItems = timeItems.map((item) => ({
key: item.enum,
diff --git a/frontend/src/components/TimePreferenceDropDown/index.tsx b/frontend/src/components/TimePreferenceDropDown/index.tsx
index 3ce9795f15..b24c07b3f2 100644
--- a/frontend/src/components/TimePreferenceDropDown/index.tsx
+++ b/frontend/src/components/TimePreferenceDropDown/index.tsx
@@ -3,7 +3,7 @@ import TimeItems, {
timePreferance,
timePreferenceType,
} from 'container/NewWidget/RightContainer/timeItems';
-import React, { useCallback, useMemo } from 'react';
+import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { menuItems } from './config';
import { TextContainer } from './styles';
@@ -44,7 +44,7 @@ interface TimeMenuItemOnChangeHandlerEvent {
}
interface TimePreferenceDropDownProps {
- setSelectedTime: React.Dispatch>;
+ setSelectedTime: Dispatch>;
selectedTime: timePreferance;
}
diff --git a/frontend/src/components/ValueGraph/index.tsx b/frontend/src/components/ValueGraph/index.tsx
index c5de1785c6..e75f338945 100644
--- a/frontend/src/components/ValueGraph/index.tsx
+++ b/frontend/src/components/ValueGraph/index.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { Value } from './styles';
function ValueGraph({ value }: ValueGraphProps): JSX.Element {
diff --git a/frontend/src/components/WelcomeLeftContainer/index.tsx b/frontend/src/components/WelcomeLeftContainer/index.tsx
index ef69a4599d..3e8ec68e42 100644
--- a/frontend/src/components/WelcomeLeftContainer/index.tsx
+++ b/frontend/src/components/WelcomeLeftContainer/index.tsx
@@ -1,5 +1,5 @@
import { Card, Space, Typography } from 'antd';
-import React from 'react';
+import { ReactChild } from 'react';
import { useTranslation } from 'react-i18next';
import { Container, LeftContainer, Logo } from './styles';
@@ -34,7 +34,7 @@ function WelcomeLeftContainer({
interface WelcomeLeftContainerProps {
version: string;
- children: React.ReactChild;
+ children: ReactChild;
}
export default WelcomeLeftContainer;
diff --git a/frontend/src/constants/alerts.ts b/frontend/src/constants/alerts.ts
new file mode 100644
index 0000000000..3565ded3d7
--- /dev/null
+++ b/frontend/src/constants/alerts.ts
@@ -0,0 +1,9 @@
+import { AlertTypes } from 'types/api/alerts/alertTypes';
+import { DataSource } from 'types/common/queryBuilder';
+
+export const ALERTS_DATA_SOURCE_MAP: Record = {
+ [AlertTypes.METRICS_BASED_ALERT]: DataSource.METRICS,
+ [AlertTypes.LOGS_BASED_ALERT]: DataSource.LOGS,
+ [AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES,
+ [AlertTypes.EXCEPTIONS_BASED_ALERT]: DataSource.TRACES,
+};
diff --git a/frontend/src/constants/dashboard.ts b/frontend/src/constants/dashboard.ts
index 587b781906..2941f00077 100644
--- a/frontend/src/constants/dashboard.ts
+++ b/frontend/src/constants/dashboard.ts
@@ -1,5 +1,3 @@
-import { EAggregateOperator, EReduceOperator } from 'types/common/dashboard';
-
export const PromQLQueryTemplate = {
query: '',
legend: '',
@@ -11,24 +9,3 @@ export const ClickHouseQueryTemplate = {
legend: '',
disabled: false,
};
-
-export const QueryBuilderQueryTemplate = {
- metricName: null,
- aggregateOperator: EAggregateOperator.NOOP,
- tagFilters: {
- op: 'AND',
- items: [],
- },
- legend: '',
- disabled: false,
- // Specific to TIME_SERIES type graph
- groupBy: [],
- // Specific to VALUE type graph
- reduceTo: EReduceOperator['Latest of values in timeframe'],
-};
-
-export const QueryBuilderFormulaTemplate = {
- expression: '',
- disabled: false,
- legend: '',
-};
diff --git a/frontend/src/constants/featureKeys.ts b/frontend/src/constants/featureKeys.ts
deleted file mode 100644
index 6684f3ddae..0000000000
--- a/frontend/src/constants/featureKeys.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// keep this consistent with backend model>features.go
-export enum FeatureKeys {
- SSO = 'SSO',
- ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
- BASIC_PLAN = 'BASIC_PLAN',
- DISABLE_UPSELL = 'DISABLE_UPSELL',
-}
diff --git a/frontend/src/constants/features.ts b/frontend/src/constants/features.ts
index ee7d323b30..cceaf2817b 100644
--- a/frontend/src/constants/features.ts
+++ b/frontend/src/constants/features.ts
@@ -1,6 +1,11 @@
// keep this consistent with backend constants.go
export enum FeatureKeys {
SSO = 'SSO',
- ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
- BASIC_PLAN = 'BASIC_PLAN',
+ DurationSort = 'DurationSort',
+ TimestampSort = 'TimestampSort',
+ SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
+ CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
+ QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
+ QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
+ DISABLE_UPSELL = 'DISABLE_UPSELL',
}
diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts
index 29221e9f9c..35c1e2c2ca 100644
--- a/frontend/src/constants/query.ts
+++ b/frontend/src/constants/query.ts
@@ -1,5 +1,4 @@
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export enum METRICS_PAGE_QUERY_PARAM {
+export enum QueryParams {
interval = 'interval',
startTime = 'startTime',
endTime = 'endTime',
@@ -12,4 +11,5 @@ export enum METRICS_PAGE_QUERY_PARAM {
selectedTags = 'selectedTags',
aggregationOption = 'aggregationOption',
entity = 'entity',
+ resourceAttributes = 'resourceAttribute',
}
diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts
new file mode 100644
index 0000000000..525c679681
--- /dev/null
+++ b/frontend/src/constants/queryBuilder.ts
@@ -0,0 +1,235 @@
+// ** Helpers
+import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
+import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
+import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import {
+ HavingForm,
+ IBuilderFormula,
+ IBuilderQuery,
+} from 'types/api/queryBuilder/queryBuilderData';
+import {
+ BoolOperators,
+ DataSource,
+ MetricAggregateOperator,
+ NumberOperators,
+ PanelTypeKeys,
+ QueryAdditionalFilter,
+ ReduceOperators,
+ StringOperators,
+} from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+import { v4 as uuid } from 'uuid';
+
+import {
+ logsAggregateOperatorOptions,
+ metricAggregateOperatorOptions,
+ tracesAggregateOperatorOptions,
+} from './queryBuilderOperators';
+
+export const MAX_FORMULAS = 20;
+export const MAX_QUERIES = 26;
+
+export const selectValueDivider = '--';
+
+export const formulasNames: string[] = Array.from(
+ Array(MAX_FORMULAS),
+ (_, i) => `F${i + 1}`,
+);
+const alpha: number[] = Array.from(Array(MAX_QUERIES), (_, i) => i + 65);
+export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
+
+export enum QueryBuilderKeys {
+ GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
+ GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
+ GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY',
+}
+
+export const mapOfOperators = {
+ metrics: metricAggregateOperatorOptions,
+ logs: logsAggregateOperatorOptions,
+ traces: tracesAggregateOperatorOptions,
+};
+
+export const mapOfFilters: Record = {
+ metrics: [
+ // eslint-disable-next-line sonarjs/no-duplicate-string
+ { text: 'Aggregation interval', field: 'stepInterval' },
+ { text: 'Having', field: 'having' },
+ ],
+ logs: [
+ { text: 'Order by', field: 'orderBy' },
+ { text: 'Limit', field: 'limit' },
+ { text: 'Having', field: 'having' },
+ { text: 'Aggregation interval', field: 'stepInterval' },
+ ],
+ traces: [
+ { text: 'Order by', field: 'orderBy' },
+ { text: 'Limit', field: 'limit' },
+ { text: 'Having', field: 'having' },
+ { text: 'Aggregation interval', field: 'stepInterval' },
+ ],
+};
+
+export const REDUCE_TO_VALUES: SelectOption[] = [
+ { value: 'last', label: 'Latest of values in timeframe' },
+ { value: 'sum', label: 'Sum of values in timeframe' },
+ { value: 'avg', label: 'Average of values in timeframe' },
+ { value: 'max', label: 'Max of values in timeframe' },
+ { value: 'min', label: 'Min of values in timeframe' },
+];
+
+export const initialHavingValues: HavingForm = {
+ columnName: '',
+ op: '',
+ value: [],
+};
+
+export const initialAggregateAttribute: IBuilderQuery['aggregateAttribute'] = {
+ id: uuid(),
+ dataType: null,
+ key: '',
+ isColumn: null,
+ type: null,
+};
+
+export const initialQueryBuilderFormValues: IBuilderQuery = {
+ dataSource: DataSource.METRICS,
+ queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
+ aggregateOperator: MetricAggregateOperator.NOOP,
+ aggregateAttribute: initialAggregateAttribute,
+ filters: { items: [], op: 'AND' },
+ expression: createNewBuilderItemName({
+ existNames: [],
+ sourceNames: alphabet,
+ }),
+ disabled: false,
+ having: [],
+ stepInterval: 30,
+ limit: null,
+ orderBy: [],
+ groupBy: [],
+ legend: '',
+ reduceTo: 'sum',
+};
+
+export const initialFormulaBuilderFormValues: IBuilderFormula = {
+ queryName: createNewBuilderItemName({
+ existNames: [],
+ sourceNames: formulasNames,
+ }),
+ expression: '',
+ disabled: false,
+ legend: '',
+};
+
+export const operatorsByTypes: Record = {
+ string: Object.values(StringOperators),
+ number: Object.values(NumberOperators),
+ bool: Object.values(BoolOperators),
+};
+
+export const PANEL_TYPES: Record = {
+ TIME_SERIES: 'graph',
+ VALUE: 'value',
+ TABLE: 'table',
+ LIST: 'list',
+ EMPTY_WIDGET: 'EMPTY_WIDGET',
+};
+
+export type IQueryBuilderState = 'search';
+
+export const QUERY_BUILDER_SEARCH_VALUES = {
+ MULTIPLY: 'MULTIPLY_VALUE',
+ SINGLE: 'SINGLE_VALUE',
+ NON: 'NON_VALUE',
+ NOT_VALID: 'NOT_VALID',
+};
+
+export const OPERATORS = {
+ IN: 'IN',
+ NIN: 'NOT_IN',
+ LIKE: 'LIKE',
+ NLIKE: 'NOT_LIKE',
+ '=': '=',
+ '!=': '!=',
+ EXISTS: 'EXISTS',
+ NOT_EXISTS: 'NOT_EXISTS',
+ CONTAINS: 'CONTAINS',
+ NOT_CONTAINS: 'NOT_CONTAINS',
+ '>=': '>=',
+ '>': '>',
+ '<=': '<=',
+ '<': '<',
+};
+
+export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
+ string: [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.IN,
+ OPERATORS.NIN,
+ OPERATORS.LIKE,
+ OPERATORS.NLIKE,
+ OPERATORS.CONTAINS,
+ OPERATORS.NOT_CONTAINS,
+ OPERATORS.EXISTS,
+ OPERATORS.NOT_EXISTS,
+ ],
+ int64: [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.IN,
+ OPERATORS.NIN,
+ OPERATORS.EXISTS,
+ OPERATORS.NOT_EXISTS,
+ OPERATORS['>='],
+ OPERATORS['>'],
+ OPERATORS['<='],
+ OPERATORS['<'],
+ ],
+ float64: [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.IN,
+ OPERATORS.NIN,
+ OPERATORS.EXISTS,
+ OPERATORS.NOT_EXISTS,
+ OPERATORS['>='],
+ OPERATORS['>'],
+ OPERATORS['<='],
+ OPERATORS['<'],
+ ],
+ bool: [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.EXISTS,
+ OPERATORS.NOT_EXISTS,
+ ],
+ universal: [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.IN,
+ OPERATORS.NIN,
+ OPERATORS.EXISTS,
+ OPERATORS.NOT_EXISTS,
+ OPERATORS.LIKE,
+ OPERATORS.NLIKE,
+ OPERATORS['>='],
+ OPERATORS['>'],
+ OPERATORS['<='],
+ OPERATORS['<'],
+ OPERATORS.CONTAINS,
+ OPERATORS.NOT_CONTAINS,
+ ],
+};
+
+export const HAVING_OPERATORS: string[] = [
+ OPERATORS['='],
+ OPERATORS['!='],
+ OPERATORS.IN,
+ OPERATORS.NIN,
+ OPERATORS['>='],
+ OPERATORS['>'],
+ OPERATORS['<='],
+ OPERATORS['<'],
+];
diff --git a/frontend/src/constants/queryBuilderOperators.ts b/frontend/src/constants/queryBuilderOperators.ts
new file mode 100644
index 0000000000..7c5cff2b69
--- /dev/null
+++ b/frontend/src/constants/queryBuilderOperators.ts
@@ -0,0 +1,304 @@
+import {
+ LogsAggregatorOperator,
+ MetricAggregateOperator,
+ TracesAggregatorOperator,
+} from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+
+export const metricAggregateOperatorOptions: SelectOption[] = [
+ {
+ value: MetricAggregateOperator.NOOP,
+ label: 'NOOP',
+ },
+ {
+ value: MetricAggregateOperator.COUNT,
+ label: 'Count',
+ },
+ {
+ value: MetricAggregateOperator.COUNT_DISTINCT,
+ // eslint-disable-next-line sonarjs/no-duplicate-string
+ label: 'Count Distinct',
+ },
+ {
+ value: MetricAggregateOperator.SUM,
+ label: 'Sum',
+ },
+ {
+ value: MetricAggregateOperator.AVG,
+ label: 'Avg',
+ },
+ {
+ value: MetricAggregateOperator.MAX,
+ label: 'Max',
+ },
+ {
+ value: MetricAggregateOperator.MIN,
+ label: 'Min',
+ },
+ {
+ value: MetricAggregateOperator.P05,
+ label: 'P05',
+ },
+ {
+ value: MetricAggregateOperator.P10,
+ label: 'P10',
+ },
+ {
+ value: MetricAggregateOperator.P20,
+ label: 'P20',
+ },
+ {
+ value: MetricAggregateOperator.P25,
+ label: 'P25',
+ },
+ {
+ value: MetricAggregateOperator.P50,
+ label: 'P50',
+ },
+ {
+ value: MetricAggregateOperator.P75,
+ label: 'P75',
+ },
+ {
+ value: MetricAggregateOperator.P90,
+ label: 'P90',
+ },
+ {
+ value: MetricAggregateOperator.P95,
+ label: 'P95',
+ },
+ {
+ value: MetricAggregateOperator.P99,
+ label: 'P99',
+ },
+ {
+ value: MetricAggregateOperator.RATE,
+ label: 'Rate',
+ },
+ {
+ value: MetricAggregateOperator.SUM_RATE,
+ label: 'Sum_rate',
+ },
+ {
+ value: MetricAggregateOperator.AVG_RATE,
+ label: 'Avg_rate',
+ },
+ {
+ value: MetricAggregateOperator.MAX_RATE,
+ label: 'Max_rate',
+ },
+ {
+ value: MetricAggregateOperator.MIN_RATE,
+ label: 'Min_rate',
+ },
+ {
+ value: MetricAggregateOperator.RATE_SUM,
+ label: 'Rate_sum',
+ },
+ {
+ value: MetricAggregateOperator.RATE_AVG,
+ label: 'Rate_avg',
+ },
+ {
+ value: MetricAggregateOperator.RATE_MIN,
+ label: 'Rate_min',
+ },
+ {
+ value: MetricAggregateOperator.RATE_MAX,
+ label: 'Rate_max',
+ },
+ {
+ value: MetricAggregateOperator.HIST_QUANTILE_50,
+ label: 'Hist_quantile_50',
+ },
+ {
+ value: MetricAggregateOperator.HIST_QUANTILE_75,
+ label: 'Hist_quantile_75',
+ },
+ {
+ value: MetricAggregateOperator.HIST_QUANTILE_90,
+ label: 'Hist_quantile_90',
+ },
+ {
+ value: MetricAggregateOperator.HIST_QUANTILE_95,
+ label: 'Hist_quantile_95',
+ },
+ {
+ value: MetricAggregateOperator.HIST_QUANTILE_99,
+ label: 'Hist_quantile_99',
+ },
+];
+
+export const tracesAggregateOperatorOptions: SelectOption[] = [
+ {
+ value: TracesAggregatorOperator.NOOP,
+ label: 'NOOP',
+ },
+ {
+ value: TracesAggregatorOperator.COUNT,
+ label: 'Count',
+ },
+ {
+ value: TracesAggregatorOperator.COUNT_DISTINCT,
+ label: 'Count Distinct',
+ },
+ {
+ value: TracesAggregatorOperator.SUM,
+ label: 'Sum',
+ },
+ {
+ value: TracesAggregatorOperator.AVG,
+ label: 'Avg',
+ },
+ {
+ value: TracesAggregatorOperator.MAX,
+ label: 'Max',
+ },
+ {
+ value: TracesAggregatorOperator.MIN,
+ label: 'Min',
+ },
+ {
+ value: TracesAggregatorOperator.P05,
+ label: 'P05',
+ },
+ {
+ value: TracesAggregatorOperator.P10,
+ label: 'P10',
+ },
+ {
+ value: TracesAggregatorOperator.P20,
+ label: 'P20',
+ },
+ {
+ value: TracesAggregatorOperator.P25,
+ label: 'P25',
+ },
+ {
+ value: TracesAggregatorOperator.P50,
+ label: 'P50',
+ },
+ {
+ value: TracesAggregatorOperator.P75,
+ label: 'P75',
+ },
+ {
+ value: TracesAggregatorOperator.P90,
+ label: 'P90',
+ },
+ {
+ value: TracesAggregatorOperator.P95,
+ label: 'P95',
+ },
+ {
+ value: TracesAggregatorOperator.P99,
+ label: 'P99',
+ },
+ {
+ value: TracesAggregatorOperator.RATE,
+ label: 'Rate',
+ },
+ {
+ value: TracesAggregatorOperator.RATE_SUM,
+ label: 'Rate_sum',
+ },
+ {
+ value: TracesAggregatorOperator.RATE_AVG,
+ label: 'Rate_avg',
+ },
+ {
+ value: TracesAggregatorOperator.RATE_MIN,
+ label: 'Rate_min',
+ },
+ {
+ value: TracesAggregatorOperator.RATE_MAX,
+ label: 'Rate_max',
+ },
+];
+
+export const logsAggregateOperatorOptions: SelectOption[] = [
+ {
+ value: LogsAggregatorOperator.NOOP,
+ label: 'NOOP',
+ },
+ {
+ value: LogsAggregatorOperator.COUNT,
+ label: 'Count',
+ },
+ {
+ value: LogsAggregatorOperator.COUNT_DISTINCT,
+ label: 'Count Distinct',
+ },
+ {
+ value: LogsAggregatorOperator.SUM,
+ label: 'Sum',
+ },
+ {
+ value: LogsAggregatorOperator.AVG,
+ label: 'Avg',
+ },
+ {
+ value: LogsAggregatorOperator.MAX,
+ label: 'Max',
+ },
+ {
+ value: LogsAggregatorOperator.MIN,
+ label: 'Min',
+ },
+ {
+ value: LogsAggregatorOperator.P05,
+ label: 'P05',
+ },
+ {
+ value: LogsAggregatorOperator.P10,
+ label: 'P10',
+ },
+ {
+ value: LogsAggregatorOperator.P20,
+ label: 'P20',
+ },
+ {
+ value: LogsAggregatorOperator.P25,
+ label: 'P25',
+ },
+ {
+ value: LogsAggregatorOperator.P50,
+ label: 'P50',
+ },
+ {
+ value: LogsAggregatorOperator.P75,
+ label: 'P75',
+ },
+ {
+ value: LogsAggregatorOperator.P90,
+ label: 'P90',
+ },
+ {
+ value: LogsAggregatorOperator.P95,
+ label: 'P95',
+ },
+ {
+ value: LogsAggregatorOperator.P99,
+ label: 'P99',
+ },
+ {
+ value: LogsAggregatorOperator.RATE,
+ label: 'Rate',
+ },
+ {
+ value: LogsAggregatorOperator.RATE_SUM,
+ label: 'Rate_sum',
+ },
+ {
+ value: LogsAggregatorOperator.RATE_AVG,
+ label: 'Rate_avg',
+ },
+ {
+ value: LogsAggregatorOperator.RATE_MIN,
+ label: 'Rate_min',
+ },
+ {
+ value: LogsAggregatorOperator.RATE_MAX,
+ label: 'Rate_max',
+ },
+];
diff --git a/frontend/src/constants/reactQueryKeys.ts b/frontend/src/constants/reactQueryKeys.ts
new file mode 100644
index 0000000000..7cc38c153e
--- /dev/null
+++ b/frontend/src/constants/reactQueryKeys.ts
@@ -0,0 +1,3 @@
+export const REACT_QUERY_KEY = {
+ GET_ALL_LICENCES: 'GET_ALL_LICENCES',
+};
diff --git a/frontend/src/constants/regExp.ts b/frontend/src/constants/regExp.ts
new file mode 100644
index 0000000000..3ea1e3179c
--- /dev/null
+++ b/frontend/src/constants/regExp.ts
@@ -0,0 +1,5 @@
+export const FORMULA_REGEXP = /F\d+/;
+
+export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/;
+
+export const TYPE_ADDON_REGEXP = /_(.+)/;
diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts
index c596918129..b5a92b3a0e 100644
--- a/frontend/src/constants/routes.ts
+++ b/frontend/src/constants/routes.ts
@@ -16,8 +16,8 @@ const ROUTES = {
LIST_ALL_ALERT: '/alerts',
ALERTS_NEW: '/alerts/new',
ALL_CHANNELS: '/settings/channels',
- CHANNELS_NEW: '/setting/channels/new',
- CHANNELS_EDIT: '/setting/channels/edit/:id',
+ CHANNELS_NEW: '/settings/channels/new',
+ CHANNELS_EDIT: '/settings/channels/:id',
ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail',
VERSION: '/status',
diff --git a/frontend/src/constants/theme.ts b/frontend/src/constants/theme.ts
index 36dfe4bd87..ce6cdd354a 100644
--- a/frontend/src/constants/theme.ts
+++ b/frontend/src/constants/theme.ts
@@ -38,6 +38,8 @@ const themeColors = {
whiteCream: '#ffffffd5',
black: '#000000',
lightgrey: '#ddd',
+ borderLightGrey: '#d9d9d9',
+ borderDarkGrey: '#424242',
};
export { themeColors };
diff --git a/frontend/src/constants/useQueryKeys.ts b/frontend/src/constants/useQueryKeys.ts
deleted file mode 100644
index 705d5ef350..0000000000
--- a/frontend/src/constants/useQueryKeys.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export enum QueryBuilderKeys {
- GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
-}
diff --git a/frontend/src/container/AllAlertChannels/AlertChannels.tsx b/frontend/src/container/AllAlertChannels/AlertChannels.tsx
index 72d5a54b85..e3dccb6160 100644
--- a/frontend/src/container/AllAlertChannels/AlertChannels.tsx
+++ b/frontend/src/container/AllAlertChannels/AlertChannels.tsx
@@ -6,7 +6,7 @@ import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback, useState } from 'react';
+import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath } from 'react-router-dom';
diff --git a/frontend/src/container/AllAlertChannels/Delete.tsx b/frontend/src/container/AllAlertChannels/Delete.tsx
index 1a4456cced..c5edbf1e6a 100644
--- a/frontend/src/container/AllAlertChannels/Delete.tsx
+++ b/frontend/src/container/AllAlertChannels/Delete.tsx
@@ -1,7 +1,7 @@
import { Button } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import deleteChannel from 'api/channels/delete';
-import React, { useState } from 'react';
+import { Dispatch, SetStateAction, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Channels } from 'types/api/channels/getAll';
@@ -55,7 +55,7 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
interface DeleteProps {
notifications: NotificationInstance;
- setChannels: React.Dispatch>;
+ setChannels: Dispatch>;
id: string;
}
diff --git a/frontend/src/container/AllAlertChannels/index.tsx b/frontend/src/container/AllAlertChannels/index.tsx
index 99636806ea..8038f17778 100644
--- a/frontend/src/container/AllAlertChannels/index.tsx
+++ b/frontend/src/container/AllAlertChannels/index.tsx
@@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import history from 'lib/history';
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx
index c3b0580f44..4bc7d199e1 100644
--- a/frontend/src/container/AllError/index.tsx
+++ b/frontend/src/container/AllError/index.tsx
@@ -23,7 +23,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
-import React, { useCallback, useEffect, useMemo } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx
index c31a2bf6f9..b127d6c429 100644
--- a/frontend/src/container/AppLayout/index.tsx
+++ b/frontend/src/container/AppLayout/index.tsx
@@ -6,7 +6,7 @@ import Header from 'container/Header';
import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav';
import { useNotifications } from 'hooks/useNotifications';
-import React, { ReactNode, useEffect, useRef } from 'react';
+import { ReactNode, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
@@ -18,7 +18,7 @@ import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
- UPDATE_FEATURE_FLAGS,
+ UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app';
@@ -27,7 +27,9 @@ import AppReducer from 'types/reducer/app';
import { ChildrenContainer, Layout } from './styles';
function AppLayout(props: AppLayoutProps): JSX.Element {
- const { isLoggedIn } = useSelector((state) => state.app);
+ const { isLoggedIn, user } = useSelector(
+ (state) => state.app,
+ );
const { pathname } = useLocation();
const { t } = useTranslation();
@@ -39,21 +41,21 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
] = useQueries([
{
queryFn: getUserVersion,
- queryKey: 'getUserVersion',
+ queryKey: ['getUserVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getUserLatestVersion,
- queryKey: 'getUserLatestVersion',
+ queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getFeaturesFlags,
- queryKey: 'getFeatureFlags',
+ queryKey: ['getFeatureFlags', user?.accessJwt],
},
{
queryFn: getDynamicConfigs,
- queryKey: 'getDynamicConfigs',
+ queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]);
@@ -129,19 +131,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
message: t('oops_something_went_wrong_version'),
});
}
- if (
- getFeaturesResponse.isFetched &&
- getFeaturesResponse.isSuccess &&
- getFeaturesResponse.data &&
- getFeaturesResponse.data.payload
- ) {
- dispatch({
- type: UPDATE_FEATURE_FLAGS,
- payload: {
- ...getFeaturesResponse.data.payload,
- },
- });
- }
if (
getUserVersionResponse.isFetched &&
@@ -173,20 +162,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
});
}
- if (
- getFeaturesResponse.isFetched &&
- getFeaturesResponse.isSuccess &&
- getFeaturesResponse.data &&
- getFeaturesResponse.data.payload
- ) {
- dispatch({
- type: UPDATE_FEATURE_FLAGS,
- payload: {
- ...getFeaturesResponse.data.payload,
- },
- });
- }
-
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
@@ -226,6 +201,29 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
notifications,
]);
+ useEffect(() => {
+ if (
+ getFeaturesResponse.isFetched &&
+ getFeaturesResponse.isSuccess &&
+ getFeaturesResponse.data &&
+ getFeaturesResponse.data.payload
+ ) {
+ dispatch({
+ type: UPDATE_FEATURE_FLAG_RESPONSE,
+ payload: {
+ featureFlag: getFeaturesResponse.data.payload,
+ refetch: getFeaturesResponse.refetch,
+ },
+ });
+ }
+ }, [
+ dispatch,
+ getFeaturesResponse.data,
+ getFeaturesResponse.isFetched,
+ getFeaturesResponse.isSuccess,
+ getFeaturesResponse.refetch,
+ ]);
+
const isToDisplayLayout = isLoggedIn;
return (
diff --git a/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx b/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
index 84ac44e60e..fa2f471113 100644
--- a/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
+++ b/frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
@@ -1,4 +1,4 @@
-import React, { PureComponent } from 'react';
+import { PureComponent } from 'react';
interface State {
hasError: boolean;
diff --git a/frontend/src/container/ConfigDropdown/Config/Link.tsx b/frontend/src/container/ConfigDropdown/Config/Link.tsx
index 2cc39b7779..b31e374538 100644
--- a/frontend/src/container/ConfigDropdown/Config/Link.tsx
+++ b/frontend/src/container/ConfigDropdown/Config/Link.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
@@ -16,7 +16,7 @@ function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
}
interface LinkContainerProps {
- children: React.ReactNode;
+ children: ReactNode;
href: string;
}
diff --git a/frontend/src/container/ConfigDropdown/Config/index.tsx b/frontend/src/container/ConfigDropdown/Config/index.tsx
index 3d356a0af4..52879ce2d7 100644
--- a/frontend/src/container/ConfigDropdown/Config/index.tsx
+++ b/frontend/src/container/ConfigDropdown/Config/index.tsx
@@ -1,7 +1,7 @@
import { Menu, Space } from 'antd';
import Spinner from 'components/Spinner';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { Suspense, useMemo } from 'react';
+import { lazy, Suspense, useMemo } from 'react';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import ErrorLink from './ErrorLink';
@@ -17,7 +17,7 @@ function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
const items = sortedConfig.map((item) => {
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
- const Component = React.lazy(
+ const Component = lazy(
() => import(`@ant-design/icons/es/icons/${iconName}.js`),
);
return {
diff --git a/frontend/src/container/ConfigDropdown/index.tsx b/frontend/src/container/ConfigDropdown/index.tsx
index 1ddd676948..cae4d087d6 100644
--- a/frontend/src/container/ConfigDropdown/index.tsx
+++ b/frontend/src/container/ConfigDropdown/index.tsx
@@ -6,7 +6,7 @@ import {
} from '@ant-design/icons';
import { Dropdown, Space } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
diff --git a/frontend/src/container/CreateAlertChannels/defaults.ts b/frontend/src/container/CreateAlertChannels/defaults.ts
index ac15056703..e37ad6be03 100644
--- a/frontend/src/container/CreateAlertChannels/defaults.ts
+++ b/frontend/src/container/CreateAlertChannels/defaults.ts
@@ -1,15 +1,17 @@
import { PagerChannel } from './config';
export const PagerInitialConfig: Partial = {
- description: `{{ range .Alerts -}}
- *Alert:* {{ if .Annotations.title }} {{ .Annotations.title }} {{ else }} {{ .Annotations.summary }} {{end}} {{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
-
- *Description:* {{ .Annotations.description }}
-
- *Details:*
- {{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
- {{ end }}
- {{ end }}`,
+ description: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
+ {{- if gt (len .CommonLabels) (len .GroupLabels) -}}
+ {{" "}}(
+ {{- with .CommonLabels.Remove .GroupLabels.Names }}
+ {{- range $index, $label := .SortedPairs -}}
+ {{ if $index }}, {{ end }}
+ {{- $label.Name }}="{{ $label.Value -}}"
+ {{- end }}
+ {{- end -}}
+ )
+ {{- end }}`,
severity: '{{ (index .Alerts 0).Labels.severity }}',
client: 'SigNoz Alert Manager',
client_url: 'https://enter-signoz-host-n-port-here/alerts',
diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx
index a5db46da07..1261f53567 100644
--- a/frontend/src/container/CreateAlertChannels/index.tsx
+++ b/frontend/src/container/CreateAlertChannels/index.tsx
@@ -9,7 +9,7 @@ import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback, useState } from 'react';
+import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx
index cc2da48727..8385c8462e 100644
--- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx
+++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx
@@ -1,5 +1,4 @@
import { Row } from 'antd';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts
index 5ce07a03be..2b85a052b2 100644
--- a/frontend/src/container/CreateAlertRule/defaults.ts
+++ b/frontend/src/container/CreateAlertRule/defaults.ts
@@ -1,3 +1,7 @@
+import {
+ initialQueryBuilderFormValues,
+ PANEL_TYPES,
+} from 'constants/queryBuilder';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
AlertDef,
@@ -5,6 +9,12 @@ import {
defaultEvalWindow,
defaultMatchType,
} from 'types/api/alerts/def';
+import { EQueryType } from 'types/common/dashboard';
+import {
+ DataSource,
+ LogsAggregatorOperator,
+ TracesAggregatorOperator,
+} from 'types/common/queryBuilder';
const defaultAlertDescription =
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})';
@@ -19,28 +29,24 @@ const defaultAnnotations = {
export const alertDefaults: AlertDef = {
alertType: AlertTypes.METRICS_BASED_ALERT,
condition: {
- compositeMetricQuery: {
+ compositeQuery: {
builderQueries: {
A: {
- queryName: 'A',
- name: 'A',
- formulaOnly: false,
- metricName: '',
- tagFilters: {
- op: 'AND',
- items: [],
- },
- groupBy: [],
- aggregateOperator: 1,
- expression: 'A',
- disabled: false,
- toggleDisable: false,
- toggleDelete: false,
+ ...initialQueryBuilderFormValues,
},
},
promQueries: {},
- chQueries: {},
- queryType: 1,
+ chQueries: {
+ A: {
+ name: 'A',
+ query: ``,
+ rawQuery: ``,
+ legend: '',
+ disabled: false,
+ },
+ },
+ queryType: EQueryType.CLICKHOUSE,
+ panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: defaultMatchType,
@@ -55,23 +61,12 @@ export const alertDefaults: AlertDef = {
export const logAlertDefaults: AlertDef = {
alertType: AlertTypes.LOGS_BASED_ALERT,
condition: {
- compositeMetricQuery: {
+ compositeQuery: {
builderQueries: {
A: {
- queryName: 'A',
- name: 'A',
- formulaOnly: false,
- metricName: '',
- tagFilters: {
- op: 'AND',
- items: [],
- },
- groupBy: [],
- aggregateOperator: 1,
- expression: 'A',
- disabled: false,
- toggleDisable: false,
- toggleDelete: false,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: LogsAggregatorOperator.COUNT,
+ dataSource: DataSource.LOGS,
},
},
promQueries: {},
@@ -84,7 +79,8 @@ export const logAlertDefaults: AlertDef = {
disabled: false,
},
},
- queryType: 2,
+ queryType: EQueryType.CLICKHOUSE,
+ panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',
@@ -100,23 +96,12 @@ export const logAlertDefaults: AlertDef = {
export const traceAlertDefaults: AlertDef = {
alertType: AlertTypes.TRACES_BASED_ALERT,
condition: {
- compositeMetricQuery: {
+ compositeQuery: {
builderQueries: {
A: {
- queryName: 'A',
- name: 'A',
- formulaOnly: false,
- metricName: '',
- tagFilters: {
- op: 'AND',
- items: [],
- },
- groupBy: [],
- aggregateOperator: 1,
- expression: 'A',
- disabled: false,
- toggleDisable: false,
- toggleDelete: false,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: TracesAggregatorOperator.COUNT,
+ dataSource: DataSource.TRACES,
},
},
promQueries: {},
@@ -129,7 +114,8 @@ export const traceAlertDefaults: AlertDef = {
disabled: false,
},
},
- queryType: 2,
+ queryType: EQueryType.CLICKHOUSE,
+ panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',
@@ -145,23 +131,12 @@ export const traceAlertDefaults: AlertDef = {
export const exceptionAlertDefaults: AlertDef = {
alertType: AlertTypes.EXCEPTIONS_BASED_ALERT,
condition: {
- compositeMetricQuery: {
+ compositeQuery: {
builderQueries: {
A: {
- queryName: 'A',
- name: 'A',
- formulaOnly: false,
- metricName: '',
- tagFilters: {
- op: 'AND',
- items: [],
- },
- groupBy: [],
- aggregateOperator: 1,
- expression: 'A',
- disabled: false,
- toggleDisable: false,
- toggleDelete: false,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: TracesAggregatorOperator.COUNT,
+ dataSource: DataSource.TRACES,
},
},
promQueries: {},
@@ -174,7 +149,8 @@ export const exceptionAlertDefaults: AlertDef = {
disabled: false,
},
},
- queryType: 2,
+ queryType: EQueryType.CLICKHOUSE,
+ panelType: PANEL_TYPES.TIME_SERIES,
},
op: defaultCompareOp,
matchType: '4',
diff --git a/frontend/src/container/CreateAlertRule/index.tsx b/frontend/src/container/CreateAlertRule/index.tsx
index ae3d21897e..b0cb5546c1 100644
--- a/frontend/src/container/CreateAlertRule/index.tsx
+++ b/frontend/src/container/CreateAlertRule/index.tsx
@@ -1,6 +1,6 @@
import { Form, Row } from 'antd';
import FormAlertRules from 'container/FormAlertRules';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx
index 5711635167..42ebd543c6 100644
--- a/frontend/src/container/EditAlertChannels/index.tsx
+++ b/frontend/src/container/EditAlertChannels/index.tsx
@@ -19,7 +19,7 @@ import {
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback, useState } from 'react';
+import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
diff --git a/frontend/src/container/EditRules/index.tsx b/frontend/src/container/EditRules/index.tsx
index 89c9e66410..b6a32615a6 100644
--- a/frontend/src/container/EditRules/index.tsx
+++ b/frontend/src/container/EditRules/index.tsx
@@ -1,6 +1,5 @@
import { Form } from 'antd';
import FormAlertRules from 'container/FormAlertRules';
-import React from 'react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
diff --git a/frontend/src/container/ErrorDetails/index.tsx b/frontend/src/container/ErrorDetails/index.tsx
index 999e8013d9..561114a289 100644
--- a/frontend/src/container/ErrorDetails/index.tsx
+++ b/frontend/src/container/ErrorDetails/index.tsx
@@ -7,7 +7,7 @@ import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { urlKey } from 'pages/ErrorDetails/utils';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
diff --git a/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx b/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx
index 21311e416d..cc6c45843d 100644
--- a/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx
+++ b/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx
@@ -1,6 +1,6 @@
import { Form, Input, Select } from 'antd';
import { LabelFilterStatement } from 'container/CreateAlertChannels/config';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
const { Option } = Select;
@@ -55,9 +55,7 @@ function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element {
}
export interface LabelFilterProps {
- setFilter: React.Dispatch<
- React.SetStateAction>>
- >;
+ setFilter: Dispatch>>>;
}
export default LabelFilterForm;
diff --git a/frontend/src/container/FormAlertChannels/Settings/Pager.tsx b/frontend/src/container/FormAlertChannels/Settings/Pager.tsx
index 5c0ca2e7c8..ec228f4b8d 100644
--- a/frontend/src/container/FormAlertChannels/Settings/Pager.tsx
+++ b/frontend/src/container/FormAlertChannels/Settings/Pager.tsx
@@ -1,5 +1,5 @@
import { Form, Input } from 'antd';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { PagerChannel } from '../../CreateAlertChannels/config';
@@ -148,7 +148,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
}
interface PagerFormProps {
- setSelectedConfig: React.Dispatch>>;
+ setSelectedConfig: Dispatch>>;
}
export default PagerForm;
diff --git a/frontend/src/container/FormAlertChannels/Settings/Slack.tsx b/frontend/src/container/FormAlertChannels/Settings/Slack.tsx
index 9415a500d7..c344df8ff5 100644
--- a/frontend/src/container/FormAlertChannels/Settings/Slack.tsx
+++ b/frontend/src/container/FormAlertChannels/Settings/Slack.tsx
@@ -1,5 +1,5 @@
import { Form, Input } from 'antd';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { SlackChannel } from '../../CreateAlertChannels/config';
@@ -66,7 +66,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
}
interface SlackProps {
- setSelectedConfig: React.Dispatch>>;
+ setSelectedConfig: Dispatch>>;
}
export default Slack;
diff --git a/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx b/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx
index 537d826313..ead1464734 100644
--- a/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx
+++ b/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx
@@ -1,5 +1,5 @@
import { Form, Input } from 'antd';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { WebhookChannel } from '../../CreateAlertChannels/config';
@@ -53,9 +53,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
}
interface WebhookProps {
- setSelectedConfig: React.Dispatch<
- React.SetStateAction>
- >;
+ setSelectedConfig: Dispatch>>;
}
export default WebhookSettings;
diff --git a/frontend/src/container/FormAlertChannels/index.tsx b/frontend/src/container/FormAlertChannels/index.tsx
index 91de745b3b..14f6d32413 100644
--- a/frontend/src/container/FormAlertChannels/index.tsx
+++ b/frontend/src/container/FormAlertChannels/index.tsx
@@ -11,7 +11,7 @@ import {
WebhookType,
} from 'container/CreateAlertChannels/config';
import history from 'lib/history';
-import React from 'react';
+import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import PagerSettings from './Settings/Pager';
@@ -37,7 +37,7 @@ function FormAlertChannels({
}: FormAlertChannelsProps): JSX.Element {
const { t } = useTranslation('channels');
- const renderSettings = (): React.ReactElement | null => {
+ const renderSettings = (): ReactElement | null => {
switch (type) {
case SlackType:
return ;
@@ -115,8 +115,8 @@ function FormAlertChannels({
interface FormAlertChannelsProps {
formInstance: FormInstance;
type: ChannelType;
- setSelectedConfig: React.Dispatch<
- React.SetStateAction>
+ setSelectedConfig: Dispatch<
+ SetStateAction>
>;
onTypeChangeHandler: (value: ChannelType) => void;
onSaveHandler: (props: ChannelType) => void;
diff --git a/frontend/src/container/FormAlertRules/BasicInfo.tsx b/frontend/src/container/FormAlertRules/BasicInfo.tsx
index 130f4fb3dd..f4d99126ec 100644
--- a/frontend/src/container/FormAlertRules/BasicInfo.tsx
+++ b/frontend/src/container/FormAlertRules/BasicInfo.tsx
@@ -1,5 +1,4 @@
import { Form, Select } from 'antd';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { AlertDef, Labels } from 'types/api/alerts/def';
diff --git a/frontend/src/container/FormAlertRules/ChQuerySection/ChQuerySection.tsx b/frontend/src/container/FormAlertRules/ChQuerySection/ChQuerySection.tsx
index aa316fe558..fc8fb5dc09 100644
--- a/frontend/src/container/FormAlertRules/ChQuerySection/ChQuerySection.tsx
+++ b/frontend/src/container/FormAlertRules/ChQuerySection/ChQuerySection.tsx
@@ -1,6 +1,5 @@
import ClickHouseQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query';
import { IClickHouseQueryHandleChange } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/types';
-import React from 'react';
import { IChQueries } from 'types/api/alerts/compositeQuery';
import { rawQueryToIChQuery, toIClickHouseQuery } from './transform';
diff --git a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx
index 662a14c1e6..c2d78e661c 100644
--- a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx
+++ b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx
@@ -2,7 +2,7 @@ import { Select } from 'antd';
import getChannels from 'api/channels/getAll';
import useFetch from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
-import React from 'react';
+import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { StyledSelect } from './styles';
@@ -33,8 +33,8 @@ function ChannelSelect({
description: errorMessage,
});
}
- const renderOptions = (): React.ReactNode[] => {
- const children: React.ReactNode[] = [];
+ const renderOptions = (): ReactNode[] => {
+ const children: ReactNode[] = [];
if (loading || payload === undefined || payload.length === 0) {
return children;
diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
index e7ee323dce..f358c6cbe4 100644
--- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
+++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx
@@ -1,12 +1,13 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { StaticLineProps } from 'components/Graph';
import Spinner from 'components/Spinner';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import GridGraphComponent from 'container/GridGraphComponent';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import getChartData from 'lib/getChartData';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
@@ -32,7 +33,7 @@ interface QueryResponseError {
function ChartPreview({
name,
query,
- graphType = 'TIME_SERIES',
+ graphType = PANEL_TYPES.TIME_SERIES,
selectedTime = 'GLOBAL_TIME',
selectedInterval = '5min',
headline,
@@ -59,15 +60,16 @@ function ChartPreview({
switch (query?.queryType) {
case EQueryType.PROM:
- return query.promQL?.length > 0 && query.promQL[0].query !== '';
+ return query.promql?.length > 0 && query.promql[0].query !== '';
case EQueryType.CLICKHOUSE:
return (
- query.clickHouse?.length > 0 && query.clickHouse[0].rawQuery?.length > 0
+ query.clickhouse_sql?.length > 0 &&
+ query.clickhouse_sql[0].rawQuery?.length > 0
);
case EQueryType.QUERY_BUILDER:
return (
- query.metricsBuilder?.queryBuilder?.length > 0 &&
- query.metricsBuilder?.queryBuilder[0].metricName !== ''
+ query.builder.queryData.length > 0 &&
+ query.builder.queryData[0].queryName !== ''
);
default:
return false;
@@ -83,13 +85,13 @@ function ChartPreview({
queryFn: () =>
GetMetricQueryRange({
query: query || {
- queryType: 1,
- promQL: [],
- metricsBuilder: {
- formulas: [],
- queryBuilder: [],
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: {
+ queryFormulas: [],
+ queryData: [],
},
- clickHouse: [],
+ clickhouse_sql: [],
},
globalSelectedInterval: selectedInterval,
graphType,
@@ -127,7 +129,7 @@ function ChartPreview({
title={name}
data={chartDataSet}
isStacked
- GRAPH_TYPES={graphType || 'TIME_SERIES'}
+ GRAPH_TYPES={graphType || PANEL_TYPES.TIME_SERIES}
name={name || 'Chart Preview'}
staticLine={staticLine}
/>
@@ -137,7 +139,7 @@ function ChartPreview({
}
ChartPreview.defaultProps = {
- graphType: 'TIME_SERIES',
+ graphType: PANEL_TYPES.TIME_SERIES,
selectedTime: 'GLOBAL_TIME',
selectedInterval: '5min',
headline: undefined,
diff --git a/frontend/src/container/FormAlertRules/PromqlSection.tsx b/frontend/src/container/FormAlertRules/PromqlSection.tsx
index 129e5bb92d..df2fd860d0 100644
--- a/frontend/src/container/FormAlertRules/PromqlSection.tsx
+++ b/frontend/src/container/FormAlertRules/PromqlSection.tsx
@@ -1,6 +1,5 @@
import PromQLQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query';
import { IPromQLQueryHandleChange } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/types';
-import React from 'react';
import { IPromQueries } from 'types/api/alerts/compositeQuery';
function PromqlSection({
diff --git a/frontend/src/container/FormAlertRules/QuerySection.tsx b/frontend/src/container/FormAlertRules/QuerySection.tsx
index ab39c51083..b24d1ab7a3 100644
--- a/frontend/src/container/FormAlertRules/QuerySection.tsx
+++ b/frontend/src/container/FormAlertRules/QuerySection.tsx
@@ -1,35 +1,20 @@
-import { PlusOutlined } from '@ant-design/icons';
import { Button, Tabs } from 'antd';
-import MetricsBuilderFormula from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula';
-import MetricsBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query';
-import {
- IQueryBuilderFormulaHandleChange,
- IQueryBuilderQueryHandleChange,
-} from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types';
-import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback } from 'react';
+import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
+import { PANEL_TYPES } from 'constants/queryBuilder';
+import { QueryBuilder } from 'container/QueryBuilder';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
-import {
- IChQueries,
- IFormulaQueries,
- IMetricQueries,
- IPromQueries,
-} from 'types/api/alerts/compositeQuery';
-import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
+import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
+import { EQueryType } from 'types/common/dashboard';
import ChQuerySection from './ChQuerySection';
import PromqlSection from './PromqlSection';
-import { FormContainer, QueryButton, StepHeading } from './styles';
-import { toIMetricsBuilderQuery } from './utils';
+import { FormContainer, StepHeading } from './styles';
function QuerySection({
queryCategory,
setQueryCategory,
- metricQueries,
- setMetricQueries,
- formulaQueries,
- setFormulaQueries,
promQueries,
setPromQueries,
chQueries,
@@ -40,9 +25,9 @@ function QuerySection({
// init namespace for translations
const { t } = useTranslation('alerts');
- const handleQueryCategoryChange = (s: string): void => {
+ const handleQueryCategoryChange = (queryType: string): void => {
if (
- parseInt(s, 10) === EQueryType.PROM &&
+ queryType === EQueryType.PROM &&
(!promQueries || Object.keys(promQueries).length === 0)
) {
setPromQueries({
@@ -57,7 +42,7 @@ function QuerySection({
}
if (
- parseInt(s, 10) === EQueryType.CLICKHOUSE &&
+ queryType === EQueryType.CLICKHOUSE &&
(!chQueries || Object.keys(chQueries).length === 0)
) {
setChQueries({
@@ -70,148 +55,9 @@ function QuerySection({
},
});
}
- setQueryCategory(parseInt(s, 10));
+ setQueryCategory(queryType as EQueryType);
};
- const getNextQueryLabel = useCallback((): string => {
- let maxAscii = 0;
-
- Object.keys(metricQueries).forEach((key) => {
- const n = key.charCodeAt(0);
- if (n > maxAscii) {
- maxAscii = n - 64;
- }
- });
-
- return String.fromCharCode(64 + maxAscii + 1);
- }, [metricQueries]);
-
- const handleFormulaChange = ({
- formulaIndex,
- expression,
- legend,
- toggleDisable,
- toggleDelete,
- }: IQueryBuilderFormulaHandleChange): void => {
- const allFormulas = formulaQueries;
- const current = allFormulas[formulaIndex];
- if (expression !== undefined) {
- current.expression = expression;
- }
-
- if (legend !== undefined) {
- current.legend = legend;
- }
-
- if (toggleDisable) {
- current.disabled = !current.disabled;
- }
-
- if (toggleDelete) {
- delete allFormulas[formulaIndex];
- } else {
- allFormulas[formulaIndex] = current;
- }
-
- setFormulaQueries({
- ...allFormulas,
- });
- };
-
- const handleMetricQueryChange = ({
- queryIndex,
- aggregateFunction,
- metricName,
- tagFilters,
- groupBy,
- legend,
- toggleDisable,
- toggleDelete,
- }: IQueryBuilderQueryHandleChange): void => {
- const allQueries = metricQueries;
- const current = metricQueries[queryIndex];
- if (aggregateFunction) {
- current.aggregateOperator = aggregateFunction;
- }
- if (metricName) {
- current.metricName = metricName;
- }
-
- if (tagFilters && current.tagFilters) {
- current.tagFilters.items = tagFilters;
- }
-
- if (legend) {
- current.legend = legend;
- }
-
- if (groupBy) {
- current.groupBy = groupBy;
- }
-
- if (toggleDisable) {
- current.disabled = !current.disabled;
- }
-
- if (toggleDelete) {
- delete allQueries[queryIndex];
- } else {
- allQueries[queryIndex] = current;
- }
-
- setMetricQueries({
- ...allQueries,
- });
- };
- const { notifications } = useNotifications();
-
- const addMetricQuery = useCallback(() => {
- if (Object.keys(metricQueries).length > 5) {
- notifications.error({
- message: t('metric_query_max_limit'),
- });
- return;
- }
-
- const queryLabel = getNextQueryLabel();
-
- const queries = metricQueries;
- queries[queryLabel] = {
- name: queryLabel,
- queryName: queryLabel,
- metricName: '',
- formulaOnly: false,
- aggregateOperator: EAggregateOperator.NOOP,
- legend: '',
- tagFilters: {
- op: 'AND',
- items: [],
- },
- groupBy: [],
- disabled: false,
- expression: queryLabel,
- };
- setMetricQueries({ ...queries });
- }, [t, getNextQueryLabel, metricQueries, setMetricQueries, notifications]);
-
- const addFormula = useCallback(() => {
- // defaulting to F1 as only one formula is supported
- // in alert definition
- const queryLabel = 'F1';
-
- const formulas = formulaQueries;
- formulas[queryLabel] = {
- queryName: queryLabel,
- name: queryLabel,
- formulaOnly: true,
- expression: 'A',
- disabled: false,
- legend: '',
- };
-
- setFormulaQueries({ ...formulas });
- }, [formulaQueries, setFormulaQueries]);
-
const renderPromqlUI = (): JSX.Element => (
);
@@ -220,61 +66,14 @@ function QuerySection({
);
- const renderFormulaButton = (): JSX.Element => (
- }>
- {t('button_formula')}
-
- );
-
- const renderQueryButton = (): JSX.Element => (
- }>
- {t('button_query')}
-
- );
-
const renderMetricUI = (): JSX.Element => (
-
- {metricQueries &&
- Object.keys(metricQueries).map((key: string) => {
- // todo(amol): need to handle this in fetch
- const current = metricQueries[key];
- current.name = key;
-
- return (
-
- );
- })}
-
- {queryCategory !== EQueryType.PROM && renderQueryButton()}
-
- {formulaQueries &&
- Object.keys(formulaQueries).map((key: string) => {
- // todo(amol): need to handle this in fetch
- const current = formulaQueries[key];
- current.name = key;
-
- return (
-
- );
- })}
- {queryCategory === EQueryType.QUERY_BUILDER &&
- (!formulaQueries || Object.keys(formulaQueries).length === 0) &&
- metricQueries &&
- Object.keys(metricQueries).length > 0 &&
- renderFormulaButton()}
-
-
+
);
const handleRunQuery = (): void => {
@@ -284,20 +83,22 @@ function QuerySection({
const tabs = [
{
label: t('tab_qb'),
- key: EQueryType.QUERY_BUILDER.toString(),
- disabled: true,
+ key: EQueryType.QUERY_BUILDER,
},
{
label: t('tab_chquery'),
- key: EQueryType.CLICKHOUSE.toString(),
+ key: EQueryType.CLICKHOUSE,
},
];
- const items = [
- { label: t('tab_qb'), key: EQueryType.QUERY_BUILDER.toString() },
- { label: t('tab_chquery'), key: EQueryType.CLICKHOUSE.toString() },
- { label: t('tab_promql'), key: EQueryType.PROM.toString() },
- ];
+ const items = useMemo(
+ () => [
+ { label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
+ { label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
+ { label: t('tab_promql'), key: EQueryType.PROM },
+ ],
+ [t],
+ );
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
switch (typ) {
@@ -308,16 +109,14 @@ function QuerySection({
- {queryCategory === EQueryType.CLICKHOUSE && (
-
- Run Query
-
- )}
+
+ Run Query
+
}
items={tabs}
@@ -329,16 +128,14 @@ function QuerySection({
- {queryCategory === EQueryType.CLICKHOUSE && (
-
- Run Query
-
- )}
+
+ Run Query
+
}
items={items}
@@ -372,10 +169,6 @@ function QuerySection({
interface QuerySectionProps {
queryCategory: EQueryType;
setQueryCategory: (n: EQueryType) => void;
- metricQueries: IMetricQueries;
- setMetricQueries: (b: IMetricQueries) => void;
- formulaQueries: IFormulaQueries;
- setFormulaQueries: (b: IFormulaQueries) => void;
promQueries: IPromQueries;
setPromQueries: (p: IPromQueries) => void;
chQueries: IChQueries;
diff --git a/frontend/src/container/FormAlertRules/RuleOptions.tsx b/frontend/src/container/FormAlertRules/RuleOptions.tsx
index 071ff84d7f..f24f38b017 100644
--- a/frontend/src/container/FormAlertRules/RuleOptions.tsx
+++ b/frontend/src/container/FormAlertRules/RuleOptions.tsx
@@ -1,5 +1,4 @@
import { Form, Select, Typography } from 'antd';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import {
AlertDef,
diff --git a/frontend/src/container/FormAlertRules/UserGuide/index.tsx b/frontend/src/container/FormAlertRules/UserGuide/index.tsx
index 33312a9fd7..86992d7226 100644
--- a/frontend/src/container/FormAlertRules/UserGuide/index.tsx
+++ b/frontend/src/container/FormAlertRules/UserGuide/index.tsx
@@ -1,6 +1,5 @@
import { Col, Row, Typography } from 'antd';
import TextToolTip from 'components/TextToolTip';
-import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { EQueryType } from 'types/common/dashboard';
diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx
index 9a3ea93466..44dd192729 100644
--- a/frontend/src/container/FormAlertRules/index.tsx
+++ b/frontend/src/container/FormAlertRules/index.tsx
@@ -1,22 +1,22 @@
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
-import { Col, FormInstance, Modal, Typography } from 'antd';
+import { Col, FormInstance, Modal, Tooltip, Typography } from 'antd';
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
+import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback, useEffect, useState } from 'react';
+import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
+import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
+import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { AlertTypes } from 'types/api/alerts/alertTypes';
-import {
- IChQueries,
- IFormulaQueries,
- IMetricQueries,
- IPromQueries,
-} from 'types/api/alerts/compositeQuery';
+import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
import {
AlertDef,
defaultEvalWindow,
@@ -36,15 +36,8 @@ import {
PanelContainer,
StyledLeftContainer,
} from './styles';
-import useDebounce from './useDebounce';
import UserGuide from './UserGuide';
-import {
- prepareBuilderQueries,
- prepareStagedQuery,
- toChartInterval,
- toFormulaQueries,
- toMetricQueries,
-} from './utils';
+import { prepareStagedQuery, toChartInterval } from './utils';
function FormAlertRules({
alertType,
@@ -55,33 +48,21 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
+ const { queryBuilderData, initQueryBuilderData } = useQueryBuilder();
+
// use query client
const ruleCache = useQueryClient();
const [loading, setLoading] = useState(false);
- // queryRunId helps to override of query caching for clickhouse query
- // tab. A random string will be assigned for each execution
- const [runQueryId, setRunQueryId] = useState();
-
// alertDef holds the form values to be posted
const [alertDef, setAlertDef] = useState(initialValue);
// initQuery contains initial query when component was mounted
- const initQuery = initialValue?.condition?.compositeMetricQuery;
+ const initQuery = initialValue.condition.compositeQuery;
const [queryCategory, setQueryCategory] = useState(
- initQuery?.queryType,
- );
-
- // local state to handle metric queries
- const [metricQueries, setMetricQueries] = useState(
- toMetricQueries(initQuery?.builderQueries),
- );
-
- // local state to handle formula queries
- const [formulaQueries, setFormulaQueries] = useState(
- toFormulaQueries(initQuery?.builderQueries),
+ initQuery.queryType,
);
// local state to handle promql queries
@@ -106,43 +87,31 @@ function FormAlertRules({
// run query button is provided.
const [manualStagedQuery, setManualStagedQuery] = useState();
- // delay to reduce load on backend api with auto-run query. only for clickhouse
- // queries we have manual run, hence both debounce and debounceStagedQuery are not required
- const debounceDelay = queryCategory !== EQueryType.CLICKHOUSE ? 1000 : 0;
-
- // debounce query to delay backend api call and chart update.
- // used in query builder and promql tabs to enable auto-refresh
- // of chart on user edit
- const debouncedStagedQuery = useDebounce(stagedQuery, debounceDelay);
-
// this use effect initiates staged query and
// other queries based on server data.
// useful when fetching of initial values (from api)
// is delayed
useEffect(() => {
- const initQuery = initialValue?.condition?.compositeMetricQuery;
- const typ = initQuery?.queryType;
+ const initQuery = initialValue?.condition?.compositeQuery;
+ const type = initQuery.queryType;
- // extract metric query from builderQueries
- const mq = toMetricQueries(initQuery?.builderQueries);
-
- // extract formula query from builderQueries
- const fq = toFormulaQueries(initQuery?.builderQueries);
+ const builderData = mapQueryDataFromApi(
+ initialValue?.condition?.compositeQuery?.builderQueries || {},
+ );
// prepare staged query
const sq = prepareStagedQuery(
- typ,
- mq,
- fq,
+ type,
+ builderData.queryData,
+ builderData.queryFormulas,
initQuery?.promQueries,
initQuery?.chQueries,
);
const pq = initQuery?.promQueries;
const chq = initQuery?.chQueries;
- setQueryCategory(typ);
- setMetricQueries(mq);
- setFormulaQueries(fq);
+ setQueryCategory(type);
+ initQueryBuilderData(builderData);
setPromQueries(pq);
setStagedQuery(sq);
@@ -151,7 +120,7 @@ function FormAlertRules({
setChQueries(chq);
setAlertDef(initialValue);
- }, [initialValue]);
+ }, [initialValue, initQueryBuilderData]);
// this useEffect updates staging query when
// any of its sub-parameters changes
@@ -159,16 +128,15 @@ function FormAlertRules({
// prepare staged query
const sq: StagedQuery = prepareStagedQuery(
queryCategory,
- metricQueries,
- formulaQueries,
+ queryBuilderData.queryData,
+ queryBuilderData.queryFormulas,
promQueries,
chQueries,
);
setStagedQuery(sq);
- }, [queryCategory, chQueries, metricQueries, formulaQueries, promQueries]);
+ }, [queryCategory, chQueries, queryBuilderData, promQueries]);
const onRunQuery = (): void => {
- setRunQueryId(Math.random().toString(36).substring(2, 15));
setManualStagedQuery(stagedQuery);
};
@@ -179,6 +147,7 @@ function FormAlertRules({
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
const onQueryCategoryChange = (val: EQueryType): void => {
+ console.log('onQueryCategoryChange', val);
setQueryCategory(val);
if (val === EQueryType.PROM) {
setAlertDef({
@@ -190,6 +159,15 @@ function FormAlertRules({
evalWindow: defaultEvalWindow,
});
}
+
+ const sq: StagedQuery = prepareStagedQuery(
+ val,
+ queryBuilderData.queryData,
+ queryBuilderData.queryFormulas,
+ promQueries,
+ chQueries,
+ );
+ setManualStagedQuery(sq);
};
const { notifications } = useNotifications();
@@ -244,10 +222,9 @@ function FormAlertRules({
}, [t, chQueries, queryCategory, notifications]);
const validateQBParams = useCallback((): boolean => {
- let retval = true;
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
- if (!metricQueries || Object.keys(metricQueries).length === 0) {
+ if (!queryBuilderData.queryData || queryBuilderData.queryData.length === 0) {
notifications.error({
message: 'Error',
description: t('condition_required'),
@@ -255,7 +232,7 @@ function FormAlertRules({
return false;
}
- if (!alertDef.condition?.target) {
+ if (alertDef.condition?.target !== 0 && !alertDef.condition?.target) {
notifications.error({
message: 'Error',
description: t('target_missing'),
@@ -263,27 +240,8 @@ function FormAlertRules({
return false;
}
- Object.keys(metricQueries).forEach((key) => {
- if (metricQueries[key].metricName === '') {
- notifications.error({
- message: 'Error',
- description: t('metricname_missing', { where: metricQueries[key].name }),
- });
- retval = false;
- }
- });
-
- Object.keys(formulaQueries).forEach((key) => {
- if (formulaQueries[key].expression === '') {
- notifications.error({
- message: 'Error',
- description: t('expression_missing', formulaQueries[key].name),
- });
- retval = false;
- }
- });
- return retval;
- }, [t, alertDef, queryCategory, metricQueries, formulaQueries, notifications]);
+ return true;
+ }, [t, alertDef, queryCategory, queryBuilderData, notifications]);
const isFormValid = useCallback((): boolean => {
if (!alertDef.alert || alertDef.alert === '') {
@@ -321,11 +279,12 @@ function FormAlertRules({
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
condition: {
...alertDef.condition,
- compositeMetricQuery: {
- builderQueries: prepareBuilderQueries(metricQueries, formulaQueries),
+ compositeQuery: {
+ builderQueries: mapQueryDataToApi(queryBuilderData).data,
promQueries,
chQueries,
queryType: queryCategory,
+ panelType: initQuery.panelType,
},
},
};
@@ -335,13 +294,17 @@ function FormAlertRules({
const memoizedPreparePostData = useCallback(preparePostData, [
queryCategory,
alertDef,
- metricQueries,
- formulaQueries,
+ queryBuilderData,
promQueries,
chQueries,
alertType,
+ initQuery,
]);
+ const isAlertAvialable = useIsFeatureDisabled(
+ FeatureKeys.QUERY_BUILDER_ALERTS,
+ );
+
const saveRule = useCallback(async () => {
if (!isFormValid()) {
return;
@@ -458,7 +421,7 @@ function FormAlertRules({
headline={ }
name=""
threshold={alertDef.condition?.target}
- query={debouncedStagedQuery}
+ query={manualStagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);
@@ -468,7 +431,7 @@ function FormAlertRules({
headline={ }
name="Chart Preview"
threshold={alertDef.condition?.target}
- query={debouncedStagedQuery}
+ query={manualStagedQuery}
/>
);
@@ -478,10 +441,15 @@ function FormAlertRules({
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
- userQueryKey={runQueryId}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);
+
+ const isNewRule = ruleId === 0;
+
+ const isAlertAvialableToSave =
+ isAlertAvialable && isNewRule && queryCategory === EQueryType.QUERY_BUILDER;
+
return (
<>
{Element}
@@ -498,10 +466,6 @@ function FormAlertRules({
- }
- >
- {ruleId > 0 ? t('button_savechanges') : t('button_createrule')}
-
+
+ }
+ disabled={isAlertAvialableToSave}
+ >
+ {isNewRule ? t('button_createrule') : t('button_savechanges')}
+
+
+
): void => {
+ const handleChange = (e: ChangeEvent): void => {
setCurrentVal(e.target?.value);
};
diff --git a/frontend/src/container/FormAlertRules/styles.ts b/frontend/src/container/FormAlertRules/styles.ts
index 23cf11c8ac..e5cd42001d 100644
--- a/frontend/src/container/FormAlertRules/styles.ts
+++ b/frontend/src/container/FormAlertRules/styles.ts
@@ -82,7 +82,6 @@ export const InputSmall = styled(Input)`
`;
export const FormContainer = styled(Card)`
- padding: 2em;
margin-top: 1rem;
display: flex;
flex-direction: column;
diff --git a/frontend/src/container/FormAlertRules/useDebounce.js b/frontend/src/container/FormAlertRules/useDebounce.js
deleted file mode 100644
index e430f55d63..0000000000
--- a/frontend/src/container/FormAlertRules/useDebounce.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-// @ts-nocheck
-
-import { useEffect, useState } from 'react';
-
-// see https://github.com/tannerlinsley/react-query/issues/293
-// see https://usehooks.com/useDebounce/
-export default function useDebounce(value, delay) {
- // State and setters for debounced value
- const [debouncedValue, setDebouncedValue] = useState(value);
-
- useEffect(
- () => {
- // Update debounced value after delay
- const handler = setTimeout(() => {
- setDebouncedValue(value);
- }, delay);
-
- // Cancel the timeout if value changes (also on delay change or unmount)
- // This is how we prevent debounced value from updating if value is changed ...
- // .. within the delay period. Timeout gets cleared and restarted.
- return () => {
- clearTimeout(handler);
- };
- },
- [value, delay] // Only re-call effect if value or delay changes
- );
-
- return debouncedValue;
-}
diff --git a/frontend/src/container/FormAlertRules/utils.ts b/frontend/src/container/FormAlertRules/utils.ts
index a3978a9007..abbfb55df5 100644
--- a/frontend/src/container/FormAlertRules/utils.ts
+++ b/frontend/src/container/FormAlertRules/utils.ts
@@ -1,102 +1,27 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
import {
- IBuilderQueries,
IChQueries,
IChQuery,
- IFormulaQueries,
- IFormulaQuery,
- IMetricQueries,
- IMetricQuery,
IPromQueries,
IPromQuery,
} from 'types/api/alerts/compositeQuery';
+import { Query as IStagedQuery } from 'types/api/dashboard/getAll';
import {
- IMetricsBuilderQuery,
- Query as IStagedQuery,
-} from 'types/api/dashboard/getAll';
+ IBuilderFormula,
+ IBuilderQuery,
+} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
-export const toFormulaQueries = (b: IBuilderQueries): IFormulaQueries => {
- const f: IFormulaQueries = {};
- if (!b) return f;
- Object.keys(b).forEach((key) => {
- if (key === 'F1') {
- f[key] = b[key] as IFormulaQuery;
- }
- });
-
- return f;
-};
-
-export const toMetricQueries = (b: IBuilderQueries): IMetricQueries => {
- const m: IMetricQueries = {};
- if (!b) return m;
- Object.keys(b).forEach((key) => {
- if (key !== 'F1') {
- m[key] = b[key] as IMetricQuery;
- }
- });
-
- return m;
-};
-
-export const toIMetricsBuilderQuery = (
- q: IMetricQuery,
-): IMetricsBuilderQuery => ({
- name: q.name,
- metricName: q.metricName,
- tagFilters: q.tagFilters,
- groupBy: q.groupBy,
- aggregateOperator: q.aggregateOperator,
- disabled: q.disabled,
- legend: q.legend,
-});
-
-export const prepareBuilderQueries = (
- m: IMetricQueries,
- f: IFormulaQueries,
-): IBuilderQueries => {
- if (!m) return {};
- const b: IBuilderQueries = {
- ...m,
- };
-
- Object.keys(f).forEach((key) => {
- b[key] = {
- ...f[key],
- aggregateOperator: undefined,
- metricName: '',
- };
- });
- return b;
-};
-
export const prepareStagedQuery = (
t: EQueryType,
- m: IMetricQueries,
- f: IFormulaQueries,
+ m: IBuilderQuery[],
+ f: IBuilderFormula[],
p: IPromQueries,
c: IChQueries,
): IStagedQuery => {
- const qbList: IMetricQuery[] = [];
- const formulaList: IFormulaQuery[] = [];
const promList: IPromQuery[] = [];
const chQueryList: IChQuery[] = [];
- // convert map[string]IMetricQuery to IMetricQuery[]
- if (m) {
- Object.keys(m).forEach((key) => {
- qbList.push(m[key]);
- });
- }
-
- // convert map[string]IFormulaQuery to IFormulaQuery[]
- if (f) {
- Object.keys(f).forEach((key) => {
- formulaList.push(f[key]);
- });
- }
-
// convert map[string]IPromQuery to IPromQuery[]
if (p) {
Object.keys(p).forEach((key) => {
@@ -112,12 +37,12 @@ export const prepareStagedQuery = (
return {
queryType: t,
- promQL: promList,
- metricsBuilder: {
- formulas: formulaList,
- queryBuilder: qbList,
+ promql: promList,
+ builder: {
+ queryFormulas: f,
+ queryData: m,
},
- clickHouse: chQueryList,
+ clickhouse_sql: chQueryList,
};
};
diff --git a/frontend/src/container/GantChart/SpanLength/index.tsx b/frontend/src/container/GantChart/SpanLength/index.tsx
index efe62deaff..9e3611bb01 100644
--- a/frontend/src/container/GantChart/SpanLength/index.tsx
+++ b/frontend/src/container/GantChart/SpanLength/index.tsx
@@ -1,6 +1,5 @@
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React from 'react';
import { toFixed } from 'utils/toFixed';
import { SpanBorder, SpanLine, SpanText, SpanWrapper } from './styles';
diff --git a/frontend/src/container/GantChart/SpanName/index.tsx b/frontend/src/container/GantChart/SpanName/index.tsx
index 7f536624b9..90c6ffa80d 100644
--- a/frontend/src/container/GantChart/SpanName/index.tsx
+++ b/frontend/src/container/GantChart/SpanName/index.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { Container, Service, Span, SpanWrapper } from './styles';
function SpanNameComponent({
diff --git a/frontend/src/container/GantChart/Trace/index.tsx b/frontend/src/container/GantChart/Trace/index.tsx
index e419283757..d38e3594ae 100644
--- a/frontend/src/container/GantChart/Trace/index.tsx
+++ b/frontend/src/container/GantChart/Trace/index.tsx
@@ -4,7 +4,15 @@ import { StyledCol, StyledRow } from 'components/Styled';
import { IIntervalUnit } from 'container/TraceDetail/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import {
+ Dispatch,
+ MouseEventHandler,
+ SetStateAction,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import { ITraceMetaData } from '..';
@@ -71,7 +79,7 @@ function Trace(props: TraceProps): JSX.Element {
const ref = useRef(null);
- React.useEffect(() => {
+ useEffect(() => {
if (activeSelectedId === id) {
ref.current?.scrollIntoView({
block: 'nearest',
@@ -97,7 +105,7 @@ function Trace(props: TraceProps): JSX.Element {
setActiveSelectedId(id);
};
- const onClickTreeExpansion: React.MouseEventHandler = (
+ const onClickTreeExpansion: MouseEventHandler = (
event,
): void => {
event.stopPropagation();
@@ -207,8 +215,8 @@ interface ITraceGlobal {
interface TraceProps extends ITraceTree, ITraceGlobal {
activeHoverId: string;
- setActiveHoverId: React.Dispatch>;
- setActiveSelectedId: React.Dispatch>;
+ setActiveHoverId: Dispatch>;
+ setActiveSelectedId: Dispatch>;
activeSelectedId: string;
level: number;
activeSpanPath: string[];
diff --git a/frontend/src/container/GantChart/index.tsx b/frontend/src/container/GantChart/index.tsx
index dbe707c2d7..0d54a837f2 100644
--- a/frontend/src/container/GantChart/index.tsx
+++ b/frontend/src/container/GantChart/index.tsx
@@ -1,6 +1,6 @@
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
import { IIntervalUnit } from 'container/TraceDetail/utils';
-import React, { useEffect, useState } from 'react';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import { CardContainer, CardWrapper, CollapseButton } from './styles';
@@ -79,8 +79,8 @@ export interface GanttChartProps {
traceMetaData: ITraceMetaData;
activeSelectedId: string;
activeHoverId: string;
- setActiveHoverId: React.Dispatch>;
- setActiveSelectedId: React.Dispatch>;
+ setActiveHoverId: Dispatch>;
+ setActiveSelectedId: Dispatch>;
spanId: string;
intervalUnit: IIntervalUnit;
}
diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx
index 7530364d65..5491e1394b 100644
--- a/frontend/src/container/GeneralSettings/GeneralSettings.tsx
+++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx
@@ -6,7 +6,7 @@ import TextToolTip from 'components/TextToolTip';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import find from 'lodash-es/find';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
@@ -519,7 +519,7 @@ function GeneralSettings({
category.retentionFields.length > 0
) {
return (
-
+
@@ -576,7 +576,7 @@ function GeneralSettings({
-
+
);
}
return null;
diff --git a/frontend/src/container/GeneralSettings/Retention.tsx b/frontend/src/container/GeneralSettings/Retention.tsx
index 806f811bba..6228391503 100644
--- a/frontend/src/container/GeneralSettings/Retention.tsx
+++ b/frontend/src/container/GeneralSettings/Retention.tsx
@@ -1,6 +1,13 @@
import { Col, Row, Select } from 'antd';
import { find } from 'lodash-es';
-import React, { useEffect, useRef, useState } from 'react';
+import {
+ ChangeEvent,
+ Dispatch,
+ SetStateAction,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import {
Input,
@@ -62,8 +69,8 @@ function Retention({
}, [selectedTimeUnit, selectedValue, setRetentionValue]);
const onChangeHandler = (
- e: React.ChangeEvent,
- func: React.Dispatch>,
+ e: ChangeEvent,
+ func: Dispatch>,
): void => {
interacted.current = true;
const { value } = e.target;
@@ -111,7 +118,7 @@ function Retention({
interface RetentionProps {
retentionValue: number | null;
text: string;
- setRetentionValue: React.Dispatch>;
+ setRetentionValue: Dispatch>;
hide: boolean;
}
diff --git a/frontend/src/container/GeneralSettings/StatusMessage.tsx b/frontend/src/container/GeneralSettings/StatusMessage.tsx
index bb62f34007..a673bb8bb8 100644
--- a/frontend/src/container/GeneralSettings/StatusMessage.tsx
+++ b/frontend/src/container/GeneralSettings/StatusMessage.tsx
@@ -1,7 +1,7 @@
import { green, orange, volcano } from '@ant-design/colors';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Card, Col, Row } from 'antd';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { TStatus } from 'types/api/settings/getRetention';
diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx
index 2e803563e4..d82889f694 100644
--- a/frontend/src/container/GeneralSettings/index.tsx
+++ b/frontend/src/container/GeneralSettings/index.tsx
@@ -2,12 +2,14 @@ import { Typography } from 'antd';
import getDisks from 'api/disks/getDisks';
import getRetentionPeriodApi from 'api/settings/getRetention';
import Spinner from 'components/Spinner';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { TTTLType } from 'types/api/settings/common';
import { PayloadProps as GetRetentionPeriodAPIPayloadProps } from 'types/api/settings/getRetention';
+import AppReducer from 'types/reducer/app';
import GeneralSettingsContainer from './GeneralSettings';
@@ -17,6 +19,7 @@ type TRetentionAPIReturn = Promise<
function GeneralSettings(): JSX.Element {
const { t } = useTranslation('common');
+ const { user } = useSelector((state) => state.app);
const [
getRetentionPeriodMetricsApiResponse,
@@ -27,20 +30,20 @@ function GeneralSettings(): JSX.Element {
{
queryFn: (): TRetentionAPIReturn<'metrics'> =>
getRetentionPeriodApi('metrics'),
- queryKey: 'getRetentionPeriodApiMetrics',
+ queryKey: ['getRetentionPeriodApiMetrics', user?.accessJwt],
},
{
queryFn: (): TRetentionAPIReturn<'traces'> =>
getRetentionPeriodApi('traces'),
- queryKey: 'getRetentionPeriodApiTraces',
+ queryKey: ['getRetentionPeriodApiTraces', user?.accessJwt],
},
{
queryFn: (): TRetentionAPIReturn<'logs'> => getRetentionPeriodApi('logs'),
- queryKey: 'getRetentionPeriodApiLogs',
+ queryKey: ['getRetentionPeriodApiLogs', user?.accessJwt],
},
{
queryFn: getDisks,
- queryKey: 'getDisks',
+ queryKey: ['getDisks', user?.accessJwt],
},
]);
diff --git a/frontend/src/container/GridGraphComponent/index.tsx b/frontend/src/container/GridGraphComponent/index.tsx
index f5612b0a40..22fa25f98d 100644
--- a/frontend/src/container/GridGraphComponent/index.tsx
+++ b/frontend/src/container/GridGraphComponent/index.tsx
@@ -3,9 +3,9 @@ import { ChartData } from 'chart.js';
import Graph, { GraphOnClickHandler, StaticLineProps } from 'components/Graph';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import ValueGraph from 'components/ValueGraph';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import history from 'lib/history';
-import React from 'react';
import { TitleContainer, ValueContainer } from './styles';
@@ -25,7 +25,7 @@ function GridGraphComponent({
const isDashboardPage = location.split('/').length === 3;
- if (GRAPH_TYPES === 'TIME_SERIES') {
+ if (GRAPH_TYPES === PANEL_TYPES.TIME_SERIES) {
return (
('');
const [hovered, setHovered] = useState(false);
const [modal, setModal] = useState(false);
const [deleteModal, setDeleteModal] = useState(false);
- const { minTime, maxTime } = useSelector(
- (state) => state.globalTime,
- );
- const { selectedTime: globalSelectedInterval } = useSelector<
+ const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
+ const { featureResponse } = useSelector(
+ (state) => state.app,
+ );
const { dashboards } = useSelector(
(state) => state.dashboards,
);
@@ -111,7 +124,7 @@ function GridCardGraph({
const prevChartDataSetRef = usePreviousValue(chartData);
const onToggleModal = useCallback(
- (func: React.Dispatch>) => {
+ (func: Dispatch>) => {
func((value) => !value);
},
[],
@@ -122,9 +135,27 @@ function GridCardGraph({
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
- deleteWidget({ widgetId, setLayout });
- onToggleModal(setDeleteModal);
- }, [deleteWidget, layout, onToggleModal, setLayout, widget]);
+ featureResponse
+ .refetch()
+ .then(() => {
+ deleteWidget({ widgetId, setLayout });
+ onToggleModal(setDeleteModal);
+ })
+ .catch(() => {
+ notifications.error({
+ message: t('common:something_went_wrong'),
+ });
+ });
+ }, [
+ widget,
+ layout,
+ featureResponse,
+ deleteWidget,
+ setLayout,
+ onToggleModal,
+ notifications,
+ t,
+ ]);
const getModals = (): JSX.Element => (
<>
@@ -294,7 +325,7 @@ interface GridCardGraphProps extends DispatchProps {
// eslint-disable-next-line react/require-default-props
layout?: Layout[];
// eslint-disable-next-line react/require-default-props
- setLayout?: React.Dispatch>;
+ setLayout?: Dispatch>;
onDragSelect?: (start: number, end: number) => void;
}
diff --git a/frontend/src/container/GridGraphLayout/GraphLayout.tsx b/frontend/src/container/GridGraphLayout/GraphLayout.tsx
index d615fb1a13..02cbb7ed6c 100644
--- a/frontend/src/container/GridGraphLayout/GraphLayout.tsx
+++ b/frontend/src/container/GridGraphLayout/GraphLayout.tsx
@@ -1,7 +1,7 @@
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { Layout } from 'react-grid-layout';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -102,7 +102,7 @@ interface GraphLayoutProps {
onAddPanelHandler: VoidFunction;
onLayoutChangeHandler: (layout: Layout[]) => Promise;
widgets: Widgets[] | undefined;
- setLayout: React.Dispatch>;
+ setLayout: Dispatch>;
}
export default GraphLayout;
diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts b/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts
index 2d9771d235..0ac1105ba6 100644
--- a/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts
+++ b/frontend/src/container/GridGraphLayout/WidgetHeader/config.ts
@@ -1,6 +1,7 @@
import { themeColors } from 'constants/theme';
+import { CSSProperties } from 'react';
-const positionCss: React.CSSProperties['position'] = 'fixed';
+const positionCss: CSSProperties['position'] = 'fixed';
export const spinnerStyles = { position: positionCss, right: '0.5rem' };
export const tooltipStyles = {
@@ -13,7 +14,7 @@ export const tooltipStyles = {
export const errorTooltipPosition = 'top';
-export const overlayStyles: React.CSSProperties = {
+export const overlayStyles: CSSProperties = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx
index 6a3795e6a2..ed7eab478d 100644
--- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx
+++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx
@@ -10,7 +10,7 @@ import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import Spinner from 'components/Spinner';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
-import React, { useCallback, useMemo, useState } from 'react';
+import { useCallback, useMemo, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx
index 2d57c98eda..d299e01e7e 100644
--- a/frontend/src/container/GridGraphLayout/index.tsx
+++ b/frontend/src/container/GridGraphLayout/index.tsx
@@ -3,11 +3,17 @@
import updateDashboardApi from 'api/dashboard/update';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useEffect, useState } from 'react';
+import {
+ Dispatch,
+ SetStateAction,
+ useCallback,
+ useEffect,
+ useState,
+} from 'react';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next';
import { connect, useDispatch, useSelector } from 'react-redux';
-import { bindActionCreators, Dispatch } from 'redux';
+import { bindActionCreators, Dispatch as ReduxDispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { AppDispatch } from 'store';
import { UpdateTimeInterval } from 'store/actions';
@@ -66,7 +72,7 @@ function GridGraph(props: Props): JSX.Element {
const [selectedDashboard] = dashboards;
const { data } = selectedDashboard;
const { widgets } = data;
- const dispatch: AppDispatch = useDispatch>();
+ const dispatch: AppDispatch = useDispatch>();
const [layouts, setLayout] = useState(
getPreLayouts(widgets, selectedDashboard.data.layout || []),
@@ -84,6 +90,8 @@ function GridGraph(props: Props): JSX.Element {
[dispatch],
);
+ const { notifications } = useNotifications();
+
useEffect(() => {
(async (): Promise => {
if (!isAddWidget) {
@@ -119,6 +127,12 @@ function GridGraph(props: Props): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ const { featureResponse } = useSelector(
+ (state) => state.app,
+ );
+
+ const errorMessage = t('common:something_went_wrong');
+
const onLayoutSaveHandler = useCallback(
async (layout: Layout[]) => {
try {
@@ -128,44 +142,62 @@ function GridGraph(props: Props): JSX.Element {
errorMessage: '',
loading: true,
}));
- const updatedDashboard: Dashboard = {
- ...selectedDashboard,
- data: {
- title: data.title,
- description: data.description,
- name: data.name,
- tags: data.tags,
- widgets: data.widgets,
- variables: data.variables,
- layout,
- },
- uuid: selectedDashboard.uuid,
- };
- // Save layout only when users has the has the permission to do so.
- if (saveLayoutPermission) {
- const response = await updateDashboardApi(updatedDashboard);
- if (response.statusCode === 200) {
- setSaveLayoutState((state) => ({
- ...state,
- error: false,
- errorMessage: '',
- loading: false,
- }));
- dispatch({
- type: UPDATE_DASHBOARD,
- payload: updatedDashboard,
- });
- } else {
+
+ featureResponse
+ .refetch()
+ .then(async () => {
+ const updatedDashboard: Dashboard = {
+ ...selectedDashboard,
+ data: {
+ title: data.title,
+ description: data.description,
+ name: data.name,
+ tags: data.tags,
+ widgets: data.widgets,
+ variables: data.variables,
+ layout,
+ },
+ uuid: selectedDashboard.uuid,
+ };
+ // Save layout only when users has the has the permission to do so.
+ if (saveLayoutPermission) {
+ const response = await updateDashboardApi(updatedDashboard);
+ if (response.statusCode === 200) {
+ setSaveLayoutState((state) => ({
+ ...state,
+ error: false,
+ errorMessage: '',
+ loading: false,
+ }));
+ dispatch({
+ type: UPDATE_DASHBOARD,
+ payload: updatedDashboard,
+ });
+ } else {
+ setSaveLayoutState((state) => ({
+ ...state,
+ error: true,
+ errorMessage: response.error || errorMessage,
+ loading: false,
+ }));
+ }
+ }
+ })
+ .catch(() => {
setSaveLayoutState((state) => ({
...state,
error: true,
- errorMessage: response.error || 'Something went wrong',
+ errorMessage,
loading: false,
}));
- }
- }
+ notifications.error({
+ message: errorMessage,
+ });
+ });
} catch (error) {
- console.error(error);
+ notifications.error({
+ message: errorMessage,
+ });
}
},
[
@@ -176,6 +208,9 @@ function GridGraph(props: Props): JSX.Element {
data.variables,
data.widgets,
dispatch,
+ errorMessage,
+ featureResponse,
+ notifications,
saveLayoutPermission,
selectedDashboard,
],
@@ -207,8 +242,6 @@ function GridGraph(props: Props): JSX.Element {
[widgets, onDragSelect],
);
- const { notifications } = useNotifications();
-
const onEmptyWidgetHandler = useCallback(async () => {
try {
const id = 'empty';
@@ -239,10 +272,10 @@ function GridGraph(props: Props): JSX.Element {
setLayoutFunction(layout);
} catch (error) {
notifications.error({
- message: error instanceof Error ? error.toString() : 'Something went wrong',
+ message: error instanceof Error ? error.toString() : errorMessage,
});
}
- }, [data, selectedDashboard, setLayoutFunction, notifications]);
+ }, [data, selectedDashboard, setLayoutFunction, notifications, errorMessage]);
const onLayoutChangeHandler = async (layout: Layout[]): Promise => {
setLayoutFunction(layout);
@@ -253,49 +286,63 @@ function GridGraph(props: Props): JSX.Element {
const onAddPanelHandler = useCallback(() => {
try {
setAddPanelLoading(true);
- const isEmptyLayoutPresent =
- layouts.find((e) => e.i === 'empty') !== undefined;
+ featureResponse
+ .refetch()
+ .then(() => {
+ const isEmptyLayoutPresent =
+ layouts.find((e) => e.i === 'empty') !== undefined;
- if (!isEmptyLayoutPresent) {
- onEmptyWidgetHandler()
- .then(() => {
- setAddPanelLoading(false);
+ if (!isEmptyLayoutPresent) {
+ onEmptyWidgetHandler()
+ .then(() => {
+ setAddPanelLoading(false);
+ toggleAddWidget(true);
+ })
+ .catch(() => {
+ notifications.error({
+ message: errorMessage,
+ });
+ });
+ } else {
toggleAddWidget(true);
- })
- .catch(() => {
- notifications.error(t('something_went_wrong'));
- });
- } else {
- toggleAddWidget(true);
- setAddPanelLoading(false);
- }
+ setAddPanelLoading(false);
+ }
+ })
+ .catch(() =>
+ notifications.error({
+ message: errorMessage,
+ }),
+ );
} catch (error) {
- if (typeof error === 'string') {
- notifications.error({
- message: error || t('something_went_wrong'),
- });
- }
+ notifications.error({
+ message: errorMessage,
+ });
}
- }, [layouts, onEmptyWidgetHandler, t, toggleAddWidget, notifications]);
+ }, [
+ featureResponse,
+ layouts,
+ onEmptyWidgetHandler,
+ toggleAddWidget,
+ notifications,
+ errorMessage,
+ ]);
return (
);
}
interface ComponentProps {
- setLayout: React.Dispatch>;
+ setLayout: Dispatch>;
}
export interface LayoutProps extends Layout {
@@ -312,7 +359,7 @@ export interface State {
interface DispatchProps {
toggleAddWidget: (
props: ToggleAddWidgetProps,
- ) => (dispatch: Dispatch) => void;
+ ) => (dispatch: ReduxDispatch) => void;
}
const mapDispatchToProps = (
diff --git a/frontend/src/container/GridGraphLayout/utils.ts b/frontend/src/container/GridGraphLayout/utils.ts
index 95d3574b7e..04944451fa 100644
--- a/frontend/src/container/GridGraphLayout/utils.ts
+++ b/frontend/src/container/GridGraphLayout/utils.ts
@@ -3,8 +3,8 @@ import updateDashboardApi from 'api/dashboard/update';
import {
ClickHouseQueryTemplate,
PromQLQueryTemplate,
- QueryBuilderQueryTemplate,
} from 'constants/dashboard';
+import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import GetQueryName from 'lib/query/GetQueryName';
import { Layout } from 'react-grid-layout';
@@ -42,26 +42,21 @@ export const UpdateDashboard = async (
panelTypes: graphType,
query: {
queryType: EQueryType.QUERY_BUILDER,
- promQL: [
+ promql: [
{
name: GetQueryName([]) || '',
...PromQLQueryTemplate,
},
],
- clickHouse: [
+ clickhouse_sql: [
{
name: GetQueryName([]) || '',
...ClickHouseQueryTemplate,
},
],
- metricsBuilder: {
- formulas: [],
- queryBuilder: [
- {
- name: GetQueryName([]) || '',
- ...QueryBuilderQueryTemplate,
- },
- ],
+ builder: {
+ queryFormulas: [],
+ queryData: [initialQueryBuilderFormValues],
},
},
queryData: {
diff --git a/frontend/src/container/Header/CurrentOrganization/index.tsx b/frontend/src/container/Header/CurrentOrganization/index.tsx
index 7b32c3f913..dcf001c024 100644
--- a/frontend/src/container/Header/CurrentOrganization/index.tsx
+++ b/frontend/src/container/Header/CurrentOrganization/index.tsx
@@ -4,7 +4,6 @@ import { INVITE_MEMBERS_HASH } from 'constants/app';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
diff --git a/frontend/src/container/Header/ManageLicense/index.tsx b/frontend/src/container/Header/ManageLicense/index.tsx
index 37c776ce2a..377af48103 100644
--- a/frontend/src/container/Header/ManageLicense/index.tsx
+++ b/frontend/src/container/Header/ManageLicense/index.tsx
@@ -1,9 +1,7 @@
-import { Typography } from 'antd';
-import { FeatureKeys } from 'constants/features';
+import { Spin, Typography } from 'antd';
import ROUTES from 'constants/routes';
-import useFeatureFlags from 'hooks/useFeatureFlag';
+import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import history from 'lib/history';
-import React from 'react';
import {
FreePlanIcon,
@@ -12,7 +10,22 @@ import {
} from './styles';
function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
- const isEnterprise = useFeatureFlags(FeatureKeys.ENTERPRISE_PLAN);
+ const { data, isLoading } = useLicense();
+
+ const onManageLicense = (): void => {
+ onToggle();
+ history.push(ROUTES.LIST_LICENSES);
+ };
+
+ if (isLoading || data?.payload === undefined) {
+ return ;
+ }
+
+ const isEnterprise = data?.payload?.some(
+ (license) =>
+ license.isCurrent && license.planKey === LICENSE_PLAN_KEY.ENTERPRISE_PLAN,
+ );
+
return (
<>
SIGNOZ STATUS
@@ -23,14 +36,7 @@ function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
{!isEnterprise ? 'Free Plan' : 'Enterprise Plan'}
- {
- onToggle();
- history.push(ROUTES.LIST_LICENSES);
- }}
- >
- Manage Licenses
-
+ Manage Licenses
>
);
diff --git a/frontend/src/container/Header/SignedIn/index.tsx b/frontend/src/container/Header/SignedIn/index.tsx
index b1804ea93a..33caab1670 100644
--- a/frontend/src/container/Header/SignedIn/index.tsx
+++ b/frontend/src/container/Header/SignedIn/index.tsx
@@ -1,7 +1,7 @@
import { Avatar, Typography } from 'antd';
import ROUTES from 'constants/routes';
import history from 'lib/history';
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx
index a34287e665..8b906eab74 100644
--- a/frontend/src/container/Header/index.tsx
+++ b/frontend/src/container/Header/index.tsx
@@ -3,14 +3,14 @@ import {
CaretUpFilled,
LogoutOutlined,
} from '@ant-design/icons';
-import type { MenuProps } from 'antd';
-import { Divider, Dropdown, Space, Typography } from 'antd';
+import { Button, Divider, Dropdown, MenuProps, Space, Typography } from 'antd';
import { Logout } from 'api/utils';
import ROUTES from 'constants/routes';
import Config from 'container/ConfigDropdown';
import { useIsDarkMode, useThemeMode } from 'hooks/useDarkMode';
-import React, {
+import {
Dispatch,
+ KeyboardEvent,
SetStateAction,
useCallback,
useMemo,
@@ -50,14 +50,11 @@ function HeaderContainer(): JSX.Element {
[],
);
- const onLogoutKeyDown = useCallback(
- (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' || e.key === 'Space') {
- Logout();
- }
- },
- [],
- );
+ const onLogoutKeyDown = useCallback((e: KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === 'Space') {
+ Logout();
+ }
+ }, []);
const menu: MenuProps = useMemo(
() => ({
@@ -91,6 +88,13 @@ function HeaderContainer(): JSX.Element {
[onToggleHandler, onLogoutKeyDown],
);
+ const onClickSignozCloud = (): void => {
+ window.open(
+ 'https://signoz.io/pricing/?utm_source=product_navbar&utm_medium=frontend',
+ '_blank',
+ );
+ };
+
return (
@@ -106,7 +110,11 @@ function HeaderContainer(): JSX.Element {
-
+
+
+ Try Signoz Cloud
+
+
>();
- const { refetch } = useQuery({
- queryFn: getFeaturesFlags,
- queryKey: 'getFeatureFlags',
- enabled: false,
- });
+ const { featureResponse } = useSelector(
+ (state) => state.app,
+ );
const { notifications } = useNotifications();
@@ -47,16 +43,8 @@ function ApplyLicenseForm({
});
if (response.statusCode === 200) {
- const [featureFlagsResponse] = await Promise.all([
- refetch(),
- licenseRefetch(),
- ]);
- if (featureFlagsResponse.data?.payload) {
- dispatch({
- type: UPDATE_FEATURE_FLAGS,
- payload: featureFlagsResponse.data.payload,
- });
- }
+ await Promise.all([featureResponse?.refetch(), licenseRefetch()]);
+
notifications.success({
message: 'Success',
description: t('license_applied'),
diff --git a/frontend/src/container/Licenses/ListLicenses.tsx b/frontend/src/container/Licenses/ListLicenses.tsx
index 950eabdfc3..d0ca5f0782 100644
--- a/frontend/src/container/Licenses/ListLicenses.tsx
+++ b/frontend/src/container/Licenses/ListLicenses.tsx
@@ -1,6 +1,5 @@
import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { License } from 'types/api/licenses/def';
import { PayloadProps } from 'types/api/licenses/getAll';
diff --git a/frontend/src/container/Licenses/index.tsx b/frontend/src/container/Licenses/index.tsx
index d7dc4ab22b..b4d068d908 100644
--- a/frontend/src/container/Licenses/index.tsx
+++ b/frontend/src/container/Licenses/index.tsx
@@ -1,19 +1,14 @@
import { Tabs, Typography } from 'antd';
-import getAll from 'api/licenses/getAll';
import Spinner from 'components/Spinner';
-import React from 'react';
+import useLicense from 'hooks/useLicense';
import { useTranslation } from 'react-i18next';
-import { useQuery } from 'react-query';
import ApplyLicenseForm from './ApplyLicenseForm';
import ListLicenses from './ListLicenses';
function Licenses(): JSX.Element {
const { t } = useTranslation(['licenses']);
- const { data, isError, isLoading, refetch } = useQuery({
- queryFn: getAll,
- queryKey: 'getAllLicenses',
- });
+ const { data, isError, isLoading, refetch } = useLicense();
if (isError || data?.error) {
return {data?.error} ;
diff --git a/frontend/src/container/ListAlertRules/DeleteAlert.tsx b/frontend/src/container/ListAlertRules/DeleteAlert.tsx
index 8ff3927d68..8f960b6ba7 100644
--- a/frontend/src/container/ListAlertRules/DeleteAlert.tsx
+++ b/frontend/src/container/ListAlertRules/DeleteAlert.tsx
@@ -1,9 +1,12 @@
import { NotificationInstance } from 'antd/es/notification/interface';
import deleteAlerts from 'api/alerts/delete';
import { State } from 'hooks/useFetch';
-import React, { useState } from 'react';
+import { Dispatch, SetStateAction, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
import { GettableAlert } from 'types/api/alerts/get';
+import AppReducer from 'types/reducer/app';
import { ColumnButton } from './styles';
@@ -22,15 +25,14 @@ function DeleteAlert({
payload: undefined,
});
+ const { featureResponse } = useSelector(
+ (state) => state.app,
+ );
+
const defaultErrorMessage = 'Something went wrong';
const onDeleteHandler = async (id: number): Promise => {
try {
- setDeleteAlertState((state) => ({
- ...state,
- loading: true,
- }));
-
const response = await deleteAlerts({
id,
});
@@ -72,11 +74,32 @@ function DeleteAlert({
}
};
+ const onClickHandler = (): void => {
+ setDeleteAlertState((state) => ({
+ ...state,
+ loading: true,
+ }));
+ featureResponse
+ .refetch()
+ .then(() => {
+ onDeleteHandler(id);
+ })
+ .catch(() => {
+ setDeleteAlertState((state) => ({
+ ...state,
+ loading: false,
+ }));
+ notifications.error({
+ message: defaultErrorMessage,
+ });
+ });
+ };
+
return (
=> onDeleteHandler(id)}
+ onClick={onClickHandler}
type="link"
>
Delete
@@ -86,7 +109,7 @@ function DeleteAlert({
interface DeleteAlertProps {
id: GettableAlert['id'];
- setData: React.Dispatch>;
+ setData: Dispatch>;
notifications: NotificationInstance;
}
diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx
index be54f0e914..6d02cae58b 100644
--- a/frontend/src/container/ListAlertRules/ListAlert.tsx
+++ b/frontend/src/container/ListAlertRules/ListAlert.tsx
@@ -9,7 +9,7 @@ import useComponentPermission from 'hooks/useComponentPermission';
import useInterval from 'hooks/useInterval';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback, useState } from 'react';
+import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
@@ -26,7 +26,9 @@ import ToggleAlertState from './ToggleAlertState';
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const [data, setData] = useState(allAlertRules || []);
const { t } = useTranslation('common');
- const { role } = useSelector((state) => state.app);
+ const { role, featureResponse } = useSelector(
+ (state) => state.app,
+ );
const [addNewAlert, action] = useComponentPermission(
['add_new_alert', 'action'],
role,
@@ -48,12 +50,28 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
})();
}, 30000);
+ const handleError = useCallback((): void => {
+ notificationsApi.error({
+ message: t('something_went_wrong'),
+ });
+ }, [notificationsApi, t]);
+
const onClickNewAlertHandler = useCallback(() => {
- history.push(ROUTES.ALERTS_NEW);
- }, []);
+ featureResponse
+ .refetch()
+ .then(() => {
+ history.push(ROUTES.ALERTS_NEW);
+ })
+ .catch(handleError);
+ }, [featureResponse, handleError]);
const onEditHandler = (id: string): void => {
- history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
+ featureResponse
+ .refetch()
+ .then(() => {
+ history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
+ })
+ .catch(handleError);
};
const columns: ColumnsType = [
diff --git a/frontend/src/container/ListAlertRules/TableComponents/Status.tsx b/frontend/src/container/ListAlertRules/TableComponents/Status.tsx
index d935b8d5ba..94e56723af 100644
--- a/frontend/src/container/ListAlertRules/TableComponents/Status.tsx
+++ b/frontend/src/container/ListAlertRules/TableComponents/Status.tsx
@@ -1,5 +1,4 @@
import { Tag } from 'antd';
-import React from 'react';
import { GettableAlert } from 'types/api/alerts/get';
function Status({ status }: StatusProps): JSX.Element {
diff --git a/frontend/src/container/ListAlertRules/ToggleAlertState.tsx b/frontend/src/container/ListAlertRules/ToggleAlertState.tsx
index 5410159226..edb894abe8 100644
--- a/frontend/src/container/ListAlertRules/ToggleAlertState.tsx
+++ b/frontend/src/container/ListAlertRules/ToggleAlertState.tsx
@@ -1,7 +1,7 @@
import patchAlert from 'api/alerts/patch';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useState } from 'react';
+import { Dispatch, SetStateAction, useState } from 'react';
import { GettableAlert } from 'types/api/alerts/get';
import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch';
@@ -104,7 +104,7 @@ function ToggleAlertState({
interface ToggleAlertStateProps {
id: GettableAlert['id'];
disabled: boolean;
- setData: React.Dispatch>;
+ setData: Dispatch>;
}
export default ToggleAlertState;
diff --git a/frontend/src/container/ListAlertRules/index.tsx b/frontend/src/container/ListAlertRules/index.tsx
index 078769141e..3880a7c2e6 100644
--- a/frontend/src/container/ListAlertRules/index.tsx
+++ b/frontend/src/container/ListAlertRules/index.tsx
@@ -3,7 +3,7 @@ import getAll from 'api/alerts/getAll';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx
index 75fa6a581d..b737f7d0f9 100644
--- a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx
+++ b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx
@@ -4,9 +4,10 @@ import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd';
import createDashboard from 'api/dashboard/create';
import Editor from 'components/Editor';
import ROUTES from 'constants/routes';
+import { MESSAGE } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { generatePath } from 'react-router-dom';
@@ -28,6 +29,8 @@ function ImportJSON({
const [isCreateDashboardError, setIsCreateDashboardError] = useState(
false,
);
+ const [isFeatureAlert, setIsFeatureAlert] = useState(false);
+
const dispatch = useDispatch>();
const [dashboardCreating, setDashboardCreating] = useState(false);
@@ -99,6 +102,15 @@ function ImportJSON({
}),
);
}, 10);
+ } else if (response.error === 'feature usage exceeded') {
+ setIsFeatureAlert(true);
+ notifications.error({
+ message:
+ response.error ||
+ t('something_went_wrong', {
+ ns: 'common',
+ }),
+ });
} else {
setIsCreateDashboardError(true);
notifications.error({
@@ -112,6 +124,7 @@ function ImportJSON({
setDashboardCreating(false);
} catch {
setDashboardCreating(false);
+ setIsFeatureAlert(false);
setIsCreateDashboardError(true);
}
@@ -124,6 +137,13 @@ function ImportJSON({
);
+ const onCancelHandler = (): void => {
+ setIsUploadJSONError(false);
+ setIsCreateDashboardError(false);
+ setIsFeatureAlert(false);
+ onModalHandler();
+ };
+
return (
{t('import_json')}
@@ -148,6 +168,11 @@ function ImportJSON({
{t('load_json')}
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
+ {isFeatureAlert && (
+
+ {MESSAGE.CREATE_DASHBOARD}
+
+ )}
}
>
diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx b/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx
index 04825b7a81..5405660523 100644
--- a/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx
+++ b/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { QueryChipContainer, QueryChipItem } from './styles';
import { IQueryStructure } from './types';
diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx b/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx
index b8e16cd4db..eeddb1b6f3 100644
--- a/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx
+++ b/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx
@@ -4,7 +4,13 @@ import { Button, Select } from 'antd';
import { RefSelectProps } from 'antd/lib/select';
import history from 'lib/history';
import { filter, map } from 'lodash-es';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
+import {
+ MutableRefObject,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
import { Dashboard } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid';
@@ -31,7 +37,7 @@ function SearchFilter({
const [optionsData, setOptionsData] = useState(
OptionsSchemas.attribute,
);
- const selectRef = useRef() as React.MutableRefObject;
+ const selectRef = useRef() as MutableRefObject;
const [selectedValues, setSelectedValues] = useState([]);
const [staging, setStaging] = useState([]);
const [queries, setQueries] = useState([]);
diff --git a/frontend/src/container/ListOfDashboard/TableComponents/CreatedBy.tsx b/frontend/src/container/ListOfDashboard/TableComponents/CreatedBy.tsx
index faa5d45e32..d463f80c03 100644
--- a/frontend/src/container/ListOfDashboard/TableComponents/CreatedBy.tsx
+++ b/frontend/src/container/ListOfDashboard/TableComponents/CreatedBy.tsx
@@ -1,7 +1,6 @@
import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
-import React from 'react';
import { Data } from '..';
diff --git a/frontend/src/container/ListOfDashboard/TableComponents/Date.tsx b/frontend/src/container/ListOfDashboard/TableComponents/Date.tsx
index 6b93b4b220..c96ac1ebf1 100644
--- a/frontend/src/container/ListOfDashboard/TableComponents/Date.tsx
+++ b/frontend/src/container/ListOfDashboard/TableComponents/Date.tsx
@@ -1,7 +1,6 @@
import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
-import React from 'react';
import { Data } from '..';
diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx
index fd9ef16001..800dd8d1e6 100644
--- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx
+++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx
@@ -1,6 +1,5 @@
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Modal } from 'antd';
-import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx b/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx
index e54431063f..af53580926 100644
--- a/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx
+++ b/frontend/src/container/ListOfDashboard/TableComponents/Name.tsx
@@ -1,6 +1,5 @@
import ROUTES from 'constants/routes';
import history from 'lib/history';
-import React from 'react';
import { generatePath } from 'react-router-dom';
import { Data } from '..';
diff --git a/frontend/src/container/ListOfDashboard/TableComponents/Tags.tsx b/frontend/src/container/ListOfDashboard/TableComponents/Tags.tsx
index 40ecbe7c65..bc698487d2 100644
--- a/frontend/src/container/ListOfDashboard/TableComponents/Tags.tsx
+++ b/frontend/src/container/ListOfDashboard/TableComponents/Tags.tsx
@@ -1,6 +1,5 @@
/* eslint-disable react/destructuring-assignment */
import { Tag } from 'antd';
-import React from 'react';
import { Data } from '../index';
diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx
index ca5d68d9bb..9a91d72331 100644
--- a/frontend/src/container/ListOfDashboard/index.tsx
+++ b/frontend/src/container/ListOfDashboard/index.tsx
@@ -16,8 +16,9 @@ import ROUTES from 'constants/routes';
import SearchFilter from 'container/ListOfDashboard/SearchFilter';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
-import React, {
+import {
Dispatch,
+ Key,
useCallback,
useEffect,
useMemo,
@@ -74,49 +75,52 @@ function ListOfAllDashboard(): JSX.Element {
errorMessage: '',
});
- const columns: TableColumnProps[] = [
- {
- title: 'Name',
- dataIndex: 'name',
- width: 100,
- render: Name,
- },
- {
- title: 'Description',
- width: 100,
- dataIndex: 'description',
- },
- {
- title: 'Tags (can be multiple)',
- dataIndex: 'tags',
- width: 80,
- render: Tags,
- },
- {
- title: 'Created At',
- dataIndex: 'createdBy',
- width: 80,
- sorter: (a: Data, b: Data): number => {
- const prev = new Date(a.createdBy).getTime();
- const next = new Date(b.createdBy).getTime();
-
- return prev - next;
+ const columns: TableColumnProps[] = useMemo(
+ () => [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ width: 100,
+ render: Name,
},
- render: Createdby,
- },
- {
- title: 'Last Updated Time',
- width: 90,
- dataIndex: 'lastUpdatedTime',
- sorter: (a: Data, b: Data): number => {
- const prev = new Date(a.lastUpdatedTime).getTime();
- const next = new Date(b.lastUpdatedTime).getTime();
-
- return prev - next;
+ {
+ title: 'Description',
+ width: 100,
+ dataIndex: 'description',
},
- render: DateComponent,
- },
- ];
+ {
+ title: 'Tags (can be multiple)',
+ dataIndex: 'tags',
+ width: 80,
+ render: Tags,
+ },
+ {
+ title: 'Created At',
+ dataIndex: 'createdBy',
+ width: 80,
+ sorter: (a: Data, b: Data): number => {
+ const prev = new Date(a.createdBy).getTime();
+ const next = new Date(b.createdBy).getTime();
+
+ return prev - next;
+ },
+ render: Createdby,
+ },
+ {
+ title: 'Last Updated Time',
+ width: 90,
+ dataIndex: 'lastUpdatedTime',
+ sorter: (a: Data, b: Data): number => {
+ const prev = new Date(a.lastUpdatedTime).getTime();
+ const next = new Date(b.lastUpdatedTime).getTime();
+
+ return prev - next;
+ },
+ render: DateComponent,
+ },
+ ],
+ [],
+ );
if (action) {
columns.push({
@@ -199,7 +203,7 @@ function ListOfAllDashboard(): JSX.Element {
setUploadedGrafana(uploadedGrafana);
};
- const getMenuItems = useCallback(() => {
+ const getMenuItems = useMemo(() => {
const menuItems: ItemType[] = [];
if (createNewDashboard) {
menuItems.push({
@@ -227,7 +231,7 @@ function ListOfAllDashboard(): JSX.Element {
const menu: MenuProps = useMemo(
() => ({
- items: getMenuItems(),
+ items: getMenuItems,
}),
[getMenuItems],
);
@@ -245,7 +249,7 @@ function ListOfAllDashboard(): JSX.Element {
}}
/>
{newDashboard && (
-
+
}
type="primary"
@@ -260,11 +264,12 @@ function ListOfAllDashboard(): JSX.Element {
),
[
- getText,
newDashboard,
- newDashboardState.error,
- newDashboardState.loading,
+ loading,
menu,
+ newDashboardState.loading,
+ newDashboardState.error,
+ getText,
],
);
@@ -304,7 +309,7 @@ function ListOfAllDashboard(): JSX.Element {
}
export interface Data {
- key: React.Key;
+ key: Key;
name: string;
description: string;
tags: string[];
diff --git a/frontend/src/container/LogControls/index.tsx b/frontend/src/container/LogControls/index.tsx
index 41bfeafb53..f688e2866f 100644
--- a/frontend/src/container/LogControls/index.tsx
+++ b/frontend/src/container/LogControls/index.tsx
@@ -7,7 +7,7 @@ import { Button, Divider, Select } from 'antd';
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
import { defaultSelectStyle } from 'pages/Logs/config';
-import React, { memo, useMemo } from 'react';
+import { memo, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/LogDetailedView/ActionItem.tsx b/frontend/src/container/LogDetailedView/ActionItem.tsx
index 10a7419bff..28e429d7e2 100644
--- a/frontend/src/container/LogDetailedView/ActionItem.tsx
+++ b/frontend/src/container/LogDetailedView/ActionItem.tsx
@@ -2,7 +2,7 @@ import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Col, Popover } from 'antd';
import getStep from 'lib/getStep';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
-import React, { memo, useMemo } from 'react';
+import { memo, useMemo } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/LogDetailedView/JsonView.tsx b/frontend/src/container/LogDetailedView/JsonView.tsx
index 95e5c558a1..e510d46d10 100644
--- a/frontend/src/container/LogDetailedView/JsonView.tsx
+++ b/frontend/src/container/LogDetailedView/JsonView.tsx
@@ -2,7 +2,7 @@ import { blue } from '@ant-design/colors';
import { CopyFilled } from '@ant-design/icons';
import { Button, Row } from 'antd';
import Editor from 'components/Editor';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useCopyToClipboard } from 'react-use';
import { ILog } from 'types/api/logs/log';
diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx
index eea61f2a32..2959be7dcc 100644
--- a/frontend/src/container/LogDetailedView/TableView.tsx
+++ b/frontend/src/container/LogDetailedView/TableView.tsx
@@ -1,18 +1,25 @@
import { blue, orange } from '@ant-design/colors';
-import { Input } from 'antd';
+import { LinkOutlined } from '@ant-design/icons';
+import { Input, Space, Tooltip } from 'antd';
import { ColumnsType } from 'antd/es/table';
import Editor from 'components/Editor';
import AddToQueryHOC from 'components/Logs/AddToQueryHOC';
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import { ResizeTable } from 'components/ResizeTable';
-import flatten from 'flat';
+import ROUTES from 'constants/routes';
+import history from 'lib/history';
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
import { isEmpty } from 'lodash-es';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { generatePath } from 'react-router-dom';
+import { Dispatch } from 'redux';
+import AppActions from 'types/actions';
+import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
import { ILog } from 'types/api/logs/log';
import ActionItem from './ActionItem';
-import { recursiveParseJSON } from './utils';
+import { flattenObject, recursiveParseJSON } from './utils';
// Fields which should be restricted from adding it to query
const RESTRICTED_FIELDS = ['timestamp'];
@@ -23,8 +30,10 @@ interface TableViewProps {
function TableView({ logData }: TableViewProps): JSX.Element | null {
const [fieldSearchInput, setFieldSearchInput] = useState('');
- const flattenLogData: Record | null = useMemo(
- () => (logData ? flatten(logData) : null),
+ const dispatch = useDispatch>();
+
+ const flattenLogData: Record | null = useMemo(
+ () => (logData ? flattenObject(logData) : null),
[logData],
);
if (logData === null) {
@@ -41,6 +50,29 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
value: JSON.stringify(flattenLogData[key]),
}));
+ const onTraceHandler = (record: DataType) => (): void => {
+ if (flattenLogData === null) return;
+
+ const traceId = flattenLogData[record.field];
+
+ const spanId = flattenLogData?.span_id;
+
+ if (traceId) {
+ dispatch({
+ type: SET_DETAILED_LOG_DATA,
+ payload: null,
+ });
+
+ const basePath = generatePath(ROUTES.TRACE_DETAIL, {
+ id: traceId,
+ });
+
+ const route = spanId ? `${basePath}?spanId=${spanId}` : basePath;
+
+ history.push(route);
+ }
+ };
+
if (!dataSource) {
return null;
}
@@ -62,11 +94,38 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
dataIndex: 'field',
key: 'field',
width: 30,
+ align: 'left',
ellipsis: true,
- render: (field: string): JSX.Element => {
+ render: (field: string, record): JSX.Element => {
const fieldKey = field.split('.').slice(-1);
const renderedField = {field} ;
+ if (record.field === 'trace_id') {
+ const traceId = flattenLogData[record.field];
+
+ return (
+
+ {renderedField}
+
+ {traceId && (
+
+
+
+
+
+ )}
+
+ );
+ }
+
if (!RESTRICTED_FIELDS.includes(fieldKey[0])) {
return (
@@ -89,7 +148,7 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
if (!isEmpty(parsedBody)) {
return (
{
it('should return an empty object if the input is not valid JSON', () => {
@@ -45,3 +45,104 @@ describe('recursiveParseJSON', () => {
expect(result).toEqual({ name: 'John", "Doe', age: 30 });
});
});
+
+describe('flattenObject in the objects recursively', () => {
+ it('should flatten nested objects correctly', () => {
+ const nestedObj = {
+ a: {
+ b: {
+ c: 1,
+ d: 2,
+ },
+ e: 3,
+ },
+ f: 4,
+ };
+ const expected = {
+ 'a.b.c': 1,
+ 'a.b.d': 2,
+ 'a.e': 3,
+ f: 4,
+ };
+
+ expect(flattenObject(nestedObj)).toEqual(expected);
+ });
+
+ it('should return an empty object when input is empty', () => {
+ const nestedObj = {};
+ const expected = {};
+
+ expect(flattenObject(nestedObj)).toEqual(expected);
+ });
+
+ it('should handle non-nested objects correctly', () => {
+ const nestedObj = {
+ a: 1,
+ b: 2,
+ c: 3,
+ };
+ const expected = {
+ a: 1,
+ b: 2,
+ c: 3,
+ };
+
+ expect(flattenObject(nestedObj)).toEqual(expected);
+ });
+
+ it('should handle null and undefined correctly', () => {
+ const nestedObj = {
+ a: null,
+ b: undefined,
+ };
+ const expected = {
+ a: null,
+ b: undefined,
+ };
+
+ expect(flattenObject(nestedObj)).toEqual(expected);
+ });
+
+ it('should handle arrays correctly', () => {
+ const objWithArray = {
+ a: [1, 2, 3],
+ b: 2,
+ };
+ const expected = {
+ a: [1, 2, 3],
+ b: 2,
+ };
+
+ expect(flattenObject(objWithArray)).toEqual(expected);
+ });
+
+ it('should handle nested objects in arrays correctly', () => {
+ const objWithArray = {
+ a: [{ b: 1 }, { c: 2 }],
+ d: 3,
+ };
+ const expected = {
+ a: [{ b: 1 }, { c: 2 }],
+ d: 3,
+ };
+
+ expect(flattenObject(objWithArray)).toEqual(expected);
+ });
+
+ it('should handle objects with arrays and nested objects correctly', () => {
+ const complexObj = {
+ a: {
+ b: [1, 2, { c: 3 }],
+ d: 4,
+ },
+ e: 5,
+ };
+ const expected = {
+ 'a.b': [1, 2, { c: 3 }],
+ 'a.d': 4,
+ e: 5,
+ };
+
+ expect(flattenObject(complexObj)).toEqual(expected);
+ });
+});
diff --git a/frontend/src/container/LogDetailedView/utils.ts b/frontend/src/container/LogDetailedView/utils.ts
index 00a89c96d1..4a73c61933 100644
--- a/frontend/src/container/LogDetailedView/utils.ts
+++ b/frontend/src/container/LogDetailedView/utils.ts
@@ -4,8 +4,31 @@ export const recursiveParseJSON = (obj: string): Record => {
if (typeof value === 'string') {
return recursiveParseJSON(value);
}
+ if (typeof value === 'object') {
+ Object.entries(value).forEach(([key, val]) => {
+ if (typeof val === 'string') {
+ value[key] = val.trim();
+ } else if (typeof val === 'object') {
+ value[key] = recursiveParseJSON(JSON.stringify(val));
+ }
+ });
+ }
return value;
} catch (e) {
return {};
}
};
+
+type AnyObject = { [key: string]: any };
+
+export function flattenObject(obj: AnyObject, prefix = ''): AnyObject {
+ return Object.keys(obj).reduce((acc: AnyObject, k: string): AnyObject => {
+ const pre = prefix.length ? `${prefix}.` : '';
+ if (typeof obj[k] === 'object' && obj[k] !== null && !Array.isArray(obj[k])) {
+ Object.assign(acc, flattenObject(obj[k], pre + k));
+ } else {
+ acc[pre + k] = obj[k];
+ }
+ return acc;
+ }, {});
+}
diff --git a/frontend/src/container/LogLiveTail/index.tsx b/frontend/src/container/LogLiveTail/index.tsx
index 1139342a9d..08faeda94c 100644
--- a/frontend/src/container/LogLiveTail/index.tsx
+++ b/frontend/src/container/LogLiveTail/index.tsx
@@ -11,7 +11,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import getStep from 'lib/getStep';
import { throttle } from 'lodash-es';
-import React, { useCallback, useEffect, useMemo, useRef } from 'react';
+import { useCallback, useEffect, useMemo, useRef } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/Login/index.tsx b/frontend/src/container/Login/index.tsx
index cb9775fbe8..2d0d5854dc 100644
--- a/frontend/src/container/Login/index.tsx
+++ b/frontend/src/container/Login/index.tsx
@@ -6,10 +6,13 @@ import afterLogin from 'AppRoutes/utils';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
+import AppReducer from 'types/reducer/app';
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
@@ -34,6 +37,7 @@ function Login({
}: LoginProps): JSX.Element {
const { t } = useTranslation(['login']);
const [isLoading, setIsLoading] = useState(false);
+ const { user } = useSelector((state) => state.app);
const [precheckResult, setPrecheckResult] = useState({
sso: false,
@@ -49,7 +53,7 @@ function Login({
const getUserVersionResponse = useQuery({
queryFn: getUserVersion,
- queryKey: 'getUserVersion',
+ queryKey: ['getUserVersion', user?.accessJwt],
enabled: true,
});
diff --git a/frontend/src/container/LogsAggregate/index.tsx b/frontend/src/container/LogsAggregate/index.tsx
index c1217c0e46..9dc0871129 100644
--- a/frontend/src/container/LogsAggregate/index.tsx
+++ b/frontend/src/container/LogsAggregate/index.tsx
@@ -4,7 +4,7 @@ import Spinner from 'components/Spinner';
import dayjs from 'dayjs';
import useInterval from 'hooks/useInterval';
import getStep from 'lib/getStep';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/LogsFilters/FieldItem.tsx b/frontend/src/container/LogsFilters/FieldItem.tsx
index a62b16d1fc..dd788e8671 100644
--- a/frontend/src/container/LogsFilters/FieldItem.tsx
+++ b/frontend/src/container/LogsFilters/FieldItem.tsx
@@ -1,7 +1,7 @@
import { LoadingOutlined } from '@ant-design/icons';
import { Button, Popover, Spin, Typography } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useCallback, useMemo, useState } from 'react';
+import { ReactNode, useCallback, useMemo, useState } from 'react';
import {
IField,
IInterestingFields,
@@ -70,7 +70,7 @@ function FieldItem({
interface FieldItemProps {
name: string;
- buttonIcon: React.ReactNode;
+ buttonIcon: ReactNode;
buttonOnClick: (props: {
fieldData: IInterestingFields | ISelectedFields;
fieldIndex: number;
diff --git a/frontend/src/container/LogsFilters/index.tsx b/frontend/src/container/LogsFilters/index.tsx
index 26c3bd739a..1980e4f115 100644
--- a/frontend/src/container/LogsFilters/index.tsx
+++ b/frontend/src/container/LogsFilters/index.tsx
@@ -2,7 +2,7 @@ import { CloseOutlined, PlusCircleFilled } from '@ant-design/icons';
import { Col, Input } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading';
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
-import React, { useCallback, useState } from 'react';
+import { ChangeEvent, useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ILogsReducer } from 'types/reducer/logs';
@@ -24,7 +24,7 @@ function LogsFilters(): JSX.Element {
>([]);
const [filterValuesInput, setFilterValuesInput] = useState('');
- const handleSearch = (e: React.ChangeEvent): void => {
+ const handleSearch = (e: ChangeEvent): void => {
setFilterValuesInput((e.target as HTMLInputElement).value);
};
diff --git a/frontend/src/container/LogsFilters/types.ts b/frontend/src/container/LogsFilters/types.ts
index ece94aeaec..a062701e2a 100644
--- a/frontend/src/container/LogsFilters/types.ts
+++ b/frontend/src/container/LogsFilters/types.ts
@@ -1,10 +1,11 @@
+import { SetStateAction } from 'react';
import {
IField,
IInterestingFields,
ISelectedFields,
} from 'types/api/logs/fields';
-type SetLoading = (value: React.SetStateAction) => void;
+type SetLoading = (value: SetStateAction) => void;
export type IHandleInterestProps = {
fieldData: IInterestingFields;
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/ActionBar.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/ActionBar.tsx
index 05ba17b8f6..1c223fb721 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/ActionBar.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/ActionBar.tsx
@@ -1,5 +1,4 @@
import { Button, Row } from 'antd';
-import React from 'react';
interface SearchFieldsActionBarProps {
applyUpdate: VoidFunction;
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/FieldKey.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/FieldKey.tsx
index f454267cd7..eac4f1302d 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/FieldKey.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/FieldKey.tsx
@@ -1,5 +1,4 @@
import { Typography } from 'antd';
-import React from 'react';
interface FieldKeyProps {
name: string;
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
index 9aff0b6dd7..4b01ba02e2 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/QueryBuilder/QueryBuilder.tsx
@@ -6,7 +6,7 @@ import {
QueryOperatorsMultiVal,
QueryOperatorsSingleVal,
} from 'lib/logql/tokens';
-import React, { useCallback, useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ILogsReducer } from 'types/reducer/logs';
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/Suggestions.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/Suggestions.tsx
index 56255d1ed1..dccb4c0c1b 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/Suggestions.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/Suggestions.tsx
@@ -1,7 +1,6 @@
import { Button } from 'antd';
import CategoryHeading from 'components/Logs/CategoryHeading';
import map from 'lodash-es/map';
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
// import { ADD_SEARCH_FIELD_QUERY_STRING } from 'types/actions/logs';
diff --git a/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx b/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx
index 07662da8e2..2630f65a4b 100644
--- a/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx
+++ b/frontend/src/container/LogsSearchFilter/SearchFields/index.tsx
@@ -1,7 +1,7 @@
import { useNotifications } from 'hooks/useNotifications';
import { reverseParser } from 'lib/logql';
import { flatten } from 'lodash-es';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ILogsReducer } from 'types/reducer/logs';
diff --git a/frontend/src/container/LogsSearchFilter/index.tsx b/frontend/src/container/LogsSearchFilter/index.tsx
index efe7a39534..d3de26658b 100644
--- a/frontend/src/container/LogsSearchFilter/index.tsx
+++ b/frontend/src/container/LogsSearchFilter/index.tsx
@@ -2,14 +2,7 @@ import { Input, InputRef, Popover } from 'antd';
import useUrlQuery from 'hooks/useUrlQuery';
import getStep from 'lib/getStep';
import debounce from 'lodash-es/debounce';
-import React, {
- memo,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from 'react';
+import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/LogsSearchFilter/useSearchParser.ts b/frontend/src/container/LogsSearchFilter/useSearchParser.ts
index a3dab8a75c..54b483f4e7 100644
--- a/frontend/src/container/LogsSearchFilter/useSearchParser.ts
+++ b/frontend/src/container/LogsSearchFilter/useSearchParser.ts
@@ -3,7 +3,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { parseQuery } from 'lib/logql';
import isEqual from 'lodash-es/isEqual';
-import { useCallback, useEffect, useMemo } from 'react';
+import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
@@ -28,7 +28,7 @@ export function useSearchParser(): {
} = useSelector((store) => store.logs);
const urlQuery = useUrlQuery();
- const parsedFilters = useMemo(() => urlQuery.get('q'), [urlQuery]);
+ const parsedFilters = urlQuery.get('q');
const { minTime, maxTime, selectedTime } = useSelector<
AppState,
@@ -62,16 +62,12 @@ export function useSearchParser(): {
},
// need to hide this warning as we don't want to update the query string on every change
// eslint-disable-next-line react-hooks/exhaustive-deps
- [dispatch, parsedQuery, selectedTime],
+ [dispatch, parsedQuery, selectedTime, queryString],
);
useEffect(() => {
- if (!queryString && parsedFilters) {
- updateQueryString(parsedFilters);
- } else if (queryString) {
- updateQueryString(queryString);
- }
- }, [queryString, updateQueryString, parsedFilters]);
+ updateQueryString(parsedFilters || '');
+ }, [parsedFilters, updateQueryString]);
return {
queryString,
diff --git a/frontend/src/container/LogsTable/index.tsx b/frontend/src/container/LogsTable/index.tsx
index 2b0013035d..464258877e 100644
--- a/frontend/src/container/LogsTable/index.tsx
+++ b/frontend/src/container/LogsTable/index.tsx
@@ -6,7 +6,7 @@ import LogsTableView from 'components/Logs/TableView';
import Spinner from 'components/Spinner';
import { contentStyle } from 'container/Trace/Search/config';
import useFontFaceObserver from 'hooks/useFontObserver';
-import React, { memo, useCallback, useMemo } from 'react';
+import { memo, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { Virtuoso } from 'react-virtuoso';
// interfaces
diff --git a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
index 0175aafac8..82b3856f98 100644
--- a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
+++ b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
@@ -1,3 +1,4 @@
+import { PANEL_TYPES } from 'constants/queryBuilder';
import { Widgets } from 'types/api/dashboard/getAll';
import { v4 } from 'uuid';
@@ -7,7 +8,7 @@ export const getWidgetQueryBuilder = (query: Widgets['query']): Widgets => ({
isStacked: false,
nullZeroValues: '',
opacity: '0',
- panelTypes: 'TIME_SERIES',
+ panelTypes: PANEL_TYPES.TIME_SERIES,
query,
queryData: {
data: { queryData: [] },
diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts
index 0b0f9ad4c8..e85b4d4ace 100644
--- a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts
+++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts
@@ -1,8 +1,6 @@
-import {
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
- IQueryBuilderTagFilterItems,
-} from 'types/api/dashboard/getAll';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
+import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
@@ -13,16 +11,25 @@ export const databaseCallsRPS = ({
servicename,
legend,
tagFilterItems,
-}: DatabaseCallsRPSProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricName = 'signoz_db_latency_count';
- const groupBy = ['db_system'];
- const itemsA = [
+}: DatabaseCallsRPSProps): QueryBuilderData => {
+ const metricName: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_db_latency_count',
+ type: null,
+ };
+ const groupBy: BaseAutocompleteData[] = [
+ { dataType: 'string', isColumn: false, key: 'db_system', type: 'tag' },
+ ];
+ const itemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -40,20 +47,32 @@ export const databaseCallsRPS = ({
export const databaseCallsAvgDuration = ({
servicename,
tagFilterItems,
-}: DatabaseCallProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricNameA = 'signoz_db_latency_sum';
- const metricNameB = 'signoz_db_latency_count';
+}: DatabaseCallProps): QueryBuilderData => {
+ const metricNameA: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_db_latency_sum',
+ type: null,
+ };
+ const metricNameB: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_db_latency_count',
+ type: null,
+ };
const expression = 'A/B';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
- const additionalItemsA = [
+ const additionalItemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -79,5 +98,5 @@ interface DatabaseCallsRPSProps extends DatabaseCallProps {
interface DatabaseCallProps {
servicename: string | undefined;
- tagFilterItems: IQueryBuilderTagFilterItems[] | [];
+ tagFilterItems: TagFilterItem[];
}
diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts
index c202fefe65..69af5042f2 100644
--- a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts
+++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts
@@ -1,45 +1,67 @@
-import {
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
- IQueryBuilderTagFilterItems,
-} from 'types/api/dashboard/getAll';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
+import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula,
} from './MetricsPageQueriesFactory';
-const groupBy = ['address'];
+const groupBy: BaseAutocompleteData[] = [
+ { dataType: 'string', isColumn: false, key: 'address', type: 'tag' },
+];
export const externalCallErrorPercent = ({
servicename,
legend,
tagFilterItems,
-}: ExternalCallDurationByAddressProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricNameA = 'signoz_external_call_latency_count';
- const metricNameB = 'signoz_external_call_latency_count';
- const additionalItemsA = [
+}: ExternalCallDurationByAddressProps): QueryBuilderData => {
+ const metricNameA: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_count',
+ type: null,
+ };
+ const metricNameB: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_count',
+ type: null,
+ };
+ const additionalItemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
- key: 'status_code',
+ key: {
+ dataType: 'int64',
+ isColumn: false,
+ key: 'status_code',
+ type: 'tag',
+ },
op: 'IN',
value: ['STATUS_CODE_ERROR'],
},
...tagFilterItems,
];
- const additionalItemsB = [
+ const additionalItemsB: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -64,20 +86,32 @@ export const externalCallErrorPercent = ({
export const externalCallDuration = ({
servicename,
tagFilterItems,
-}: ExternalCallProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricNameA = 'signoz_external_call_latency_sum';
- const metricNameB = 'signoz_external_call_latency_count';
+}: ExternalCallProps): QueryBuilderData => {
+ const metricNameA: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_sum',
+ type: null,
+ };
+ const metricNameB: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_count',
+ type: null,
+ };
const expression = 'A/B';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
- const additionalItemsA = [
+ const additionalItemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -101,15 +135,22 @@ export const externalCallRpsByAddress = ({
servicename,
legend,
tagFilterItems,
-}: ExternalCallDurationByAddressProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricName = 'signoz_external_call_latency_count';
- const itemsA = [
+}: ExternalCallDurationByAddressProps): QueryBuilderData => {
+ const metricName: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_count',
+ type: null,
+ };
+ const itemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -127,19 +168,31 @@ export const externalCallDurationByAddress = ({
servicename,
legend,
tagFilterItems,
-}: ExternalCallDurationByAddressProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => {
- const metricNameA = 'signoz_external_call_latency_sum';
- const metricNameB = 'signoz_external_call_latency_count';
+}: ExternalCallDurationByAddressProps): QueryBuilderData => {
+ const metricNameA: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_sum',
+ type: null,
+ };
+ const metricNameB: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_external_call_latency_count',
+ type: null,
+ };
const expression = 'A/B';
const legendFormula = legend;
const disabled = true;
- const additionalItemsA = [
+ const additionalItemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
@@ -166,5 +219,5 @@ interface ExternalCallDurationByAddressProps extends ExternalCallProps {
export interface ExternalCallProps {
servicename: string | undefined;
- tagFilterItems: IQueryBuilderTagFilterItems[];
+ tagFilterItems: TagFilterItem[];
}
diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts
index 4654ebf20f..57d4829ea3 100644
--- a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts
+++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts
@@ -1,29 +1,31 @@
import {
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
- IQueryBuilderTagFilterItems,
-} from 'types/api/dashboard/getAll';
+ initialFormulaBuilderFormValues,
+ initialQueryBuilderFormValues,
+} from 'constants/queryBuilder';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
+import {
+ MetricAggregateOperator,
+ QueryBuilderData,
+} from 'types/common/queryBuilder';
export const getQueryBuilderQueries = ({
metricName,
- groupBy,
+ groupBy = [],
legend,
itemsA,
-}: BuilderQueriesProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => ({
- formulas: [],
- queryBuilder: [
+}: BuilderQueriesProps): QueryBuilderData => ({
+ queryFormulas: [],
+ queryData: [
{
- aggregateOperator: 18,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled: false,
groupBy,
+ aggregateAttribute: metricName,
legend,
- metricName,
- name: 'A',
- reduceTo: 1,
- tagFilters: {
+ reduceTo: 'sum',
+ filters: {
items: itemsA,
op: 'AND',
},
@@ -37,45 +39,43 @@ export const getQueryBuilderQuerieswithFormula = ({
additionalItemsA,
additionalItemsB,
legend,
- groupBy,
+ groupBy = [],
disabled,
expression,
legendFormula,
-}: BuilderQuerieswithFormulaProps): {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-} => ({
- formulas: [
+}: BuilderQuerieswithFormulaProps): QueryBuilderData => ({
+ queryFormulas: [
{
- disabled: false,
+ ...initialFormulaBuilderFormValues,
expression,
- name: 'F1',
legend: legendFormula,
},
],
- queryBuilder: [
+ queryData: [
{
- aggregateOperator: 18,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
- metricName: metricNameA,
- name: 'A',
- reduceTo: 1,
- tagFilters: {
+ aggregateAttribute: metricNameA,
+ reduceTo: 'sum',
+ filters: {
items: additionalItemsA,
op: 'AND',
},
},
{
- aggregateOperator: 18,
+ ...initialQueryBuilderFormValues,
+ aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
- metricName: metricNameB,
- name: 'B',
- reduceTo: 1,
- tagFilters: {
+ aggregateAttribute: metricNameB,
+ queryName: 'B',
+ expression: 'B',
+ reduceTo: 'sum',
+ filters: {
items: additionalItemsB,
op: 'AND',
},
@@ -84,20 +84,20 @@ export const getQueryBuilderQuerieswithFormula = ({
});
interface BuilderQueriesProps {
- metricName: string;
- groupBy?: string[];
+ metricName: BaseAutocompleteData;
+ groupBy?: BaseAutocompleteData[];
legend: string;
- itemsA: IQueryBuilderTagFilterItems[];
+ itemsA: TagFilterItem[];
}
interface BuilderQuerieswithFormulaProps {
- metricNameA: string;
- metricNameB: string;
+ metricNameA: BaseAutocompleteData;
+ metricNameB: BaseAutocompleteData;
legend: string;
disabled: boolean;
- groupBy?: string[];
+ groupBy?: BaseAutocompleteData[];
expression: string;
legendFormula: string;
- additionalItemsA: IQueryBuilderTagFilterItems[];
- additionalItemsB: IQueryBuilderTagFilterItems[];
+ additionalItemsA: TagFilterItem[];
+ additionalItemsB: TagFilterItem[];
}
diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts
index 8c5f95f943..97a72c4fd2 100644
--- a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts
+++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts
@@ -1,8 +1,6 @@
-import {
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
- IQueryBuilderTagFilterItems,
-} from 'types/api/dashboard/getAll';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
+import { QueryBuilderData } from 'types/common/queryBuilder';
import {
getQueryBuilderQueries,
@@ -13,19 +11,35 @@ export const operationPerSec = ({
servicename,
tagFilterItems,
topLevelOperations,
-}: OperationPerSecProps): IOverviewQueries => {
- const metricName = 'signoz_latency_count';
+}: OperationPerSecProps): QueryBuilderData => {
+ const metricName: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_latency_count',
+ type: null,
+ };
const legend = 'Operations';
- const itemsA = [
+
+ const itemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
- key: 'operation',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'operation',
+ type: 'tag',
+ },
op: 'IN',
value: topLevelOperations,
},
@@ -43,41 +57,76 @@ export const errorPercentage = ({
servicename,
tagFilterItems,
topLevelOperations,
-}: OperationPerSecProps): IOverviewQueries => {
- const metricNameA = 'signoz_calls_total';
- const metricNameB = 'signoz_calls_total';
- const additionalItemsA = [
+}: OperationPerSecProps): QueryBuilderData => {
+ const metricNameA: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_calls_total',
+ type: null,
+ };
+ const metricNameB: BaseAutocompleteData = {
+ dataType: 'float64',
+ isColumn: true,
+ key: 'signoz_calls_total',
+ type: null,
+ };
+ const additionalItemsA: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
- key: 'operation',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'operation',
+ type: 'tag',
+ },
op: 'IN',
value: topLevelOperations,
},
{
id: '',
- key: 'status_code',
+ key: {
+ dataType: 'int64',
+ isColumn: false,
+ key: 'status_code',
+ type: 'tag',
+ },
op: 'IN',
value: ['STATUS_CODE_ERROR'],
},
...tagFilterItems,
];
- const additionalItemsB = [
+ const additionalItemsB: TagFilterItem[] = [
{
id: '',
- key: 'service_name',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'service_name',
+ type: 'resource',
+ },
op: 'IN',
value: [`${servicename}`],
},
{
id: '',
- key: 'operation',
+ key: {
+ dataType: 'string',
+ isColumn: false,
+ key: 'operation',
+ type: 'tag',
+ },
op: 'IN',
value: topLevelOperations,
},
@@ -102,11 +151,6 @@ export const errorPercentage = ({
export interface OperationPerSecProps {
servicename: string | undefined;
- tagFilterItems: IQueryBuilderTagFilterItems[];
+ tagFilterItems: TagFilterItem[];
topLevelOperations: string[];
}
-
-interface IOverviewQueries {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
-}
diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx
index f5ce2a0188..ca4c374125 100644
--- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx
+++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx
@@ -9,9 +9,11 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
+import { EQueryType } from 'types/common/dashboard';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
@@ -27,7 +29,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState(0);
const { queries } = useResourceAttribute();
- const tagFilterItems = useMemo(
+ const tagFilterItems: TagFilterItem[] = useMemo(
() =>
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
[queries],
@@ -46,27 +48,27 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
const databaseCallsRPSWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: databaseCallsRPS({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: databaseCallsRPS({
servicename,
legend,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: databaseCallsAvgDuration({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: databaseCallsAvgDuration({
servicename,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx
index c615179d65..1a4a511653 100644
--- a/frontend/src/container/MetricsApplication/Tabs/External.tsx
+++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx
@@ -11,9 +11,10 @@ import {
convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
+import { EQueryType } from 'types/common/dashboard';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
@@ -39,14 +40,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallErrorWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: externalCallErrorPercent({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: externalCallErrorPercent({
servicename,
legend: legend.address,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@@ -59,13 +60,13 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallDurationWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: externalCallDuration({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: externalCallDuration({
servicename,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@@ -73,14 +74,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallRPSWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: externalCallRpsByAddress({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: externalCallRpsByAddress({
servicename,
legend: legend.address,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@@ -88,14 +89,14 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
const externalCallDurationAddressWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: externalCallDurationByAddress({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: externalCallDurationByAddress({
servicename,
legend: legend.address,
tagFilterItems,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
index f540a69896..3fb81df2e0 100644
--- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
+++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx
@@ -1,8 +1,10 @@
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import Graph from 'components/Graph';
-import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
+import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
+import { routeConfig } from 'container/SideNav/config';
+import { getQueryString } from 'container/SideNav/helper';
import useResourceAttribute from 'hooks/useResourceAttribute';
import {
convertRawQueriesToTraceSelectedTags,
@@ -11,12 +13,13 @@ import {
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
import { colors } from 'lib/getRandomColor';
import history from 'lib/history';
-import React, { useCallback, useMemo, useState } from 'react';
+import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { useParams } from 'react-router-dom';
+import { useLocation, useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
+import { EQueryType } from 'types/common/dashboard';
import MetricReducer from 'types/reducer/metrics';
import {
@@ -35,6 +38,7 @@ import {
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState(0);
+ const { search } = useLocation();
const handleSetTimeStamp = useCallback((selectTime: number) => {
setSelectedTimeStamp(selectTime);
@@ -79,14 +83,14 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const operationPerSecWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: operationPerSec({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: operationPerSec({
servicename,
tagFilterItems,
topLevelOperations,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[getWidgetQueryBuilder, servicename, topLevelOperations, tagFilterItems],
);
@@ -94,14 +98,14 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const errorPercentageWidget = useMemo(
() =>
getWidgetQueryBuilder({
- queryType: 1,
- promQL: [],
- metricsBuilder: errorPercentage({
+ queryType: EQueryType.QUERY_BUILDER,
+ promql: [],
+ builder: errorPercentage({
servicename,
tagFilterItems,
topLevelOperations,
}),
- clickHouse: [],
+ clickhouse_sql: [],
}),
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
);
@@ -122,14 +126,19 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
const currentTime = timestamp;
const tPlusOne = timestamp + 60 * 1000;
- const urlParams = new URLSearchParams();
- urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
- urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
+ const urlParams = new URLSearchParams(search);
+ urlParams.set(QueryParams.startTime, currentTime.toString());
+ urlParams.set(QueryParams.endTime, tPlusOne.toString());
+
+ const avialableParams = routeConfig[ROUTES.TRACE];
+ const queryString = getQueryString(avialableParams, urlParams);
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`,
+ }?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&${queryString.join(
+ '',
+ )}`,
);
};
diff --git a/frontend/src/container/MetricsApplication/Tabs/util.ts b/frontend/src/container/MetricsApplication/Tabs/util.ts
index f6e78a6d6f..b33bbf0767 100644
--- a/frontend/src/container/MetricsApplication/Tabs/util.ts
+++ b/frontend/src/container/MetricsApplication/Tabs/util.ts
@@ -1,8 +1,11 @@
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
-import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
+import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
+import { routeConfig } from 'container/SideNav/config';
+import { getQueryString } from 'container/SideNav/helper';
import history from 'lib/history';
-import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
+import { Dispatch, SetStateAction } from 'react';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { Tags } from 'types/reducer/trace';
export const dbSystemTags: Tags[] = [
@@ -31,24 +34,24 @@ export function onViewTracePopupClick({
const currentTime = timestamp;
const tPlusOne = timestamp + 60 * 1000;
- const urlParams = new URLSearchParams();
- urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
- urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
+ const urlParams = new URLSearchParams(window.location.search);
+ urlParams.set(QueryParams.startTime, currentTime.toString());
+ urlParams.set(QueryParams.endTime, tPlusOne.toString());
+ const avialableParams = routeConfig[ROUTES.TRACE];
+ const queryString = getQueryString(avialableParams, urlParams);
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${
isExternalCall ? '&spanKind=3' : ''
- }`,
+ }&${queryString.join('&')}`,
);
};
}
export function onGraphClickHandler(
- setSelectedTimeStamp: (
- n: number,
- ) => void | React.Dispatch>,
+ setSelectedTimeStamp: (n: number) => void | Dispatch>,
) {
return async (
event: ChartEvent,
@@ -86,9 +89,7 @@ export function onGraphClickHandler(
};
}
-export const handleNonInQueryRange = (
- tags: IQueryBuilderTagFilterItems[],
-): IQueryBuilderTagFilterItems[] =>
+export const handleNonInQueryRange = (tags: TagFilterItem[]): TagFilterItem[] =>
tags.map((tag) => {
if (tag.op === 'Not IN') {
return {
diff --git a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
index 010a60c8e2..540bc0d546 100644
--- a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
+++ b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx
@@ -1,12 +1,11 @@
import { Tooltip, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
-import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
+import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import history from 'lib/history';
-import React from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
@@ -29,14 +28,8 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
const handleOnClick = (operation: string): void => {
const urlParams = new URLSearchParams();
const { servicename } = params;
- urlParams.set(
- METRICS_PAGE_QUERY_PARAM.startTime,
- (minTime / 1000000).toString(),
- );
- urlParams.set(
- METRICS_PAGE_QUERY_PARAM.endTime,
- (maxTime / 1000000).toString(),
- );
+ urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
+ urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
history.push(
`${
@@ -45,7 +38,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
);
};
- const columns: ColumnsType = [
+ const columns: ColumnsType = [
{
title: 'Name',
dataIndex: 'name',
@@ -64,7 +57,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
dataIndex: 'p50',
key: 'p50',
width: 50,
- sorter: (a: DataProps, b: DataProps): number => a.p50 - b.p50,
+ sorter: (a: TopOperationList, b: TopOperationList): number => a.p50 - b.p50,
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
@@ -72,7 +65,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
dataIndex: 'p95',
key: 'p95',
width: 50,
- sorter: (a: DataProps, b: DataProps): number => a.p95 - b.p95,
+ sorter: (a: TopOperationList, b: TopOperationList): number => a.p95 - b.p95,
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
@@ -80,7 +73,7 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
dataIndex: 'p99',
key: 'p99',
width: 50,
- sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
+ sorter: (a: TopOperationList, b: TopOperationList): number => a.p99 - b.p99,
render: (value: number): string => (value / 1000000).toFixed(2),
},
{
@@ -88,9 +81,19 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
dataIndex: 'numCalls',
key: 'numCalls',
width: 50,
- sorter: (a: TopOperationListItem, b: TopOperationListItem): number =>
+ sorter: (a: TopOperationList, b: TopOperationList): number =>
a.numCalls - b.numCalls,
},
+ {
+ title: 'Error Rate',
+ dataIndex: 'errorCount',
+ key: 'errorCount',
+ width: 50,
+ sorter: (a: TopOperationList, b: TopOperationList): number =>
+ a.errorCount - b.errorCount,
+ render: (value: number, record: TopOperationList): string =>
+ `${((value / record.numCalls) * 100).toFixed(2)} %`,
+ },
];
return (
@@ -105,18 +108,17 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
);
}
-interface TopOperationListItem {
+export interface TopOperationList {
p50: number;
p95: number;
p99: number;
numCalls: number;
name: string;
+ errorCount: number;
}
-type DataProps = TopOperationListItem;
-
interface TopOperationsTableProps {
- data: TopOperationListItem[];
+ data: TopOperationList[];
}
export default TopOperationsTable;
diff --git a/frontend/src/container/MetricsApplication/index.tsx b/frontend/src/container/MetricsApplication/index.tsx
index 0e189fb1bd..e0d76100ec 100644
--- a/frontend/src/container/MetricsApplication/index.tsx
+++ b/frontend/src/container/MetricsApplication/index.tsx
@@ -1,7 +1,7 @@
import RouteTab from 'components/RouteTab';
import ROUTES from 'constants/routes';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
-import React, { memo } from 'react';
+import { memo } from 'react';
import { generatePath, useParams } from 'react-router-dom';
import { useLocation } from 'react-use';
diff --git a/frontend/src/container/MetricsTable/Metrics.test.tsx b/frontend/src/container/MetricsTable/Metrics.test.tsx
new file mode 100644
index 0000000000..74239c19f7
--- /dev/null
+++ b/frontend/src/container/MetricsTable/Metrics.test.tsx
@@ -0,0 +1,70 @@
+import { render, RenderResult, screen, waitFor } from '@testing-library/react';
+import { ReactElement } from 'react';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import {
+ combineReducers,
+ legacy_createStore as createStore,
+ Store,
+} from 'redux';
+
+import { InitialValue } from '../../store/reducers/metric';
+import Metrics from './index';
+
+const rootReducer = combineReducers({
+ metrics: (state = InitialValue) => state,
+});
+
+const mockStore = createStore(rootReducer);
+
+const renderWithReduxAndRouter = (mockStore: Store) => (
+ component: ReactElement,
+): RenderResult =>
+ render(
+
+ {component}
+ ,
+ );
+
+describe('Metrics Component', () => {
+ it('renders without errors', async () => {
+ renderWithReduxAndRouter(mockStore)( );
+
+ await waitFor(() => {
+ expect(screen.getByText(/application/i)).toBeInTheDocument();
+ expect(screen.getByText(/p99 latency \(in ms\)/i)).toBeInTheDocument();
+ expect(screen.getByText(/error rate \(% of total\)/i)).toBeInTheDocument();
+ expect(screen.getByText(/operations per second/i)).toBeInTheDocument();
+ });
+ });
+
+ it('renders loading when required conditions are met', async () => {
+ const customStore = createStore(rootReducer, {
+ metrics: {
+ services: [],
+ loading: true,
+ error: false,
+ },
+ });
+
+ const { container } = renderWithReduxAndRouter(customStore)( );
+
+ const spinner = container.querySelector('.ant-spin-nested-loading');
+
+ expect(spinner).toBeInTheDocument();
+ });
+
+ it('renders no data when required conditions are met', async () => {
+ const customStore = createStore(rootReducer, {
+ metrics: {
+ services: [],
+ loading: false,
+ error: false,
+ },
+ });
+
+ renderWithReduxAndRouter(customStore)( );
+
+ expect(screen.getByText('No data')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx b/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx
index 2018d49a9d..aedc3d4e43 100644
--- a/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx
+++ b/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx
@@ -1,6 +1,5 @@
import { Button, Typography } from 'antd';
import Modal from 'components/Modal';
-import React from 'react';
function SkipOnBoardingModal({ onContinueClick }: Props): JSX.Element {
return (
diff --git a/frontend/src/container/MetricsTable/index.tsx b/frontend/src/container/MetricsTable/index.tsx
index c815769f54..9fee92c811 100644
--- a/frontend/src/container/MetricsTable/index.tsx
+++ b/frontend/src/container/MetricsTable/index.tsx
@@ -11,7 +11,9 @@ import localStorageSet from 'api/browser/localstorage/set';
import { ResizeTable } from 'components/ResizeTable';
import { SKIP_ONBOARDING } from 'constants/onboarding';
import ROUTES from 'constants/routes';
-import React, { useCallback, useMemo, useState } from 'react';
+import { routeConfig } from 'container/SideNav/config';
+import { getQueryString } from 'container/SideNav/helper';
+import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link, useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
@@ -88,11 +90,17 @@ function Metrics(): JSX.Element {
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase()),
- render: (text: string): JSX.Element => (
-
- {text}
-
- ),
+ render: (metrics: string): JSX.Element => {
+ const urlParams = new URLSearchParams(search);
+ const avialableParams = routeConfig[ROUTES.SERVICE_METRICS];
+ const queryString = getQueryString(avialableParams, urlParams);
+
+ return (
+
+ {metrics}
+
+ );
+ },
}),
[filterDropdown, FilterIcon, search],
);
diff --git a/frontend/src/container/MySettings/Password/index.tsx b/frontend/src/container/MySettings/Password/index.tsx
index 1d810de6cb..33b8f9c5f2 100644
--- a/frontend/src/container/MySettings/Password/index.tsx
+++ b/frontend/src/container/MySettings/Password/index.tsx
@@ -2,7 +2,7 @@ import { Button, Space, Typography } from 'antd';
import changeMyPassword from 'api/user/changeMyPassword';
import { useNotifications } from 'hooks/useNotifications';
import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/MySettings/UpdateName/index.tsx b/frontend/src/container/MySettings/UpdateName/index.tsx
index df20b7141d..6da15a237a 100644
--- a/frontend/src/container/MySettings/UpdateName/index.tsx
+++ b/frontend/src/container/MySettings/UpdateName/index.tsx
@@ -1,7 +1,7 @@
import { Button, Space, Typography } from 'antd';
import editUser from 'api/user/editUser';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
diff --git a/frontend/src/container/MySettings/index.tsx b/frontend/src/container/MySettings/index.tsx
index 7ce9e02e42..f0938e6440 100644
--- a/frontend/src/container/MySettings/index.tsx
+++ b/frontend/src/container/MySettings/index.tsx
@@ -1,5 +1,4 @@
import { Space, Typography } from 'antd';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import Password from './Password';
diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
index c7936db261..3051fa07c3 100644
--- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
+++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
@@ -3,7 +3,7 @@
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useCallback } from 'react';
+import { CSSProperties, useCallback } from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
@@ -54,7 +54,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
[data, toggleAddWidget, notifications],
);
const isDarkMode = useIsDarkMode();
- const fillColor: React.CSSProperties['color'] = isDarkMode ? 'white' : 'black';
+ const fillColor: CSSProperties['color'] = isDarkMode ? 'white' : 'black';
return (
diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
index 12eeab0751..5395b491a3 100644
--- a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
+++ b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
@@ -1,20 +1,22 @@
import TimeSeries from 'assets/Dashboard/TimeSeries';
import ValueIcon from 'assets/Dashboard/Value';
+import { PANEL_TYPES } from 'constants/queryBuilder';
+import { CSSProperties } from 'react';
const Items: ItemsProps[] = [
{
- name: 'TIME_SERIES',
+ name: PANEL_TYPES.TIME_SERIES,
Icon: TimeSeries,
display: 'Time Series',
},
{
- name: 'VALUE',
+ name: PANEL_TYPES.VALUE,
Icon: ValueIcon,
display: 'Value',
},
];
-export type ITEMS = 'TIME_SERIES' | 'VALUE' | 'EMPTY_WIDGET';
+export type ITEMS = 'graph' | 'value' | 'list' | 'table' | 'EMPTY_WIDGET';
interface ItemsProps {
name: ITEMS;
@@ -23,7 +25,7 @@ interface ItemsProps {
}
interface IconProps {
- fillColor: React.CSSProperties['color'];
+ fillColor: CSSProperties['color'];
}
export default Items;
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx
index b7e2caf881..15b89f4b8e 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx
@@ -1,7 +1,7 @@
import { PlusOutlined } from '@ant-design/icons';
import { Col, Tooltip, Typography } from 'antd';
import Input from 'components/Input';
-import React, { useState } from 'react';
+import { Dispatch, SetStateAction, useState } from 'react';
import { InputContainer, NewTagContainer, TagsContainer } from './styles';
@@ -38,7 +38,7 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const onChangeHandler = (
value: string,
- func: React.Dispatch>,
+ func: Dispatch>,
): void => {
func(value);
};
@@ -113,7 +113,7 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
interface AddTagsProps {
tags: string[];
- setTags: React.Dispatch>;
+ setTags: Dispatch>;
}
export default AddTags;
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx
index aad663d7a3..849ad8791f 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx
@@ -1,5 +1,5 @@
import { Input } from 'antd';
-import React, { useCallback } from 'react';
+import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
import { Container } from './styles';
@@ -10,7 +10,7 @@ function Description({
setDescription,
}: DescriptionProps): JSX.Element {
const onChangeHandler = useCallback(
- (e: React.ChangeEvent) => {
+ (e: ChangeEvent) => {
setDescription(e.target.value);
},
[setDescription],
@@ -29,7 +29,7 @@ function Description({
interface DescriptionProps {
description: string;
- setDescription: React.Dispatch>;
+ setDescription: Dispatch>;
}
export default Description;
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx
index 4a8fce57a2..a8f1892fa4 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx
@@ -1,7 +1,7 @@
import { SaveOutlined } from '@ant-design/icons';
import { Col, Divider, Input, Space, Typography } from 'antd';
import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags';
-import React, { useCallback, useState } from 'react';
+import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx
index 80b77283aa..05802a9b9e 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx
@@ -15,7 +15,7 @@ import Editor from 'components/Editor';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { map } from 'lodash-es';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import {
IDashboardVariable,
TSortVariableValuesType,
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx
index 9ca7c6fb83..3f90bf565b 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx
@@ -4,7 +4,7 @@ import { Button, Modal, Row, Space, Tag } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import { ResizeTable } from 'components/ResizeTable';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useRef, useState } from 'react';
+import { useRef, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx
index 40176c3d1e..50a69495fa 100644
--- a/frontend/src/container/NewDashboard/DashboardSettings/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx
@@ -1,5 +1,4 @@
import { Tabs } from 'antd';
-import React from 'react';
import GeneralDashboardSettings from './General';
import VariablesSetting from './Variables';
diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx
new file mode 100644
index 0000000000..7543821b60
--- /dev/null
+++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx
@@ -0,0 +1,168 @@
+import '@testing-library/jest-dom/extend-expect';
+
+import { fireEvent, render, screen } from '@testing-library/react';
+import React, { useEffect } from 'react';
+import { IDashboardVariable } from 'types/api/dashboard/getAll';
+
+import VariableItem from './VariableItem';
+
+const mockVariableData: IDashboardVariable = {
+ description: 'Test Variable',
+ type: 'TEXTBOX',
+ textboxValue: 'defaultValue',
+ sort: 'DISABLED',
+ multiSelect: false,
+ showALLOption: false,
+ name: 'testVariable',
+};
+
+// New mock data for a custom variable
+const mockCustomVariableData: IDashboardVariable = {
+ ...mockVariableData,
+ name: 'customVariable',
+ type: 'CUSTOM',
+ customValue: 'option1,option2,option3',
+};
+
+const mockOnValueUpdate = jest.fn();
+const mockOnAllSelectedUpdate = jest.fn();
+
+describe('VariableItem', () => {
+ let useEffectSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ useEffectSpy = jest.spyOn(React, 'useEffect');
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ useEffectSpy.mockRestore();
+ });
+
+ test('renders component with default props', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('$testVariable')).toBeInTheDocument();
+ });
+
+ test('renders Input when the variable type is TEXTBOX', () => {
+ render(
+ ,
+ );
+ expect(screen.getByPlaceholderText('Enter value')).toBeInTheDocument();
+ });
+
+ test('calls onChange event handler when Input value changes', () => {
+ render(
+ ,
+ );
+ const inputElement = screen.getByPlaceholderText('Enter value');
+ fireEvent.change(inputElement, { target: { value: 'newValue' } });
+
+ expect(mockOnValueUpdate).toHaveBeenCalledTimes(1);
+ expect(mockOnValueUpdate).toHaveBeenCalledWith('testVariable', 'newValue');
+ expect(mockOnAllSelectedUpdate).toHaveBeenCalledTimes(1);
+ expect(mockOnAllSelectedUpdate).toHaveBeenCalledWith('testVariable', false);
+ });
+
+ test('renders a Select element when variable type is CUSTOM', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('$customVariable')).toBeInTheDocument();
+ expect(screen.getByTestId('variable-select')).toBeInTheDocument();
+ });
+
+ test('renders a Select element with all selected', async () => {
+ const customVariableData = {
+ ...mockCustomVariableData,
+ allSelected: true,
+ };
+
+ render(
+ ,
+ );
+
+ expect(screen.getByTitle('ALL')).toBeInTheDocument();
+ });
+
+ test('calls useEffect when the component mounts', () => {
+ render(
+ ,
+ );
+
+ expect(useEffect).toHaveBeenCalled();
+ });
+
+ test('calls useEffect only once when the component mounts', () => {
+ // Render the component
+ const { rerender } = render(
+ ,
+ );
+
+ // Create an updated version of the mock data
+ const updatedMockCustomVariableData = {
+ ...mockCustomVariableData,
+ selectedValue: 'option1',
+ };
+
+ // Re-render the component with the updated data
+ rerender(
+ ,
+ );
+
+ // Check if the useEffect is called with the correct arguments
+ expect(useEffectSpy).toHaveBeenCalledTimes(4);
+ });
+});
diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx
index 0aa6e61b90..ef85297d92 100644
--- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx
+++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx
@@ -4,32 +4,24 @@ import { Input, Popover, Select, Typography } from 'antd';
import query from 'api/dashboard/variables/query';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
-import { map } from 'lodash-es';
-import React, { memo, useCallback, useEffect, useState } from 'react';
+import map from 'lodash-es/map';
+import { memo, useCallback, useEffect, useState } from 'react';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { variablePropsToPayloadVariables } from '../utils';
import { SelectItemStyle, VariableContainer, VariableName } from './styles';
import { areArraysEqual } from './util';
-const { Option } = Select;
-
const ALL_SELECT_VALUE = '__ALL__';
interface VariableItemProps {
variableData: IDashboardVariable;
existingVariables: Record;
onValueUpdate: (
- name: string | undefined,
- arg1:
- | string
- | number
- | boolean
- | (string | number | boolean)[]
- | null
- | undefined,
+ name: string,
+ arg1: IDashboardVariable['selectedValue'],
) => void;
- onAllSelectedUpdate: (name: string | undefined, arg1: boolean) => void;
+ onAllSelectedUpdate: (name: string, arg1: boolean) => void;
lastUpdatedVar: string;
}
function VariableItem({
@@ -101,8 +93,10 @@ function VariableItem({
} else {
[value] = newOptionsData;
}
- onValueUpdate(variableData.name, value);
- onAllSelectedUpdate(variableData.name, allSelected);
+ if (variableData.name) {
+ onValueUpdate(variableData.name, value);
+ onAllSelectedUpdate(variableData.name, allSelected);
+ }
}
setOptionsData(newOptionsData);
}
@@ -133,17 +127,18 @@ function VariableItem({
}, [variableData, existingVariables]);
const handleChange = (value: string | string[]): void => {
- if (
- value === ALL_SELECT_VALUE ||
- (Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
- (Array.isArray(value) && value.length === 0)
- ) {
- onValueUpdate(variableData.name, optionsData);
- onAllSelectedUpdate(variableData.name, true);
- } else {
- onValueUpdate(variableData.name, value);
- onAllSelectedUpdate(variableData.name, false);
- }
+ if (variableData.name)
+ if (
+ value === ALL_SELECT_VALUE ||
+ (Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
+ (Array.isArray(value) && value.length === 0)
+ ) {
+ onValueUpdate(variableData.name, optionsData);
+ onAllSelectedUpdate(variableData.name, true);
+ } else {
+ onValueUpdate(variableData.name, value);
+ onAllSelectedUpdate(variableData.name, false);
+ }
};
const selectValue = variableData.allSelected
@@ -182,10 +177,21 @@ function VariableItem({
style={SelectItemStyle}
loading={isLoading}
showArrow
+ data-testid="variable-select"
>
- {enableSelectAll && ALL }
+ {enableSelectAll && (
+
+ ALL
+
+ )}
{map(optionsData, (option) => (
- {option.toString()}
+
+ {option.toString()}
+
))}
)
diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx
index 9652782fd5..67d961f5c9 100644
--- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx
+++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx
@@ -2,7 +2,7 @@ import { Row } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import { useNotifications } from 'hooks/useNotifications';
import { map, sortBy } from 'lodash-es';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/utils.test.ts b/frontend/src/container/NewDashboard/DashboardVariablesSelection/utils.test.ts
new file mode 100644
index 0000000000..c258849a74
--- /dev/null
+++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/utils.test.ts
@@ -0,0 +1,33 @@
+import { areArraysEqual } from './util';
+
+describe('areArraysEqual', () => {
+ it('should return true for equal arrays with same order', () => {
+ const array1 = [1, 'a', true, 5, 'hello'];
+ const array2 = [1, 'a', true, 5, 'hello'];
+ expect(areArraysEqual(array1, array2)).toBe(true);
+ });
+
+ it('should return false for equal arrays with different order', () => {
+ const array1 = [1, 'a', true, 5, 'hello'];
+ const array2 = ['hello', 1, true, 'a', 5];
+ expect(areArraysEqual(array1, array2)).toBe(false);
+ });
+
+ it('should return false for arrays with different lengths', () => {
+ const array1 = [1, 'a', true, 5, 'hello'];
+ const array2 = [1, 'a', true, 5];
+ expect(areArraysEqual(array1, array2)).toBe(false);
+ });
+
+ it('should return false for arrays with different elements', () => {
+ const array1 = [1, 'a', true, 5, 'hello'];
+ const array2 = [1, 'a', true, 5, 'world'];
+ expect(areArraysEqual(array1, array2)).toBe(false);
+ });
+
+ it('should return true for empty arrays', () => {
+ const array1: string[] = [];
+ const array2: string[] = [];
+ expect(areArraysEqual(array1, array2)).toBe(true);
+ });
+});
diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx
index a8b8871190..32f78d7ec2 100644
--- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx
+++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard/index.tsx
@@ -1,12 +1,12 @@
import Input from 'components/Input';
-import React, { useCallback } from 'react';
+import { ChangeEvent, Dispatch, SetStateAction, useCallback } from 'react';
function NameOfTheDashboard({
setName,
name,
}: NameOfTheDashboardProps): JSX.Element {
const onChangeHandler = useCallback(
- (e: React.ChangeEvent) => {
+ (e: ChangeEvent) => {
setName(e.target.value);
},
[setName],
@@ -24,7 +24,7 @@ function NameOfTheDashboard({
interface NameOfTheDashboardProps {
name: string;
- setName: React.Dispatch>;
+ setName: Dispatch>;
}
export default NameOfTheDashboard;
diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx
index b8f6617204..33af3bdb3a 100644
--- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx
+++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx
@@ -1,6 +1,6 @@
import { SettingOutlined } from '@ant-design/icons';
import { Button } from 'antd';
-import React, { useState } from 'react';
+import { useState } from 'react';
import DashboardSettingsContent from '../DashboardSettings';
import { DrawerContainer } from './styles';
diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx
index 6e5bd00e9f..51d83b23a3 100644
--- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx
+++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx
@@ -1,7 +1,7 @@
import { Button, Modal, Typography } from 'antd';
import Editor from 'components/Editor';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { DashboardData } from 'types/api/dashboard/getAll';
diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx
index c1a98cce8f..2faafd7cbb 100644
--- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx
+++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx
@@ -1,7 +1,7 @@
import { ShareAltOutlined } from '@ant-design/icons';
import { Button, Card, Col, Row, Space, Tag, Typography } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/NewDashboard/GridGraphs/index.tsx b/frontend/src/container/NewDashboard/GridGraphs/index.tsx
index 5436f43704..b6fb01700a 100644
--- a/frontend/src/container/NewDashboard/GridGraphs/index.tsx
+++ b/frontend/src/container/NewDashboard/GridGraphs/index.tsx
@@ -1,6 +1,5 @@
import GridGraphLayout from 'container/GridGraphLayout';
import ComponentsSlider from 'container/NewDashboard/ComponentsSlider';
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import DashboardReducer from 'types/reducer/dashboards';
diff --git a/frontend/src/container/NewDashboard/index.tsx b/frontend/src/container/NewDashboard/index.tsx
index 8fbe49de84..5eff095c6f 100644
--- a/frontend/src/container/NewDashboard/index.tsx
+++ b/frontend/src/container/NewDashboard/index.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import Description from './DescriptionOfDashboard';
import GridGraphs from './GridGraphs';
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/Options.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/Options.ts
deleted file mode 100644
index 1ee8680391..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/Options.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { EAggregateOperator } from 'types/common/dashboard';
-
-export const AggregateFunctions = Object.keys(EAggregateOperator)
- .filter((key) => Number.isNaN(parseInt(key, 10)))
- .map((key) => ({
- label: key,
- value: EAggregateOperator[key as keyof typeof EAggregateOperator],
- }));
-
-export const TagKeyOperator = [
- { label: 'In', value: 'IN' },
- { label: 'Not In', value: 'NIN' },
- { label: 'Like', value: 'LIKE' },
- { label: 'Not Like', value: 'NLIKE' },
- // { label: 'Equal', value: 'EQ' },
- // { label: 'Not Equal', value: 'NEQ' },
- // { label: 'REGEX', value: 'REGEX' },
-];
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx
index 6584cdc390..b6bc5a6f17 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx
@@ -6,7 +6,7 @@ import {
RightOutlined,
} from '@ant-design/icons';
import { Button, Row } from 'antd';
-import React, { useState } from 'react';
+import { ReactNode, useState } from 'react';
import { QueryWrapper } from '../styles';
@@ -15,7 +15,7 @@ interface IQueryHeaderProps {
onDisable: VoidFunction;
name: string;
onDelete: VoidFunction;
- children: React.ReactNode;
+ children: ReactNode;
}
function QueryHeader({
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/index.tsx
index 95b0fa8bfd..ac3d7b6d55 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/index.tsx
@@ -1,10 +1,9 @@
import { PlusOutlined } from '@ant-design/icons';
import { ClickHouseQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
-import React from 'react';
import { Query } from 'types/api/dashboard/getAll';
+import { EQueryType } from 'types/common/dashboard';
-import { WIDGET_CLICKHOUSE_QUERY_KEY_NAME } from '../../constants';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import ClickHouseQueryBuilder from './query';
@@ -13,7 +12,7 @@ import { IClickHouseQueryHandleChange } from './types';
interface IClickHouseQueryContainerProps {
queryData: Query;
updateQueryData: (args: IHandleUpdatedQuery) => void;
- clickHouseQueries: Query['clickHouse'];
+ clickHouseQueries: Query['clickhouse_sql'];
}
function ClickHouseQueryContainer({
queryData,
@@ -35,7 +34,7 @@ function ClickHouseQueryContainer({
// hence, this method is only applies when queryIndex is in number format.
if (typeof queryIndex === 'number') {
- const allQueries = queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME];
+ const allQueries = queryData[EQueryType.CLICKHOUSE];
const currentIndexQuery = allQueries[queryIndex];
@@ -57,8 +56,8 @@ function ClickHouseQueryContainer({
}
};
const addQueryHandler = (): void => {
- queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME].push({
- name: GetQueryName(queryData[WIDGET_CLICKHOUSE_QUERY_KEY_NAME]) || '',
+ queryData[EQueryType.CLICKHOUSE].push({
+ name: GetQueryName(queryData[EQueryType.CLICKHOUSE]) || '',
...ClickHouseQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query.tsx
index 389b7e15c4..539a72e3a2 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query.tsx
@@ -1,6 +1,5 @@
import { Input } from 'antd';
import MonacoEditor from 'components/Editor';
-import React from 'react';
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
import QueryHeader from '../QueryHeader';
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/index.tsx
index 55adbd740b..dde28d8af7 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/index.tsx
@@ -1,10 +1,9 @@
import { PlusOutlined } from '@ant-design/icons';
import { PromQLQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
-import React from 'react';
import { IPromQLQuery, Query } from 'types/api/dashboard/getAll';
+import { EQueryType } from 'types/common/dashboard';
-import { WIDGET_PROMQL_QUERY_KEY_NAME } from '../../constants';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import PromQLQueryBuilder from './query';
@@ -28,7 +27,7 @@ function PromQLQueryContainer({
toggleDisable,
toggleDelete,
}: IPromQLQueryHandleChange): void => {
- const allQueries = queryData[WIDGET_PROMQL_QUERY_KEY_NAME];
+ const allQueries = queryData[EQueryType.PROM];
const currentIndexQuery = allQueries[queryIndex as number];
if (query !== undefined) currentIndexQuery.query = query;
if (legend !== undefined) currentIndexQuery.legend = legend;
@@ -42,8 +41,8 @@ function PromQLQueryContainer({
updateQueryData({ updatedQuery: { ...queryData } });
};
const addQueryHandler = (): void => {
- queryData[WIDGET_PROMQL_QUERY_KEY_NAME].push({
- name: GetQueryName(queryData[WIDGET_PROMQL_QUERY_KEY_NAME]) || '',
+ queryData[EQueryType.PROM].push({
+ name: GetQueryName(queryData[EQueryType.PROM]) || '',
...PromQLQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query.tsx
index 6cffd55d8d..3781fc71de 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query.tsx
@@ -1,5 +1,4 @@
import { Input } from 'antd';
-import React from 'react';
import { IPromQLQuery } from 'types/api/dashboard/getAll';
import QueryHeader from '../QueryHeader';
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.ts
deleted file mode 100644
index f082f4091f..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { createMachine } from 'xstate';
-
-export const ResourceAttributesFilterMachine =
- /** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */
- createMachine({
- tsTypes: {} as import('./MetricTagKey.machine.typegen').Typegen0,
- initial: 'Idle',
- states: {
- TagKey: {
- on: {
- NEXT: {
- actions: 'onSelectOperator',
- target: 'Operator',
- },
- onBlur: {
- actions: 'onBlurPurge',
- target: 'Idle',
- },
- RESET: {
- target: 'Idle',
- },
- },
- },
- Operator: {
- on: {
- NEXT: {
- actions: 'onSelectTagValue',
- target: 'TagValue',
- },
- // onBlur: {
- // actions: 'onBlurPurge',
- // target: 'Idle',
- // },
- RESET: {
- target: 'Idle',
- },
- },
- },
- TagValue: {
- on: {
- onBlur: {
- actions: ['onValidateQuery'],
- // target: 'Idle',
- },
- RESET: {
- target: 'Idle',
- },
- },
- },
- Idle: {
- on: {
- NEXT: {
- actions: 'onSelectTagKey',
- description: 'Select Category',
- target: 'TagKey',
- },
- },
- },
- },
- id: 'Dashboard Search And Filter',
- });
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.typegen.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.typegen.ts
deleted file mode 100644
index f2019aaa26..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/MetricTagKey.machine.typegen.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-// This file was automatically generated. Edits will be overwritten
-
-export interface Typegen0 {
- '@@xstate/typegen': true;
- internalEvents: {
- 'xstate.init': { type: 'xstate.init' };
- };
- invokeSrcNameMap: {};
- missingImplementations: {
- actions:
- | 'onBlurPurge'
- | 'onSelectOperator'
- | 'onSelectTagKey'
- | 'onSelectTagValue'
- | 'onValidateQuery';
- delays: never;
- guards: never;
- services: never;
- };
- eventsCausingActions: {
- onBlurPurge: 'onBlur';
- onSelectOperator: 'NEXT';
- onSelectTagKey: 'NEXT';
- onSelectTagValue: 'NEXT';
- onValidateQuery: 'onBlur';
- };
- eventsCausingDelays: {};
- eventsCausingGuards: {};
- eventsCausingServices: {};
- matchesStates: 'Idle' | 'Operator' | 'TagKey' | 'TagValue';
- tags: never;
-}
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/QueryChip.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/QueryChip.tsx
deleted file mode 100644
index e992536b74..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/QueryChip.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-
-import { QueryChipContainer, QueryChipItem } from './styles';
-import { ITagKeyValueQuery } from './types';
-
-interface IQueryChipProps {
- queryData: ITagKeyValueQuery;
- onClose: (id: string) => void;
- disabled?: boolean;
-}
-
-export default function QueryChip({
- queryData,
- onClose,
- disabled,
-}: IQueryChipProps): JSX.Element {
- return (
-
- {queryData.key}
- {queryData.op}
- {
- if (!disabled) onClose(queryData.id);
- }}
- >
- {queryData.value.join(', ')}
-
-
- );
-}
-QueryChip.defaultProps = {
- disabled: false,
-};
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/index.tsx
deleted file mode 100644
index d25c4e73dc..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/index.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import { CloseCircleFilled } from '@ant-design/icons';
-import { useMachine } from '@xstate/react';
-import { Button, Select, Spin } from 'antd';
-import { useIsDarkMode } from 'hooks/useDarkMode';
-import { map } from 'lodash-es';
-import React, { useCallback, useEffect, useState } from 'react';
-import { IMetricsBuilderQuery } from 'types/api/dashboard/getAll';
-import { v4 as uuid } from 'uuid';
-
-import { ResourceAttributesFilterMachine } from './MetricTagKey.machine';
-import QueryChip from './QueryChip';
-import { QueryChipItem, SearchContainer } from './styles';
-import { IOption, ITagKeyValueQuery } from './types';
-import {
- createQuery,
- GetTagKeys,
- GetTagValues,
- OperatorSchema,
- SingleValueOperators,
-} from './utils';
-
-interface IMetricTagKeyFilterProps {
- metricName: IMetricsBuilderQuery['metricName'];
- onSetQuery: (args: IMetricsBuilderQuery['tagFilters']['items']) => void;
- selectedTagFilters: IMetricsBuilderQuery['tagFilters']['items'];
-}
-
-function MetricTagKeyFilter({
- metricName,
- onSetQuery,
- selectedTagFilters: selectedTagQueries,
-}: IMetricTagKeyFilterProps): JSX.Element | null {
- const isDarkMode = useIsDarkMode();
- const [loading, setLoading] = useState(true);
- const [selectedValues, setSelectedValues] = useState([]);
- const [staging, setStaging] = useState([]);
- const [queries, setQueries] = useState([]);
- const [optionsData, setOptionsData] = useState<{
- mode: undefined | 'tags' | 'multiple';
- options: IOption[];
- }>({
- mode: undefined,
- options: [],
- });
-
- const dispatchQueries = (
- updatedQueries: IMetricsBuilderQuery['tagFilters']['items'],
- ): void => {
- onSetQuery(updatedQueries);
- setQueries(updatedQueries);
- };
- const handleLoading = (isLoading: boolean): void => {
- setLoading(isLoading);
- if (isLoading) {
- setOptionsData({ mode: undefined, options: [] });
- }
- };
- const [state, send] = useMachine(ResourceAttributesFilterMachine, {
- actions: {
- onSelectTagKey: () => {
- handleLoading(true);
- GetTagKeys(metricName || '')
- .then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined }))
- .finally(() => {
- handleLoading(false);
- });
- },
- onSelectOperator: () => {
- setOptionsData({ options: OperatorSchema, mode: undefined });
- },
- onSelectTagValue: () => {
- handleLoading(true);
-
- GetTagValues(staging[0], metricName || '')
- .then((tagValuesOptions) =>
- setOptionsData({ options: tagValuesOptions, mode: 'tags' }),
- )
- .finally(() => {
- handleLoading(false);
- });
- },
- onBlurPurge: () => {
- setSelectedValues([]);
- setStaging([]);
- },
- onValidateQuery: (): void => {
- if (staging.length < 2 || selectedValues.length === 0) {
- return;
- }
-
- const generatedQuery = createQuery([...staging, selectedValues]);
-
- if (generatedQuery) {
- dispatchQueries([...queries, generatedQuery]);
- setSelectedValues([]);
- setStaging([]);
- send('RESET');
- }
- },
- },
- });
-
- useEffect(() => {
- setQueries(selectedTagQueries);
- }, [selectedTagQueries]);
-
- const handleFocus = (): void => {
- if (state.value === 'Idle') {
- send('NEXT');
- }
- };
-
- const handleBlur = useCallback((): void => {
- send('onBlur');
- }, [send]);
-
- useEffect(() => {
- handleBlur();
- }, [handleBlur, metricName]);
-
- const handleChange = (value: never | string[]): void => {
- if (!optionsData.mode) {
- setStaging((prevStaging) => [...prevStaging, String(value)]);
- setSelectedValues([]);
- send('NEXT');
- return;
- }
- if (
- state.value === 'TagValue' &&
- SingleValueOperators.includes(staging[staging.length - 1]) &&
- Array.isArray(value)
- ) {
- setSelectedValues([value[value.length - 1]]);
- return;
- }
-
- setSelectedValues([...value]);
- };
-
- const handleClose = (id: string): void => {
- dispatchQueries(queries.filter((queryData) => queryData.id !== id));
- };
-
- const handleClearAll = (): void => {
- send('RESET');
- dispatchQueries([]);
- setStaging([]);
- setSelectedValues([]);
- };
-
- return (
-
-
- {queries.length > 0 &&
- map(
- queries,
- (query): JSX.Element => (
-
- ),
- )}
-
-
- {map(staging, (item) => (
- {item}
- ))}
-
-
-
-
- Loading...{' '}
-
- ) : (
-
- No resource attributes available to filter. Please refer docs to send
- attributes.
-
- )
- }
- />
-
- {queries.length || staging.length || selectedValues.length ? (
- }
- type="text"
- />
- ) : null}
-
-
- );
-}
-
-export default MetricTagKeyFilter;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/styles.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/styles.ts
deleted file mode 100644
index fcb7e20372..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/styles.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { grey } from '@ant-design/colors';
-import { Tag } from 'antd';
-import styled from 'styled-components';
-
-export const SearchContainer = styled.div<{
- isDarkMode: boolean;
- disabled?: boolean;
-}>`
- background: ${({ isDarkMode }): string => (isDarkMode ? '#000' : '#fff')};
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 0.2rem;
- border: 1px solid #ccc5;
- ${({ disabled }): string => (disabled ? `cursor: not-allowed;` : '')}
-`;
-export const QueryChipContainer = styled.span`
- display: flex;
- align-items: center;
- margin-right: 0.5rem;
- &:hover {
- & > * {
- background: ${grey.primary}44;
- }
- }
-`;
-
-export const QueryChipItem = styled(Tag)`
- margin-right: 0.1rem;
-`;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/types.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/types.ts
deleted file mode 100644
index 89340d6cbe..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/types.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export interface IOption {
- label: string;
- value: string;
-}
-
-export interface IMetricBuilderTagKeyQuery {
- id: string;
- tagKey: string;
- operator: string;
- tagValue: string[];
-}
-
-export interface ITagKeyValueQuery {
- id: string;
- key: string;
- op: string;
- value: string[];
-}
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/utils.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/utils.ts
deleted file mode 100644
index ba3ce8bca9..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/MetricTagKeyFilter/utils.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import {
- getResourceAttributesTagKeys,
- getResourceAttributesTagValues,
-} from 'api/metrics/getResourceAttributes';
-import { v4 as uuid } from 'uuid';
-
-import { TagKeyOperator } from '../../Options';
-import { IOption, ITagKeyValueQuery } from './types';
-
-export const OperatorSchema: IOption[] = TagKeyOperator;
-
-export const GetTagKeys = async (metricName: string): Promise => {
- const { payload } = await getResourceAttributesTagKeys({ metricName });
- if (!payload || !payload?.data) {
- return [];
- }
- return payload.data.map((tagKey: string) => ({
- label: tagKey,
- value: tagKey,
- }));
-};
-
-export const GetTagValues = async (
- tagKey: string,
- metricName: string,
-): Promise => {
- const { payload } = await getResourceAttributesTagValues({
- tagKey,
- metricName,
- });
-
- if (!payload || !payload?.data) {
- return [];
- }
- return payload.data.map((tagValue: string) => ({
- label: tagValue,
- value: tagValue,
- }));
-};
-
-export const createQuery = (
- selectedItems: Array = [],
-): ITagKeyValueQuery | null => {
- if (selectedItems.length === 3) {
- return {
- id: uuid().slice(0, 8),
- key: typeof selectedItems[0] === 'string' ? selectedItems[0] : '',
- op: typeof selectedItems[1] === 'string' ? selectedItems[1] : '',
- value: selectedItems[2] as string[],
- };
- }
- return null;
-};
-
-export const SingleValueOperators = ['LIKE', 'NLIKE'];
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula.tsx
deleted file mode 100644
index 9a5ce5e8c5..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/formula.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Input } from 'antd';
-import React from 'react';
-import { IMetricsBuilderFormula } from 'types/api/dashboard/getAll';
-
-import QueryHeader from '../QueryHeader';
-import { IQueryBuilderFormulaHandleChange } from './types';
-
-const { TextArea } = Input;
-
-interface IMetricsBuilderFormulaProps {
- formulaData: IMetricsBuilderFormula;
- formulaIndex: number | string;
- handleFormulaChange: (args: IQueryBuilderFormulaHandleChange) => void;
-}
-function MetricsBuilderFormula({
- formulaData,
- formulaIndex,
- handleFormulaChange,
-}: IMetricsBuilderFormulaProps): JSX.Element {
- return (
-
- handleFormulaChange({ formulaIndex, toggleDisable: true })
- }
- onDelete={(): void => {
- handleFormulaChange({ formulaIndex, toggleDelete: true });
- }}
- >
-
- );
-}
-
-export default MetricsBuilderFormula;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx
deleted file mode 100644
index 9b7762fcbf..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/index.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { PlusOutlined } from '@ant-design/icons';
-import {
- QueryBuilderFormulaTemplate,
- QueryBuilderQueryTemplate,
-} from 'constants/dashboard';
-import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
-import { useNotifications } from 'hooks/useNotifications';
-import GetFormulaName from 'lib/query/GetFormulaName';
-import GetQueryName from 'lib/query/GetQueryName';
-import React from 'react';
-import { Query } from 'types/api/dashboard/getAll';
-
-import {
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME,
- WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
-} from '../../constants';
-import { QueryButton } from '../../styles';
-import { IHandleUpdatedQuery } from '../../types';
-import MetricsBuilderFormula from './formula';
-import MetricsBuilder from './query';
-import {
- IQueryBuilderFormulaHandleChange,
- IQueryBuilderQueryHandleChange,
-} from './types';
-import { canCreateQueryAndFormula } from './utils';
-
-interface IQueryBuilderQueryContainerProps {
- queryData: Query;
- updateQueryData: (args: IHandleUpdatedQuery) => void;
- metricsBuilderQueries: Query['metricsBuilder'];
- selectedGraph: GRAPH_TYPES;
-}
-
-function QueryBuilderQueryContainer({
- queryData,
- updateQueryData,
- metricsBuilderQueries,
- selectedGraph,
-}: IQueryBuilderQueryContainerProps): JSX.Element | null {
- const { notifications } = useNotifications();
- const handleQueryBuilderQueryChange = ({
- queryIndex,
- aggregateFunction,
- metricName,
- tagFilters,
- groupBy,
- legend,
- toggleDisable,
- toggleDelete,
- reduceTo,
- }: IQueryBuilderQueryHandleChange): void => {
- const allQueries =
- queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder;
- const currentIndexQuery = allQueries[queryIndex as number];
- if (aggregateFunction) {
- currentIndexQuery.aggregateOperator = aggregateFunction;
- }
-
- if (metricName !== undefined) {
- currentIndexQuery.metricName = metricName;
- }
-
- if (tagFilters) {
- currentIndexQuery.tagFilters.items = tagFilters;
- }
-
- if (groupBy) {
- currentIndexQuery.groupBy = groupBy;
- }
-
- if (reduceTo) {
- currentIndexQuery.reduceTo = reduceTo;
- }
-
- if (legend !== undefined) {
- currentIndexQuery.legend = legend;
- }
- if (toggleDisable) {
- currentIndexQuery.disabled = !currentIndexQuery.disabled;
- }
- if (toggleDelete) {
- allQueries.splice(queryIndex as number, 1);
- }
- updateQueryData({ updatedQuery: { ...queryData } });
- };
- const handleQueryBuilderFormulaChange = ({
- formulaIndex,
- expression,
- legend,
- toggleDisable,
- toggleDelete,
- }: IQueryBuilderFormulaHandleChange): void => {
- const allFormulas =
- queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
- ];
- const currentIndexFormula = allFormulas[formulaIndex as number];
-
- if (expression !== undefined) {
- currentIndexFormula.expression = expression;
- }
- if (legend !== undefined) {
- currentIndexFormula.legend = legend;
- }
-
- if (toggleDisable) {
- currentIndexFormula.disabled = !currentIndexFormula.disabled;
- }
-
- if (toggleDelete) {
- allFormulas.splice(formulaIndex as number, 1);
- }
- updateQueryData({ updatedQuery: { ...queryData } });
- };
- const addQueryHandler = (): void => {
- if (!canCreateQueryAndFormula(queryData)) {
- notifications.error({
- message:
- 'Unable to create query. You can create at max 10 queries and formulae.',
- });
- return;
- }
- queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder.push({
- name:
- GetQueryName(queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder) ||
- '',
- ...QueryBuilderQueryTemplate,
- });
- updateQueryData({ updatedQuery: { ...queryData } });
- };
-
- const addFormulaHandler = (): void => {
- if (!canCreateQueryAndFormula(queryData)) {
- notifications.error({
- message:
- 'Unable to create formula. You can create at max 10 queries and formulae.',
- });
- return;
- }
- queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
- ].push({
- name:
- GetFormulaName(
- queryData[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
- ],
- ) || '',
- ...QueryBuilderFormulaTemplate,
- });
- updateQueryData({ updatedQuery: { ...queryData } });
- };
-
- if (!metricsBuilderQueries) {
- return null;
- }
- return (
- <>
- {metricsBuilderQueries.queryBuilder.map((q, idx) => (
-
- ))}
- }>
- Query
-
-
- {metricsBuilderQueries.formulas.map((f, idx) => (
-
- ))}
- }>
- Formula
-
-
- >
- );
-}
-
-export default QueryBuilderQueryContainer;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query.tsx
deleted file mode 100644
index 7b9681f89e..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/query.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-import { AutoComplete, Col, Input, Row, Select, Spin } from 'antd';
-import { getMetricName } from 'api/metrics/getMetricName';
-import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
-import React, { useEffect, useState } from 'react';
-import { IMetricsBuilderQuery } from 'types/api/dashboard/getAll';
-import { EReduceOperator } from 'types/common/dashboard';
-
-import { AggregateFunctions } from '../Options';
-import QueryHeader from '../QueryHeader';
-import MetricTagKeyFilter from './MetricTagKeyFilter';
-import { IOption } from './MetricTagKeyFilter/types';
-import { GetTagKeys } from './MetricTagKeyFilter/utils';
-import { IQueryBuilderQueryHandleChange } from './types';
-
-const { Option } = Select;
-
-interface IMetricsBuilderProps {
- queryIndex: number | string;
- selectedGraph: GRAPH_TYPES;
- queryData: IMetricsBuilderQuery;
- handleQueryChange: (args: IQueryBuilderQueryHandleChange) => void;
-}
-
-function MetricsBuilder({
- queryIndex,
- selectedGraph,
- queryData,
- handleQueryChange,
-}: IMetricsBuilderProps): JSX.Element {
- const [groupByOptions, setGroupByOptions] = useState([]);
- const [metricName, setMetricName] = useState(
- queryData.metricName,
- );
-
- const [metricNameList, setMetricNameList] = useState([]);
- const [metricNameLoading, setMetricNameLoading] = useState(false);
-
- const handleMetricNameSelect = (e: string): void => {
- handleQueryChange({ queryIndex, metricName: e });
- setMetricName(e);
- };
-
- const handleMetricNameSearch = async (searchQuery = ''): Promise => {
- handleMetricNameSelect(searchQuery);
- setMetricNameList([]);
- setMetricNameLoading(true);
- const { payload } = await getMetricName(searchQuery);
- setMetricNameLoading(false);
- if (!payload || !payload.data) {
- return;
- }
- setMetricNameList(payload.data);
- };
- const [aggregateFunctionList, setAggregateFunctionList] = useState(
- AggregateFunctions,
- );
- const handleAggregateFunctionsSearch = (searchQuery = ''): void => {
- setAggregateFunctionList(
- AggregateFunctions.filter(({ label }) =>
- label.includes(searchQuery.toUpperCase()),
- ) || [],
- );
- };
-
- useEffect(() => {
- GetTagKeys(metricName || '').then((tagKeys) => {
- setGroupByOptions(tagKeys);
- });
- }, [metricName]);
-
- // TODO: rewrite to Form component from antd
-
- return (
-
- handleQueryChange({ queryIndex, toggleDisable: true })
- }
- onDelete={(): void => {
- handleQueryChange({ queryIndex, toggleDelete: true });
- }}
- >
-
-
-
- handleQueryChange({ queryIndex, aggregateFunction: e })
- }
- defaultValue={queryData.aggregateOperator || AggregateFunctions[0]}
- style={{ minWidth: 150 }}
- options={aggregateFunctionList}
- showSearch
- onSearch={handleAggregateFunctionsSearch}
- filterOption={false}
- />
-
-
-
-
- Metrics
-
-
- : null}
- options={metricNameList.map((option) => ({
- label: option,
- value: option,
- }))}
- defaultValue={queryData.metricName}
- value={metricName}
- onSelect={handleMetricNameSelect}
- />
-
-
-
-
- WHERE
-
-
- handleQueryChange({ queryIndex, tagFilters: updatedTagFilters })
- }
- />
-
-
- {selectedGraph === 'TIME_SERIES' ? (
- <>
- {' '}
-
- GROUP BY
-
- : null}
- options={groupByOptions}
- defaultValue={queryData.groupBy}
- onChange={(e): void => {
- handleQueryChange({ queryIndex, groupBy: e });
- }}
- />
- >
- ) : (
- <>
-
- REDUCE TO
-
- !(parseInt(op, 10) >= 0))
- .map((op) => ({
- label: op,
- value: EReduceOperator[op as keyof typeof EReduceOperator],
- }))}
- defaultValue={
- EReduceOperator[
- (queryData.reduceTo as unknown) as keyof typeof EReduceOperator
- ]
- }
- onChange={(e): void => {
- handleQueryChange({ queryIndex, reduceTo: e });
- }}
- />
- >
- )}
-
-
-
-
- {
- handleQueryChange({ queryIndex, legend: e.target.value });
- }}
- size="middle"
- defaultValue={queryData.legend}
- addonBefore="Legend Format"
- />
-
-
-
- );
-}
-
-export default MetricsBuilder;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types.ts
deleted file mode 100644
index d468579d2a..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/types.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
-} from 'types/api/dashboard/getAll';
-
-export interface IQueryBuilderQueryHandleChange {
- queryIndex: number | string;
- aggregateFunction?: IMetricsBuilderQuery['aggregateOperator'];
- metricName?: IMetricsBuilderQuery['metricName'];
- tagFilters?: IMetricsBuilderQuery['tagFilters']['items'];
- groupBy?: IMetricsBuilderQuery['groupBy'];
- legend?: IMetricsBuilderQuery['legend'];
- toggleDisable?: boolean;
- toggleDelete?: boolean;
- reduceTo?: IMetricsBuilderQuery['reduceTo'];
-}
-
-export interface IQueryBuilderFormulaHandleChange {
- formulaIndex: number | string;
- expression?: IMetricsBuilderFormula['expression'];
- toggleDisable?: IMetricsBuilderFormula['disabled'];
- legend?: IMetricsBuilderFormula['legend'];
- toggleDelete?: boolean;
-}
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/utils.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/utils.ts
deleted file mode 100644
index e634d2dce3..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/queryBuilder/utils.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Query } from 'types/api/dashboard/getAll';
-
-import {
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME,
- WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
-} from '../../constants';
-
-const QUERY_AND_FORMULA_LIMIT = 10;
-
-export const canCreateQueryAndFormula = (query: Query): boolean => {
- const queries = query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME].queryBuilder;
- const formulas =
- query[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME][
- WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME
- ];
-
- return queries.length + formulas.length < QUERY_AND_FORMULA_LIMIT;
-};
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/TabHeader.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/TabHeader.tsx
deleted file mode 100644
index c7f4e49735..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/TabHeader.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Tooltip } from 'antd';
-import React from 'react';
-
-interface ITabHeaderProps {
- tabName: string;
- hasUnstagedChanges: boolean;
-}
-
-function TabHeader({
- tabName,
- hasUnstagedChanges,
-}: ITabHeaderProps): JSX.Element {
- return (
-
- {tabName}
- {hasUnstagedChanges && (
-
-
-
- )}
-
- );
-}
-
-export default TabHeader;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/constants.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/constants.ts
deleted file mode 100644
index a16ed33a74..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/constants.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/* eslint-disable */
-// @ts-ignore
-// @ts-nocheck
-
-import { EQueryType } from 'types/common/dashboard';
-
-import { EQueryTypeToQueryKeyMapping } from './types';
-
-export const WIDGET_PROMQL_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.PROM =
- EQueryTypeToQueryKeyMapping[EQueryType[EQueryType.PROM]];
-
-export const WIDGET_CLICKHOUSE_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.CLICKHOUSE = EQueryTypeToQueryKeyMapping[
- EQueryType[EQueryType.CLICKHOUSE]
-] as string;
-
-export const WIDGET_QUERY_BUILDER_QUERY_KEY_NAME: EQueryTypeToQueryKeyMapping.QUERY_BUILDER = EQueryTypeToQueryKeyMapping[
- EQueryType[EQueryType.QUERY_BUILDER]
-] as string;
-
-type TFormulas = 'formulas';
-export const WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME: TFormulas = 'formulas';
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
index 806ee579e9..aac9518aca 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
@@ -1,11 +1,10 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import { Button, Tabs } from 'antd';
+import { Button, Tabs, Typography } from 'antd';
import TextToolTip from 'components/TextToolTip';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
-import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
import { QueryBuilder } from 'container/QueryBuilder';
-import { cloneDeep, isEqual } from 'lodash-es';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { cloneDeep } from 'lodash-es';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
@@ -21,24 +20,12 @@ import { EQueryType } from 'types/common/dashboard';
import DashboardReducer from 'types/reducer/dashboards';
import { v4 as uuid } from 'uuid';
-import {
- WIDGET_CLICKHOUSE_QUERY_KEY_NAME,
- WIDGET_PROMQL_QUERY_KEY_NAME,
- WIDGET_QUERY_BUILDER_QUERY_KEY_NAME,
-} from './constants';
import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
-import QueryBuilderQueryContainer from './QueryBuilder/queryBuilder';
-import TabHeader from './TabHeader';
import { IHandleUpdatedQuery } from './types';
-import { getQueryKey } from './utils/getQueryKey';
-import { showUnstagedStashConfirmBox } from './utils/userSettings';
-function QuerySection({
- handleUnstagedChanges,
- updateQuery,
- selectedGraph,
-}: QueryProps): JSX.Element {
+function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
+ const { queryBuilderData, initQueryBuilderData } = useQueryBuilder();
const [localQueryChanges, setLocalQueryChanges] = useState({} as Query);
const [rctTabKey, setRctTabKey] = useState<
Record
@@ -47,9 +34,12 @@ function QuerySection({
CLICKHOUSE: uuid(),
PROM: uuid(),
});
- const { dashboards } = useSelector(
- (state) => state.dashboards,
- );
+
+ const { dashboards, isLoadingQueryResult } = useSelector<
+ AppState,
+ DashboardReducer
+ >((state) => state.dashboards);
+
const [selectedDashboards] = dashboards;
const { search } = useLocation();
const { widgets } = selectedDashboards.data;
@@ -67,24 +57,11 @@ function QuerySection({
);
const { query } = selectedWidget || {};
+
useEffect(() => {
+ initQueryBuilderData(query.builder);
setLocalQueryChanges(cloneDeep(query) as Query);
- }, [query]);
-
- const queryDiff = (
- queryA: Query,
- queryB: Query,
- queryCategory: EQueryType,
- ): boolean => {
- const keyOfConcern = getQueryKey(queryCategory);
- return !isEqual(queryA[keyOfConcern], queryB[keyOfConcern]);
- };
-
- useEffect(() => {
- handleUnstagedChanges(
- queryDiff(query, localQueryChanges, parseInt(`${queryCategory}`, 10)),
- );
- }, [handleUnstagedChanges, localQueryChanges, query, queryCategory]);
+ }, [query, initQueryBuilderData]);
const regenRctKeys = (): void => {
setRctTabKey((prevState) => {
@@ -99,32 +76,20 @@ function QuerySection({
const handleStageQuery = (): void => {
updateQuery({
- updatedQuery: localQueryChanges,
+ updatedQuery: {
+ ...localQueryChanges,
+ builder: queryBuilderData,
+ },
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
});
};
const handleQueryCategoryChange = (qCategory: string): void => {
- // If true, then it means that the user has made some changes and haven't staged them
- const unstagedChanges = queryDiff(
- query,
- localQueryChanges,
- parseInt(`${queryCategory}`, 10),
- );
-
- if (unstagedChanges && showUnstagedStashConfirmBox()) {
- // eslint-disable-next-line no-alert
- window.confirm(
- "You are trying to navigate to different tab with unstaged changes. Your current changes will be purged. Press 'Stage & Run Query' to stage them.",
- );
- return;
- }
-
- setQueryCategory(parseInt(`${qCategory}`, 10));
+ setQueryCategory(qCategory as EQueryType);
const newLocalQuery = {
...cloneDeep(query),
- queryType: parseInt(`${qCategory}`, 10),
+ queryType: qCategory as EQueryType,
};
setLocalQueryChanges(newLocalQuery);
regenRctKeys();
@@ -143,48 +108,15 @@ function QuerySection({
const items = [
{
- key: EQueryType.QUERY_BUILDER.toString(),
+ key: EQueryType.QUERY_BUILDER,
label: 'Query Builder',
- tab: (
-
- ),
- children: (
- {
- handleLocalQueryUpdate({ updatedQuery });
- }}
- metricsBuilderQueries={
- localQueryChanges[WIDGET_QUERY_BUILDER_QUERY_KEY_NAME]
- }
- selectedGraph={selectedGraph}
- />
-
- // TODO: uncomment for testing new QueryBuilder
- //
- ),
+ tab: Query Builder ,
+ children: ,
},
{
- key: EQueryType.CLICKHOUSE.toString(),
+ key: EQueryType.CLICKHOUSE,
label: 'ClickHouse Query',
- tab: (
-
- ),
+ tab: ClickHouse Query ,
children: (
{
handleLocalQueryUpdate({ updatedQuery });
}}
- clickHouseQueries={localQueryChanges[WIDGET_CLICKHOUSE_QUERY_KEY_NAME]}
+ clickHouseQueries={localQueryChanges[EQueryType.CLICKHOUSE]}
/>
),
},
{
- key: EQueryType.PROM.toString(),
+ key: EQueryType.PROM,
label: 'PromQL',
- tab: (
-
- ),
+ tab: PromQL ,
children: (
{
handleLocalQueryUpdate({ updatedQuery });
}}
- promQLQueries={localQueryChanges[WIDGET_PROMQL_QUERY_KEY_NAME]}
+ promQLQueries={localQueryChanges[EQueryType.PROM]}
/>
),
},
];
return (
- <>
-
-
-
-
- Stage & Run Query
-
-
- }
- items={items}
- />
-
- {/* {localQueryChanges.map((e, index) => (
- //
-
- handleLocalQueryUpdate({ currentIndex: index, updatedQuery })
- }
- onDelete={() => handleDeleteQuery({ currentIndex: index })}
- queryData={e}
- queryCategory={queryCategory}
- />
- ))} */}
- >
+
+
+
+ Stage & Run Query
+
+
+ }
+ items={items}
+ />
);
}
interface DispatchProps {
- // createQuery: ({
- // widgetId,
- // }: CreateQueryProps) => (dispatch: Dispatch) => void;
updateQuery: (
props: UpdateQueryProps,
) => (dispatch: Dispatch) => void;
- // getQueryResults: (
- // props: GetQueryResultsProps,
- // ) => (dispatch: Dispatch) => void;
- // updateQueryType: (
- // props: UpdateQueryTypeProps,
- // ) => (dispatch: Dispatch) => void;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch,
): DispatchProps => ({
- // createQuery: bindActionCreators(CreateQuery, dispatch),
updateQuery: bindActionCreators(UpdateQuery, dispatch),
- // getQueryResults: bindActionCreators(GetQueryResults, dispatch),
- // updateQueryType: bindActionCreators(UpdateQueryType, dispatch),
});
interface QueryProps extends DispatchProps {
selectedGraph: GRAPH_TYPES;
- selectedTime: timePreferance;
- handleUnstagedChanges: (arg0: boolean) => void;
}
export default connect(null, mapDispatchToProps)(QuerySection);
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/types.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/types.ts
index 3db9cb18b2..ca39d0db09 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/types.ts
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/types.ts
@@ -1,19 +1,5 @@
import { Query } from 'types/api/dashboard/getAll';
-export type TQueryCategories = 'query_builder' | 'clickhouse_query' | 'promql';
-
-export enum EQueryCategories {
- query_builder = 0,
- clickhouse_query,
- promql,
-}
-
-export enum EQueryTypeToQueryKeyMapping {
- QUERY_BUILDER = 'metricsBuilder',
- CLICKHOUSE = 'clickHouse',
- PROM = 'promQL',
-}
-
export interface IHandleUpdatedQuery {
updatedQuery: Query;
}
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/getQueryKey.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/getQueryKey.ts
deleted file mode 100644
index 88b6eeb27a..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/getQueryKey.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { EQueryType } from 'types/common/dashboard';
-
-import { EQueryTypeToQueryKeyMapping } from '../types';
-
-export const getQueryKey = (
- queryCategory: EQueryType,
-): EQueryTypeToQueryKeyMapping =>
- EQueryTypeToQueryKeyMapping[
- EQueryType[queryCategory] as keyof typeof EQueryTypeToQueryKeyMapping
- ];
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/userSettings.ts b/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/userSettings.ts
deleted file mode 100644
index f05671556b..0000000000
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/utils/userSettings.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import getLocalStorageApi from 'api/browser/localstorage/get';
-import setLocalStorageApi from 'api/browser/localstorage/set';
-
-const UNSTAGE_CONFIRM_BOX_SHOW_COUNT = 2;
-const UNSTAGE_CONFIRM_BOX_KEY =
- 'DASHBOARD_METRICS_BUILDER_UNSTAGE_STASH_CONFIRM_SHOW_COUNT';
-
-export const showUnstagedStashConfirmBox = (): boolean => {
- const showCountTillNow: number = parseInt(
- getLocalStorageApi(UNSTAGE_CONFIRM_BOX_KEY) || '',
- 10,
- );
- if (Number.isNaN(showCountTillNow)) {
- setLocalStorageApi(UNSTAGE_CONFIRM_BOX_KEY, '1');
- return true;
- }
-
- if (showCountTillNow >= UNSTAGE_CONFIRM_BOX_SHOW_COUNT) {
- return false;
- }
- setLocalStorageApi(UNSTAGE_CONFIRM_BOX_KEY, `${showCountTillNow + 1}`);
- return true;
-};
diff --git a/frontend/src/container/NewWidget/LeftContainer/QueryTypeTag.tsx b/frontend/src/container/NewWidget/LeftContainer/QueryTypeTag.tsx
index 32745ee95e..d119bb7c27 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QueryTypeTag.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QueryTypeTag.tsx
@@ -1,11 +1,7 @@
-import React from 'react';
import { EQueryType } from 'types/common/dashboard';
import { Tag } from '../styles';
-interface IQueryTypeTagProps {
- queryType: EQueryType | undefined;
-}
function QueryTypeTag({ queryType }: IQueryTypeTagProps): JSX.Element {
switch (queryType) {
case EQueryType.QUERY_BUILDER:
@@ -32,4 +28,12 @@ function QueryTypeTag({ queryType }: IQueryTypeTagProps): JSX.Element {
}
}
+interface IQueryTypeTagProps {
+ queryType?: EQueryType;
+}
+
+QueryTypeTag.defaultProps = {
+ queryType: EQueryType.QUERY_BUILDER,
+};
+
export default QueryTypeTag;
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
index 5103b262a3..6fc58de892 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { EQueryType } from 'types/common/dashboard';
import QueryTypeTag from '../QueryTypeTag';
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx
index 8c06836b2d..3f9903264a 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx
@@ -2,7 +2,6 @@ import { Card, Typography } from 'antd';
import GridGraphComponent from 'container/GridGraphComponent';
import { NewWidgetProps } from 'container/NewWidget';
import getChartData from 'lib/getChartData';
-import React from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
index 342599c49a..506916855d 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
@@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import { Card } from 'container/GridGraphLayout/styles';
-import React, { memo } from 'react';
+import { memo } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/NewWidget/LeftContainer/index.tsx b/frontend/src/container/NewWidget/LeftContainer/index.tsx
index 9e02adcd68..1a0fc25516 100644
--- a/frontend/src/container/NewWidget/LeftContainer/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/index.tsx
@@ -1,34 +1,22 @@
-import React, { memo } from 'react';
+import { memo } from 'react';
import { NewWidgetProps } from '../index';
-import { timePreferance } from '../RightContainer/timeItems';
import QuerySection from './QuerySection';
import { QueryContainer } from './styles';
import WidgetGraph from './WidgetGraph';
function LeftContainer({
selectedGraph,
- selectedTime,
yAxisUnit,
- handleUnstagedChanges,
-}: LeftContainerProps): JSX.Element {
+}: NewWidgetProps): JSX.Element {
return (
<>
-
+
>
);
}
-interface LeftContainerProps extends NewWidgetProps {
- selectedTime: timePreferance;
- handleUnstagedChanges: (arg0: boolean) => void;
-}
-
export default memo(LeftContainer);
diff --git a/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx b/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx
index c93c2e8e20..b1cf81de7a 100644
--- a/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx
+++ b/frontend/src/container/NewWidget/RightContainer/YAxisUnitSelector.tsx
@@ -1,6 +1,6 @@
import { AutoComplete, Col, Input, Typography } from 'antd';
import { find } from 'lodash-es';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { flattenedCategories } from './dataFormatCategories';
@@ -19,7 +19,7 @@ function YAxisUnitSelector({
fieldLabel,
}: {
defaultValue: string;
- onSelect: React.Dispatch>;
+ onSelect: Dispatch>;
fieldLabel: string;
}): JSX.Element {
const onSelectHandler = (selectedValue: string): void => {
diff --git a/frontend/src/container/NewWidget/RightContainer/index.tsx b/frontend/src/container/NewWidget/RightContainer/index.tsx
index a666a90753..0f9ab1c597 100644
--- a/frontend/src/container/NewWidget/RightContainer/index.tsx
+++ b/frontend/src/container/NewWidget/RightContainer/index.tsx
@@ -5,7 +5,7 @@ import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import GraphTypes, {
ITEMS,
} from 'container/NewDashboard/ComponentsSlider/menuItems';
-import React, { useCallback } from 'react';
+import { Dispatch, SetStateAction, useCallback } from 'react';
import { Container, Title } from './styles';
import { timePreferance } from './timeItems';
@@ -27,7 +27,7 @@ function RightContainer({
setGraphHandler,
}: RightContainerProps): JSX.Element {
const onChangeHandler = useCallback(
- (setFunc: React.Dispatch>, value: string) => {
+ (setFunc: Dispatch>, value: string) => {
setFunc(value);
},
[],
@@ -135,20 +135,20 @@ function RightContainer({
interface RightContainerProps {
title: string;
- setTitle: React.Dispatch>;
+ setTitle: Dispatch>;
description: string;
- setDescription: React.Dispatch>;
+ setDescription: Dispatch>;
stacked: boolean;
- setStacked: React.Dispatch>;
+ setStacked: Dispatch>;
opacity: string;
- setOpacity: React.Dispatch>;
+ setOpacity: Dispatch>;
selectedNullZeroValue: string;
- setSelectedNullZeroValue: React.Dispatch>;
+ setSelectedNullZeroValue: Dispatch>;
selectedGraph: GRAPH_TYPES;
- setSelectedTime: React.Dispatch>;
+ setSelectedTime: Dispatch>;
selectedTime: timePreferance;
yAxisUnit: string;
- setYAxisUnit: React.Dispatch>;
+ setYAxisUnit: Dispatch>;
setGraphHandler: (type: ITEMS) => void;
}
diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx
index c9ccd7c1f9..a429a8eaa1 100644
--- a/frontend/src/container/NewWidget/index.tsx
+++ b/frontend/src/container/NewWidget/index.tsx
@@ -1,11 +1,15 @@
-import { Button, Modal, Typography } from 'antd';
+import { LockFilled } from '@ant-design/icons';
+import { Button, Modal, Tooltip, Typography } from 'antd';
+import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
+import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
+import { useNotifications } from 'hooks/useNotifications';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import history from 'lib/history';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
@@ -22,6 +26,7 @@ import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { FLUSH_DASHBOARD } from 'types/actions/dashboard';
import { Widgets } from 'types/api/dashboard/getAll';
+import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -35,7 +40,6 @@ import {
LeftContainerWrapper,
PanelContainer,
RightContainerWrapper,
- Tag,
} from './styles';
function NewWidget({
@@ -52,6 +56,10 @@ function NewWidget({
GlobalReducer
>((state) => state.globalTime);
+ const { featureResponse } = useSelector(
+ (state) => state.app,
+ );
+
const [selectedDashboard] = dashboards;
const { widgets } = selectedDashboard.data;
@@ -85,7 +93,6 @@ function NewWidget({
selectedWidget?.nullZeroValues || 'zero',
);
const [saveModal, setSaveModal] = useState(false);
- const [hasUnstagedChanges, setHasUnstagedChanges] = useState(false);
const [graphType, setGraphType] = useState(selectedGraph);
const getSelectedTime = useCallback(
@@ -101,22 +108,34 @@ function NewWidget({
enum: selectedWidget?.timePreferance || 'GLOBAL_TIME',
});
+ const { notifications } = useNotifications();
+
const onClickSaveHandler = useCallback(() => {
// update the global state
- saveSettingOfPanel({
- uuid: selectedDashboard.uuid,
- description,
- isStacked: stacked,
- nullZeroValues: selectedNullZeroValue,
- opacity,
- timePreferance: selectedTime.enum,
- title,
- yAxisUnit,
- widgetId: query.get('widgetId') || '',
- dashboardId,
- graphType,
- });
+ featureResponse
+ .refetch()
+ .then(() => {
+ saveSettingOfPanel({
+ uuid: selectedDashboard.uuid,
+ description,
+ isStacked: stacked,
+ nullZeroValues: selectedNullZeroValue,
+ opacity,
+ timePreferance: selectedTime.enum,
+ title,
+ yAxisUnit,
+ widgetId: query.get('widgetId') || '',
+ dashboardId,
+ graphType,
+ });
+ })
+ .catch(() => {
+ notifications.error({
+ message: 'Something went wrong',
+ });
+ });
}, [
+ featureResponse,
saveSettingOfPanel,
selectedDashboard.uuid,
description,
@@ -129,6 +148,7 @@ function NewWidget({
query,
dashboardId,
graphType,
+ notifications,
]);
const onClickDiscardHandler = useCallback(() => {
@@ -169,24 +189,45 @@ function NewWidget({
getQueryResult();
}, [getQueryResult]);
+ const onSaveDashboard = useCallback((): void => {
+ setSaveModal(true);
+ }, []);
+
+ const isQueryBuilderActive = useIsFeatureDisabled(
+ FeatureKeys.QUERY_BUILDER_PANELS,
+ );
+
return (
- setSaveModal(true)}>
- Save
-
- {/* Apply */}
+ {isQueryBuilderActive && (
+
+ }
+ type="primary"
+ disabled={isQueryBuilderActive}
+ onClick={onSaveDashboard}
+ >
+ Save
+
+
+ )}
+
+ {!isQueryBuilderActive && (
+
+ Save
+
+ )}
Discard
-
+
@@ -219,26 +260,16 @@ function NewWidget({
destroyOnClose
closable
onCancel={(): void => setSaveModal(false)}
- onOk={(): void => {
- onClickSaveHandler();
- }}
+ onOk={onClickSaveHandler}
centered
open={saveModal}
width={600}
>
- {hasUnstagedChanges ? (
-
- Looks like you have unstaged changes. Would you like to SAVE the last
- staged changes? If you want to stage new changes - Press{' '}
- Stage & Run Query and then try saving again.
-
- ) : (
-
- Your graph built with{' '}
- query will be
- saved. Press OK to confirm.
-
- )}
+
+ Your graph built with{' '}
+ query will be
+ saved. Press OK to confirm.
+
);
diff --git a/frontend/src/container/NewWidget/styles.ts b/frontend/src/container/NewWidget/styles.ts
index 386f044a0b..c9941ad07c 100644
--- a/frontend/src/container/NewWidget/styles.ts
+++ b/frontend/src/container/NewWidget/styles.ts
@@ -11,12 +11,14 @@ export const Container = styled.div`
export const RightContainerWrapper = styled(Col)`
&&& {
min-width: 200px;
+ margin-bottom: 1rem;
}
`;
export const LeftContainerWrapper = styled(Col)`
&&& {
margin-right: 1rem;
+ margin-bottom: 1rem;
max-width: 70%;
}
`;
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/AddDomain/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/AddDomain/index.tsx
index fa317c8a51..0c23824aed 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/AddDomain/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/AddDomain/index.tsx
@@ -3,10 +3,10 @@ import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Typography } from 'antd';
import { useForm } from 'antd/es/form/Form';
import createDomainApi from 'api/SAML/postDomain';
-import { FeatureKeys } from 'constants/featureKeys';
-import useFeatureFlag from 'hooks/useFeatureFlag';
+import { FeatureKeys } from 'constants/features';
+import useFeatureFlag from 'hooks/useFeatureFlag/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -18,7 +18,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
const { t } = useTranslation(['common', 'organizationsettings']);
const [isAddDomains, setIsDomain] = useState(false);
const [form] = useForm();
- const SSOFlag = useFeatureFlag(FeatureKeys.SSO);
+ const isSsoFlagEnabled = useFeatureFlag(FeatureKeys.SSO);
const { org } = useSelector((state) => state.app);
@@ -58,7 +58,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
ns: 'organizationsettings',
})}
- {SSOFlag && (
+ {isSsoFlagEnabled && (
setIsDomain(true)}
type="primary"
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Create/Row/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Create/Row/index.tsx
index 96c5d0a2ef..25f939e1c0 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Create/Row/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Create/Row/index.tsx
@@ -1,5 +1,5 @@
import { Button, Space, Typography } from 'antd';
-import React from 'react';
+import { ReactNode } from 'react';
import { IconContainer, TitleContainer, TitleText } from './styles';
@@ -29,7 +29,7 @@ function Row({
export interface RowProps {
onClickHandler: VoidFunction;
- Icon: React.ReactNode;
+ Icon: ReactNode;
title: string;
subTitle: string;
buttonText: string;
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Create/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Create/index.tsx
index 0d16a4f19e..7ca16570de 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Create/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Create/index.tsx
@@ -1,6 +1,6 @@
import { GoogleSquareFilled, KeyOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
-import React, { useCallback, useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
import { AuthDomain, GOOGLE_AUTH, SAML } from 'types/api/SAML/listDomain';
import Row, { RowProps } from './Row';
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditGoogleAuth.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditGoogleAuth.tsx
index 82b4cd7e8e..2939b8d272 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditGoogleAuth.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditGoogleAuth.tsx
@@ -1,6 +1,5 @@
import { InfoCircleFilled } from '@ant-design/icons';
import { Card, Form, Input, Space, Typography } from 'antd';
-import React from 'react';
function EditGoogleAuth(): JSX.Element {
return (
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditSAML.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditSAML.tsx
index 3ff035f411..b445e5cae9 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditSAML.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/EditSAML.tsx
@@ -1,6 +1,5 @@
import { InfoCircleFilled } from '@ant-design/icons';
import { Card, Form, Input, Space, Typography } from 'antd';
-import React from 'react';
function EditSAML(): JSX.Element {
return (
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx
index b93fecd114..dc47de5faa 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Edit/index.tsx
@@ -1,7 +1,7 @@
import { Button, Form, Space } from 'antd';
import { useForm } from 'antd/lib/form/Form';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback } from 'react';
+import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AuthDomain, GOOGLE_AUTH, SAML } from 'types/api/SAML/listDomain';
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/Switch/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/Switch/index.tsx
index 9643effafb..ca4b3a539e 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/Switch/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/Switch/index.tsx
@@ -1,5 +1,5 @@
import { Switch } from 'antd';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { AuthDomain } from 'types/api/SAML/listDomain';
import { isSSOConfigValid } from '../helpers';
diff --git a/frontend/src/container/OrganizationSettings/AuthDomains/index.tsx b/frontend/src/container/OrganizationSettings/AuthDomains/index.tsx
index adcd23aef8..44a30ede05 100644
--- a/frontend/src/container/OrganizationSettings/AuthDomains/index.tsx
+++ b/frontend/src/container/OrganizationSettings/AuthDomains/index.tsx
@@ -7,10 +7,10 @@ import updateDomain from 'api/SAML/updateDomain';
import { ResizeTable } from 'components/ResizeTable';
import TextToolTip from 'components/TextToolTip';
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
-import { FeatureKeys } from 'constants/featureKeys';
-import useFeatureFlag from 'hooks/useFeatureFlag';
+import { FeatureKeys } from 'constants/features';
+import useFeatureFlag from 'hooks/useFeatureFlag/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useState } from 'react';
+import { Dispatch, SetStateAction, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
@@ -68,7 +68,7 @@ function AuthDomains(): JSX.Element {
);
const onCloseHandler = useCallback(
- (func: React.Dispatch>) => (): void => {
+ (func: Dispatch>) => (): void => {
func(false);
},
[],
@@ -111,7 +111,7 @@ function AuthDomains(): JSX.Element {
);
const onOpenHandler = useCallback(
- (func: React.Dispatch>) => (): void => {
+ (func: Dispatch>) => (): void => {
func(true);
},
[],
diff --git a/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx b/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx
index db51e685b0..47bab40f39 100644
--- a/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx
+++ b/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx
@@ -1,7 +1,6 @@
import { gold } from '@ant-design/colors';
import { ExclamationCircleTwoTone } from '@ant-design/icons';
import { Space, Typography } from 'antd';
-import React from 'react';
function DeleteMembersDetails({
name,
diff --git a/frontend/src/container/OrganizationSettings/DisplayName/index.tsx b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx
index e7b7cc6959..8bd8e2f891 100644
--- a/frontend/src/container/OrganizationSettings/DisplayName/index.tsx
+++ b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx
@@ -1,7 +1,7 @@
import { Button, Form, Input } from 'antd';
import editOrg from 'api/user/editOrg';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
diff --git a/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx
index a6e9254771..7069b66db3 100644
--- a/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx
+++ b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx
@@ -3,7 +3,14 @@ import { Button, Input, Select, Space, Tooltip } from 'antd';
import getResetPasswordToken from 'api/user/getResetPasswordToken';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useEffect, useState } from 'react';
+import {
+ ChangeEventHandler,
+ Dispatch,
+ SetStateAction,
+ useCallback,
+ useEffect,
+ useState,
+} from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import { ROLES } from 'types/roles';
@@ -31,7 +38,7 @@ function EditMembersDetails({
`${window.location.origin}${ROUTES.PASSWORD_RESET}?token=${token}`;
const onChangeHandler = useCallback(
- (setFunc: React.Dispatch>, value: string) => {
+ (setFunc: Dispatch>, value: string) => {
setFunc(value);
},
[],
@@ -53,7 +60,7 @@ function EditMembersDetails({
}
}, [state.error, state.value, t, notifications]);
- const onPasswordChangeHandler: React.ChangeEventHandler = useCallback(
+ const onPasswordChangeHandler: ChangeEventHandler = useCallback(
(event) => {
setPasswordLink(event.target.value);
},
@@ -163,9 +170,9 @@ interface EditMembersDetailsProps {
emailAddress: string;
name: string;
role: ROLES;
- setEmailAddress: React.Dispatch>;
- setName: React.Dispatch>;
- setRole: React.Dispatch>;
+ setEmailAddress: Dispatch>;
+ setName: Dispatch>;
+ setRole: Dispatch>;
id: string;
}
diff --git a/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx
index 42d22e479a..d059c2683c 100644
--- a/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx
+++ b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx
@@ -1,6 +1,6 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Select, Space, Typography } from 'antd';
-import React, { useCallback, useEffect } from 'react';
+import { Dispatch, SetStateAction, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { InviteTeamMembersProps } from '../PendingInvitesContainer/index';
@@ -101,7 +101,7 @@ function InviteTeamMembers({ allMembers, setAllMembers }: Props): JSX.Element {
interface Props {
allMembers: InviteTeamMembersProps[];
- setAllMembers: React.Dispatch>;
+ setAllMembers: Dispatch>;
}
export default InviteTeamMembers;
diff --git a/frontend/src/container/OrganizationSettings/Members/index.tsx b/frontend/src/container/OrganizationSettings/Members/index.tsx
index ccb1c367e7..eccda855a0 100644
--- a/frontend/src/container/OrganizationSettings/Members/index.tsx
+++ b/frontend/src/container/OrganizationSettings/Members/index.tsx
@@ -7,7 +7,7 @@ import updateRole from 'api/user/updateRole';
import { ResizeTable } from 'components/ResizeTable';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useEffect, useState } from 'react';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
@@ -29,7 +29,7 @@ function UserFunction({
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const onModalToggleHandler = (
- func: React.Dispatch>,
+ func: Dispatch>,
value: boolean,
): void => {
func(value);
@@ -236,7 +236,7 @@ function Members(): JSX.Element {
getOrgUser({
orgId: (org || [])[0].id,
}),
- queryKey: 'getOrgUser',
+ queryKey: ['getOrgUser', org?.[0].id],
});
const [dataSource, setDataSource] = useState([]);
@@ -329,7 +329,7 @@ interface DataType {
}
interface UserFunctionProps extends DataType {
- setDataSource: React.Dispatch>;
+ setDataSource: Dispatch>;
}
export default Members;
diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx
index 5c1518eee2..ecb374fcd0 100644
--- a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx
+++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx
@@ -8,12 +8,15 @@ import { ResizeTable } from 'components/ResizeTable';
import { INVITE_MEMBERS_HASH } from 'constants/app';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
+import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
+import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/user/getPendingInvites';
+import AppReducer from 'types/reducer/app';
import { ROLES } from 'types/roles';
import InviteTeamMembers from '../InviteTeamMembers';
@@ -28,6 +31,7 @@ function PendingInvitesContainer(): JSX.Element {
const { t } = useTranslation(['organizationsettings', 'common']);
const [state, setText] = useCopyToClipboard();
const { notifications } = useNotifications();
+ const { user } = useSelector((state) => state.app);
useEffect(() => {
if (state.error) {
@@ -46,8 +50,8 @@ function PendingInvitesContainer(): JSX.Element {
}, [state.error, state.value, t, notifications]);
const getPendingInvitesResponse = useQuery({
- queryFn: () => getPendingInvites(),
- queryKey: 'getPendingInvites',
+ queryFn: getPendingInvites,
+ queryKey: ['getPendingInvites', user?.accessJwt],
});
const toggleModal = (value: boolean): void => {
diff --git a/frontend/src/container/OrganizationSettings/index.tsx b/frontend/src/container/OrganizationSettings/index.tsx
index fc2b2434b8..e36cf2ae60 100644
--- a/frontend/src/container/OrganizationSettings/index.tsx
+++ b/frontend/src/container/OrganizationSettings/index.tsx
@@ -1,7 +1,6 @@
import { Divider, Space } from 'antd';
-import { FeatureKeys } from 'constants/featureKeys';
-import useFeatureFlag from 'hooks/useFeatureFlag';
-import React from 'react';
+import { FeatureKeys } from 'constants/features';
+import { useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
@@ -14,8 +13,11 @@ import PendingInvitesContainer from './PendingInvitesContainer';
function OrganizationSettings(): JSX.Element {
const { org } = useSelector((state) => state.app);
- const sso = useFeatureFlag(FeatureKeys.SSO);
- const noUpsell = useFeatureFlag(FeatureKeys.DISABLE_UPSELL);
+ const isNotSSO = useIsFeatureDisabled(FeatureKeys.SSO);
+
+ const isNoUpSell = useIsFeatureDisabled(FeatureKeys.DISABLE_UPSELL);
+
+ const isAuthDomain = !isNoUpSell || (isNoUpSell && !isNotSSO);
if (!org) {
return
;
@@ -38,7 +40,7 @@ function OrganizationSettings(): JSX.Element {
- {(!noUpsell || (noUpsell && sso)) && }
+ {isAuthDomain && }
>
);
}
diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts
index 3c0ccef6e8..41ea0b5b3f 100644
--- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts
+++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts
@@ -1,3 +1,4 @@
+import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { DataSource } from 'types/common/queryBuilder';
export type QueryBuilderConfig =
@@ -9,4 +10,5 @@ export type QueryBuilderConfig =
export type QueryBuilderProps = {
config?: QueryBuilderConfig;
+ panelType: ITEMS;
};
diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.tsx b/frontend/src/container/QueryBuilder/QueryBuilder.tsx
index e8384eb2d9..d5015cce0d 100644
--- a/frontend/src/container/QueryBuilder/QueryBuilder.tsx
+++ b/frontend/src/container/QueryBuilder/QueryBuilder.tsx
@@ -1,35 +1,108 @@
+import { PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Row } from 'antd';
+import { MAX_FORMULAS, MAX_QUERIES } from 'constants/queryBuilder';
// ** Hooks
-import { useQueryBuilder } from 'hooks/useQueryBuilder';
-import React from 'react';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+// ** Constants
+import { memo, useEffect, useMemo } from 'react';
// ** Components
-import { Query } from './components';
+import { Formula, Query } from './components';
// ** Types
import { QueryBuilderProps } from './QueryBuilder.interfaces';
+// ** Styles
-// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element {
- const { queryBuilderData } = useQueryBuilder();
+export const QueryBuilder = memo(function QueryBuilder({
+ config,
+ panelType,
+}: QueryBuilderProps): JSX.Element {
+ const {
+ queryBuilderData,
+ setupInitialDataSource,
+ resetQueryBuilderInfo,
+ addNewQuery,
+ addNewFormula,
+ handleSetPanelType,
+ } = useQueryBuilder();
- // Here we can use Form from antd library and fill context data or edit
- // Connect form with adding or removing items from the list
+ useEffect(() => {
+ if (config && config.queryVariant === 'static') {
+ setupInitialDataSource(config.initialDataSource);
+ }
+ }, [config, setupInitialDataSource]);
- // Here will be map of query queryBuilderData.queryData and queryBuilderData.queryFormulas components
- // Each component can be part of antd Form list where we can add or remove items
- // Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context
- // With button to add him
- return (
-
- {queryBuilderData.queryData.map((query, index) => (
- 1}
- queryVariant={config?.queryVariant || 'dropdown'}
- query={query}
- />
- ))}
-
+ useEffect(() => {
+ handleSetPanelType(panelType);
+ }, [handleSetPanelType, panelType]);
+
+ useEffect(
+ () => (): void => {
+ resetQueryBuilderInfo();
+ },
+ [resetQueryBuilderInfo],
);
-}
+
+ const isDisabledQueryButton = useMemo(
+ () => queryBuilderData.queryData.length >= MAX_QUERIES,
+ [queryBuilderData],
+ );
+
+ const isDisabledFormulaButton = useMemo(
+ () => queryBuilderData.queryFormulas.length >= MAX_FORMULAS,
+ [queryBuilderData],
+ );
+
+ const isAvailableToDisableQuery = useMemo(
+ () =>
+ queryBuilderData.queryData.length > 1 ||
+ queryBuilderData.queryFormulas.length > 0,
+ [queryBuilderData],
+ );
+
+ return (
+
+
+
+ {queryBuilderData.queryData.map((query, index) => (
+
+
+
+ ))}
+ {queryBuilderData.queryFormulas.map((formula, index) => (
+
+
+
+ ))}
+
+
+
+
+
+ }
+ onClick={addNewQuery}
+ >
+ Query
+
+
+
+ }
+ >
+ Formula
+
+
+
+
+ );
+});
diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts
new file mode 100644
index 0000000000..2bb8a0e1fa
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.interfaces.ts
@@ -0,0 +1,6 @@
+import { ReactNode } from 'react';
+
+export type AdditionalFiltersProps = {
+ listOfAdditionalFilter: string[];
+ children: ReactNode;
+};
diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts
new file mode 100644
index 0000000000..8f23dd0e3c
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.styled.ts
@@ -0,0 +1,34 @@
+import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
+import { Col, Typography } from 'antd';
+import styled, { css } from 'styled-components';
+
+const IconCss = css`
+ margin-right: 0.6875rem;
+ transition: all 0.2s ease;
+`;
+
+export const StyledIconOpen = styled(PlusSquareOutlined)`
+ ${IconCss}
+`;
+
+export const StyledIconClose = styled(MinusSquareOutlined)`
+ ${IconCss}
+`;
+
+export const StyledInner = styled(Col)`
+ width: fit-content;
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.875rem;
+ min-height: 1.375rem;
+ cursor: pointer;
+ &:hover {
+ ${StyledIconOpen}, ${StyledIconClose} {
+ opacity: 0.7;
+ }
+ }
+`;
+
+export const StyledLink = styled(Typography.Link)`
+ pointer-events: none;
+`;
diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx
new file mode 100644
index 0000000000..79aa1b0715
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/AdditionalFiltersToggler.tsx
@@ -0,0 +1,55 @@
+import { Col, Row } from 'antd';
+import { Fragment, memo, ReactNode, useState } from 'react';
+
+// ** Types
+import { AdditionalFiltersProps } from './AdditionalFiltersToggler.interfaces';
+// ** Styles
+import {
+ StyledIconClose,
+ StyledIconOpen,
+ StyledInner,
+ StyledLink,
+} from './AdditionalFiltersToggler.styled';
+
+export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({
+ children,
+ listOfAdditionalFilter,
+}: AdditionalFiltersProps): JSX.Element {
+ const [isOpenedFilters, setIsOpenedFilters] = useState(false);
+
+ const handleToggleOpenFilters = (): void => {
+ setIsOpenedFilters((prevState) => !prevState);
+ };
+
+ const filtersTexts: ReactNode = listOfAdditionalFilter.map((str, index) => {
+ const isNextLast = index + 1 === listOfAdditionalFilter.length - 1;
+
+ if (index === listOfAdditionalFilter.length - 1) {
+ return (
+
+ {listOfAdditionalFilter.length > 1 && 'and'}{' '}
+ {str.toUpperCase()}
+
+ );
+ }
+
+ return (
+
+ {str.toUpperCase()}
+ {isNextLast ? ' ' : ', '}
+
+ );
+ });
+
+ return (
+
+
+
+ {isOpenedFilters ? : }
+ {!isOpenedFilters && Add conditions for {filtersTexts} }
+
+
+ {isOpenedFilters && {children}}
+
+ );
+});
diff --git a/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts
new file mode 100644
index 0000000000..d08059abdf
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/AdditionalFiltersToggler/index.ts
@@ -0,0 +1 @@
+export { AdditionalFiltersToggler } from './AdditionalFiltersToggler';
diff --git a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx
index 4efe754f1a..52b3d030cb 100644
--- a/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx
+++ b/frontend/src/container/QueryBuilder/components/DataSourceDropdown/DataSourceDropdown.tsx
@@ -1,5 +1,5 @@
import { Select } from 'antd';
-import React from 'react';
+import { memo } from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
// ** Helpers
@@ -10,7 +10,9 @@ import { QueryLabelProps } from './DataSourceDropdown.interfaces';
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
-export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
+export const DataSourceDropdown = memo(function DataSourceDropdown(
+ props: QueryLabelProps,
+): JSX.Element {
const { onChange, value, style } = props;
const dataSourceOptions: SelectOption<
@@ -30,4 +32,4 @@ export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
style={style}
/>
);
-}
+});
diff --git a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts
index 8eb97a32f3..bcf0d2d04c 100644
--- a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts
+++ b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.styled.ts
@@ -1,13 +1,19 @@
+import { themeColors } from 'constants/theme';
import styled from 'styled-components';
-export const StyledLabel = styled.div`
+interface Props {
+ isDarkMode: boolean;
+}
+export const StyledLabel = styled.div`
padding: 0 0.6875rem;
- min-width: 6.5rem;
- width: fit-content;
min-height: 2rem;
+ min-width: 5.625rem;
display: inline-flex;
+ white-space: nowrap;
align-items: center;
border-radius: 0.125rem;
- border: 0.0625rem solid #434343;
- background-color: #141414;
+ border: ${({ isDarkMode }): string =>
+ `1px solid ${
+ isDarkMode ? themeColors.borderDarkGrey : themeColors.borderLightGrey
+ }`};
`;
diff --git a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx
index aa9eb85bf1..8fd4421f2a 100644
--- a/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx
+++ b/frontend/src/container/QueryBuilder/components/FilterLabel/FilterLabel.tsx
@@ -1,10 +1,15 @@
-import React from 'react';
+import { useIsDarkMode } from 'hooks/useDarkMode';
+import { memo } from 'react';
// ** Types
import { FilterLabelProps } from './FilterLabel.interfaces';
// ** Styles
import { StyledLabel } from './FilterLabel.styled';
-export function FilterLabel({ label }: FilterLabelProps): JSX.Element {
- return {label} ;
-}
+export const FilterLabel = memo(function FilterLabel({
+ label,
+}: FilterLabelProps): JSX.Element {
+ const isDarkMode = useIsDarkMode();
+
+ return {label} ;
+});
diff --git a/frontend/src/container/QueryBuilder/components/Formula/Formula.interfaces.ts b/frontend/src/container/QueryBuilder/components/Formula/Formula.interfaces.ts
index 3f6d7e4978..aa0cb1c475 100644
--- a/frontend/src/container/QueryBuilder/components/Formula/Formula.interfaces.ts
+++ b/frontend/src/container/QueryBuilder/components/Formula/Formula.interfaces.ts
@@ -1,2 +1,3 @@
-// TODO: temporary type
-export type FormulaProps = { test: string };
+import { IBuilderFormula } from 'types/api/queryBuilder/queryBuilderData';
+
+export type FormulaProps = { formula: IBuilderFormula; index: number };
diff --git a/frontend/src/container/QueryBuilder/components/Formula/Formula.tsx b/frontend/src/container/QueryBuilder/components/Formula/Formula.tsx
index 175dc34076..bee7c21bc1 100644
--- a/frontend/src/container/QueryBuilder/components/Formula/Formula.tsx
+++ b/frontend/src/container/QueryBuilder/components/Formula/Formula.tsx
@@ -1,5 +1,73 @@
-import React from 'react';
+import { Col, Input } from 'antd';
+// ** Components
+import { ListItemWrapper, ListMarker } from 'container/QueryBuilder/components';
+// ** Hooks
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { ChangeEvent, useCallback } from 'react';
+import { IBuilderFormula } from 'types/api/queryBuilder/queryBuilderData';
-export function Formula(): JSX.Element {
- return null
;
+// ** Types
+import { FormulaProps } from './Formula.interfaces';
+
+const { TextArea } = Input;
+
+export function Formula({ index, formula }: FormulaProps): JSX.Element {
+ const { removeEntityByIndex, handleSetFormulaData } = useQueryBuilder();
+
+ const handleDelete = useCallback(() => {
+ removeEntityByIndex('queryFormulas', index);
+ }, [index, removeEntityByIndex]);
+
+ const handleToggleDisableFormula = useCallback((): void => {
+ const newFormula: IBuilderFormula = {
+ ...formula,
+ disabled: !formula.disabled,
+ };
+
+ handleSetFormulaData(index, newFormula);
+ }, [index, formula, handleSetFormulaData]);
+
+ const handleChange = useCallback(
+ (e: ChangeEvent) => {
+ const { name, value } = e.target;
+ const newFormula: IBuilderFormula = {
+ ...formula,
+ [name]: value,
+ };
+
+ handleSetFormulaData(index, newFormula);
+ },
+ [index, formula, handleSetFormulaData],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.interfaces.ts b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.interfaces.ts
new file mode 100644
index 0000000000..60c516be52
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.interfaces.ts
@@ -0,0 +1,12 @@
+import { ReactNode } from 'react';
+
+export type HavingFilterTagProps = {
+ label: ReactNode;
+ value: string;
+ disabled: boolean;
+ onClose: VoidFunction;
+ closable: boolean;
+ onUpdate: (value: string) => void;
+};
+
+export type HavingTagRenderProps = Omit;
diff --git a/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.styled.ts b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.styled.ts
new file mode 100644
index 0000000000..e733133cc7
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.styled.ts
@@ -0,0 +1,13 @@
+import { Tag, Typography } from 'antd';
+import styled from 'styled-components';
+
+export const StyledText = styled(Typography.Text)`
+ cursor: pointer;
+`;
+
+export const StyledTag = styled(Tag)`
+ margin-top: 0.125rem;
+ margin-bottom: 0.125rem;
+ padding-left: 0.5rem;
+ display: flex;
+`;
diff --git a/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.tsx b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.tsx
new file mode 100644
index 0000000000..1b8ceeaaf0
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.tsx
@@ -0,0 +1,21 @@
+import { HavingFilterTagProps } from './HavingFilterTag.interfaces';
+import { StyledTag, StyledText } from './HavingFilterTag.styled';
+
+export function HavingFilterTag({
+ value,
+ closable,
+ onClose,
+ onUpdate,
+}: HavingFilterTagProps): JSX.Element {
+ const handleClick = (): void => {
+ onUpdate(value);
+ };
+
+ return (
+
+
+ {value}
+
+
+ );
+}
diff --git a/frontend/src/container/QueryBuilder/components/HavingFilterTag/index.ts b/frontend/src/container/QueryBuilder/components/HavingFilterTag/index.ts
new file mode 100644
index 0000000000..f6f74956a7
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/HavingFilterTag/index.ts
@@ -0,0 +1 @@
+export { HavingFilterTag } from './HavingFilterTag';
diff --git a/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.interfaces.ts b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.interfaces.ts
new file mode 100644
index 0000000000..c5ab00faf5
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.interfaces.ts
@@ -0,0 +1,6 @@
+import { ReactNode } from 'react';
+
+export type ListItemWrapperProps = {
+ onDelete: () => void;
+ children: ReactNode;
+};
diff --git a/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.styled.ts b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.styled.ts
new file mode 100644
index 0000000000..cbe1b79f9c
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.styled.ts
@@ -0,0 +1,26 @@
+import { CloseCircleOutlined } from '@ant-design/icons';
+import { Row } from 'antd';
+import styled from 'styled-components';
+
+export const StyledDeleteEntity = styled(CloseCircleOutlined)`
+ position: absolute;
+ top: 0.5rem;
+ right: 0.9375rem;
+ z-index: 1;
+ cursor: pointer;
+ opacity: 0.45;
+ width: 1.3125rem;
+ height: 1.3125rem;
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+`;
+
+export const StyledRow = styled(Row)`
+ padding-right: 3rem;
+`;
+
+export const StyledFilterRow = styled(Row)`
+ margin-bottom: 0.875rem;
+`;
diff --git a/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.tsx b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.tsx
new file mode 100644
index 0000000000..7abc9e39b5
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/ListItemWrapper/ListItemWrapper.tsx
@@ -0,0 +1,16 @@
+// ** Types
+import { ListItemWrapperProps } from './ListItemWrapper.interfaces';
+// ** Styles
+import { StyledDeleteEntity, StyledRow } from './ListItemWrapper.styled';
+
+export function ListItemWrapper({
+ children,
+ onDelete,
+}: ListItemWrapperProps): JSX.Element {
+ return (
+
+
+ {children}
+
+ );
+}
diff --git a/frontend/src/container/QueryBuilder/components/ListItemWrapper/index.ts b/frontend/src/container/QueryBuilder/components/ListItemWrapper/index.ts
new file mode 100644
index 0000000000..c8d9f7e5da
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/components/ListItemWrapper/index.ts
@@ -0,0 +1 @@
+export { ListItemWrapper } from './ListItemWrapper';
diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.interfaces.ts b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.interfaces.ts
index c2ab29c2b8..bb360ed69f 100644
--- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.interfaces.ts
+++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.interfaces.ts
@@ -5,7 +5,7 @@ export type ListMarkerProps = {
labelName: string;
index: number;
className?: string;
- isAvailableToDisable: boolean;
- toggleDisabled: (index: number) => void;
+ isAvailableToDisable?: boolean;
+ onDisable: (index: number) => void;
style?: CSSProperties;
};
diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts
index f876af973b..76a70c25c7 100644
--- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts
+++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.styled.ts
@@ -1,9 +1,12 @@
import { Button } from 'antd';
import styled from 'styled-components';
-export const StyledButton = styled(Button)`
+export const StyledButton = styled(Button)<{ $isAvailableToDisable: boolean }>`
min-width: 2rem;
height: 2.25rem;
- padding: 0.125rem;
+ padding: ${(props): string =>
+ props.$isAvailableToDisable ? '0.43rem' : '0.43rem 0.68rem'};
border-radius: 0.375rem;
+ pointer-events: ${(props): string =>
+ props.$isAvailableToDisable ? 'default' : 'none'};
`;
diff --git a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx
index 3af9d3aa37..0e197e3b3a 100644
--- a/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx
+++ b/frontend/src/container/QueryBuilder/components/ListMarker/ListMarker.tsx
@@ -1,26 +1,26 @@
import { EyeFilled, EyeInvisibleFilled } from '@ant-design/icons';
import { ButtonProps } from 'antd';
-import React from 'react';
+import { memo } from 'react';
// ** Types
import { ListMarkerProps } from './ListMarker.interfaces';
// ** Styles
import { StyledButton } from './ListMarker.styled';
-export function ListMarker({
+export const ListMarker = memo(function ListMarker({
isDisabled,
labelName,
index,
- isAvailableToDisable,
+ isAvailableToDisable = true,
className,
- toggleDisabled,
+ onDisable,
style,
}: ListMarkerProps): JSX.Element {
const buttonProps: Partial = isAvailableToDisable
? {
type: isDisabled ? 'default' : 'primary',
icon: isDisabled ? : ,
- onClick: (): void => toggleDisabled(index),
+ onClick: (): void => onDisable(index),
}
: { type: 'primary' };
@@ -30,9 +30,10 @@ export function ListMarker({
icon={buttonProps.icon}
onClick={buttonProps.onClick}
className={className}
- style={{ marginRight: '0.1rem', ...style }}
+ $isAvailableToDisable={isAvailableToDisable}
+ style={style}
>
{labelName}
);
-}
+});
diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts
index baf1bbac98..414e61678b 100644
--- a/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts
+++ b/frontend/src/container/QueryBuilder/components/Query/Query.interfaces.ts
@@ -1,8 +1,8 @@
-import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export type QueryProps = {
index: number;
isAvailableToDisable: boolean;
- query: IBuilderQueryForm;
+ query: IBuilderQuery;
queryVariant: 'static' | 'dropdown';
};
diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx
index 81427bc260..5db70136cc 100644
--- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx
+++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx
@@ -1,95 +1,325 @@
-/* eslint-disable react/jsx-props-no-spreading */
-import { Col, Row } from 'antd';
+import { Col, Input, Row } from 'antd';
+// ** Constants
+import { PANEL_TYPES } from 'constants/queryBuilder';
// ** Components
import {
+ AdditionalFiltersToggler,
DataSourceDropdown,
FilterLabel,
+ ListItemWrapper,
ListMarker,
} from 'container/QueryBuilder/components';
import {
AggregatorFilter,
+ GroupByFilter,
+ HavingFilter,
OperatorsSelect,
+ OrderByFilter,
+ ReduceToFilter,
} from 'container/QueryBuilder/filters';
-// Context
-import { useQueryBuilder } from 'hooks/useQueryBuilder';
+import AggregateEveryFilter from 'container/QueryBuilder/filters/AggregateEveryFilter';
+import LimitFilter from 'container/QueryBuilder/filters/LimitFilter/LimitFilter';
+import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { useQueryOperations } from 'hooks/queryBuilder/useQueryOperations';
// ** Hooks
-import React from 'react';
-import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
-import { DataSource } from 'types/common/queryBuilder';
-// ** Constants
-import {
- LogsAggregatorOperator,
- MetricAggregateOperator,
- TracesAggregatorOperator,
-} from 'types/common/queryBuilder';
+import { ChangeEvent, memo, ReactNode, useCallback } from 'react';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { transformToUpperCase } from 'utils/transformToUpperCase';
// ** Types
import { QueryProps } from './Query.interfaces';
-const mapOfOperators: Record = {
- metrics: Object.values(MetricAggregateOperator),
- logs: Object.values(LogsAggregatorOperator),
- traces: Object.values(TracesAggregatorOperator),
-};
-
-export function Query({
+export const Query = memo(function Query({
index,
isAvailableToDisable,
queryVariant,
query,
}: QueryProps): JSX.Element {
- const { handleSetQueryData } = useQueryBuilder();
+ const { panelType } = useQueryBuilder();
+ const {
+ operators,
+ isMetricsDataSource,
+ listOfAdditionalFilters,
+ handleChangeAggregatorAttribute,
+ handleChangeDataSource,
+ handleChangeQueryData,
+ handleChangeOperator,
+ handleDeleteQuery,
+ } = useQueryOperations({ index, query });
- const currentListOfOperators = mapOfOperators[query.dataSource];
+ const handleChangeAggregateEvery = useCallback(
+ (value: IBuilderQuery['stepInterval']) => {
+ handleChangeQueryData('stepInterval', value);
+ },
+ [handleChangeQueryData],
+ );
- const handleChangeOperator = (value: string): void => {
- handleSetQueryData(index, { aggregateOperator: value });
- };
+ const handleChangeLimit = useCallback(
+ (value: IBuilderQuery['limit']) => {
+ handleChangeQueryData('limit', value);
+ },
+ [handleChangeQueryData],
+ );
- const handleChangeDataSource = (nextSource: DataSource): void => {
- handleSetQueryData(index, { dataSource: nextSource });
- };
+ const handleChangeHavingFilter = useCallback(
+ (value: IBuilderQuery['having']) => {
+ handleChangeQueryData('having', value);
+ },
+ [handleChangeQueryData],
+ );
- const handleToggleDisableQuery = (): void => {
- handleSetQueryData(index, { disabled: !query.disabled });
- };
+ const handleChangeOrderByKeys = useCallback(
+ (value: IBuilderQuery['orderBy']) => {
+ handleChangeQueryData('orderBy', value);
+ },
+ [handleChangeQueryData],
+ );
- const handleChangeAggregatorAttribute = (value: AutocompleteData): void => {
- handleSetQueryData(index, { aggregateAttribute: value });
- };
+ const handleToggleDisableQuery = useCallback(() => {
+ handleChangeQueryData('disabled', !query.disabled);
+ }, [handleChangeQueryData, query]);
+
+ const handleChangeTagFilters = useCallback(
+ (value: IBuilderQuery['filters']) => {
+ handleChangeQueryData('filters', value);
+ },
+ [handleChangeQueryData],
+ );
+
+ const handleChangeReduceTo = useCallback(
+ (value: IBuilderQuery['reduceTo']) => {
+ handleChangeQueryData('reduceTo', value);
+ },
+ [handleChangeQueryData],
+ );
+
+ const handleChangeGroupByKeys = useCallback(
+ (value: IBuilderQuery['groupBy']) => {
+ handleChangeQueryData('groupBy', value);
+ },
+ [handleChangeQueryData],
+ );
+
+ const handleChangeQueryLegend = useCallback(
+ (event: ChangeEvent) => {
+ handleChangeQueryData('legend', event.target.value);
+ },
+ [handleChangeQueryData],
+ );
+
+ const renderAdditionalFilters = useCallback((): ReactNode => {
+ switch (panelType) {
+ case PANEL_TYPES.TIME_SERIES: {
+ return (
+ <>
+ {!isMetricsDataSource && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {!isMetricsDataSource && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ case PANEL_TYPES.VALUE: {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ default: {
+ return null;
+ }
+ }
+ }, [
+ panelType,
+ query,
+ isMetricsDataSource,
+ handleChangeAggregateEvery,
+ handleChangeHavingFilter,
+ handleChangeLimit,
+ handleChangeOrderByKeys,
+ ]);
return (
-
-
-
- {queryVariant === 'dropdown' ? (
-
- ) : (
-
- )}
+
+
+
+
+
+
+
+ {queryVariant === 'dropdown' ? (
+
+ ) : (
+
+ )}
+
+ {isMetricsDataSource && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {isMetricsDataSource && (
+
+
+
+ )}
+
+
+
+
+
+
+
+ {!isMetricsDataSource && (
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ {panelType === PANEL_TYPES.VALUE ? (
+
+ ) : (
+
+ )}
+
+
-
-
+
+
+ {renderAdditionalFilters()}
+
+
-
+
+
+
+
);
-}
+});
diff --git a/frontend/src/container/QueryBuilder/components/index.ts b/frontend/src/container/QueryBuilder/components/index.ts
index f21c33d5c9..7585633dce 100644
--- a/frontend/src/container/QueryBuilder/components/index.ts
+++ b/frontend/src/container/QueryBuilder/components/index.ts
@@ -1,5 +1,8 @@
+export { AdditionalFiltersToggler } from './AdditionalFiltersToggler';
export { DataSourceDropdown } from './DataSourceDropdown';
export { FilterLabel } from './FilterLabel';
export { Formula } from './Formula';
+export { HavingFilterTag } from './HavingFilterTag';
+export { ListItemWrapper } from './ListItemWrapper';
export { ListMarker } from './ListMarker';
export { Query } from './Query';
diff --git a/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx b/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx
new file mode 100644
index 0000000000..c787039dc7
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/AggregateEveryFilter/index.tsx
@@ -0,0 +1,68 @@
+import { Input } from 'antd';
+import getStep from 'lib/getStep';
+import { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+import { GlobalReducer } from 'types/reducer/globalTime';
+
+import { selectStyle } from '../QueryBuilderSearch/config';
+
+function AggregateEveryFilter({
+ onChange,
+ query,
+}: AggregateEveryFilterProps): JSX.Element {
+ const { maxTime, minTime } = useSelector(
+ (state) => state.globalTime,
+ );
+
+ const stepInterval = useMemo(
+ () =>
+ getStep({
+ start: minTime,
+ end: maxTime,
+ inputFormat: 'ns',
+ }),
+ [maxTime, minTime],
+ );
+
+ const handleKeyDown = (event: {
+ keyCode: number;
+ which: number;
+ preventDefault: () => void;
+ }): void => {
+ const keyCode = event.keyCode || event.which;
+ const isBackspace = keyCode === 8;
+ const isNumeric =
+ (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105);
+
+ if (!isNumeric && !isBackspace) {
+ event.preventDefault();
+ }
+ };
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ return (
+ onChange(Number(event.target.value))}
+ onKeyDown={handleKeyDown}
+ />
+ );
+}
+
+interface AggregateEveryFilterProps {
+ onChange: (values: number) => void;
+ query: IBuilderQuery;
+}
+
+export default AggregateEveryFilter;
diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts
index 81ba34a2c2..cff5c295df 100644
--- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts
+++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.intefaces.ts
@@ -1,7 +1,7 @@
-import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
-import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export type AgregatorFilterProps = {
- onChange: (value: AutocompleteData) => void;
- query: IBuilderQueryForm;
+ onChange: (value: BaseAutocompleteData) => void;
+ query: IBuilderQuery;
};
diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
index 13a25216ce..63fbc6e4b5 100644
--- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
+++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
@@ -2,58 +2,84 @@
import { AutoComplete, Spin } from 'antd';
// ** Api
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
+import {
+ initialAggregateAttribute,
+ QueryBuilderKeys,
+ selectValueDivider,
+} from 'constants/queryBuilder';
+import useDebounce from 'hooks/useDebounce';
+import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue';
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
-import React, { useMemo, useState } from 'react';
+import { memo, useCallback, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
-import { SelectOption } from 'types/common/select';
+import { DataSource } from 'types/common/queryBuilder';
+import { ExtendedSelectOption } from 'types/common/select';
import { transformToUpperCase } from 'utils/transformToUpperCase';
+import { v4 as uuid } from 'uuid';
+import { selectStyle } from '../QueryBuilderSearch/config';
// ** Types
import { AgregatorFilterProps } from './AggregatorFilter.intefaces';
-export function AggregatorFilter({
+export const AggregatorFilter = memo(function AggregatorFilter({
onChange,
query,
}: AgregatorFilterProps): JSX.Element {
- const [searchText, setSearchText] = useState('');
-
+ const [optionsData, setOptionsData] = useState([]);
+ const debouncedValue = useDebounce(query.aggregateAttribute.key, 300);
const { data, isFetching } = useQuery(
[
- 'GET_AGGREGATE_ATTRIBUTE',
- searchText,
+ QueryBuilderKeys.GET_AGGREGATE_ATTRIBUTE,
+ debouncedValue,
query.aggregateOperator,
query.dataSource,
],
async () =>
getAggregateAttribute({
+ searchText: debouncedValue,
aggregateOperator: query.aggregateOperator,
dataSource: query.dataSource,
- searchText,
}),
- { enabled: !!query.aggregateOperator && !!query.dataSource },
+ {
+ enabled: !!query.aggregateOperator && !!query.dataSource,
+ onSuccess: (data) => {
+ const options: ExtendedSelectOption[] =
+ data?.payload?.attributeKeys?.map((item) => ({
+ label: transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ }),
+ value: `${transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ })}${selectValueDivider}${item.id || uuid()}`,
+ key: item.id || uuid(),
+ })) || [];
+
+ setOptionsData(options);
+ },
+ },
);
- const handleSearchAttribute = (searchText: string): void => {
- setSearchText(searchText);
- };
+ const handleChangeAttribute = useCallback(
+ (
+ value: string,
+ option: ExtendedSelectOption | ExtendedSelectOption[],
+ ): void => {
+ const currentOption = option as ExtendedSelectOption;
- const optionsData: SelectOption[] =
- data?.payload?.attributeKeys?.map((item) => ({
- label: transformStringWithPrefix({
- str: item.key,
- prefix: item.type || '',
- condition: !item.isColumn,
- }),
- value: item.key,
- })) || [];
+ const { key } = getFilterObjectValue(value);
- const handleChangeAttribute = (value: string): void => {
- const currentAttributeObj = data?.payload?.attributeKeys?.find(
- (item) => item.key === value,
- ) || { key: value, type: null, dataType: null, isColumn: null };
+ const currentAttributeObj = data?.payload?.attributeKeys?.find(
+ (item) => currentOption.key === item.id,
+ ) || { ...initialAggregateAttribute, key };
- onChange(currentAttributeObj);
- };
+ onChange(currentAttributeObj);
+ },
+ [data, onChange],
+ );
const value = useMemo(
() =>
@@ -65,20 +91,21 @@ export function AggregatorFilter({
[query],
);
+ const placeholder: string =
+ query.dataSource === DataSource.METRICS
+ ? `${transformToUpperCase(query.dataSource)} name`
+ : 'Aggregate attribute';
+
return (
: null}
options={optionsData}
value={value}
onChange={handleChangeAttribute}
/>
);
-}
+});
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.interfaces.ts b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.interfaces.ts
new file mode 100644
index 0000000000..cea028d3ee
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.interfaces.ts
@@ -0,0 +1,8 @@
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+
+export type GroupByFilterProps = {
+ query: IBuilderQuery;
+ onChange: (values: BaseAutocompleteData[]) => void;
+ disabled: boolean;
+};
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
new file mode 100644
index 0000000000..50fe67a29b
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
@@ -0,0 +1,157 @@
+import { Select, Spin } from 'antd';
+import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
+// ** Constants
+import { QueryBuilderKeys, selectValueDivider } from 'constants/queryBuilder';
+import useDebounce from 'hooks/useDebounce';
+import { getFilterObjectValue } from 'lib/newQueryBuilder/getFilterObjectValue';
+// ** Components
+// ** Helpers
+import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
+import { memo, useEffect, useState } from 'react';
+import { useQuery } from 'react-query';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { SelectOption } from 'types/common/select';
+import { v4 as uuid } from 'uuid';
+
+import { selectStyle } from '../QueryBuilderSearch/config';
+import { GroupByFilterProps } from './GroupByFilter.interfaces';
+
+export const GroupByFilter = memo(function GroupByFilter({
+ query,
+ onChange,
+ disabled,
+}: GroupByFilterProps): JSX.Element {
+ const [searchText, setSearchText] = useState('');
+ const [optionsData, setOptionsData] = useState[]>(
+ [],
+ );
+ const [localValues, setLocalValues] = useState[]>(
+ [],
+ );
+ const [isFocused, setIsFocused] = useState(false);
+
+ const debouncedValue = useDebounce(searchText, 300);
+
+ const { data, isFetching } = useQuery(
+ [QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedValue, isFocused],
+ async () =>
+ getAggregateKeys({
+ aggregateAttribute: query.aggregateAttribute.key,
+ dataSource: query.dataSource,
+ aggregateOperator: query.aggregateOperator,
+ searchText: debouncedValue,
+ }),
+ {
+ enabled: !disabled && isFocused,
+ onSuccess: (data) => {
+ const keys = query.groupBy.reduce((acc, item) => {
+ acc.push(item.key);
+ return acc;
+ }, []);
+
+ const filteredOptions: BaseAutocompleteData[] =
+ data?.payload?.attributeKeys?.filter(
+ (attrKey) => !keys.includes(attrKey.key),
+ ) || [];
+
+ const options: SelectOption[] =
+ filteredOptions.map((item) => ({
+ label: transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ }),
+ value: `${transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ })}${selectValueDivider}${item.id || uuid()}`,
+ })) || [];
+
+ setOptionsData(options);
+ },
+ },
+ );
+
+ const handleSearchKeys = (searchText: string): void => {
+ setSearchText(searchText);
+ };
+
+ const handleBlur = (): void => {
+ setIsFocused(false);
+ setSearchText('');
+ };
+
+ const handleFocus = (): void => {
+ setIsFocused(true);
+ };
+
+ const handleChange = (values: SelectOption[]): void => {
+ const responseKeys = data?.payload?.attributeKeys || [];
+
+ const groupByValues: BaseAutocompleteData[] = values.map((item) => {
+ const [currentValue, id] = item.value.split(selectValueDivider);
+ const { key, type, isColumn } = getFilterObjectValue(currentValue);
+
+ const existGroupQuery = query.groupBy.find((group) => group.id === id);
+
+ if (existGroupQuery) {
+ return existGroupQuery;
+ }
+
+ const existGroupResponse = responseKeys.find((group) => group.id === id);
+
+ if (existGroupResponse) {
+ return existGroupResponse;
+ }
+
+ return {
+ id: uuid(),
+ isColumn,
+ key,
+ dataType: null,
+ type,
+ };
+ });
+
+ onChange(groupByValues);
+ };
+
+ useEffect(() => {
+ const currentValues: SelectOption[] = query.groupBy.map(
+ (item) => ({
+ label: `${transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ })}`,
+ value: `${transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ })}${selectValueDivider}${item.id || uuid()}`,
+ }),
+ );
+
+ setLocalValues(currentValues);
+ }, [query]);
+
+ return (
+ : null}
+ onChange={handleChange}
+ />
+ );
+});
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/index.ts b/frontend/src/container/QueryBuilder/filters/GroupByFilter/index.ts
new file mode 100644
index 0000000000..4377822177
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/index.ts
@@ -0,0 +1 @@
+export { GroupByFilter } from './GroupByFilter';
diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.interfaces.ts b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.interfaces.ts
new file mode 100644
index 0000000000..d4c9ebbf88
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.interfaces.ts
@@ -0,0 +1,6 @@
+import { Having, IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+
+export type HavingFilterProps = {
+ query: IBuilderQuery;
+ onChange: (having: Having[]) => void;
+};
diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx
new file mode 100644
index 0000000000..7fc298aeb0
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/HavingFilter.tsx
@@ -0,0 +1,249 @@
+import { Select } from 'antd';
+// ** Constants
+import { HAVING_OPERATORS, initialHavingValues } from 'constants/queryBuilder';
+import { HAVING_FILTER_REGEXP } from 'constants/regExp';
+import { HavingFilterTag } from 'container/QueryBuilder/components';
+import { HavingTagRenderProps } from 'container/QueryBuilder/components/HavingFilterTag/HavingFilterTag.interfaces';
+// ** Hooks
+import { useTagValidation } from 'hooks/queryBuilder/useTagValidation';
+import {
+ transformFromStringToHaving,
+ transformHavingToStringValue,
+} from 'lib/query/transformQueryBuilderData';
+// ** Helpers
+import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { Having, HavingForm } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+
+// ** Types
+import { HavingFilterProps } from './HavingFilter.interfaces';
+
+const { Option } = Select;
+
+export function HavingFilter({
+ query,
+ onChange,
+}: HavingFilterProps): JSX.Element {
+ const { having } = query;
+ const [searchText, setSearchText] = useState('');
+ const [options, setOptions] = useState[]>([]);
+ const [localValues, setLocalValues] = useState([]);
+ const [currentFormValue, setCurrentFormValue] = useState(
+ initialHavingValues,
+ );
+
+ const { isMulti } = useTagValidation(
+ currentFormValue.op,
+ currentFormValue.value,
+ );
+
+ const aggregatorAttribute = useMemo(
+ () =>
+ transformStringWithPrefix({
+ str: query.aggregateAttribute.key,
+ prefix: query.aggregateAttribute.type || '',
+ condition: !query.aggregateAttribute.isColumn,
+ }),
+ [query],
+ );
+
+ const columnName = useMemo(
+ () => `${query.aggregateOperator.toUpperCase()}(${aggregatorAttribute})`,
+ [query, aggregatorAttribute],
+ );
+
+ const aggregatorOptions: SelectOption[] = useMemo(
+ () => [{ label: columnName, value: columnName }],
+ [columnName],
+ );
+
+ const getHavingObject = useCallback((currentSearch: string): HavingForm => {
+ const textArr = currentSearch.split(' ');
+ const [columnName = '', op = '', ...value] = textArr;
+
+ return { columnName, op, value };
+ }, []);
+
+ const generateOptions = useCallback(
+ (search: string): void => {
+ const [aggregator = '', op = '', ...restValue] = search.split(' ');
+ let newOptions: SelectOption[] = [];
+
+ const isAggregatorExist = columnName
+ .toLowerCase()
+ .includes(search.toLowerCase());
+
+ const isAggregatorChosen = aggregator === columnName;
+
+ if (isAggregatorExist || aggregator === '') {
+ newOptions = aggregatorOptions;
+ }
+
+ if ((isAggregatorChosen && op === '') || op) {
+ const filteredOperators = HAVING_OPERATORS.filter((num) =>
+ num.toLowerCase().includes(op.toLowerCase()),
+ );
+
+ newOptions = filteredOperators.map((opt) => ({
+ label: `${columnName} ${opt} ${restValue && restValue.join(' ')}`,
+ value: `${columnName} ${opt} ${restValue && restValue.join(' ')}`,
+ }));
+ }
+
+ setOptions(newOptions);
+ },
+ [columnName, aggregatorOptions],
+ );
+
+ const isValidHavingValue = useCallback(
+ (search: string): boolean => {
+ const values = getHavingObject(search).value.join(' ');
+
+ if (values) {
+ return HAVING_FILTER_REGEXP.test(values);
+ }
+
+ return true;
+ },
+ [getHavingObject],
+ );
+
+ const handleSearch = useCallback(
+ (search: string): void => {
+ const trimmedSearch = search.replace(/\s\s+/g, ' ').trimStart();
+
+ const currentSearch = isMulti
+ ? trimmedSearch
+ : trimmedSearch.split(' ').slice(0, 3).join(' ');
+
+ const isValidSearch = isValidHavingValue(currentSearch);
+
+ if (isValidSearch) {
+ setSearchText(currentSearch);
+ }
+ },
+ [isMulti, isValidHavingValue],
+ );
+
+ const resetChanges = useCallback((): void => {
+ setSearchText('');
+ setCurrentFormValue(initialHavingValues);
+ setOptions(aggregatorOptions);
+ }, [aggregatorOptions]);
+
+ const handleChange = useCallback(
+ (values: string[]): void => {
+ const having: Having[] = values.map(transformFromStringToHaving);
+
+ const isSelectable =
+ currentFormValue.value.length > 0 &&
+ currentFormValue.value.every((value) => !!value);
+
+ if (isSelectable) {
+ onChange(having);
+ resetChanges();
+ }
+ },
+ [currentFormValue, resetChanges, onChange],
+ );
+
+ const handleUpdateTag = useCallback(
+ (value: string) => {
+ const filteredValues = localValues.filter(
+ (currentValue) => currentValue !== value,
+ );
+ const having: Having[] = filteredValues.map(transformFromStringToHaving);
+
+ onChange(having);
+ setSearchText(value);
+ },
+ [localValues, onChange],
+ );
+
+ const tagRender = useCallback(
+ ({ label, value, closable, disabled, onClose }: HavingTagRenderProps) => {
+ const handleClose = (): void => {
+ onClose();
+ setSearchText('');
+ };
+ return (
+
+ );
+ },
+ [handleUpdateTag],
+ );
+
+ const handleSelect = (currentValue: string): void => {
+ const { columnName, op, value } = getHavingObject(currentValue);
+
+ const isCompletedValue = value.every((item) => !!item);
+
+ const isClearSearch = isCompletedValue && columnName && op;
+
+ setSearchText(isClearSearch ? '' : currentValue);
+ };
+
+ const parseSearchText = useCallback(
+ (text: string) => {
+ const { columnName, op, value } = getHavingObject(text);
+ setCurrentFormValue({ columnName, op, value });
+
+ generateOptions(text);
+ },
+ [generateOptions, getHavingObject],
+ );
+
+ const handleDeselect = (value: string): void => {
+ const result = localValues.filter((item) => item !== value);
+ const having: Having[] = result.map(transformFromStringToHaving);
+ onChange(having);
+ resetChanges();
+ };
+
+ useEffect(() => {
+ parseSearchText(searchText);
+ }, [searchText, parseSearchText]);
+
+ useEffect(() => {
+ setLocalValues(transformHavingToStringValue(having));
+ }, [having]);
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ return (
+
+ {options.map((opt) => (
+
+ {opt.label}
+
+ ))}
+
+ );
+}
diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/__tests__/utils.test.tsx b/frontend/src/container/QueryBuilder/filters/HavingFilter/__tests__/utils.test.tsx
new file mode 100644
index 0000000000..f22d88a66a
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/__tests__/utils.test.tsx
@@ -0,0 +1,165 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+// Constants
+import {
+ HAVING_OPERATORS,
+ initialQueryBuilderFormValues,
+} from 'constants/queryBuilder';
+import { transformFromStringToHaving } from 'lib/query/transformQueryBuilderData';
+// ** Types
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+
+// ** Components
+import { HavingFilter } from '../HavingFilter';
+
+const valueWithAttributeAndOperator: IBuilderQuery = {
+ ...initialQueryBuilderFormValues,
+ dataSource: DataSource.LOGS,
+ aggregateOperator: 'SUM',
+ aggregateAttribute: {
+ isColumn: false,
+ key: 'bytes',
+ type: 'tag',
+ dataType: 'float64',
+ },
+};
+
+describe('Having filter behaviour', () => {
+ test('Having filter render is rendered', () => {
+ const mockFn = jest.fn();
+ const { unmount } = render(
+ ,
+ );
+
+ const selectId = 'havingSelect';
+
+ const select = screen.getByTestId(selectId);
+
+ expect(select).toBeInTheDocument();
+
+ unmount();
+ });
+
+ test('Having render is disabled initially', () => {
+ const mockFn = jest.fn();
+ const { unmount } = render(
+ ,
+ );
+
+ const input = screen.getByRole('combobox');
+
+ expect(input).toBeDisabled();
+
+ unmount();
+ });
+
+ test('Is having filter is enable', () => {
+ const mockFn = jest.fn();
+ const { unmount } = render(
+ ,
+ );
+
+ const input = screen.getByRole('combobox');
+
+ expect(input).toBeEnabled();
+
+ unmount();
+ });
+
+ test('Autocomplete in the having filter', async () => {
+ const onChange = jest.fn();
+ const user = userEvent.setup();
+
+ const constructedAttribute = 'SUM(tag_bytes)';
+ const optionTestTitle = 'havingOption';
+
+ const { unmount } = render(
+ ,
+ );
+
+ // get input
+ const input = screen.getByRole('combobox');
+
+ // click on the select
+ await user.click(input);
+
+ // show predefined options for operator with attribute SUM(tag_bytes)
+ const option = screen.getByTitle(optionTestTitle);
+
+ expect(option).toBeInTheDocument();
+
+ await user.click(option);
+
+ // autocomplete input
+ expect(input).toHaveValue(constructedAttribute);
+
+ // clear value from input and write from keyboard
+ await user.clear(input);
+
+ await user.type(input, 'bytes');
+
+ // show again predefined options for operator with attribute SUM(tag_bytes)
+ const sameAttributeOption = screen.getByTitle(optionTestTitle);
+
+ expect(sameAttributeOption).toBeInTheDocument();
+
+ await user.click(sameAttributeOption);
+
+ expect(input).toHaveValue(constructedAttribute);
+
+ // show operators after SUM(tag_bytes)
+ const operatorsOptions = screen.getAllByTitle(optionTestTitle);
+
+ expect(operatorsOptions.length).toEqual(HAVING_OPERATORS.length);
+
+ // show operators after SUM(tag_bytes) when type from keyboard
+ await user.clear(input);
+
+ await user.type(input, `${constructedAttribute} !=`);
+
+ // get filtered operators
+ const filteredOperators = screen.getAllByTitle(optionTestTitle);
+
+ expect(filteredOperators.length).toEqual(1);
+
+ // clear and show again all operators
+ await user.clear(input);
+ await user.type(input, constructedAttribute);
+
+ const returnedOptions = screen.getAllByTitle(optionTestTitle);
+
+ expect(returnedOptions.length).toEqual(HAVING_OPERATORS.length);
+
+ // check write value after operator
+ await user.clear(input);
+ await user.type(input, `${constructedAttribute} != 123`);
+
+ expect(input).toHaveValue(`${constructedAttribute} != 123`);
+
+ const optionWithValue = screen.getByTitle(optionTestTitle);
+
+ // onChange after complete writting in the input or autocomplete
+ await user.click(optionWithValue);
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange).toHaveBeenCalledWith([
+ transformFromStringToHaving(`${constructedAttribute} != 123`),
+ ]);
+
+ // onChange with multiple operator
+ await user.type(input, `${constructedAttribute} IN 123 123`);
+
+ expect(input).toHaveValue(`${constructedAttribute} IN 123 123`);
+
+ const optionWithMultipleValue = screen.getByTitle(optionTestTitle);
+ await user.click(optionWithMultipleValue);
+
+ expect(onChange).toHaveBeenCalledTimes(2);
+ expect(onChange).toHaveBeenCalledWith([
+ transformFromStringToHaving(`${constructedAttribute} IN 123 123`),
+ ]);
+
+ unmount();
+ });
+});
diff --git a/frontend/src/container/QueryBuilder/filters/HavingFilter/index.ts b/frontend/src/container/QueryBuilder/filters/HavingFilter/index.ts
new file mode 100644
index 0000000000..5149764c01
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/HavingFilter/index.ts
@@ -0,0 +1 @@
+export { HavingFilter } from './HavingFilter';
diff --git a/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx b/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx
new file mode 100644
index 0000000000..a8db6b1f5d
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/LimitFilter/LimitFilter.tsx
@@ -0,0 +1,47 @@
+import { InputNumber } from 'antd';
+import { useMemo } from 'react';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+
+import { selectStyle } from '../QueryBuilderSearch/config';
+
+function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element {
+ const handleKeyDown = (event: {
+ keyCode: number;
+ which: number;
+ preventDefault: () => void;
+ }): void => {
+ const keyCode = event.keyCode || event.which;
+ const isBackspace = keyCode === 8;
+ const isNumeric =
+ (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105);
+
+ if (!isNumeric && !isBackspace) {
+ event.preventDefault();
+ }
+ };
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ return (
+
+ );
+}
+
+interface LimitFilterProps {
+ onChange: (values: number | null) => void;
+ query: IBuilderQuery;
+}
+
+export default LimitFilter;
diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts
index f47e813112..1128a4cad1 100644
--- a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts
+++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.interfaces.ts
@@ -1,7 +1,8 @@
import { SelectProps } from 'antd';
+import { SelectOption } from 'types/common/select';
export type OperatorsSelectProps = Omit & {
- operators: string[];
+ operators: SelectOption[];
onChange: (value: string) => void;
value: string;
};
diff --git a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx
index 979bac1dcc..b930ff8e1b 100644
--- a/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx
+++ b/frontend/src/container/QueryBuilder/filters/OperatorsSelect/OperatorsSelect.tsx
@@ -1,32 +1,25 @@
import { Select } from 'antd';
-import React from 'react';
-// ** Types
-import { SelectOption } from 'types/common/select';
-// ** Helpers
-import { transformToUpperCase } from 'utils/transformToUpperCase';
+import { memo } from 'react';
+// ** Types
+import { selectStyle } from '../QueryBuilderSearch/config';
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
-export function OperatorsSelect({
+export const OperatorsSelect = memo(function OperatorsSelect({
operators,
value,
onChange,
...props
}: OperatorsSelectProps): JSX.Element {
- const operatorsOptions: SelectOption[] = operators.map(
- (operator) => ({
- label: transformToUpperCase(operator),
- value: operator,
- }),
- );
-
return (
);
-}
+});
diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces.ts
new file mode 100644
index 0000000000..e817530405
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces.ts
@@ -0,0 +1,17 @@
+import {
+ IBuilderQuery,
+ OrderByPayload,
+} from 'types/api/queryBuilder/queryBuilderData';
+
+export type OrderByFilterProps = {
+ query: IBuilderQuery;
+ onChange: (values: OrderByPayload[]) => void;
+};
+
+export type OrderByFilterValue = {
+ disabled: boolean | undefined;
+ key: string;
+ label: string;
+ title: string | undefined;
+ value: string;
+};
diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx
new file mode 100644
index 0000000000..13a8baad33
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/OrderByFilter.tsx
@@ -0,0 +1,135 @@
+import { Select, Spin } from 'antd';
+import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
+import { QueryBuilderKeys } from 'constants/queryBuilder';
+import * as Papa from 'papaparse';
+import { useCallback, useMemo, useState } from 'react';
+import { useQuery } from 'react-query';
+import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
+
+import { selectStyle } from '../QueryBuilderSearch/config';
+import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils';
+import { OrderByFilterProps } from './OrderByFilter.interfaces';
+import {
+ checkIfKeyPresent,
+ getLabelFromValue,
+ mapLabelValuePairs,
+ orderByValueDelimiter,
+} from './utils';
+
+export function OrderByFilter({
+ query,
+ onChange,
+}: OrderByFilterProps): JSX.Element {
+ const [searchText, setSearchText] = useState('');
+ const [selectedValue, setSelectedValue] = useState([]);
+
+ const { data, isFetching } = useQuery(
+ [QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText],
+ async () =>
+ getAggregateKeys({
+ aggregateAttribute: query.aggregateAttribute.key,
+ dataSource: query.dataSource,
+ aggregateOperator: query.aggregateOperator,
+ searchText,
+ }),
+ { enabled: !!query.aggregateAttribute.key, keepPreviousData: true },
+ );
+
+ const handleSearchKeys = useCallback(
+ (searchText: string): void => setSearchText(searchText),
+ [],
+ );
+
+ const noAggregationOptions = useMemo(
+ () =>
+ data?.payload?.attributeKeys
+ ? mapLabelValuePairs(data?.payload?.attributeKeys).flat()
+ : [],
+ [data?.payload?.attributeKeys],
+ );
+
+ const aggregationOptions = useMemo(
+ () =>
+ mapLabelValuePairs(query.groupBy)
+ .flat()
+ .concat([
+ {
+ label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) asc`,
+ value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}asc`,
+ },
+ {
+ label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) desc`,
+ value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}desc`,
+ },
+ ]),
+ [query.aggregateAttribute.key, query.aggregateOperator, query.groupBy],
+ );
+
+ const optionsData = useMemo(() => {
+ const options =
+ query.aggregateOperator === MetricAggregateOperator.NOOP
+ ? noAggregationOptions
+ : aggregationOptions;
+ return options.filter(
+ (option) =>
+ !getLabelFromValue(selectedValue).includes(
+ getRemoveOrderFromValue(option.value),
+ ),
+ );
+ }, [
+ aggregationOptions,
+ noAggregationOptions,
+ query.aggregateOperator,
+ selectedValue,
+ ]);
+
+ const handleChange = (values: string[]): void => {
+ setSelectedValue(values);
+ const orderByValues: OrderByPayload[] = values.map((item) => {
+ const match = Papa.parse(item, { delimiter: '|' });
+ if (match) {
+ const [columnName, order] = match.data.flat() as string[];
+ return {
+ columnName: checkIfKeyPresent(columnName, query.aggregateAttribute.key)
+ ? '#SIGNOZ_VALUE'
+ : columnName,
+ order,
+ };
+ }
+
+ return {
+ columnName: item,
+ order: '',
+ };
+ });
+ onChange(orderByValues);
+ };
+
+ const isDisabledSelect = useMemo(
+ () =>
+ !query.aggregateAttribute.key ||
+ query.aggregateOperator === MetricAggregateOperator.NOOP,
+ [query.aggregateAttribute.key, query.aggregateOperator],
+ );
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ return (
+ : null}
+ onChange={handleChange}
+ />
+ );
+}
diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts
new file mode 100644
index 0000000000..ece36ae2f6
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/index.ts
@@ -0,0 +1 @@
+export { OrderByFilter } from './OrderByFilter';
diff --git a/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts
new file mode 100644
index 0000000000..9907e5b88f
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/OrderByFilter/utils.ts
@@ -0,0 +1,44 @@
+import { IOption } from 'hooks/useResourceAttribute/types';
+import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
+import * as Papa from 'papaparse';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+export const orderByValueDelimiter = '|';
+
+export function mapLabelValuePairs(
+ arr: BaseAutocompleteData[],
+): Array[] {
+ return arr.map((item) => {
+ const label = transformStringWithPrefix({
+ str: item.key,
+ prefix: item.type || '',
+ condition: !item.isColumn,
+ });
+ const value = item.key;
+ return [
+ {
+ label: `${label} asc`,
+ value: `${value}${orderByValueDelimiter}asc`,
+ },
+ {
+ label: `${label} desc`,
+ value: `${value}${orderByValueDelimiter}desc`,
+ },
+ ];
+ });
+}
+
+export function getLabelFromValue(arr: string[]): string[] {
+ return arr.flat().map((item) => {
+ const match = Papa.parse(item, { delimiter: orderByValueDelimiter });
+ if (match) {
+ const [key] = match.data as string[];
+ return key[0];
+ }
+ return item;
+ });
+}
+
+export function checkIfKeyPresent(str: string, valueToCheck: string): boolean {
+ return new RegExp(`\\(${valueToCheck}\\)`).test(str);
+}
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts
new file mode 100644
index 0000000000..a93171168f
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/config.ts
@@ -0,0 +1 @@
+export const selectStyle = { width: '100%', minWidth: '7.7rem' };
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx
new file mode 100644
index 0000000000..950114c30f
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx
@@ -0,0 +1,176 @@
+import { Select, Spin, Tag, Tooltip } from 'antd';
+import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete';
+import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues';
+import {
+ KeyboardEvent,
+ ReactElement,
+ ReactNode,
+ useEffect,
+ useMemo,
+} from 'react';
+import {
+ IBuilderQuery,
+ TagFilter,
+} from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+import { v4 as uuid } from 'uuid';
+
+import { selectStyle } from './config';
+import { StyledCheckOutlined, TypographyText } from './style';
+import {
+ getOperatorValue,
+ getRemovePrefixFromKey,
+ getTagToken,
+ isExistsNotExistsOperator,
+ isInNInOperator,
+} from './utils';
+
+function QueryBuilderSearch({
+ query,
+ onChange,
+}: QueryBuilderSearchProps): JSX.Element {
+ const {
+ updateTag,
+ handleClearTag,
+ handleKeyDown,
+ handleSearch,
+ handleSelect,
+ tags,
+ options,
+ searchValue,
+ isMulti,
+ isFetching,
+ setSearchKey,
+ searchKey,
+ } = useAutoComplete(query);
+
+ const { keys } = useFetchKeysAndValues(searchValue, query, searchKey);
+
+ const onTagRender = ({
+ value,
+ closable,
+ onClose,
+ }: CustomTagProps): ReactElement => {
+ const { tagOperator } = getTagToken(value);
+ const isInNin = isInNInOperator(tagOperator);
+ const chipValue = isInNin
+ ? value?.trim()?.replace(/,\s*$/, '')
+ : value?.trim();
+
+ const onCloseHandler = (): void => {
+ onClose();
+ handleSearch('');
+ setSearchKey('');
+ };
+
+ const tagEditHandler = (value: string): void => {
+ updateTag(value);
+ handleSearch(value);
+ };
+
+ return (
+
+
+ tagEditHandler(value)}
+ >
+ {chipValue}
+
+
+
+ );
+ };
+
+ const onChangeHandler = (value: string[]): void => {
+ if (!isMulti) handleSearch(value[value.length - 1]);
+ };
+
+ const onInputKeyDownHandler = (event: KeyboardEvent): void => {
+ if (isMulti || event.key === 'Backspace') handleKeyDown(event);
+ if (isExistsNotExistsOperator(searchValue)) handleKeyDown(event);
+ };
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ const queryTags = useMemo(() => {
+ if (!query.aggregateAttribute.key && isMetricsDataSource) return [];
+ return tags;
+ }, [isMetricsDataSource, query.aggregateAttribute.key, tags]);
+
+ useEffect(() => {
+ const initialTagFilters: TagFilter = { items: [], op: 'AND' };
+ initialTagFilters.items = tags.map((tag) => {
+ const { tagKey, tagOperator, tagValue } = getTagToken(tag);
+ const filterAttribute = (keys || []).find(
+ (key) => key.key === getRemovePrefixFromKey(tagKey),
+ );
+ return {
+ id: uuid().slice(0, 8),
+ key: filterAttribute ?? {
+ key: tagKey,
+ dataType: null,
+ type: null,
+ isColumn: null,
+ },
+ op: getOperatorValue(tagOperator),
+ value:
+ tagValue[tagValue.length - 1] === ''
+ ? tagValue?.slice(0, -1)
+ : tagValue ?? '',
+ };
+ });
+ onChange(initialTagFilters);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [keys, tags]);
+
+ return (
+ : null}
+ >
+ {options.map((option) => (
+
+ {option.label}
+ {option.selected && }
+
+ ))}
+
+ );
+}
+
+interface QueryBuilderSearchProps {
+ query: IBuilderQuery;
+ onChange: (value: TagFilter) => void;
+}
+
+export interface CustomTagProps {
+ label: ReactNode;
+ value: string;
+ disabled: boolean;
+ onClose: () => void;
+ closable: boolean;
+}
+
+export default QueryBuilderSearch;
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts
new file mode 100644
index 0000000000..07ccce0cb9
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/style.ts
@@ -0,0 +1,17 @@
+import { CheckOutlined } from '@ant-design/icons';
+import { Typography } from 'antd';
+import styled from 'styled-components';
+
+export const TypographyText = styled(Typography.Text)<{
+ $isInNin: boolean;
+ $isEnabled: boolean;
+}>`
+ width: ${({ $isInNin }): string => ($isInNin ? '10rem' : 'auto')};
+ cursor: ${({ $isEnabled }): string =>
+ $isEnabled ? 'not-allowed' : 'pointer'};
+ pointer-events: ${({ $isEnabled }): string => ($isEnabled ? 'none' : 'auto')};
+`;
+
+export const StyledCheckOutlined = styled(CheckOutlined)`
+ float: right;
+`;
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts
new file mode 100644
index 0000000000..9364d5b042
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/utils.ts
@@ -0,0 +1,120 @@
+import { OPERATORS } from 'constants/queryBuilder';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import * as Papa from 'papaparse';
+
+import { orderByValueDelimiter } from '../OrderByFilter/utils';
+
+export const tagRegexp = /([a-zA-Z0-9_.:@$()\-/\\]+)\s*(!=|=|<=|<|>=|>|IN|NOT_IN|LIKE|NOT_LIKE|EXISTS|NOT_EXISTS|CONTAINS|NOT_CONTAINS)\s*([\s\S]*)/g;
+
+export function isInNInOperator(value: string): boolean {
+ return value === OPERATORS.IN || value === OPERATORS.NIN;
+}
+
+interface ITagToken {
+ tagKey: string;
+ tagOperator: string;
+ tagValue: string[];
+}
+
+export function getTagToken(tag: string): ITagToken {
+ const matches = tag?.matchAll(tagRegexp);
+ const [match] = matches ? Array.from(matches) : [];
+
+ if (match) {
+ const [, matchTagKey, matchTagOperator, matchTagValue] = match;
+ return {
+ tagKey: matchTagKey,
+ tagOperator: matchTagOperator,
+ tagValue: isInNInOperator(matchTagOperator)
+ ? Papa.parse(matchTagValue).data.flat()
+ : matchTagValue,
+ } as ITagToken;
+ }
+
+ return {
+ tagKey: tag,
+ tagOperator: '',
+ tagValue: [],
+ };
+}
+
+export function isExistsNotExistsOperator(value: string): boolean {
+ const { tagOperator } = getTagToken(value);
+ return (
+ tagOperator?.trim() === OPERATORS.NOT_EXISTS ||
+ tagOperator?.trim() === OPERATORS.EXISTS
+ );
+}
+
+export function getRemovePrefixFromKey(tag: string): string {
+ return tag?.replace(/^(tag_|resource_)/, '').trim();
+}
+
+export function getOperatorValue(op: string): string {
+ switch (op) {
+ case 'IN':
+ return 'in';
+ case 'NOT_IN':
+ return 'nin';
+ case 'LIKE':
+ return 'like';
+ case 'NOT_LIKE':
+ return 'nlike';
+ case 'EXISTS':
+ return 'exists';
+ case 'NOT_EXISTS':
+ return 'nexists';
+ case 'CONTAINS':
+ return 'contains';
+ case 'NOT_CONTAINS':
+ return 'ncontains';
+ default:
+ return op;
+ }
+}
+
+export function getOperatorFromValue(op: string): string {
+ switch (op) {
+ case 'in':
+ return 'IN';
+ case 'nin':
+ return 'NOT_IN';
+ case 'like':
+ return 'LIKE';
+ case 'nlike':
+ return 'NOT_LIKE';
+ case 'exists':
+ return 'EXISTS';
+ case 'nexists':
+ return 'NOT_EXISTS';
+ case 'contains':
+ return 'CONTAINS';
+ case 'ncontains':
+ return 'NOT_CONTAINS';
+ default:
+ return op;
+ }
+}
+
+export function replaceStringWithMaxLength(
+ mainString: string,
+ array: string[],
+ replacementString: string,
+): string {
+ const lastSearchValue = array.pop() ?? ''; // Remove the last search value from the array
+ if (lastSearchValue === '') return `${mainString}${replacementString},`; // if user select direclty from options
+ return mainString.replace(lastSearchValue, `${replacementString},`);
+}
+
+export function checkCommaInValue(str: string): string {
+ return str.includes(',') ? `"${str}"` : str;
+}
+
+export function getRemoveOrderFromValue(tag: string): string {
+ const match = Papa.parse(tag, { delimiter: orderByValueDelimiter });
+ if (match) {
+ const [key] = match.data.flat() as string[];
+ return key;
+ }
+ return tag;
+}
diff --git a/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts
new file mode 100644
index 0000000000..25af1530d8
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.interfaces.ts
@@ -0,0 +1,8 @@
+import { SelectProps } from 'antd';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { ReduceOperators } from 'types/common/queryBuilder';
+
+export type ReduceToFilterProps = Omit & {
+ query: IBuilderQuery;
+ onChange: (value: ReduceOperators) => void;
+};
diff --git a/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx
new file mode 100644
index 0000000000..1bd5173f87
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/ReduceToFilter.tsx
@@ -0,0 +1,34 @@
+import { Select } from 'antd';
+import { REDUCE_TO_VALUES } from 'constants/queryBuilder';
+import { memo } from 'react';
+// ** Types
+import { ReduceOperators } from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+
+import { ReduceToFilterProps } from './ReduceToFilter.interfaces';
+
+export const ReduceToFilter = memo(function ReduceToFilter({
+ query,
+ onChange,
+}: ReduceToFilterProps): JSX.Element {
+ const currentValue =
+ REDUCE_TO_VALUES.find((option) => option.value === query.reduceTo) ||
+ REDUCE_TO_VALUES[0];
+
+ const handleChange = (
+ newValue: SelectOption,
+ ): void => {
+ onChange(newValue.value);
+ };
+
+ return (
+
+ );
+});
diff --git a/frontend/src/container/QueryBuilder/filters/ReduceToFilter/index.ts b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/index.ts
new file mode 100644
index 0000000000..6c7359fdd2
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/filters/ReduceToFilter/index.ts
@@ -0,0 +1 @@
+export { ReduceToFilter } from './ReduceToFilter';
diff --git a/frontend/src/container/QueryBuilder/filters/index.ts b/frontend/src/container/QueryBuilder/filters/index.ts
index 79fc32a7a4..d15a242fde 100644
--- a/frontend/src/container/QueryBuilder/filters/index.ts
+++ b/frontend/src/container/QueryBuilder/filters/index.ts
@@ -1,2 +1,6 @@
export { AggregatorFilter } from './AggregatorFilter';
+export { GroupByFilter } from './GroupByFilter';
+export { HavingFilter } from './HavingFilter';
export { OperatorsSelect } from './OperatorsSelect';
+export { OrderByFilter } from './OrderByFilter';
+export { ReduceToFilter } from './ReduceToFilter';
diff --git a/frontend/src/container/QueryBuilder/type.ts b/frontend/src/container/QueryBuilder/type.ts
new file mode 100644
index 0000000000..d7d746497e
--- /dev/null
+++ b/frontend/src/container/QueryBuilder/type.ts
@@ -0,0 +1,17 @@
+import { IQueryBuilderState } from 'constants/queryBuilder';
+
+export interface InitialStateI {
+ search: string;
+}
+
+export interface ContextValueI {
+ values: InitialStateI;
+ onChangeHandler: (type: IQueryBuilderState) => (value: string) => void;
+ onSubmitHandler: VoidFunction;
+}
+
+export type Option = {
+ value: string;
+ label: string;
+ selected?: boolean;
+};
diff --git a/frontend/src/container/ResetPassword/index.tsx b/frontend/src/container/ResetPassword/index.tsx
index 69e0b7b047..eac4b098cd 100644
--- a/frontend/src/container/ResetPassword/index.tsx
+++ b/frontend/src/container/ResetPassword/index.tsx
@@ -6,7 +6,7 @@ import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { Label } from 'pages/SignUp/styles';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-use';
diff --git a/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
index 3c208b621d..1be1dd3486 100644
--- a/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
+++ b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
@@ -4,7 +4,7 @@ import useResourceAttribute, {
isResourceEmpty,
} from 'hooks/useResourceAttribute';
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
-import React, { useMemo } from 'react';
+import { ReactNode, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import QueryChip from './components/QueryChip';
@@ -78,7 +78,7 @@ function ResourceAttributesFilter({
}
interface ResourceAttributesFilterProps {
- suffixIcon?: React.ReactNode;
+ suffixIcon?: ReactNode;
}
ResourceAttributesFilter.defaultProps = {
diff --git a/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx b/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
index fd912e3ade..363e6d5143 100644
--- a/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
+++ b/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
@@ -1,5 +1,4 @@
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
-import React from 'react';
import { QueryChipContainer, QueryChipItem } from '../../styles';
import { IQueryChipProps } from './types';
diff --git a/frontend/src/container/SideNav/Slack.tsx b/frontend/src/container/SideNav/Slack.tsx
index f4f1e8e5c6..118ac924c1 100644
--- a/frontend/src/container/SideNav/Slack.tsx
+++ b/frontend/src/container/SideNav/Slack.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
interface ISlackProps {
width?: number;
height?: number;
diff --git a/frontend/src/container/SideNav/config.ts b/frontend/src/container/SideNav/config.ts
index 87cd2cff60..f14da541c7 100644
--- a/frontend/src/container/SideNav/config.ts
+++ b/frontend/src/container/SideNav/config.ts
@@ -1 +1,38 @@
+import { QueryParams } from 'constants/query';
+import ROUTES from 'constants/routes';
+
export const styles = { background: '#1f1f1f' };
+
+export const routeConfig: Record = {
+ [ROUTES.SERVICE_METRICS]: [QueryParams.resourceAttributes],
+ [ROUTES.SERVICE_MAP]: [QueryParams.resourceAttributes],
+ [ROUTES.ALL_ERROR]: [QueryParams.resourceAttributes],
+ [ROUTES.ALERTS_NEW]: [QueryParams.resourceAttributes],
+ [ROUTES.ALL_CHANNELS]: [QueryParams.resourceAttributes],
+ [ROUTES.ALL_DASHBOARD]: [QueryParams.resourceAttributes],
+ [ROUTES.APPLICATION]: [QueryParams.resourceAttributes],
+ [ROUTES.CHANNELS_EDIT]: [QueryParams.resourceAttributes],
+ [ROUTES.CHANNELS_NEW]: [QueryParams.resourceAttributes],
+ [ROUTES.DASHBOARD]: [QueryParams.resourceAttributes],
+ [ROUTES.DASHBOARD_WIDGET]: [QueryParams.resourceAttributes],
+ [ROUTES.EDIT_ALERTS]: [QueryParams.resourceAttributes],
+ [ROUTES.ERROR_DETAIL]: [QueryParams.resourceAttributes],
+ [ROUTES.HOME_PAGE]: [QueryParams.resourceAttributes],
+ [ROUTES.INSTRUMENTATION]: [QueryParams.resourceAttributes],
+ [ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes],
+ [ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes],
+ [ROUTES.LOGIN]: [QueryParams.resourceAttributes],
+ [ROUTES.LOGS]: [QueryParams.resourceAttributes],
+ [ROUTES.MY_SETTINGS]: [QueryParams.resourceAttributes],
+ [ROUTES.NOT_FOUND]: [QueryParams.resourceAttributes],
+ [ROUTES.ORG_SETTINGS]: [QueryParams.resourceAttributes],
+ [ROUTES.PASSWORD_RESET]: [QueryParams.resourceAttributes],
+ [ROUTES.SETTINGS]: [QueryParams.resourceAttributes],
+ [ROUTES.SIGN_UP]: [QueryParams.resourceAttributes],
+ [ROUTES.SOMETHING_WENT_WRONG]: [QueryParams.resourceAttributes],
+ [ROUTES.TRACE]: [QueryParams.resourceAttributes],
+ [ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
+ [ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
+ [ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
+ [ROUTES.VERSION]: [QueryParams.resourceAttributes],
+};
diff --git a/frontend/src/container/SideNav/helper.test.ts b/frontend/src/container/SideNav/helper.test.ts
new file mode 100644
index 0000000000..ada551242e
--- /dev/null
+++ b/frontend/src/container/SideNav/helper.test.ts
@@ -0,0 +1,34 @@
+import { getQueryString } from './helper';
+
+describe('getQueryString', () => {
+ it('returns an array of query strings for given available parameters and URLSearchParams', () => {
+ const availableParams = ['param1', 'param2', 'param3'];
+ const params = new URLSearchParams(
+ 'param1=value1¶m2=value2¶m4=value4',
+ );
+
+ const result = getQueryString(availableParams, params);
+
+ expect(result).toEqual(['param1=value1', 'param2=value2', '']);
+ });
+
+ it('returns an array of empty strings if no matching parameters are found', () => {
+ const availableParams = ['param1', 'param2', 'param3'];
+ const params = new URLSearchParams('param4=value4¶m5=value5');
+
+ const result = getQueryString(availableParams, params);
+
+ expect(result).toEqual(['', '', '']);
+ });
+
+ it('returns an empty array if the available parameters list is empty', () => {
+ const availableParams: string[] = [];
+ const params = new URLSearchParams(
+ 'param1=value1¶m2=value2¶m3=value3',
+ );
+
+ const result = getQueryString(availableParams, params);
+
+ expect(result).toEqual([]);
+ });
+});
diff --git a/frontend/src/container/SideNav/helper.ts b/frontend/src/container/SideNav/helper.ts
new file mode 100644
index 0000000000..6160702336
--- /dev/null
+++ b/frontend/src/container/SideNav/helper.ts
@@ -0,0 +1,10 @@
+export const getQueryString = (
+ avialableParams: string[],
+ params: URLSearchParams,
+): string[] =>
+ avialableParams.map((param) => {
+ if (params.has(param)) {
+ return `${param}=${params.get(param)}`;
+ }
+ return '';
+ });
diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx
index acd8d5595e..a65a590589 100644
--- a/frontend/src/container/SideNav/index.tsx
+++ b/frontend/src/container/SideNav/index.tsx
@@ -4,7 +4,13 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes';
import history from 'lib/history';
-import React, { useCallback, useLayoutEffect, useMemo, useState } from 'react';
+import {
+ ReactNode,
+ useCallback,
+ useLayoutEffect,
+ useMemo,
+ useState,
+} from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
@@ -12,7 +18,8 @@ import { SideBarCollapse } from 'store/actions/app';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
-import { styles } from './config';
+import { routeConfig, styles } from './config';
+import { getQueryString } from './helper';
import menus from './menuItems';
import Slack from './Slack';
import {
@@ -34,7 +41,7 @@ function SideNav(): JSX.Element {
AppReducer
>((state) => state.app);
- const { pathname } = useLocation();
+ const { pathname, search } = useLocation();
const { t } = useTranslation('');
const onCollapse = useCallback(() => {
@@ -47,11 +54,16 @@ function SideNav(): JSX.Element {
const onClickHandler = useCallback(
(to: string) => {
+ const params = new URLSearchParams(search);
+ const avialableParams = routeConfig[to];
+
+ const queryString = getQueryString(avialableParams, params);
+
if (pathname !== to) {
- history.push(`${to}`);
+ history.push(`${to}?${queryString.join('&')}`);
}
},
- [pathname],
+ [pathname, search],
);
const onClickSlackHandler = (): void => {
@@ -156,10 +168,10 @@ function SideNav(): JSX.Element {
interface SidebarItem {
onClick: VoidFunction;
- icon?: React.ReactNode;
- text?: React.ReactNode;
+ icon?: ReactNode;
+ text?: ReactNode;
key: string;
- label?: React.ReactNode;
+ label?: ReactNode;
}
export default SideNav;
diff --git a/frontend/src/container/Timeline/index.tsx b/frontend/src/container/Timeline/index.tsx
index 9deeaaa261..058ca3e315 100644
--- a/frontend/src/container/Timeline/index.tsx
+++ b/frontend/src/container/Timeline/index.tsx
@@ -2,7 +2,7 @@ import { StyledDiv } from 'components/Styled';
import { ITraceMetaData } from 'container/GantChart';
import { IIntervalUnit, INTERVAL_UNITS } from 'container/TraceDetail/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useEffect, useState } from 'react';
+import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useMeasure } from 'react-use';
import { styles, Svg, TimelineInterval } from './styles';
@@ -105,7 +105,7 @@ interface TimelineProps {
levels: number;
};
globalTraceMetadata: ITraceMetaData;
- setIntervalUnit: React.Dispatch>;
+ setIntervalUnit: Dispatch>;
}
export default Timeline;
diff --git a/frontend/src/container/TopNav/AutoRefresh/index.tsx b/frontend/src/container/TopNav/AutoRefresh/index.tsx
index ab02e27a3d..e83276e983 100644
--- a/frontend/src/container/TopNav/AutoRefresh/index.tsx
+++ b/frontend/src/container/TopNav/AutoRefresh/index.tsx
@@ -14,7 +14,7 @@ import set from 'api/browser/localstorage/set';
import { DASHBOARD_TIME_IN_DURATION } from 'constants/app';
import useUrlQuery from 'hooks/useUrlQuery';
import _omit from 'lodash-es/omit';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { useInterval } from 'react-use';
diff --git a/frontend/src/container/TopNav/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx
index a4ada700d5..01ec22677c 100644
--- a/frontend/src/container/TopNav/Breadcrumbs/index.tsx
+++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx
@@ -1,6 +1,5 @@
import { Breadcrumb } from 'antd';
import ROUTES from 'constants/routes';
-import React from 'react';
import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
const breadcrumbNameMap = {
@@ -9,6 +8,7 @@ const breadcrumbNameMap = {
[ROUTES.SERVICE_MAP]: 'Service Map',
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
[ROUTES.INSTRUMENTATION]: 'Get Started',
+ [ROUTES.ALL_CHANNELS]: 'Channels',
[ROUTES.SETTINGS]: 'Settings',
[ROUTES.DASHBOARD]: 'Dashboard',
[ROUTES.ALL_ERROR]: 'Exceptions',
diff --git a/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx b/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx
index 719de038c3..a178b2edec 100644
--- a/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx
+++ b/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx
@@ -1,5 +1,4 @@
import { fireEvent, render, screen } from '@testing-library/react';
-import React from 'react';
import CustomDateTimeModal from './index';
diff --git a/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx b/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx
index 2682815a74..57cc54e5e7 100644
--- a/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx
+++ b/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx
@@ -1,6 +1,6 @@
import { DatePicker, Modal } from 'antd';
import dayjs, { Dayjs } from 'dayjs';
-import React, { useState } from 'react';
+import { useState } from 'react';
export type DateTimeRangeType = [Dayjs | null, Dayjs | null] | null;
@@ -13,6 +13,7 @@ function CustomDateTimeModal({
}: CustomDateTimeModalProps): JSX.Element {
const [selectedDate, setDateTime] = useState();
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
const onModalOkHandler = (date_time: any): void => {
onCreate(date_time);
setDateTime(date_time);
diff --git a/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx b/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
index f4597e5727..6e608b1189 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { RefreshTextContainer, Typography } from './styles';
diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
index 91567db64d..db4a0d8c42 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
@@ -5,7 +5,7 @@ import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import dayjs, { Dayjs } from 'dayjs';
import getTimeString from 'lib/getTimeString';
-import React, { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
diff --git a/frontend/src/container/TopNav/index.tsx b/frontend/src/container/TopNav/index.tsx
index 1cfbf19dbc..38efc91466 100644
--- a/frontend/src/container/TopNav/index.tsx
+++ b/frontend/src/container/TopNav/index.tsx
@@ -1,6 +1,6 @@
import { Col } from 'antd';
import ROUTES from 'constants/routes';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { matchPath, useHistory } from 'react-router-dom';
import ShowBreadcrumbs from './Breadcrumbs';
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx
index ee42069fe3..35c60b1c68 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx
@@ -2,7 +2,7 @@ import { Checkbox, Tooltip, Typography } from 'antd';
import getFilters from 'api/trace/getFilters';
import { AxiosError } from 'axios';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { getFilter, updateURL } from 'store/actions/trace/util';
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx
index 440c9652a7..f9960c4ea9 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/CommonCheckBox/index.tsx
@@ -1,5 +1,5 @@
import { Button, Input } from 'antd';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx
index 0de6229661..c246d5eae9 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx
@@ -2,7 +2,8 @@ import { Slider } from 'antd';
import { SliderRangeProps } from 'antd/lib/slider';
import getFilters from 'api/trace/getFilters';
import useDebouncedFn from 'hooks/useDebouncedFunction';
-import React, {
+import {
+ ChangeEventHandler,
useCallback,
useEffect,
useMemo,
@@ -148,9 +149,7 @@ function Duration(): JSX.Element {
undefined,
);
- const onChangeMaxHandler: React.ChangeEventHandler = (
- event,
- ) => {
+ const onChangeMaxHandler: ChangeEventHandler = (event) => {
const { value } = event.target;
const min = preMin;
const max = value;
@@ -159,9 +158,7 @@ function Duration(): JSX.Element {
debouncedFunction(min, max);
};
- const onChangeMinHandler: React.ChangeEventHandler = (
- event,
- ) => {
+ const onChangeMinHandler: ChangeEventHandler = (event) => {
const { value } = event.target;
const min = value;
const max = preMax;
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx
index eef14987e1..da2a0b234f 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx
@@ -2,7 +2,7 @@ import { Input } from 'antd';
import getFilters from 'api/trace/getFilters';
import { AxiosError } from 'axios';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useEffect, useState } from 'react';
+import { ChangeEvent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { getFilter, updateURL } from 'store/actions/trace/util';
@@ -103,7 +103,7 @@ function TraceID(): JSX.Element {
setIsLoading(false);
}
};
- const onChange = (e: React.ChangeEvent): void => {
+ const onChange = (e: ChangeEvent): void => {
setUserEnteredValue(e.target.value);
};
const onBlur = (): void => {
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx
index fda3599305..c58f67dfd8 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx
@@ -1,7 +1,6 @@
/* eslint-disable no-nested-ternary */
import { Card } from 'antd';
import Spinner from 'components/Spinner';
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
index cf272257f9..c9d0dd9373 100644
--- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx
@@ -3,7 +3,7 @@ import { Card, Divider, Typography } from 'antd';
import getFilters from 'api/trace/getFilters';
import { AxiosError } from 'axios';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useState } from 'react';
+import { MouseEventHandler, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { getFilter, updateURL } from 'store/actions/trace/util';
@@ -58,7 +58,7 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element {
const { notifications } = useNotifications();
// eslint-disable-next-line sonarjs/cognitive-complexity
- const onExpandHandler: React.MouseEventHandler = async (e) => {
+ const onExpandHandler: MouseEventHandler = async (e) => {
try {
e.preventDefault();
e.stopPropagation();
diff --git a/frontend/src/container/Trace/Filters/Panel/index.tsx b/frontend/src/container/Trace/Filters/Panel/index.tsx
index b044d57ea0..c84b9dcc79 100644
--- a/frontend/src/container/Trace/Filters/Panel/index.tsx
+++ b/frontend/src/container/Trace/Filters/Panel/index.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
diff --git a/frontend/src/container/Trace/Filters/index.tsx b/frontend/src/container/Trace/Filters/index.tsx
index 36edd206fe..21f4048507 100644
--- a/frontend/src/container/Trace/Filters/index.tsx
+++ b/frontend/src/container/Trace/Filters/index.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { TraceFilterEnum } from 'types/reducer/trace';
import Panel from './Panel';
diff --git a/frontend/src/container/Trace/Graph/index.tsx b/frontend/src/container/Trace/Graph/index.tsx
index 2e7dea478f..30e2ea9ac4 100644
--- a/frontend/src/container/Trace/Graph/index.tsx
+++ b/frontend/src/container/Trace/Graph/index.tsx
@@ -1,7 +1,7 @@
import { Typography } from 'antd';
import Graph from 'components/Graph';
import Spinner from 'components/Spinner';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useMeasure } from 'react-use';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/Trace/Graph/styles.ts b/frontend/src/container/Trace/Graph/styles.ts
index 16878bf6af..64be5e1127 100644
--- a/frontend/src/container/Trace/Graph/styles.ts
+++ b/frontend/src/container/Trace/Graph/styles.ts
@@ -1,10 +1,10 @@
import { StyledCSS } from 'container/GantChart/Trace/styles';
-import React from 'react';
+import { RefObject } from 'react';
import styled, { css } from 'styled-components';
interface Props {
center?: boolean;
- ref?: React.RefObject | null; // The ref type provided by react-use is incorrect -> https://github.com/streamich/react-use/issues/1264 Open Issue
+ ref?: RefObject | null; // The ref type provided by react-use is incorrect -> https://github.com/streamich/react-use/issues/1264 Open Issue
}
export const Container = styled.div`
diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx
index f14a35e0bd..2d17e6eae7 100644
--- a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx
+++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx
@@ -1,6 +1,12 @@
import { AutoComplete, Input } from 'antd';
import getTagFilters from 'api/trace/getTagFilter';
-import React, { useCallback, useMemo, useState } from 'react';
+import {
+ Dispatch,
+ SetStateAction,
+ useCallback,
+ useMemo,
+ useState,
+} from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -83,9 +89,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element {
interface TagsKeysProps {
index: number;
tag: FlatArray;
- setLocalSelectedTags: React.Dispatch<
- React.SetStateAction
- >;
+ setLocalSelectedTags: Dispatch>;
}
export default TagsKey;
diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx
index a74463d5a4..5f538b4453 100644
--- a/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx
+++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagValue.tsx
@@ -1,7 +1,14 @@
import { Select } from 'antd';
import { BaseOptionType } from 'antd/es/select';
import getTagValue from 'api/trace/getTagValue';
-import React, { memo, useCallback, useMemo, useState } from 'react';
+import {
+ Dispatch,
+ memo,
+ SetStateAction,
+ useCallback,
+ useMemo,
+ useState,
+} from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -139,9 +146,7 @@ function TagValue(props: TagValueProps): JSX.Element {
interface TagValueProps {
index: number;
tag: FlatArray;
- setLocalSelectedTags: React.Dispatch<
- React.SetStateAction
- >;
+ setLocalSelectedTags: Dispatch>;
tagKey: string;
}
diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx
index 90003b0142..1d8be3c7fa 100644
--- a/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx
+++ b/frontend/src/container/Trace/Search/AllTags/Tag/index.tsx
@@ -1,6 +1,6 @@
import { CloseOutlined } from '@ant-design/icons';
import { Select } from 'antd';
-import React from 'react';
+import { Dispatch, SetStateAction } from 'react';
import { TraceReducer } from 'types/reducer/trace';
import { Container, IconContainer, SelectComponent } from './styles';
@@ -172,9 +172,7 @@ interface AllTagsProps {
onCloseHandler: (index: number) => void;
index: number;
tag: FlatArray;
- setLocalSelectedTags: React.Dispatch<
- React.SetStateAction
- >;
+ setLocalSelectedTags: Dispatch>;
localSelectedTags: TraceReducer['selectedTags'];
}
diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts b/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts
index a9367b9c0f..bc51890a38 100644
--- a/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts
+++ b/frontend/src/container/Trace/Search/AllTags/Tag/utils.ts
@@ -1,5 +1,6 @@
import { AutoCompleteProps } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
+import { Dispatch, SetStateAction } from 'react';
import { PayloadProps as TagKeyPayload } from 'types/api/trace/getTagFilters';
import { PayloadProps as TagValuePayload } from 'types/api/trace/getTagValue';
import { OperatorValues, Tags } from 'types/reducer/trace';
@@ -104,9 +105,9 @@ export function separateTagValues(
export function disableTagValue(
selectedOperator: OperatorValues,
- setLocalValue: React.Dispatch>,
+ setLocalValue: Dispatch>,
selectedKeys: string,
- setLocalSelectedTags: React.Dispatch>,
+ setLocalSelectedTags: Dispatch>,
index: number,
): boolean {
if (selectedOperator === 'Exists' || selectedOperator === 'NotExists') {
@@ -205,8 +206,8 @@ export function mapOperators(selectedKey: string): AllMenuProps[] {
export function onTagKeySelect(
value: unknown,
options: AutoCompleteProps['options'],
- setSelectedKey: React.Dispatch>,
- setLocalSelectedTags: React.Dispatch>,
+ setSelectedKey: Dispatch>,
+ setLocalSelectedTags: Dispatch>,
index: number,
tag: Tags,
): void {
diff --git a/frontend/src/container/Trace/Search/AllTags/index.tsx b/frontend/src/container/Trace/Search/AllTags/index.tsx
index effb0c87b9..0d17c79c21 100644
--- a/frontend/src/container/Trace/Search/AllTags/index.tsx
+++ b/frontend/src/container/Trace/Search/AllTags/index.tsx
@@ -1,6 +1,6 @@
import { CaretRightFilled, PlusOutlined } from '@ant-design/icons';
import { Button, Space, Typography } from 'antd';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx
index d9b4741082..49ba39afef 100644
--- a/frontend/src/container/Trace/Search/index.tsx
+++ b/frontend/src/container/Trace/Search/index.tsx
@@ -1,6 +1,6 @@
import { CaretRightFilled } from '@ant-design/icons';
import { Popover } from 'antd';
-import React, { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
@@ -27,7 +27,7 @@ function Search({
const dispatch = useDispatch>();
useEffect(() => {
- if (traces.filterLoading) {
+ if (!traces.filterLoading) {
const initialTags = parseTagsToQuery(traces.selectedTags);
if (!initialTags.isError) {
setValue(initialTags.payload);
diff --git a/frontend/src/container/Trace/TraceGraphFilter/index.tsx b/frontend/src/container/Trace/TraceGraphFilter/index.tsx
index 48f6666101..bc13641d02 100644
--- a/frontend/src/container/Trace/TraceGraphFilter/index.tsx
+++ b/frontend/src/container/Trace/TraceGraphFilter/index.tsx
@@ -1,6 +1,6 @@
import { AutoComplete, Input, Space } from 'antd';
import getTagFilters from 'api/trace/getTagFilter';
-import React, { useMemo, useState } from 'react';
+import { useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx
index 058a962d02..fc76b2bd79 100644
--- a/frontend/src/container/Trace/TraceTable/index.tsx
+++ b/frontend/src/container/Trace/TraceTable/index.tsx
@@ -11,7 +11,7 @@ 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 { HTMLAttributes } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { updateURL } from 'store/actions/trace/util';
@@ -198,7 +198,7 @@ function TraceTable(): JSX.Element {
style={{
cursor: 'pointer',
}}
- onRow={(record: TableType): React.HTMLAttributes => ({
+ onRow={(record: TableType): HTMLAttributes => ({
onClick: (event): void => {
event.preventDefault();
event.stopPropagation();
diff --git a/frontend/src/container/TraceDetail/Missingtrace.tsx b/frontend/src/container/TraceDetail/Missingtrace.tsx
index 0d2e91b5d7..4375fd95d3 100644
--- a/frontend/src/container/TraceDetail/Missingtrace.tsx
+++ b/frontend/src/container/TraceDetail/Missingtrace.tsx
@@ -1,7 +1,6 @@
import { volcano } from '@ant-design/colors';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Popover, Typography } from 'antd';
-import React from 'react';
function PopOverContent(): JSX.Element {
return (
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/EllipsedButton.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/EllipsedButton.tsx
index 56ef64e4ee..0ff9e06bbf 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/EllipsedButton.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/EllipsedButton.tsx
@@ -1,5 +1,4 @@
import { StyledButton } from 'components/Styled';
-import React from 'react';
import { styles } from './styles';
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/Event.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/Event.tsx
index aeab2a5144..77692e5ea0 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/Event.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/Event.tsx
@@ -2,7 +2,6 @@ import { Collapse } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import keys from 'lodash-es/keys';
import map from 'lodash-es/map';
-import React from 'react';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import EllipsedButton from '../EllipsedButton';
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/EventStartTime.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/EventStartTime.tsx
index 40c05e6c99..11451ab01f 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/EventStartTime.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/EventStartTime.tsx
@@ -1,7 +1,6 @@
import { Popover } from 'antd';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React from 'react';
import { CustomSubText, CustomSubTitle } from '../styles';
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/RelativeStartTime.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/RelativeStartTime.tsx
index 120aedc27f..08006f25a4 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/RelativeStartTime.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/RelativeStartTime.tsx
@@ -2,7 +2,6 @@ import { InfoCircleOutlined } from '@ant-design/icons';
import { Popover, Space } from 'antd';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React from 'react';
import { CustomSubText, CustomSubTitle } from '../styles';
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/index.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/index.tsx
index c7aa3f78dd..1f6cb610be 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/index.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Events/index.tsx
@@ -1,5 +1,4 @@
import { Typography } from 'antd';
-import React from 'react';
import { ITraceEvents } from 'types/api/trace/getTraceItem';
import ErrorTag from './Event';
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/Tag.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/Tag.tsx
index 1f67dde5e1..293b6fc993 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/Tag.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/Tag.tsx
@@ -1,6 +1,6 @@
import { Tooltip } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useMemo } from 'react';
+import { Fragment, useMemo } from 'react';
import { ITraceTag } from 'types/api/trace/getTraceItem';
import EllipsedButton from '../EllipsedButton';
@@ -21,7 +21,7 @@ function Tag({ tags, onToggleHandler, setText }: TagProps): JSX.Element {
}, [tags]);
return (
-
+
{tags.value && (
{tags.key}
@@ -51,7 +51,7 @@ function Tag({ tags, onToggleHandler, setText }: TagProps): JSX.Element {
)}
-
+
);
}
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/index.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/index.tsx
index 4c9e5227ca..80f04062ba 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/index.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/Tags/index.tsx
@@ -1,7 +1,15 @@
import { Input, List, Typography } from 'antd';
import ROUTES from 'constants/routes';
import { formUrlParams } from 'container/TraceDetail/utils';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import {
+ ChangeEvent,
+ Dispatch,
+ SetStateAction,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
import { useTranslation } from 'react-i18next';
import { ITraceTag } from 'types/api/trace/getTraceItem';
@@ -34,7 +42,7 @@ function Tags({
);
const onChangeHandler = useCallback(
- (e: React.ChangeEvent): void => {
+ (e: ChangeEvent): void => {
const { value } = e.target;
const filteredTags = tags.filter((tag) => tag.key.includes(value));
setAllRenderedTags(filteredTags);
@@ -87,7 +95,7 @@ interface TagsProps extends CommonTagsProps {
export interface CommonTagsProps {
onToggleHandler: (state: boolean) => void;
- setText: React.Dispatch>;
+ setText: Dispatch>;
}
Tags.defaultProps = {
diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx
index c7e0755a37..db9fde5ecb 100644
--- a/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx
+++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/index.tsx
@@ -1,8 +1,11 @@
-import { Modal, Tabs, Tooltip, Typography } from 'antd';
+import { Button, Modal, Tabs, Tooltip, Typography } from 'antd';
import Editor from 'components/Editor';
import { StyledSpace } from 'components/Styled';
+import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useMemo, useState } from 'react';
+import history from 'lib/history';
+import { useMemo, useState } from 'react';
+import { useParams } from 'react-router-dom';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import Events from './Events';
@@ -18,6 +21,8 @@ import Tags from './Tags';
function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
const { tree, firstSpanStartTime } = props;
+ const { id: traceId } = useParams();
+
const isDarkMode = useIsDarkMode();
const OverLayComponentName = useMemo(() => tree?.name, [tree?.name]);
@@ -69,6 +74,12 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
},
];
+ const onLogsHandler = (): void => {
+ const query = encodeURIComponent(`trace_id IN ('${traceId}')`);
+
+ history.push(`${ROUTES.LOGS}?q=${query}`);
+ };
+
return (
Details for selected Span
Service
+
{tree.serviceName}
@@ -86,6 +98,8 @@ function SelectedSpanDetails(props: SelectedSpanDetailsProps): JSX.Element {
{tree.name}
+
+ Go to Related logs
{
+ const createNode = (id: string, children: ITraceTree[] = []): ITraceTree => ({
+ id,
+ name: '',
+ value: 0,
+ time: 0,
+ startTime: 0,
+ tags: [],
+ children,
+ serviceName: '',
+ serviceColour: '',
+ });
+
+ test('should return 0 for empty tree', () => {
+ const emptyTree = null;
+ expect(getTreeLevelsCount((emptyTree as unknown) as ITraceTree)).toBe(0);
+ });
+
+ test('should return 1 for a tree with a single node', () => {
+ const singleNodeTree = createNode('1');
+ expect(getTreeLevelsCount(singleNodeTree)).toBe(1);
+ });
+
+ test('should return correct depth for a balanced tree', () => {
+ const tree = createNode('1', [
+ createNode('2', [createNode('4'), createNode('5')]),
+ createNode('3', [createNode('6'), createNode('7')]),
+ ]);
+
+ expect(getTreeLevelsCount(tree)).toBe(3);
+ });
+
+ test('should return correct depth for an unbalanced tree', () => {
+ const tree = createNode('1', [
+ createNode('2', [
+ createNode('4', [createNode('8', [createNode('11')])]),
+ createNode('5'),
+ ]),
+ createNode('3', [createNode('6'), createNode('7', [createNode('10')])]),
+ ]);
+
+ expect(getTreeLevelsCount(tree)).toBe(5);
+ });
+
+ test('should return correct depth for a tree with single child nodes', () => {
+ const tree = createNode('1', [
+ createNode('2', [createNode('3', [createNode('4', [createNode('5')])])]),
+ ]);
+
+ expect(getTreeLevelsCount(tree)).toBe(5);
+ });
+});
diff --git a/frontend/src/container/TraceDetail/utils.ts b/frontend/src/container/TraceDetail/utils.ts
index 1e978f28ae..c907a1e586 100644
--- a/frontend/src/container/TraceDetail/utils.ts
+++ b/frontend/src/container/TraceDetail/utils.ts
@@ -93,7 +93,12 @@ export const getSortedData = (treeData: ITraceTree): ITraceTree => {
};
export const getTreeLevelsCount = (tree: ITraceTree): number => {
- let levels = 0;
+ if (!tree) {
+ return 0;
+ }
+
+ let levels = 1;
+
const traverse = (treeNode: ITraceTree, level: number): void => {
if (!treeNode) {
return;
diff --git a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx
index 08464ab8b0..adcaa3f003 100644
--- a/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx
+++ b/frontend/src/container/TraceFlameGraph/__tests__/TraceFlameGraph.test.tsx
@@ -1,6 +1,6 @@
import { render, renderHook } from '@testing-library/react';
import TraceFlameGraph from 'container/TraceFlameGraph';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { Provider } from 'react-redux';
import store from 'store';
diff --git a/frontend/src/container/TraceFlameGraph/index.tsx b/frontend/src/container/TraceFlameGraph/index.tsx
index 71862ca88d..7e52031646 100644
--- a/frontend/src/container/TraceFlameGraph/index.tsx
+++ b/frontend/src/container/TraceFlameGraph/index.tsx
@@ -2,7 +2,13 @@
import Color from 'color';
import { ITraceMetaData } from 'container/GantChart';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { useLayoutEffect, useMemo, useState } from 'react';
+import {
+ Dispatch,
+ SetStateAction,
+ useLayoutEffect,
+ useMemo,
+ useState,
+} from 'react';
import { ITraceTree } from 'types/api/trace/getTraceItem';
import {
@@ -18,7 +24,7 @@ interface SpanItemProps {
spanData: ITraceTree;
tooltipText: string;
onSpanSelect: (id: string) => void;
- onSpanHover: React.Dispatch>;
+ onSpanHover: Dispatch>;
hoveredSpanId: string;
selectedSpanId: string;
}
diff --git a/frontend/src/container/TriggeredAlerts/Filter.tsx b/frontend/src/container/TriggeredAlerts/Filter.tsx
index 601651cdff..4a54916685 100644
--- a/frontend/src/container/TriggeredAlerts/Filter.tsx
+++ b/frontend/src/container/TriggeredAlerts/Filter.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/no-unstable-nested-components */
import type { SelectProps } from 'antd';
import { Tag } from 'antd';
-import React, { useCallback, useMemo } from 'react';
+import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import { Container, Select } from './styles';
@@ -94,8 +94,8 @@ function Filter({
}
interface FilterProps {
- setSelectedFilter: React.Dispatch>>;
- setSelectedGroup: React.Dispatch>>;
+ setSelectedFilter: Dispatch>>;
+ setSelectedGroup: Dispatch>>;
allAlerts: Alerts[];
selectedGroup: Array;
selectedFilter: Array;
diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx
index 388e2d7499..a52f76518a 100644
--- a/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx
+++ b/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx
@@ -1,7 +1,6 @@
import { Tag, Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
-import React from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import Status from '../TableComponents/AlertStatus';
diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx
index 97619b5f12..bd30296c4a 100644
--- a/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx
+++ b/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx
@@ -1,6 +1,6 @@
import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons';
import { Tag } from 'antd';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import ExapandableRow from './ExapandableRow';
diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx
index a9e56d903d..a9ef68f3e3 100644
--- a/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx
+++ b/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx
@@ -1,5 +1,5 @@
import groupBy from 'lodash-es/groupBy';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import { Value } from '../Filter';
diff --git a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx
index 00443441ee..84219daf12 100644
--- a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx
+++ b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx
@@ -5,7 +5,6 @@ import { ResizeTable } from 'components/ResizeTable';
import AlertStatus from 'container/TriggeredAlerts/TableComponents/AlertStatus';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
import getFormattedDate from 'lib/getFormatedDate';
-import React from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import { Value } from './Filter';
diff --git a/frontend/src/container/TriggeredAlerts/TableComponents/AlertStatus.tsx b/frontend/src/container/TriggeredAlerts/TableComponents/AlertStatus.tsx
index fc91115ebe..51564e65fa 100644
--- a/frontend/src/container/TriggeredAlerts/TableComponents/AlertStatus.tsx
+++ b/frontend/src/container/TriggeredAlerts/TableComponents/AlertStatus.tsx
@@ -1,5 +1,4 @@
import { Tag } from 'antd';
-import React from 'react';
function Severity({ severity }: SeverityProps): JSX.Element {
switch (severity) {
diff --git a/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx b/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx
index b12a09d5e4..2b1da13a4b 100644
--- a/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx
+++ b/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx
@@ -1,6 +1,6 @@
import getTriggeredApi from 'api/alerts/getTriggered';
import useInterval from 'hooks/useInterval';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { Alerts } from 'types/api/alerts/getTriggered';
import Filter, { Value } from './Filter';
diff --git a/frontend/src/container/TriggeredAlerts/index.tsx b/frontend/src/container/TriggeredAlerts/index.tsx
index e9d024c81a..379c0b1106 100644
--- a/frontend/src/container/TriggeredAlerts/index.tsx
+++ b/frontend/src/container/TriggeredAlerts/index.tsx
@@ -2,7 +2,7 @@ import getTriggeredApi from 'api/alerts/getTriggered';
import Spinner from 'components/Spinner';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
-import React, { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PayloadProps } from 'types/api/alerts/getTriggered';
diff --git a/frontend/src/container/Version/index.tsx b/frontend/src/container/Version/index.tsx
index 8c7cab37e4..4cb00b7ad8 100644
--- a/frontend/src/container/Version/index.tsx
+++ b/frontend/src/container/Version/index.tsx
@@ -1,6 +1,6 @@
import { WarningFilled } from '@ant-design/icons';
import { Button, Card, Form, Space, Typography } from 'antd';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts
new file mode 100644
index 0000000000..c7519761ec
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts
@@ -0,0 +1,136 @@
+import {
+ getRemovePrefixFromKey,
+ getTagToken,
+ isExistsNotExistsOperator,
+ replaceStringWithMaxLength,
+ tagRegexp,
+} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+import { Option } from 'container/QueryBuilder/type';
+import * as Papa from 'papaparse';
+import { KeyboardEvent, useCallback, useState } from 'react';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+
+import { useFetchKeysAndValues } from './useFetchKeysAndValues';
+import { useOptions } from './useOptions';
+import { useSetCurrentKeyAndOperator } from './useSetCurrentKeyAndOperator';
+import { useTag } from './useTag';
+import { useTagValidation } from './useTagValidation';
+
+export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
+ const [searchValue, setSearchValue] = useState('');
+ const [searchKey, setSearchKey] = useState('');
+
+ const { keys, results, isFetching } = useFetchKeysAndValues(
+ searchValue,
+ query,
+ searchKey,
+ );
+
+ const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
+
+ const handleSearch = (value: string): void => {
+ const prefixFreeValue = getRemovePrefixFromKey(getTagToken(value).tagKey);
+ setSearchValue(value);
+ setSearchKey(prefixFreeValue);
+ };
+
+ const { isValidTag, isExist, isValidOperator, isMulti } = useTagValidation(
+ operator,
+ result,
+ );
+
+ const { handleAddTag, handleClearTag, tags, updateTag } = useTag(
+ key,
+ isValidTag,
+ handleSearch,
+ query,
+ setSearchKey,
+ );
+
+ const handleSelect = useCallback(
+ (value: string): void => {
+ if (isMulti) {
+ setSearchValue((prev: string) => {
+ const matches = prev?.matchAll(tagRegexp);
+ const [match] = matches ? Array.from(matches) : [];
+ const [, , , matchTagValue] = match;
+ const data = Papa.parse(matchTagValue).data.flat();
+ return replaceStringWithMaxLength(prev, data as string[], value);
+ });
+ }
+ if (!isMulti) {
+ if (isExistsNotExistsOperator(value)) handleAddTag(value);
+ if (isValidTag && !isExistsNotExistsOperator(value)) handleAddTag(value);
+ }
+ },
+ [handleAddTag, isMulti, isValidTag],
+ );
+
+ const handleKeyDown = useCallback(
+ (event: KeyboardEvent): void => {
+ if (
+ event.key === ' ' &&
+ (searchValue.endsWith(' ') || searchValue.length === 0)
+ ) {
+ event.preventDefault();
+ }
+
+ if (event.key === 'Enter' && searchValue && isValidTag) {
+ if (isMulti) {
+ event.stopPropagation();
+ }
+ event.preventDefault();
+ handleAddTag(searchValue);
+ }
+
+ if (event.key === 'Backspace' && !searchValue) {
+ event.stopPropagation();
+ const last = tags[tags.length - 1];
+ handleClearTag(last);
+ }
+ },
+ [handleAddTag, handleClearTag, isMulti, isValidTag, searchValue, tags],
+ );
+
+ const options = useOptions(
+ key,
+ keys,
+ operator,
+ searchValue,
+ isMulti,
+ isValidOperator,
+ isExist,
+ results,
+ result,
+ );
+
+ return {
+ updateTag,
+ handleSearch,
+ handleClearTag,
+ handleSelect,
+ handleKeyDown,
+ options,
+ tags,
+ searchValue,
+ isMulti,
+ isFetching,
+ setSearchKey,
+ searchKey,
+ };
+};
+
+interface IAutoComplete {
+ updateTag: (value: string) => void;
+ handleSearch: (value: string) => void;
+ handleClearTag: (value: string) => void;
+ handleSelect: (value: string) => void;
+ handleKeyDown: (event: React.KeyboardEvent) => void;
+ options: Option[];
+ tags: string[];
+ searchValue: string;
+ isMulti: boolean;
+ isFetching: boolean;
+ setSearchKey: (value: string) => void;
+ searchKey: string;
+}
diff --git a/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts
new file mode 100644
index 0000000000..245f80506f
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useFetchKeysAndValues.ts
@@ -0,0 +1,152 @@
+import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
+import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
+import { QueryBuilderKeys } from 'constants/queryBuilder';
+import {
+ getRemovePrefixFromKey,
+ getTagToken,
+ isInNInOperator,
+} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+import debounce from 'lodash-es/debounce';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { useQuery } from 'react-query';
+import { useDebounce } from 'react-use';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+
+type IuseFetchKeysAndValues = {
+ keys: BaseAutocompleteData[];
+ results: string[];
+ isFetching: boolean;
+};
+
+/**
+ * Custom hook to fetch attribute keys and values from an API
+ * @param searchValue - the search query value
+ * @param query - an object containing data for the query
+ * @returns an object containing the fetched attribute keys, results, and the status of the fetch
+ */
+
+export const useFetchKeysAndValues = (
+ searchValue: string,
+ query: IBuilderQuery,
+ searchKey: string,
+): IuseFetchKeysAndValues => {
+ const [keys, setKeys] = useState([]);
+ const [results, setResults] = useState([]);
+
+ const searchParams = useMemo(
+ () =>
+ debounce(
+ () => [
+ searchKey,
+ query.dataSource,
+ query.aggregateOperator,
+ query.aggregateAttribute.key,
+ ],
+ 300,
+ ),
+ [
+ query.aggregateAttribute.key,
+ query.aggregateOperator,
+ query.dataSource,
+ searchKey,
+ ],
+ );
+
+ const isQueryEnabled = useMemo(
+ () =>
+ query.dataSource === DataSource.METRICS
+ ? !!query.aggregateOperator &&
+ !!query.dataSource &&
+ !!query.aggregateAttribute.dataType
+ : true,
+ [
+ query.aggregateAttribute.dataType,
+ query.aggregateOperator,
+ query.dataSource,
+ ],
+ );
+
+ const { data, isFetching, status } = useQuery(
+ [QueryBuilderKeys.GET_ATTRIBUTE_KEY, searchParams()],
+ async () =>
+ getAggregateKeys({
+ searchText: searchKey,
+ dataSource: query.dataSource,
+ aggregateOperator: query.aggregateOperator,
+ aggregateAttribute: query.aggregateAttribute.key,
+ tagType: query.aggregateAttribute.type ?? null,
+ }),
+ {
+ enabled: isQueryEnabled,
+ },
+ );
+
+ /**
+ * Fetches the options to be displayed based on the selected value
+ * @param value - the selected value
+ * @param query - an object containing data for the query
+ */
+ const handleFetchOption = async (
+ value: string,
+ query: IBuilderQuery,
+ keys: BaseAutocompleteData[],
+ ): Promise => {
+ if (!value) {
+ return;
+ }
+ const { tagKey, tagOperator, tagValue } = getTagToken(value);
+ const filterAttributeKey = keys.find(
+ (item) => item.key === getRemovePrefixFromKey(tagKey),
+ );
+ setResults([]);
+
+ if (!tagKey || !tagOperator) {
+ return;
+ }
+
+ const { payload } = await getAttributesValues({
+ aggregateOperator: query.aggregateOperator,
+ dataSource: query.dataSource,
+ aggregateAttribute: query.aggregateAttribute.key,
+ attributeKey: filterAttributeKey?.key ?? tagKey,
+ filterAttributeKeyDataType: filterAttributeKey?.dataType ?? null,
+ tagType: filterAttributeKey?.type ?? null,
+ searchText: isInNInOperator(tagOperator)
+ ? tagValue[tagValue.length - 1]?.toString() ?? '' // last element of tagvalue will be always user search value
+ : tagValue?.toString() ?? '',
+ });
+
+ if (payload) {
+ const values = Object.values(payload).find((el) => !!el) || [];
+ setResults(values);
+ }
+ };
+
+ // creates a ref to the fetch function so that it doesn't change on every render
+ const clearFetcher = useRef(handleFetchOption).current;
+
+ // debounces the fetch function to avoid excessive API calls
+ useDebounce(() => clearFetcher(searchValue, query, keys), 750, [
+ clearFetcher,
+ searchValue,
+ query,
+ keys,
+ ]);
+
+ // update the fetched keys when the fetch status changes
+ useEffect(() => {
+ if (status === 'success' && data?.payload?.attributeKeys) {
+ setKeys(data?.payload.attributeKeys);
+ } else {
+ setKeys([]);
+ }
+ }, [data?.payload?.attributeKeys, status]);
+
+ return {
+ keys,
+ results,
+ isFetching,
+ };
+};
diff --git a/frontend/src/hooks/queryBuilder/useIsValidTag.test.ts b/frontend/src/hooks/queryBuilder/useIsValidTag.test.ts
new file mode 100644
index 0000000000..47ab280e81
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useIsValidTag.test.ts
@@ -0,0 +1,41 @@
+import { renderHook } from '@testing-library/react';
+
+import { useIsValidTag } from './useIsValidTag';
+
+describe('useIsValidTag', () => {
+ test('returns correct validation result for SINGLE_VALUE operator type', () => {
+ const { result } = renderHook(() => useIsValidTag('SINGLE_VALUE', 1));
+ expect(result.current).toBe(true);
+
+ const { result: result2 } = renderHook(() =>
+ useIsValidTag('SINGLE_VALUE', 0),
+ );
+ expect(result2.current).toBe(false);
+ });
+
+ test('returns correct validation result for MULTIPLY_VALUE operator type', () => {
+ const { result } = renderHook(() => useIsValidTag('MULTIPLY_VALUE', 1));
+ expect(result.current).toBe(true);
+
+ const { result: result2 } = renderHook(() =>
+ useIsValidTag('MULTIPLY_VALUE', 0),
+ );
+ expect(result2.current).toBe(false);
+ });
+
+ test('returns correct validation result for NON_VALUE operator type', () => {
+ const { result } = renderHook(() => useIsValidTag('NON_VALUE', 0));
+ expect(result.current).toBe(true);
+
+ const { result: result2 } = renderHook(() => useIsValidTag('NON_VALUE', 1));
+ expect(result2.current).toBe(false);
+ });
+
+ test('returns correct validation result for NOT_VALID operator type', () => {
+ const { result } = renderHook(() => useIsValidTag('NOT_VALID', 1));
+ expect(result.current).toBe(false);
+
+ const { result: result2 } = renderHook(() => useIsValidTag('NOT_VALID', 0));
+ expect(result2.current).toBe(false);
+ });
+});
diff --git a/frontend/src/hooks/queryBuilder/useIsValidTag.ts b/frontend/src/hooks/queryBuilder/useIsValidTag.ts
new file mode 100644
index 0000000000..216971b0ac
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useIsValidTag.ts
@@ -0,0 +1,22 @@
+import { useMemo } from 'react';
+
+import { OperatorType } from './useOperatorType';
+
+const validationMapper: Record<
+ OperatorType,
+ (resultLength: number) => boolean
+> = {
+ SINGLE_VALUE: (resultLength: number) => resultLength === 1,
+ MULTIPLY_VALUE: (resultLength: number) => resultLength >= 1,
+ NON_VALUE: (resultLength: number) => resultLength === 0,
+ NOT_VALID: () => false,
+};
+
+export const useIsValidTag = (
+ operatorType: OperatorType,
+ resultLength: number,
+): boolean =>
+ useMemo(() => validationMapper[operatorType]?.(resultLength), [
+ operatorType,
+ resultLength,
+ ]);
diff --git a/frontend/src/hooks/queryBuilder/useOperatorType.ts b/frontend/src/hooks/queryBuilder/useOperatorType.ts
new file mode 100644
index 0000000000..50cb318748
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useOperatorType.ts
@@ -0,0 +1,27 @@
+import { OPERATORS } from 'constants/queryBuilder';
+
+export type OperatorType =
+ | 'SINGLE_VALUE'
+ | 'MULTIPLY_VALUE'
+ | 'NON_VALUE'
+ | 'NOT_VALID';
+
+const operatorTypeMapper: Record = {
+ [OPERATORS.IN]: 'MULTIPLY_VALUE',
+ [OPERATORS.NIN]: 'MULTIPLY_VALUE',
+ [OPERATORS.EXISTS]: 'NON_VALUE',
+ [OPERATORS.NOT_EXISTS]: 'NON_VALUE',
+ [OPERATORS['<=']]: 'SINGLE_VALUE',
+ [OPERATORS['<']]: 'SINGLE_VALUE',
+ [OPERATORS['>=']]: 'SINGLE_VALUE',
+ [OPERATORS['>']]: 'SINGLE_VALUE',
+ [OPERATORS.LIKE]: 'SINGLE_VALUE',
+ [OPERATORS.NLIKE]: 'SINGLE_VALUE',
+ [OPERATORS.CONTAINS]: 'SINGLE_VALUE',
+ [OPERATORS.NOT_CONTAINS]: 'SINGLE_VALUE',
+ [OPERATORS['=']]: 'SINGLE_VALUE',
+ [OPERATORS['!=']]: 'SINGLE_VALUE',
+};
+
+export const useOperatorType = (operator: string): OperatorType =>
+ operatorTypeMapper[operator] || 'NOT_VALID';
diff --git a/frontend/src/hooks/queryBuilder/useOperators.ts b/frontend/src/hooks/queryBuilder/useOperators.ts
new file mode 100644
index 0000000000..141213d4ce
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useOperators.ts
@@ -0,0 +1,22 @@
+import { QUERY_BUILDER_OPERATORS_BY_TYPES } from 'constants/queryBuilder';
+import { getRemovePrefixFromKey } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+import { useMemo } from 'react';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+type IOperators =
+ | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.universal
+ | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.string
+ | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.bool
+ | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.int64
+ | typeof QUERY_BUILDER_OPERATORS_BY_TYPES.float64;
+
+export const useOperators = (
+ key: string,
+ keys: BaseAutocompleteData[],
+): IOperators =>
+ useMemo(() => {
+ const currentKey = keys?.find((el) => el.key === getRemovePrefixFromKey(key));
+ return currentKey?.dataType
+ ? QUERY_BUILDER_OPERATORS_BY_TYPES[currentKey.dataType]
+ : QUERY_BUILDER_OPERATORS_BY_TYPES.universal;
+ }, [keys, key]);
diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts
new file mode 100644
index 0000000000..07a326852f
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useOptions.ts
@@ -0,0 +1,127 @@
+import {
+ checkCommaInValue,
+ getTagToken,
+} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+import { Option } from 'container/QueryBuilder/type';
+import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+import { useOperators } from './useOperators';
+
+export const useOptions = (
+ key: string,
+ keys: BaseAutocompleteData[],
+ operator: string,
+ searchValue: string,
+ isMulti: boolean,
+ isValidOperator: boolean,
+ isExist: boolean,
+ results: string[],
+ result: string[],
+): Option[] => {
+ const [options, setOptions] = useState([]);
+ const operators = useOperators(key, keys);
+
+ const getLabel = useCallback(
+ (data: BaseAutocompleteData): Option['label'] =>
+ transformStringWithPrefix({
+ str: data?.key,
+ prefix: data?.type || '',
+ condition: !data?.isColumn,
+ }),
+ [],
+ );
+
+ const getOptionsFromKeys = useCallback(
+ (items: BaseAutocompleteData[]): Option[] =>
+ items?.map((item) => ({
+ label: `${getLabel(item)}`,
+ value: item.key,
+ })),
+ [getLabel],
+ );
+
+ const getKeyOpValue = useCallback(
+ (items: string[]): Option[] =>
+ items?.map((item) => ({
+ label: `${key} ${operator} ${item}`,
+ value: `${key} ${operator} ${item}`,
+ })),
+ [key, operator],
+ );
+
+ useEffect(() => {
+ let newOptions: Option[] = [];
+
+ if (!key) {
+ newOptions = searchValue
+ ? [
+ { label: `${searchValue} `, value: `${searchValue} ` },
+ ...getOptionsFromKeys(keys),
+ ]
+ : getOptionsFromKeys(keys);
+ } else if (key && !operator) {
+ newOptions = operators?.map((operator) => ({
+ value: `${key} ${operator} `,
+ label: `${key} ${operator} `,
+ }));
+ } else if (key && operator) {
+ if (isMulti) {
+ newOptions = results.map((item) => ({
+ label: checkCommaInValue(String(item)),
+ value: String(item),
+ }));
+ } else if (isExist) {
+ newOptions = [];
+ } else if (isValidOperator) {
+ const hasAllResults = results.every((value) => result.includes(value));
+ const values = getKeyOpValue(results);
+ newOptions = hasAllResults
+ ? [{ label: searchValue, value: searchValue }]
+ : [{ label: searchValue, value: searchValue }, ...values];
+ }
+ }
+ if (newOptions.length > 0) {
+ setOptions(newOptions);
+ }
+ }, [
+ getKeyOpValue,
+ getOptionsFromKeys,
+ isExist,
+ isMulti,
+ isValidOperator,
+ key,
+ keys,
+ operator,
+ operators,
+ result,
+ results,
+ searchValue,
+ ]);
+
+ return useMemo(
+ () =>
+ (
+ options.filter(
+ (option, index, self) =>
+ index ===
+ self.findIndex(
+ (o) => o.label === option.label && o.value === option.value, // to remove duplicate & empty options from list
+ ) && option.value !== '',
+ ) || []
+ ).map((option) => {
+ const { tagValue } = getTagToken(searchValue);
+ if (isMulti) {
+ return {
+ ...option,
+ selected: tagValue
+ .filter((i) => i.trim().replace(/^\s+/, '') === option.value)
+ .includes(option.value),
+ };
+ }
+ return option;
+ }),
+ [isMulti, options, searchValue],
+ );
+};
diff --git a/frontend/src/hooks/useQueryBuilder.ts b/frontend/src/hooks/queryBuilder/useQueryBuilder.ts
similarity index 100%
rename from frontend/src/hooks/useQueryBuilder.ts
rename to frontend/src/hooks/queryBuilder/useQueryBuilder.ts
diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts
new file mode 100644
index 0000000000..7484df159d
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts
@@ -0,0 +1,148 @@
+import {
+ initialAggregateAttribute,
+ initialQueryBuilderFormValues,
+ mapOfFilters,
+} from 'constants/queryBuilder';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
+import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import {
+ HandleChangeQueryData,
+ UseQueryOperations,
+} from 'types/common/operations.types';
+import { DataSource } from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+
+export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
+ const {
+ handleSetQueryData,
+ removeEntityByIndex,
+ panelType,
+ } = useQueryBuilder();
+ const [operators, setOperators] = useState[]>([]);
+ const [listOfAdditionalFilters, setListOfAdditionalFilters] = useState<
+ string[]
+ >([]);
+
+ const { dataSource, aggregateOperator } = query;
+
+ const handleChangeOperator = useCallback(
+ (value: string): void => {
+ const aggregateDataType: BaseAutocompleteData['dataType'] =
+ query.aggregateAttribute.dataType;
+
+ const typeOfValue = findDataTypeOfOperator(value);
+ const shouldResetAggregateAttribute =
+ (aggregateDataType === 'string' || aggregateDataType === 'bool') &&
+ typeOfValue === 'number';
+
+ const newQuery: IBuilderQuery = {
+ ...query,
+ aggregateOperator: value,
+ having: [],
+ limit: null,
+ filters: { items: [], op: 'AND' },
+ ...(shouldResetAggregateAttribute
+ ? { aggregateAttribute: initialAggregateAttribute }
+ : {}),
+ };
+
+ handleSetQueryData(index, newQuery);
+ },
+ [index, query, handleSetQueryData],
+ );
+
+ const getNewListOfAdditionalFilters = useCallback(
+ (dataSource: DataSource): string[] =>
+ mapOfFilters[dataSource].map((item) => item.text),
+ [],
+ );
+
+ const handleChangeAggregatorAttribute = useCallback(
+ (value: BaseAutocompleteData): void => {
+ const newQuery: IBuilderQuery = {
+ ...query,
+ aggregateAttribute: value,
+ having: [],
+ };
+
+ handleSetQueryData(index, newQuery);
+ },
+ [index, query, handleSetQueryData],
+ );
+
+ const handleChangeDataSource = useCallback(
+ (nextSource: DataSource): void => {
+ const newOperators = getOperatorsBySourceAndPanelType({
+ dataSource: nextSource,
+ panelType,
+ });
+
+ const entries = Object.entries(initialQueryBuilderFormValues).filter(
+ ([key]) => key !== 'queryName' && key !== 'expression',
+ );
+
+ const initCopyResult = Object.fromEntries(entries);
+
+ const newQuery: IBuilderQuery = {
+ ...query,
+ ...initCopyResult,
+ dataSource: nextSource,
+ aggregateOperator: newOperators[0].value,
+ };
+
+ setOperators(newOperators);
+ handleSetQueryData(index, newQuery);
+ },
+ [index, query, panelType, handleSetQueryData],
+ );
+
+ const handleDeleteQuery = useCallback(() => {
+ removeEntityByIndex('queryData', index);
+ }, [removeEntityByIndex, index]);
+
+ const handleChangeQueryData: HandleChangeQueryData = useCallback(
+ (key, value) => {
+ const newQuery: IBuilderQuery = {
+ ...query,
+ [key]: value,
+ };
+
+ handleSetQueryData(index, newQuery);
+ },
+ [query, index, handleSetQueryData],
+ );
+
+ const isMetricsDataSource = useMemo(
+ () => query.dataSource === DataSource.METRICS,
+ [query.dataSource],
+ );
+
+ useEffect(() => {
+ const initialOperators = getOperatorsBySourceAndPanelType({
+ dataSource,
+ panelType,
+ });
+ setOperators(initialOperators);
+ }, [dataSource, panelType]);
+
+ useEffect(() => {
+ const additionalFilters = getNewListOfAdditionalFilters(dataSource);
+
+ setListOfAdditionalFilters(additionalFilters);
+ }, [dataSource, aggregateOperator, getNewListOfAdditionalFilters]);
+
+ return {
+ isMetricsDataSource,
+ operators,
+ listOfAdditionalFilters,
+ handleChangeOperator,
+ handleChangeAggregatorAttribute,
+ handleChangeDataSource,
+ handleDeleteQuery,
+ handleChangeQueryData,
+ };
+};
diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts
new file mode 100644
index 0000000000..c848373b6c
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts
@@ -0,0 +1,35 @@
+import {
+ getRemovePrefixFromKey,
+ getTagToken,
+} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+import { useMemo } from 'react';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+type ICurrentKeyAndOperator = [string, string, string[]];
+
+export const useSetCurrentKeyAndOperator = (
+ value: string,
+ keys: BaseAutocompleteData[],
+): ICurrentKeyAndOperator => {
+ const [key, operator, result] = useMemo(() => {
+ let key = '';
+ let operator = '';
+ let result: string[] = [];
+
+ if (value) {
+ const { tagKey, tagOperator, tagValue } = getTagToken(value);
+ const isSuggestKey = keys?.some(
+ (el) => el?.key === getRemovePrefixFromKey(tagKey),
+ );
+ if (isSuggestKey || keys.length === 0) {
+ key = tagKey || '';
+ operator = tagOperator || '';
+ result = tagValue || [];
+ }
+ }
+
+ return [key, operator, result];
+ }, [value, keys]);
+
+ return [key, operator, result];
+};
diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts
new file mode 100644
index 0000000000..9989275895
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useTag.ts
@@ -0,0 +1,76 @@
+import {
+ getOperatorFromValue,
+ isExistsNotExistsOperator,
+ isInNInOperator,
+} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import * as Papa from 'papaparse';
+import { useCallback, useEffect, useState } from 'react';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+
+type IUseTag = {
+ handleAddTag: (value: string) => void;
+ handleClearTag: (value: string) => void;
+ tags: string[];
+ updateTag: (value: string) => void;
+};
+
+/**
+ * A custom React hook for handling tags.
+ * @param {string} key - A string value to identify tags.
+ * @param {boolean} isValidTag - A boolean value to indicate whether the tag is valid.
+ * @param {function} handleSearch - A callback function to handle search.
+ * @returns {IUseTag} The return object containing handlers and tags.
+ */
+
+export const useTag = (
+ key: string,
+ isValidTag: boolean,
+ handleSearch: (value: string) => void,
+ query: IBuilderQuery,
+ setSearchKey: (value: string) => void,
+): IUseTag => {
+ const [tags, setTags] = useState([]);
+
+ const updateTag = (value: string): void => {
+ const newTags = tags?.filter((item: string) => item !== value);
+ setTags(newTags);
+ };
+
+ /**
+ * Adds a new tag to the tag list.
+ * @param {string} value - The tag value to be added.
+ */
+ const handleAddTag = useCallback(
+ (value: string): void => {
+ if ((value && key && isValidTag) || isExistsNotExistsOperator(value)) {
+ setTags((prevTags) => [...prevTags, value]);
+ handleSearch('');
+ setSearchKey('');
+ }
+ },
+ [key, isValidTag, handleSearch, setSearchKey],
+ );
+
+ /**
+ * Removes a tag from the tag list.
+ * @param {string} value - The tag value to be removed.
+ */
+ const handleClearTag = useCallback((value: string): void => {
+ setTags((prevTags) => prevTags.filter((v) => v !== value));
+ }, []);
+
+ useEffect(() => {
+ const initialTags = (query?.filters?.items || []).map((ele) => {
+ if (isInNInOperator(getOperatorFromValue(ele.op))) {
+ const csvString = Papa.unparse([ele.value]);
+ return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${csvString}`;
+ }
+ return `${ele.key?.key} ${getOperatorFromValue(ele.op)} ${ele.value}`;
+ });
+ setTags(initialTags);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return { handleAddTag, handleClearTag, tags, updateTag };
+};
diff --git a/frontend/src/hooks/queryBuilder/useTagValidation.ts b/frontend/src/hooks/queryBuilder/useTagValidation.ts
new file mode 100644
index 0000000000..86aed50280
--- /dev/null
+++ b/frontend/src/hooks/queryBuilder/useTagValidation.ts
@@ -0,0 +1,33 @@
+import { QUERY_BUILDER_SEARCH_VALUES } from 'constants/queryBuilder';
+import { useMemo } from 'react';
+
+import { useIsValidTag } from './useIsValidTag';
+import { useOperatorType } from './useOperatorType';
+
+type ITagValidation = {
+ isValidTag: boolean;
+ isExist: boolean;
+ isValidOperator: boolean;
+ isMulti: boolean;
+};
+
+export const useTagValidation = (
+ operator: string,
+ result: string[],
+): ITagValidation => {
+ const operatorType = useOperatorType(operator);
+ const resultLength =
+ operatorType === 'SINGLE_VALUE' ? [result]?.length : result?.length;
+ const isValidTag = useIsValidTag(operatorType, resultLength);
+
+ const { isExist, isValidOperator, isMulti } = useMemo(() => {
+ const isExist = operatorType === QUERY_BUILDER_SEARCH_VALUES.NON;
+ const isValidOperator =
+ operatorType !== QUERY_BUILDER_SEARCH_VALUES.NOT_VALID;
+ const isMulti = operatorType === QUERY_BUILDER_SEARCH_VALUES.MULTIPLY;
+
+ return { isExist, isValidOperator, isMulti };
+ }, [operatorType]);
+
+ return { isValidTag, isExist, isValidOperator, isMulti };
+};
diff --git a/frontend/src/hooks/useDarkMode/index.tsx b/frontend/src/hooks/useDarkMode/index.tsx
index 50aa765eb1..8b58b1f77f 100644
--- a/frontend/src/hooks/useDarkMode/index.tsx
+++ b/frontend/src/hooks/useDarkMode/index.tsx
@@ -3,7 +3,14 @@ import { ThemeConfig } from 'antd/es/config-provider/context';
import get from 'api/browser/localstorage/get';
import set from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
-import React, { createContext, useCallback, useMemo, useState } from 'react';
+import {
+ createContext,
+ ReactNode,
+ useCallback,
+ useContext,
+ useMemo,
+ useState,
+} from 'react';
import { THEME_MODE } from './constant';
@@ -37,7 +44,7 @@ export function ThemeProvider({ children }: ThemeProviderProps): JSX.Element {
}
interface ThemeProviderProps {
- children: React.ReactNode;
+ children: ReactNode;
}
interface ThemeMode {
@@ -46,13 +53,13 @@ interface ThemeMode {
}
export const useThemeMode = (): ThemeMode => {
- const { theme, toggleTheme } = React.useContext(ThemeContext);
+ const { theme, toggleTheme } = useContext(ThemeContext);
return { theme, toggleTheme };
};
export const useIsDarkMode = (): boolean => {
- const { theme } = React.useContext(ThemeContext);
+ const { theme } = useContext(ThemeContext);
return theme === THEME_MODE.DARK;
};
diff --git a/frontend/src/hooks/useDebounce.tsx b/frontend/src/hooks/useDebounce.tsx
new file mode 100644
index 0000000000..88214a43b1
--- /dev/null
+++ b/frontend/src/hooks/useDebounce.tsx
@@ -0,0 +1,17 @@
+import { useEffect, useState } from 'react';
+
+export default function useDebounce(value: T, delay: number): T {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return (): void => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
diff --git a/frontend/src/hooks/useFeatureFlag.ts b/frontend/src/hooks/useFeatureFlag.ts
deleted file mode 100644
index a031b061cc..0000000000
--- a/frontend/src/hooks/useFeatureFlag.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import _get from 'lodash-es/get';
-import { useSelector } from 'react-redux';
-import { AppState } from 'store/reducers';
-import AppReducer from 'types/reducer/app';
-
-const useFeatureFlag = (flagKey: string): boolean => {
- const { featureFlags } = useSelector(
- (state) => state.app,
- );
- return _get(featureFlags, flagKey, false);
-};
-
-export default useFeatureFlag;
diff --git a/frontend/src/hooks/useFeatureFlag/constant.ts b/frontend/src/hooks/useFeatureFlag/constant.ts
new file mode 100644
index 0000000000..f7cbe7b2bb
--- /dev/null
+++ b/frontend/src/hooks/useFeatureFlag/constant.ts
@@ -0,0 +1,9 @@
+export const MESSAGE = {
+ PANEL:
+ 'You have exceeded the number of logs and traces based panels using query builder that are allowed in the community edition.',
+ ALERT:
+ 'You have exceeded the number of alerts that are allowed in the community edition.',
+ WIDGET: 'You have reached limit of {{widget}} free widgets.',
+ CREATE_DASHBOARD:
+ 'You have reached limit of creating the query builder based dashboard panels.',
+};
diff --git a/frontend/src/hooks/useFeatureFlag/index.ts b/frontend/src/hooks/useFeatureFlag/index.ts
new file mode 100644
index 0000000000..42e9247126
--- /dev/null
+++ b/frontend/src/hooks/useFeatureFlag/index.ts
@@ -0,0 +1,7 @@
+import { MESSAGE } from './constant';
+import useFeatureFlag from './useFeatureFlag';
+import useIsFeatureDisabled from './useIsFeatureDisabled';
+
+export default useFeatureFlag;
+
+export { MESSAGE, useIsFeatureDisabled };
diff --git a/frontend/src/hooks/useFeatureFlag/useFeatureFlag.ts b/frontend/src/hooks/useFeatureFlag/useFeatureFlag.ts
new file mode 100644
index 0000000000..001e81553c
--- /dev/null
+++ b/frontend/src/hooks/useFeatureFlag/useFeatureFlag.ts
@@ -0,0 +1,29 @@
+import { FeatureKeys } from 'constants/features';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
+import AppReducer from 'types/reducer/app';
+
+const useFeatureFlag = (
+ flagKey: keyof typeof FeatureKeys,
+): FlatArray | undefined => {
+ const { featureResponse = [] } = useSelector(
+ (state) => state.app,
+ );
+
+ if (featureResponse === null) return undefined;
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ const featureResponseData = featureResponse.data as FeatureFlagPayload;
+
+ const feature = featureResponseData?.find((flag) => flag.name === flagKey);
+
+ if (!feature) {
+ return undefined;
+ }
+
+ return feature;
+};
+
+export default useFeatureFlag;
diff --git a/frontend/src/hooks/useFeatureFlag/useIsFeatureDisabled.ts b/frontend/src/hooks/useFeatureFlag/useIsFeatureDisabled.ts
new file mode 100644
index 0000000000..3e9caa299b
--- /dev/null
+++ b/frontend/src/hooks/useFeatureFlag/useIsFeatureDisabled.ts
@@ -0,0 +1,11 @@
+import { FeatureKeys } from 'constants/features';
+
+import useFeatureFlag from './useFeatureFlag';
+
+const useIsFeatureDisabled = (props: keyof typeof FeatureKeys): boolean => {
+ const feature = useFeatureFlag(props);
+
+ return !feature?.active ?? false;
+};
+
+export default useIsFeatureDisabled;
diff --git a/frontend/src/hooks/useInterval.test.ts b/frontend/src/hooks/useInterval.test.ts
new file mode 100644
index 0000000000..c6626b2224
--- /dev/null
+++ b/frontend/src/hooks/useInterval.test.ts
@@ -0,0 +1,93 @@
+import { act, renderHook } from '@testing-library/react';
+
+import useInterval from './useInterval';
+
+jest.useFakeTimers();
+
+describe('useInterval', () => {
+ test('calls the callback with a given delay', () => {
+ const callback = jest.fn();
+ const delay = 1000;
+
+ renderHook(() => useInterval(callback, delay));
+
+ expect(callback).toHaveBeenCalledTimes(0);
+
+ act(() => {
+ jest.advanceTimersByTime(delay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ act(() => {
+ jest.advanceTimersByTime(delay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+ });
+
+ test('does not call the callback if not enabled', () => {
+ const callback = jest.fn();
+ const delay = 1000;
+ const enabled = false;
+
+ renderHook(() => useInterval(callback, delay, enabled));
+
+ act(() => {
+ jest.advanceTimersByTime(delay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(0);
+ });
+
+ test('cleans up the interval when unmounted', () => {
+ const callback = jest.fn();
+ const delay = 1000;
+
+ const { unmount } = renderHook(() => useInterval(callback, delay));
+
+ act(() => {
+ jest.advanceTimersByTime(delay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ unmount();
+
+ act(() => {
+ jest.advanceTimersByTime(delay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+ });
+
+ test('updates the interval when delay changes', () => {
+ const callback = jest.fn();
+ const initialDelay = 1000;
+ const newDelay = 2000;
+
+ const { rerender } = renderHook(({ delay }) => useInterval(callback, delay), {
+ initialProps: { delay: initialDelay },
+ });
+
+ act(() => {
+ jest.advanceTimersByTime(initialDelay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ rerender({ delay: newDelay });
+
+ act(() => {
+ jest.advanceTimersByTime(initialDelay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(1);
+
+ act(() => {
+ jest.advanceTimersByTime(newDelay - initialDelay);
+ });
+
+ expect(callback).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/frontend/src/hooks/useLicense/constant.ts b/frontend/src/hooks/useLicense/constant.ts
new file mode 100644
index 0000000000..7389df50df
--- /dev/null
+++ b/frontend/src/hooks/useLicense/constant.ts
@@ -0,0 +1,3 @@
+export const LICENSE_PLAN_KEY = {
+ ENTERPRISE_PLAN: 'ENTERPRISE_PLAN',
+};
diff --git a/frontend/src/hooks/useLicense/index.ts b/frontend/src/hooks/useLicense/index.ts
new file mode 100644
index 0000000000..ca8f821267
--- /dev/null
+++ b/frontend/src/hooks/useLicense/index.ts
@@ -0,0 +1,6 @@
+import { LICENSE_PLAN_KEY } from './constant';
+import useLicense from './useLicense';
+
+export default useLicense;
+
+export { LICENSE_PLAN_KEY };
diff --git a/frontend/src/hooks/useLicense/useLicense.tsx b/frontend/src/hooks/useLicense/useLicense.tsx
new file mode 100644
index 0000000000..0abcba5ce1
--- /dev/null
+++ b/frontend/src/hooks/useLicense/useLicense.tsx
@@ -0,0 +1,25 @@
+import getAll from 'api/licenses/getAll';
+import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
+import { useQuery, UseQueryResult } from 'react-query';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { ErrorResponse, SuccessResponse } from 'types/api';
+import { PayloadProps } from 'types/api/licenses/getAll';
+import AppReducer from 'types/reducer/app';
+
+const useLicense = (): UseLicense => {
+ const { user } = useSelector((state) => state.app);
+
+ return useQuery({
+ queryFn: getAll,
+ queryKey: [REACT_QUERY_KEY.GET_ALL_LICENCES, user?.email],
+ enabled: !!user?.email,
+ });
+};
+
+type UseLicense = UseQueryResult<
+ SuccessResponse | ErrorResponse,
+ unknown
+>;
+
+export default useLicense;
diff --git a/frontend/src/hooks/useNotifications.tsx b/frontend/src/hooks/useNotifications.tsx
index 8ba37c8d5c..787479e26e 100644
--- a/frontend/src/hooks/useNotifications.tsx
+++ b/frontend/src/hooks/useNotifications.tsx
@@ -1,6 +1,6 @@
import { notification } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
-import React, { createContext, useContext, useMemo } from 'react';
+import { createContext, useContext, useMemo } from 'react';
type Notification = {
notifications: NotificationInstance;
diff --git a/frontend/src/hooks/usePreviousValue.test.tsx b/frontend/src/hooks/usePreviousValue.test.tsx
new file mode 100644
index 0000000000..efc3073af7
--- /dev/null
+++ b/frontend/src/hooks/usePreviousValue.test.tsx
@@ -0,0 +1,43 @@
+import { renderHook } from '@testing-library/react';
+
+import usePreviousValue from './usePreviousValue';
+
+describe('usePreviousValue', () => {
+ test('returns the previous value of a given variable', () => {
+ const { result, rerender } = renderHook(
+ ({ value }) => usePreviousValue(value),
+ {
+ initialProps: { value: 1 },
+ baseElement: document.body,
+ },
+ );
+
+ expect(result.current).toBeUndefined();
+
+ rerender({ value: 2 });
+
+ expect(result.current).toBe(1);
+
+ rerender({ value: 3 });
+
+ expect(result.current).toBe(2);
+ });
+
+ test('works with different types of values', () => {
+ const { result, rerender } = renderHook(
+ ({ value }) => usePreviousValue(value),
+ {
+ initialProps: { value: 'a' },
+ },
+ );
+
+ expect(result.current).toBeUndefined();
+
+ rerender({ value: 'b' });
+
+ expect(result.current).toBe('a');
+
+ rerender({ value: 'c' });
+ expect(result.current).toBe('b');
+ });
+});
diff --git a/frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx b/frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx
index 11b298074e..e027c70b8f 100644
--- a/frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx
+++ b/frontend/src/hooks/useResourceAttribute/ResourceProvider.tsx
@@ -2,7 +2,7 @@ import { useMachine } from '@xstate/react';
import ROUTES from 'constants/routes';
import { encode } from 'js-base64';
import history from 'lib/history';
-import React, { useCallback, useMemo, useState } from 'react';
+import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { whilelistedKeys } from './config';
@@ -182,7 +182,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
}
interface Props {
- children: React.ReactNode;
+ children: ReactNode;
}
export default ResourceProvider;
diff --git a/frontend/src/hooks/useResourceAttribute/utils.ts b/frontend/src/hooks/useResourceAttribute/utils.ts
index c76cf534e3..2be45582c0 100644
--- a/frontend/src/hooks/useResourceAttribute/utils.ts
+++ b/frontend/src/hooks/useResourceAttribute/utils.ts
@@ -11,7 +11,7 @@ import {
} from 'hooks/useResourceAttribute/types';
import { decode } from 'js-base64';
import history from 'lib/history';
-import { IQueryBuilderTagFilterItems } from 'types/api/dashboard/getAll';
+import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { OperatorValues, Tags } from 'types/reducer/trace';
import { v4 as uuid } from 'uuid';
@@ -63,10 +63,10 @@ export const convertRawQueriesToTraceSelectedTags = (
/* Convert resource attributes to tagFilter items for queryBuilder */
export const resourceAttributesToTagFilterItems = (
queries: IResourceAttribute[],
-): IQueryBuilderTagFilterItems[] =>
+): TagFilterItem[] =>
queries.map((res) => ({
id: `${res.id}`,
- key: `${res.tagKey}`,
+ key: { key: res.tagKey, isColumn: false, type: null, dataType: null },
op: `${res.operator}`,
value: `${res.tagValue}`.split(','),
}));
diff --git a/frontend/src/hooks/useUrlQuery.test.tsx b/frontend/src/hooks/useUrlQuery.test.tsx
new file mode 100644
index 0000000000..3baf336b83
--- /dev/null
+++ b/frontend/src/hooks/useUrlQuery.test.tsx
@@ -0,0 +1,56 @@
+import { act, renderHook } from '@testing-library/react';
+import { createMemoryHistory } from 'history';
+import { Router } from 'react-router-dom';
+
+import useUrlQuery from './useUrlQuery';
+
+describe('useUrlQuery', () => {
+ test('returns URLSearchParams object for the current URL search', () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/test?param1=value1¶m2=value2'],
+ });
+
+ const { result } = renderHook(() => useUrlQuery(), {
+ wrapper: ({ children }) => {children} ,
+ });
+
+ expect(result.current.get('param1')).toBe('value1');
+ expect(result.current.get('param2')).toBe('value2');
+ });
+
+ test('updates URLSearchParams object when URL search changes', () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/test?param1=value1'],
+ });
+
+ const { result, rerender } = renderHook(() => useUrlQuery(), {
+ wrapper: ({ children }) => {children} ,
+ });
+
+ expect(result.current.get('param1')).toBe('value1');
+ expect(result.current.get('param2')).toBe(null);
+
+ act(() => {
+ history.push('/test?param1=newValue1¶m2=value2');
+ });
+
+ rerender();
+
+ expect(result.current.get('param1')).toBe('newValue1');
+ expect(result.current.get('param2')).toBe('value2');
+ });
+
+ test('returns empty URLSearchParams object when no query parameters are present', () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/test'],
+ });
+
+ const { result } = renderHook(() => useUrlQuery(), {
+ wrapper: ({ children }) => {children} ,
+ });
+
+ expect(result.current.toString()).toBe('');
+ expect(result.current.get('param1')).toBe(null);
+ expect(result.current.get('param2')).toBe(null);
+ });
+});
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 3ce9a87de2..d7985e8dc6 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -3,7 +3,6 @@ import './ReactI18';
import AppRoutes from 'AppRoutes';
import { ThemeProvider } from 'hooks/useDarkMode';
-import React from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
diff --git a/frontend/src/lib/__fixtures__/getRandomColor.ts b/frontend/src/lib/__fixtures__/getRandomColor.ts
new file mode 100644
index 0000000000..2ebbecae15
--- /dev/null
+++ b/frontend/src/lib/__fixtures__/getRandomColor.ts
@@ -0,0 +1,50 @@
+import { themeColors } from 'constants/theme';
+import { SIGNOZ_UI_COLOR_HEX } from 'lib/getRandomColor';
+import { Span } from 'types/api/trace/getTraceItem';
+
+const spans: Span[] = [
+ [
+ 1,
+ 'span1',
+ 'trace1',
+ 'serviceA',
+ 'op1',
+ '100',
+ '200',
+ [SIGNOZ_UI_COLOR_HEX],
+ [themeColors.chartcolors.turquoise],
+ [''],
+ [''],
+ false,
+ ],
+ [
+ 2,
+ 'span2',
+ 'trace2',
+ 'serviceB',
+ 'op2',
+ '200',
+ '300',
+ [SIGNOZ_UI_COLOR_HEX],
+ [themeColors.chartcolors.turquoise],
+ [''],
+ [''],
+ false,
+ ],
+ [
+ 3,
+ 'span3',
+ 'trace3',
+ 'serviceC',
+ 'op3',
+ '300',
+ '400',
+ [],
+ [],
+ [''],
+ [''],
+ false,
+ ],
+];
+
+export default spans;
diff --git a/frontend/src/lib/getLabelName.ts b/frontend/src/lib/getLabelName.ts
index 44f0173450..bfb4967428 100644
--- a/frontend/src/lib/getLabelName.ts
+++ b/frontend/src/lib/getLabelName.ts
@@ -1,7 +1,7 @@
-import { QueryData } from 'types/api/widgets/getQuery';
+import { SeriesItem } from 'types/api/widgets/getQuery';
const getLabelName = (
- metric: QueryData['metric'],
+ metric: SeriesItem['labels'],
query: string,
legends: string,
): string => {
diff --git a/frontend/src/lib/getMaxMinTime.ts b/frontend/src/lib/getMaxMinTime.ts
index 483613c440..a6bd413729 100644
--- a/frontend/src/lib/getMaxMinTime.ts
+++ b/frontend/src/lib/getMaxMinTime.ts
@@ -1,3 +1,4 @@
+import { PANEL_TYPES } from 'constants/queryBuilder';
import { GlobalTime } from 'types/actions/globalTime';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -6,7 +7,7 @@ const GetMaxMinTime = ({
minTime,
maxTime,
}: GetMaxMinProps): GlobalTime => {
- if (graphType === 'VALUE') {
+ if (graphType === PANEL_TYPES.VALUE) {
return {
maxTime,
minTime: maxTime,
diff --git a/frontend/src/lib/getRandomColor.test.ts b/frontend/src/lib/getRandomColor.test.ts
new file mode 100644
index 0000000000..906ac95f06
--- /dev/null
+++ b/frontend/src/lib/getRandomColor.test.ts
@@ -0,0 +1,29 @@
+import { themeColors } from 'constants/theme';
+import { Span } from 'types/api/trace/getTraceItem';
+
+import spans from './__fixtures__/getRandomColor';
+import { colors, spanServiceNameToColorMapping } from './getRandomColor';
+
+describe('spanServiceNameToColorMapping', () => {
+ test('should map span services to colors', () => {
+ const expectedServiceToColorMap = {
+ serviceA: themeColors.chartcolors.turquoise,
+ serviceB: themeColors.chartcolors.turquoise,
+ serviceC: colors[2], // 2 is because we have already used 0 and 1 in the above services,
+ };
+
+ const result = spanServiceNameToColorMapping(spans);
+
+ expect(result).toEqual(expectedServiceToColorMap);
+ });
+
+ test('should return an empty object when input is an empty array', () => {
+ const spans: Span[] = [];
+
+ const expectedServiceToColorMap = {};
+
+ const result = spanServiceNameToColorMapping(spans);
+
+ expect(result).toEqual(expectedServiceToColorMap);
+ });
+});
diff --git a/frontend/src/lib/getRandomColor.ts b/frontend/src/lib/getRandomColor.ts
index fc85331af7..6cca527b32 100644
--- a/frontend/src/lib/getRandomColor.ts
+++ b/frontend/src/lib/getRandomColor.ts
@@ -13,17 +13,33 @@ const getRandomColor = (): string => {
return colors[index];
};
+export const SIGNOZ_UI_COLOR_HEX = 'signoz_ui_color_hex';
+
export const spanServiceNameToColorMapping = (
spans: Span[],
): { [key: string]: string } => {
- const serviceNameSet = new Set();
+ const allServiceMap = new Map();
+
spans.forEach((spanItem) => {
- serviceNameSet.add(spanItem[3]);
+ const signozUiColorKeyIndex = spanItem[7].findIndex(
+ (span) => span === SIGNOZ_UI_COLOR_HEX,
+ );
+
+ allServiceMap.set(
+ spanItem[3],
+ signozUiColorKeyIndex === -1
+ ? undefined
+ : spanItem[8][signozUiColorKeyIndex],
+ );
});
+
const serviceToColorMap: { [key: string]: string } = {};
- Array.from(serviceNameSet).forEach((serviceName, idx) => {
- serviceToColorMap[`${serviceName}`] = colors[idx % colors.length];
+
+ Array.from(allServiceMap).forEach(([serviceName, signozColor], idx) => {
+ serviceToColorMap[`${serviceName}`] =
+ signozColor || colors[idx % colors.length];
});
+
return serviceToColorMap;
};
diff --git a/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts
new file mode 100644
index 0000000000..dcb103d6cb
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/convertNewDataToOld.ts
@@ -0,0 +1,39 @@
+import {
+ MetricRangePayloadProps,
+ MetricRangePayloadV3,
+} from 'types/api/metrics/getQueryRange';
+import { QueryData } from 'types/api/widgets/getQuery';
+
+export const convertNewDataToOld = (
+ newData: MetricRangePayloadV3,
+): MetricRangePayloadProps => {
+ const { result, resultType } = newData.data;
+ const oldResult: MetricRangePayloadProps['data']['result'] = [];
+
+ result.forEach((item) => {
+ if (item.series) {
+ item.series.forEach((serie) => {
+ const values: QueryData['values'] = serie.values.reduce<
+ QueryData['values']
+ >((acc, currentInfo) => {
+ const renderValues: [number, string] = [
+ currentInfo.timestamp / 1000,
+ currentInfo.value,
+ ];
+
+ return [...acc, renderValues];
+ }, []);
+ const result: QueryData = {
+ metric: serie.labels,
+ values,
+ queryName: item.queryName,
+ };
+
+ oldResult.push(result);
+ });
+ }
+ });
+ const oldResultType = resultType;
+
+ return { data: { result: oldResult, resultType: oldResultType } };
+};
diff --git a/frontend/src/lib/newQueryBuilder/createNewBuilderItemName.ts b/frontend/src/lib/newQueryBuilder/createNewBuilderItemName.ts
new file mode 100644
index 0000000000..0e4dbd0429
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/createNewBuilderItemName.ts
@@ -0,0 +1,17 @@
+type CreateNewBuilderItemNameParams = {
+ existNames: string[];
+ sourceNames: string[];
+};
+
+export const createNewBuilderItemName = ({
+ existNames,
+ sourceNames,
+}: CreateNewBuilderItemNameParams): string => {
+ for (let i = 0; i < sourceNames.length; i += 1) {
+ if (!existNames.includes(sourceNames[i])) {
+ return sourceNames[i];
+ }
+ }
+
+ return '';
+};
diff --git a/frontend/src/lib/newQueryBuilder/getFilterObjectValue.ts b/frontend/src/lib/newQueryBuilder/getFilterObjectValue.ts
new file mode 100644
index 0000000000..e50cf75c21
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/getFilterObjectValue.ts
@@ -0,0 +1,33 @@
+import { TYPE_ADDON_REGEXP } from 'constants/regExp';
+import {
+ AutocompleteType,
+ BaseAutocompleteData,
+} from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+const getType = (str: string): AutocompleteType | null => {
+ const types: AutocompleteType[] = ['tag', 'resource'];
+
+ let currentType: AutocompleteType | null = null;
+
+ types.forEach((type) => {
+ if (str.includes(type)) {
+ currentType = type;
+ }
+ });
+
+ return currentType;
+};
+
+export const getFilterObjectValue = (
+ value: string,
+): Omit => {
+ const type = getType(value);
+
+ if (!type) {
+ return { isColumn: true, key: value, type: null };
+ }
+
+ const splittedValue = value.split(TYPE_ADDON_REGEXP);
+
+ return { isColumn: false, key: splittedValue[1], type };
+};
diff --git a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts
new file mode 100644
index 0000000000..d9a9753d6e
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts
@@ -0,0 +1,25 @@
+import { mapOfOperators, PANEL_TYPES } from 'constants/queryBuilder';
+import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
+import { DataSource, StringOperators } from 'types/common/queryBuilder';
+import { SelectOption } from 'types/common/select';
+
+type GetQueryOperatorsParams = {
+ dataSource: DataSource;
+ panelType: GRAPH_TYPES;
+};
+
+// Modify this function if need special conditions for filtering of the operators
+export const getOperatorsBySourceAndPanelType = ({
+ dataSource,
+ panelType,
+}: GetQueryOperatorsParams): SelectOption[] => {
+ let operatorsByDataSource = mapOfOperators[dataSource];
+
+ if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) {
+ operatorsByDataSource = operatorsByDataSource.filter(
+ (operator) => operator.value !== StringOperators.NOOP,
+ );
+ }
+
+ return operatorsByDataSource;
+};
diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts
new file mode 100644
index 0000000000..04b1add490
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi.ts
@@ -0,0 +1,27 @@
+import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
+import { FORMULA_REGEXP } from 'constants/regExp';
+import {
+ IBuilderFormula,
+ IBuilderQuery,
+} from 'types/api/queryBuilder/queryBuilderData';
+import { QueryBuilderData } from 'types/common/queryBuilder';
+import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
+
+export const mapQueryDataFromApi = (
+ data: QueryDataResourse,
+): QueryBuilderData => {
+ const queryData: QueryBuilderData['queryData'] = [];
+ const queryFormulas: QueryBuilderData['queryFormulas'] = [];
+
+ Object.entries(data).forEach(([, value]) => {
+ if (FORMULA_REGEXP.test(value.queryName)) {
+ const formula = value as IBuilderFormula;
+ queryFormulas.push(formula);
+ } else {
+ const query = value as IBuilderQuery;
+ queryData.push({ ...initialQueryBuilderFormValues, ...query });
+ }
+ });
+
+ return { queryData, queryFormulas };
+};
diff --git a/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts
new file mode 100644
index 0000000000..17707df911
--- /dev/null
+++ b/frontend/src/lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi.ts
@@ -0,0 +1,49 @@
+import { QueryBuilderData } from 'types/common/queryBuilder';
+import {
+ MapFormula,
+ MapQuery,
+ MapQueryDataToApiResult,
+} from 'types/common/queryBuilderMappers.types';
+
+export const mapQueryDataToApi = (
+ data: QueryBuilderData,
+): MapQueryDataToApiResult => {
+ const newLegendMap: Record = {};
+
+ const preparedQueryData: MapQuery = data.queryData.reduce(
+ (acc, query) => {
+ const newResult: MapQuery = {
+ ...acc,
+ [query.queryName]: {
+ ...query,
+ },
+ };
+
+ newLegendMap[query.queryName] = query.legend;
+
+ return newResult;
+ },
+ {},
+ );
+
+ const preparedFormulaData: MapFormula = data.queryFormulas.reduce(
+ (acc, formula) => {
+ const newResult: MapFormula = {
+ ...acc,
+ [formula.queryName]: {
+ ...formula,
+ },
+ };
+
+ newLegendMap[formula.queryName] = formula.legend;
+
+ return newResult;
+ },
+ {},
+ );
+
+ return {
+ data: { ...preparedQueryData, ...preparedFormulaData },
+ newLegendMap,
+ };
+};
diff --git a/frontend/src/lib/query/findDataTypeOfOperator.ts b/frontend/src/lib/query/findDataTypeOfOperator.ts
new file mode 100644
index 0000000000..9833c0c23b
--- /dev/null
+++ b/frontend/src/lib/query/findDataTypeOfOperator.ts
@@ -0,0 +1,22 @@
+import { operatorsByTypes } from 'constants/queryBuilder';
+import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
+export const findDataTypeOfOperator = (value: string): LocalDataType | null => {
+ const entries = Object.entries(operatorsByTypes) as [
+ LocalDataType,
+ string[],
+ ][];
+
+ for (let i = 0; i < entries.length; i += 1) {
+ for (let j = 0; j < entries[i][1].length; j += 1) {
+ const currentOperator = entries[i][1][j];
+ const type = entries[i][0];
+
+ if (currentOperator === value) {
+ return type;
+ }
+ }
+ }
+
+ return null;
+};
diff --git a/frontend/src/lib/query/transformQueryBuilderData.ts b/frontend/src/lib/query/transformQueryBuilderData.ts
new file mode 100644
index 0000000000..82b034bb66
--- /dev/null
+++ b/frontend/src/lib/query/transformQueryBuilderData.ts
@@ -0,0 +1,34 @@
+import { OPERATORS } from 'constants/queryBuilder';
+import { Having } from 'types/api/queryBuilder/queryBuilderData';
+
+export const transformHavingToStringValue = (having: Having[]): string[] => {
+ const result: string[] = having.map((item) => {
+ const operator = Object.entries(OPERATORS).find(([key]) => key === item.op);
+ const value = Array.isArray(item.value) ? item.value.join(', ') : item.value;
+
+ return `${item.columnName} ${operator ? operator[1] : ''} ${value}`;
+ });
+
+ return result;
+};
+
+export const transformFromStringToHaving = (havingStr: string): Having => {
+ const [columnName, op, ...values] = havingStr.split(' ');
+
+ const operator = Object.entries(OPERATORS).find(([, value]) => value === op);
+
+ const currentValue = values.reduce((acc, strNum) => {
+ const num = parseFloat(strNum);
+ if (Number.isNaN(num)) {
+ return acc;
+ }
+
+ return [...acc, num];
+ }, []);
+
+ return {
+ columnName,
+ op: operator ? operator[0] : '',
+ value: currentValue.length > 1 ? currentValue : currentValue[0],
+ };
+};
diff --git a/frontend/src/modules/Servicemap/Map.tsx b/frontend/src/modules/Servicemap/Map.tsx
index 42c8a3d61e..b663aa5ed2 100644
--- a/frontend/src/modules/Servicemap/Map.tsx
+++ b/frontend/src/modules/Servicemap/Map.tsx
@@ -1,7 +1,7 @@
/* eslint-disable */
//@ts-nocheck
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React, { memo } from 'react';
+import { memo } from 'react';
import { ForceGraph2D } from 'react-force-graph';
import { getGraphData, getTooltip, transformLabel } from './utils';
diff --git a/frontend/src/modules/Servicemap/ServiceMap.tsx b/frontend/src/modules/Servicemap/ServiceMap.tsx
index 21b43c3733..5bb748188b 100644
--- a/frontend/src/modules/Servicemap/ServiceMap.tsx
+++ b/frontend/src/modules/Servicemap/ServiceMap.tsx
@@ -8,7 +8,7 @@ import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { whilelistedKeys } from 'hooks/useResourceAttribute/config';
import { IResourceAttribute } from 'hooks/useResourceAttribute/types';
-import React, { useEffect, useRef } from 'react';
+import { useEffect, useRef } from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
diff --git a/frontend/src/modules/Servicemap/index.tsx b/frontend/src/modules/Servicemap/index.tsx
index cf069e19f3..6c50cc0d26 100644
--- a/frontend/src/modules/Servicemap/index.tsx
+++ b/frontend/src/modules/Servicemap/index.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import ServiceMap from './ServiceMap';
function ServiceMapContainer(): JSX.Element {
diff --git a/frontend/src/modules/Usage/UsageExplorer.tsx b/frontend/src/modules/Usage/UsageExplorer.tsx
index 4550e9dac9..bc9bb457ab 100644
--- a/frontend/src/modules/Usage/UsageExplorer.tsx
+++ b/frontend/src/modules/Usage/UsageExplorer.tsx
@@ -3,7 +3,7 @@
import { Select, Space, Typography } from 'antd';
import Graph from 'components/Graph';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { GetService, getUsageData, UsageDataItem } from 'store/actions';
diff --git a/frontend/src/modules/Usage/index.tsx b/frontend/src/modules/Usage/index.tsx
index c2b629236e..5d664e90ef 100644
--- a/frontend/src/modules/Usage/index.tsx
+++ b/frontend/src/modules/Usage/index.tsx
@@ -1,5 +1,3 @@
-import React from 'react';
-
import { UsageExplorer } from './UsageExplorer';
function UsageExplorerContainer(): JSX.Element {
diff --git a/frontend/src/pages/AlertChannelCreate/index.tsx b/frontend/src/pages/AlertChannelCreate/index.tsx
index e2706a5a18..09d55d320e 100644
--- a/frontend/src/pages/AlertChannelCreate/index.tsx
+++ b/frontend/src/pages/AlertChannelCreate/index.tsx
@@ -4,7 +4,6 @@ import ROUTES from 'constants/routes';
import CreateAlertChannels from 'container/CreateAlertChannels';
import GeneralSettings from 'container/GeneralSettings';
import history from 'lib/history';
-import React from 'react';
import { useTranslation } from 'react-i18next';
function SettingsPage(): JSX.Element {
diff --git a/frontend/src/pages/AlertList/index.tsx b/frontend/src/pages/AlertList/index.tsx
index 8cfd48ed6c..336c399a2f 100644
--- a/frontend/src/pages/AlertList/index.tsx
+++ b/frontend/src/pages/AlertList/index.tsx
@@ -2,7 +2,6 @@ import { Tabs } from 'antd';
import AllAlertRules from 'container/ListAlertRules';
// import MapAlertChannels from 'container/MapAlertChannels';
import TriggeredAlerts from 'container/TriggeredAlerts';
-import React from 'react';
function AllAlertList(): JSX.Element {
const items = [
diff --git a/frontend/src/pages/AllErrors/index.tsx b/frontend/src/pages/AllErrors/index.tsx
index 2e7238bebc..863ba9d638 100644
--- a/frontend/src/pages/AllErrors/index.tsx
+++ b/frontend/src/pages/AllErrors/index.tsx
@@ -2,7 +2,6 @@ import RouteTab from 'components/RouteTab';
import ROUTES from 'constants/routes';
import AllErrorsContainer from 'container/AllError';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
-import React from 'react';
import { useTranslation } from 'react-i18next';
function AllErrors(): JSX.Element {
diff --git a/frontend/src/pages/ChannelsEdit/index.tsx b/frontend/src/pages/ChannelsEdit/index.tsx
index 69dd1ee2ce..5a4115e837 100644
--- a/frontend/src/pages/ChannelsEdit/index.tsx
+++ b/frontend/src/pages/ChannelsEdit/index.tsx
@@ -10,7 +10,6 @@ import {
WebhookType,
} from 'container/CreateAlertChannels/config';
import EditAlertChannels from 'container/EditAlertChannels';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
diff --git a/frontend/src/pages/CreateAlert/index.tsx b/frontend/src/pages/CreateAlert/index.tsx
index 51dbae66b3..1b5efbc496 100644
--- a/frontend/src/pages/CreateAlert/index.tsx
+++ b/frontend/src/pages/CreateAlert/index.tsx
@@ -1,5 +1,4 @@
import CreateAlertRule from 'container/CreateAlertRule';
-import React from 'react';
function CreateAlertPage(): JSX.Element {
return ;
diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx
index 4380070228..35ccabcec5 100644
--- a/frontend/src/pages/Dashboard/index.tsx
+++ b/frontend/src/pages/Dashboard/index.tsx
@@ -1,7 +1,7 @@
import { Space } from 'antd';
import ReleaseNote from 'components/ReleaseNote';
import ListOfAllDashboard from 'container/ListOfDashboard';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { connect } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { bindActionCreators } from 'redux';
diff --git a/frontend/src/pages/DashboardWidget/index.tsx b/frontend/src/pages/DashboardWidget/index.tsx
index 7e9f4e8f73..a4ed88e264 100644
--- a/frontend/src/pages/DashboardWidget/index.tsx
+++ b/frontend/src/pages/DashboardWidget/index.tsx
@@ -4,7 +4,7 @@ import ROUTES from 'constants/routes';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import NewWidget from 'container/NewWidget';
import history from 'lib/history';
-import React, { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
diff --git a/frontend/src/pages/EditRules/index.tsx b/frontend/src/pages/EditRules/index.tsx
index 7cdbbf9a1d..af884f5692 100644
--- a/frontend/src/pages/EditRules/index.tsx
+++ b/frontend/src/pages/EditRules/index.tsx
@@ -4,7 +4,7 @@ import ROUTES from 'constants/routes';
import EditRulesContainer from 'container/EditRules';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
@@ -18,13 +18,16 @@ function EditRules(): JSX.Element {
const isValidRuleId = ruleId !== null && String(ruleId).length !== 0;
- const { isLoading, data, isError } = useQuery(['ruleId', ruleId], {
- queryFn: () =>
- get({
- id: parseInt(ruleId || '', 10),
- }),
- enabled: isValidRuleId,
- });
+ const { isLoading, data, isRefetching, isError } = useQuery(
+ ['ruleId', ruleId],
+ {
+ queryFn: () =>
+ get({
+ id: parseInt(ruleId || '', 10),
+ }),
+ enabled: isValidRuleId,
+ },
+ );
const { notifications } = useNotifications();
@@ -45,7 +48,7 @@ function EditRules(): JSX.Element {
return {data?.error || t('something_went_wrong')}
;
}
- if (isLoading || !data?.payload) {
+ if (isLoading || isRefetching || !data?.payload) {
return ;
}
diff --git a/frontend/src/pages/ErrorDetails/index.tsx b/frontend/src/pages/ErrorDetails/index.tsx
index 94bd8e248e..203c6796ac 100644
--- a/frontend/src/pages/ErrorDetails/index.tsx
+++ b/frontend/src/pages/ErrorDetails/index.tsx
@@ -4,7 +4,7 @@ import getById from 'api/errors/getById';
import Spinner from 'components/Spinner';
import ROUTES from 'constants/routes';
import ErrorDetailsContainer from 'container/ErrorDetails';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
diff --git a/frontend/src/pages/GettingStarted/DocCard.tsx b/frontend/src/pages/GettingStarted/DocCard.tsx
index 70f9f3e430..6fc4a1f8e3 100644
--- a/frontend/src/pages/GettingStarted/DocCard.tsx
+++ b/frontend/src/pages/GettingStarted/DocCard.tsx
@@ -1,6 +1,5 @@
import { Typography } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import React from 'react';
import { Link } from 'react-router-dom';
import { DocCardContainer } from './styles';
diff --git a/frontend/src/pages/GettingStarted/Section.tsx b/frontend/src/pages/GettingStarted/Section.tsx
index 4a90b746de..cbddde9783 100644
--- a/frontend/src/pages/GettingStarted/Section.tsx
+++ b/frontend/src/pages/GettingStarted/Section.tsx
@@ -1,6 +1,5 @@
import { Col, Row, Typography } from 'antd';
import { map } from 'lodash-es';
-import React from 'react';
import DocCard from './DocCard';
import { TGetStartedContentSection } from './types';
diff --git a/frontend/src/pages/GettingStarted/index.tsx b/frontend/src/pages/GettingStarted/index.tsx
index cba67ec6c8..909a221bd4 100644
--- a/frontend/src/pages/GettingStarted/index.tsx
+++ b/frontend/src/pages/GettingStarted/index.tsx
@@ -1,5 +1,4 @@
import { Typography } from 'antd';
-import React from 'react';
import { GetStartedContent } from './renderConfig';
import DocSection from './Section';
diff --git a/frontend/src/pages/GettingStarted/renderConfig.tsx b/frontend/src/pages/GettingStarted/renderConfig.tsx
index 86aa6b5ed7..eccc8a9ba5 100644
--- a/frontend/src/pages/GettingStarted/renderConfig.tsx
+++ b/frontend/src/pages/GettingStarted/renderConfig.tsx
@@ -8,7 +8,6 @@ import {
} from '@ant-design/icons';
import { Typography } from 'antd';
import Slack from 'container/SideNav/Slack';
-import React from 'react';
import store from 'store';
import { TGetStartedContentSection } from './types';
diff --git a/frontend/src/pages/License/index.tsx b/frontend/src/pages/License/index.tsx
index 650098edf3..bd7134fa20 100644
--- a/frontend/src/pages/License/index.tsx
+++ b/frontend/src/pages/License/index.tsx
@@ -1,5 +1,4 @@
import Licenses from 'container/Licenses';
-import React from 'react';
function LicensePage(): JSX.Element {
return ;
diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx
index dbf271e9f4..c9be20d48e 100644
--- a/frontend/src/pages/Login/index.tsx
+++ b/frontend/src/pages/Login/index.tsx
@@ -4,7 +4,6 @@ import Spinner from 'components/Spinner';
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
import LoginContainer from 'container/Login';
import useURLQuery from 'hooks/useUrlQuery';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
@@ -24,7 +23,7 @@ function Login(): JSX.Element {
const versionResult = useQuery({
queryFn: getUserVersion,
- queryKey: 'getUserVersion',
+ queryKey: ['getUserVersion', jwt],
enabled: !isLoggedIn,
});
diff --git a/frontend/src/pages/Logs/PopoverContent.tsx b/frontend/src/pages/Logs/PopoverContent.tsx
index 8dcf819d3a..9f0214ad64 100644
--- a/frontend/src/pages/Logs/PopoverContent.tsx
+++ b/frontend/src/pages/Logs/PopoverContent.tsx
@@ -1,5 +1,4 @@
import { InputNumber, Row, Space, Typography } from 'antd';
-import React from 'react';
interface PopoverContentProps {
linesPerRow: number;
diff --git a/frontend/src/pages/Logs/config.ts b/frontend/src/pages/Logs/config.ts
index dca2a3b937..60f46195bd 100644
--- a/frontend/src/pages/Logs/config.ts
+++ b/frontend/src/pages/Logs/config.ts
@@ -1,3 +1,5 @@
+import { CSSProperties } from 'react';
+
import { ViewModeOption } from './types';
export const viewModeOptionList: ViewModeOption[] = [
@@ -20,6 +22,6 @@ export const viewModeOptionList: ViewModeOption[] = [
export const logsOptions = ['raw', 'table'];
-export const defaultSelectStyle: React.CSSProperties = {
+export const defaultSelectStyle: CSSProperties = {
minWidth: '6rem',
};
diff --git a/frontend/src/pages/Logs/index.tsx b/frontend/src/pages/Logs/index.tsx
index d92c45708e..76433a7c4d 100644
--- a/frontend/src/pages/Logs/index.tsx
+++ b/frontend/src/pages/Logs/index.tsx
@@ -6,7 +6,7 @@ import LogsAggregate from 'container/LogsAggregate';
import LogsFilters from 'container/LogsFilters';
import LogsSearchFilter from 'container/LogsSearchFilter';
import LogsTable from 'container/LogsTable';
-import React, { useCallback, useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
diff --git a/frontend/src/pages/MetricApplication/index.tsx b/frontend/src/pages/MetricApplication/index.tsx
index e29a2b6056..9ed0fd3ab2 100644
--- a/frontend/src/pages/MetricApplication/index.tsx
+++ b/frontend/src/pages/MetricApplication/index.tsx
@@ -3,7 +3,7 @@ import Spinner from 'components/Spinner';
import MetricsApplicationContainer from 'container/MetricsApplication';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
-import React, { useEffect, useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { bindActionCreators } from 'redux';
diff --git a/frontend/src/pages/Metrics/index.tsx b/frontend/src/pages/Metrics/index.tsx
index f224cec02a..1d83d28b33 100644
--- a/frontend/src/pages/Metrics/index.tsx
+++ b/frontend/src/pages/Metrics/index.tsx
@@ -8,7 +8,7 @@ import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
-import React, { useEffect, useMemo } from 'react';
+import { useEffect, useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
diff --git a/frontend/src/pages/MySettings/index.tsx b/frontend/src/pages/MySettings/index.tsx
index 7029560078..f3f821e466 100644
--- a/frontend/src/pages/MySettings/index.tsx
+++ b/frontend/src/pages/MySettings/index.tsx
@@ -1,5 +1,4 @@
import MySettingsContainer from 'container/MySettings';
-import React from 'react';
function MySettings(): JSX.Element {
return ;
diff --git a/frontend/src/pages/NewDashboard/index.tsx b/frontend/src/pages/NewDashboard/index.tsx
index 6d35ffede4..2183531f63 100644
--- a/frontend/src/pages/NewDashboard/index.tsx
+++ b/frontend/src/pages/NewDashboard/index.tsx
@@ -1,6 +1,6 @@
import Spinner from 'components/Spinner';
import NewDashboard from 'container/NewDashboard';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { connect, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
diff --git a/frontend/src/pages/ResetPassword/index.tsx b/frontend/src/pages/ResetPassword/index.tsx
index f45e7d15f3..b11df2375f 100644
--- a/frontend/src/pages/ResetPassword/index.tsx
+++ b/frontend/src/pages/ResetPassword/index.tsx
@@ -2,7 +2,6 @@ import { Typography } from 'antd';
import getUserVersion from 'api/user/getVersion';
import Spinner from 'components/Spinner';
import ResetPasswordContainer from 'container/ResetPassword';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
@@ -11,12 +10,14 @@ import AppReducer from 'types/reducer/app';
function ResetPassword(): JSX.Element {
const { t } = useTranslation('common');
- const { isLoggedIn } = useSelector((state) => state.app);
+ const { isLoggedIn, user } = useSelector(
+ (state) => state.app,
+ );
const [versionResponse] = useQueries([
{
queryFn: getUserVersion,
- queryKey: 'getUserVersion',
+ queryKey: ['getUserVersion', user?.accessJwt],
enabled: !isLoggedIn,
},
]);
diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx
index 20b76122aa..93a668b420 100644
--- a/frontend/src/pages/Settings/index.tsx
+++ b/frontend/src/pages/Settings/index.tsx
@@ -5,7 +5,6 @@ import GeneralSettings from 'container/GeneralSettings';
import OrganizationSettings from 'container/OrganizationSettings';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx
index a8c1c1687c..19288e2cd3 100644
--- a/frontend/src/pages/SignUp/SignUp.tsx
+++ b/frontend/src/pages/SignUp/SignUp.tsx
@@ -8,7 +8,7 @@ import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
@@ -62,7 +62,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
getInviteDetails({
inviteId: token || '',
}),
- queryKey: 'getInviteDetails',
+ queryKey: ['getInviteDetails', token],
enabled: token !== null,
});
diff --git a/frontend/src/pages/SignUp/index.tsx b/frontend/src/pages/SignUp/index.tsx
index 2a073a756a..cbd00547a5 100644
--- a/frontend/src/pages/SignUp/index.tsx
+++ b/frontend/src/pages/SignUp/index.tsx
@@ -1,7 +1,6 @@
import { Typography } from 'antd';
import getUserVersion from 'api/user/getVersion';
import Spinner from 'components/Spinner';
-import React from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
@@ -12,12 +11,14 @@ import SignUpComponent from './SignUp';
function SignUp(): JSX.Element {
const { t } = useTranslation('common');
- const { isLoggedIn } = useSelector((state) => state.app);
+ const { isLoggedIn, user } = useSelector(
+ (state) => state.app,
+ );
const [versionResponse] = useQueries([
{
queryFn: getUserVersion,
- queryKey: 'getUserVersion',
+ queryKey: ['getUserVersion', user?.accessJwt],
enabled: !isLoggedIn,
},
]);
diff --git a/frontend/src/pages/SignUp/styles.ts b/frontend/src/pages/SignUp/styles.ts
index f8224e7dbd..cb301285af 100644
--- a/frontend/src/pages/SignUp/styles.ts
+++ b/frontend/src/pages/SignUp/styles.ts
@@ -1,5 +1,5 @@
import { Card, Form } from 'antd';
-import React from 'react';
+import { CSSProperties } from 'react';
import styled from 'styled-components';
export const FormWrapper = styled(Card)`
@@ -25,7 +25,7 @@ export const ButtonContainer = styled.div`
`;
interface Props {
- marginTop: React.CSSProperties['marginTop'];
+ marginTop: CSSProperties['marginTop'];
}
export const MarginTop = styled.div`
diff --git a/frontend/src/pages/SomethingWentWrong/index.tsx b/frontend/src/pages/SomethingWentWrong/index.tsx
index 794f9f5ba7..f15b127bd8 100644
--- a/frontend/src/pages/SomethingWentWrong/index.tsx
+++ b/frontend/src/pages/SomethingWentWrong/index.tsx
@@ -3,7 +3,6 @@ import SomethingWentWrongAsset from 'assets/SomethingWentWrong';
import { Container } from 'components/NotFound/styles';
import ROUTES from 'constants/routes';
import history from 'lib/history';
-import React from 'react';
function SomethingWentWrong(): JSX.Element {
return (
diff --git a/frontend/src/pages/Status/index.tsx b/frontend/src/pages/Status/index.tsx
index f923b428b4..cdc67bfb42 100644
--- a/frontend/src/pages/Status/index.tsx
+++ b/frontend/src/pages/Status/index.tsx
@@ -1,5 +1,4 @@
import Version from 'container/Version';
-import React from 'react';
function Status(): JSX.Element {
return ;
diff --git a/frontend/src/pages/Trace/index.tsx b/frontend/src/pages/Trace/index.tsx
index fba0cb2afc..acf8ec2eff 100644
--- a/frontend/src/pages/Trace/index.tsx
+++ b/frontend/src/pages/Trace/index.tsx
@@ -9,7 +9,7 @@ import TraceTable from 'container/Trace/TraceTable';
import { useNotifications } from 'hooks/useNotifications';
import getStep from 'lib/getStep';
import history from 'lib/history';
-import React, { useCallback, useEffect, useState } from 'react';
+import { MouseEventHandler, useCallback, useEffect, useState } from 'react';
import { connect, useDispatch, useSelector } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
@@ -127,7 +127,7 @@ function Trace({
[dispatch],
);
- const onClickHandler: React.MouseEventHandler = useCallback(
+ const onClickHandler: MouseEventHandler = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
diff --git a/frontend/src/pages/TraceDetail/index.tsx b/frontend/src/pages/TraceDetail/index.tsx
index 97c3140408..f936bd2d0c 100644
--- a/frontend/src/pages/TraceDetail/index.tsx
+++ b/frontend/src/pages/TraceDetail/index.tsx
@@ -4,7 +4,7 @@ import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import TraceDetailContainer from 'container/TraceDetail';
import useUrlQuery from 'hooks/useUrlQuery';
-import React, { useMemo } from 'react';
+import { useMemo } from 'react';
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
import { Props as TraceDetailProps } from 'types/api/trace/getTraceItem';
diff --git a/frontend/src/pages/UnAuthorized/index.tsx b/frontend/src/pages/UnAuthorized/index.tsx
index fbf75b5f3c..c68f3ca823 100644
--- a/frontend/src/pages/UnAuthorized/index.tsx
+++ b/frontend/src/pages/UnAuthorized/index.tsx
@@ -2,7 +2,6 @@ import { Space, Typography } from 'antd';
import UnAuthorized from 'assets/UnAuthorized';
import { Button, Container } from 'components/NotFound/styles';
import ROUTES from 'constants/routes';
-import React from 'react';
function UnAuthorizePage(): JSX.Element {
return (
diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx
index 0686828318..c5a61c01e9 100644
--- a/frontend/src/providers/QueryBuilder.tsx
+++ b/frontend/src/providers/QueryBuilder.tsx
@@ -1,5 +1,16 @@
-// ** Helpers
-import React, {
+import {
+ alphabet,
+ formulasNames,
+ initialFormulaBuilderFormValues,
+ initialQueryBuilderFormValues,
+ MAX_FORMULAS,
+ MAX_QUERIES,
+ PANEL_TYPES,
+} from 'constants/queryBuilder';
+import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
+import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
+import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
+import {
createContext,
PropsWithChildren,
useCallback,
@@ -7,24 +18,30 @@ import React, {
useState,
} from 'react';
// ** Types
-// TODO: Rename Types on the Reusable type for any source
import {
IBuilderFormula,
- IBuilderQueryForm,
+ IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import {
DataSource,
- MetricAggregateOperator,
QueryBuilderContextType,
QueryBuilderData,
} from 'types/common/queryBuilder';
export const QueryBuilderContext = createContext({
queryBuilderData: { queryData: [], queryFormulas: [] },
+ initialDataSource: null,
+ panelType: PANEL_TYPES.TIME_SERIES,
resetQueryBuilderData: () => {},
+ resetQueryBuilderInfo: () => {},
handleSetQueryData: () => {},
handleSetFormulaData: () => {},
+ handleSetPanelType: () => {},
initQueryBuilderData: () => {},
+ setupInitialDataSource: () => {},
+ removeEntityByIndex: () => {},
+ addNewQuery: () => {},
+ addNewFormula: () => {},
});
const initialQueryBuilderData: QueryBuilderData = {
@@ -35,35 +52,28 @@ const initialQueryBuilderData: QueryBuilderData = {
export function QueryBuilderProvider({
children,
}: PropsWithChildren): JSX.Element {
- // ** TODO: get queryId from url for getting data for query builder
- // ** TODO: type the params which will be used for request of the data for query builder
+ const [initialDataSource, setInitialDataSource] = useState(
+ null,
+ );
+
+ const [panelType, setPanelType] = useState(
+ PANEL_TYPES.TIME_SERIES,
+ );
const [queryBuilderData, setQueryBuilderData] = useState({
- // ** TODO temporary initial value for first query for testing first filters
- queryData: [
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- {
- dataSource: DataSource.METRICS,
- queryName: 'A',
- aggregateOperator: Object.values(MetricAggregateOperator)[0],
- aggregateAttribute: {
- dataType: null,
- key: '',
- isColumn: null,
- type: null,
- },
- },
- ],
+ queryData: [],
queryFormulas: [],
});
- // ** Method for resetting query builder data
- const resetQueryBuilderData = useCallback((): void => {
+ const resetQueryBuilderInfo = useCallback((): void => {
+ setInitialDataSource(null);
+ setPanelType(PANEL_TYPES.TIME_SERIES);
+ }, []);
+
+ const resetQueryBuilderData = useCallback(() => {
setQueryBuilderData(initialQueryBuilderData);
}, []);
- // ** Method for setupping query builder data
const initQueryBuilderData = useCallback(
(queryBuilderData: QueryBuilderData): void => {
setQueryBuilderData(queryBuilderData);
@@ -71,44 +81,170 @@ export function QueryBuilderProvider({
[],
);
- const handleSetQueryData = useCallback(
- (index: number, newQueryData: Partial): void => {
- const updatedQueryBuilderData = queryBuilderData.queryData.map((item, idx) =>
- index === idx ? { ...item, ...newQueryData } : item,
- );
-
- setQueryBuilderData((prevState) => ({
- ...prevState,
- queryData: updatedQueryBuilderData,
- }));
+ const removeEntityByIndex = useCallback(
+ (type: keyof QueryBuilderData, index: number) => {
+ setQueryBuilderData((prevState) => {
+ const currentArray: (IBuilderQuery | IBuilderFormula)[] = prevState[type];
+ return {
+ ...prevState,
+ [type]: currentArray.filter((item, i) => index !== i),
+ };
+ });
},
- [queryBuilderData],
- );
- const handleSetFormulaData = useCallback(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- (index: number, formulaData: IBuilderFormula): void => {},
[],
);
- // ** TODO: Discuss with Palash how the state of the queryBuilder and queryFormulas
- // ** TODO: should be filled from url
+ const createNewQuery = useCallback(
+ (queries: IBuilderQuery[]): IBuilderQuery => {
+ const existNames = queries.map((item) => item.queryName);
- // ** TODO: put these values and setter to the context value
+ const newQuery: IBuilderQuery = {
+ ...initialQueryBuilderFormValues,
+ queryName: createNewBuilderItemName({ existNames, sourceNames: alphabet }),
+ expression: createNewBuilderItemName({
+ existNames,
+ sourceNames: alphabet,
+ }),
+ ...(initialDataSource
+ ? {
+ dataSource: initialDataSource,
+ aggregateOperator: getOperatorsBySourceAndPanelType({
+ dataSource: initialDataSource,
+ panelType,
+ })[0].value,
+ }
+ : {}),
+ };
+
+ return newQuery;
+ },
+ [initialDataSource, panelType],
+ );
+
+ const createNewFormula = useCallback((formulas: IBuilderFormula[]) => {
+ const existNames = formulas.map((item) => item.queryName);
+
+ const newFormula: IBuilderFormula = {
+ ...initialFormulaBuilderFormValues,
+ queryName: createNewBuilderItemName({
+ existNames,
+ sourceNames: formulasNames,
+ }),
+ };
+
+ return newFormula;
+ }, []);
+
+ const addNewQuery = useCallback(() => {
+ setQueryBuilderData((prevState) => {
+ if (prevState.queryData.length >= MAX_QUERIES) return prevState;
+
+ const newQuery = createNewQuery(prevState.queryData);
+
+ return { ...prevState, queryData: [...prevState.queryData, newQuery] };
+ });
+ }, [createNewQuery]);
+
+ const addNewFormula = useCallback(() => {
+ setQueryBuilderData((prevState) => {
+ if (prevState.queryFormulas.length >= MAX_FORMULAS) return prevState;
+
+ const newFormula = createNewFormula(prevState.queryFormulas);
+
+ return {
+ ...prevState,
+ queryFormulas: [...prevState.queryFormulas, newFormula],
+ };
+ });
+ }, [createNewFormula]);
+
+ const setupInitialDataSource = useCallback(
+ (newInitialDataSource: DataSource | null) =>
+ setInitialDataSource(newInitialDataSource),
+ [],
+ );
+
+ const updateQueryBuilderData = useCallback(
+ (queries: IBuilderQuery[], index: number, newQueryData: IBuilderQuery) =>
+ queries.map((item, idx) => (index === idx ? newQueryData : item)),
+ [],
+ );
+
+ const updateFormulaBuilderData = useCallback(
+ (formulas: IBuilderFormula[], index: number, newFormula: IBuilderFormula) =>
+ formulas.map((item, idx) => (index === idx ? newFormula : item)),
+ [],
+ );
+
+ const handleSetQueryData = useCallback(
+ (index: number, newQueryData: IBuilderQuery): void => {
+ setQueryBuilderData((prevState) => {
+ const updatedQueryBuilderData = updateQueryBuilderData(
+ prevState.queryData,
+ index,
+ newQueryData,
+ );
+
+ return {
+ ...prevState,
+ queryData: updatedQueryBuilderData,
+ };
+ });
+ },
+ [updateQueryBuilderData],
+ );
+ const handleSetFormulaData = useCallback(
+ (index: number, formulaData: IBuilderFormula): void => {
+ setQueryBuilderData((prevState) => {
+ const updatedFormulasBuilderData = updateFormulaBuilderData(
+ prevState.queryFormulas,
+ index,
+ formulaData,
+ );
+
+ return {
+ ...prevState,
+ queryFormulas: updatedFormulasBuilderData,
+ };
+ });
+ },
+ [updateFormulaBuilderData],
+ );
+
+ const handleSetPanelType = useCallback((newPanelType: GRAPH_TYPES) => {
+ setPanelType(newPanelType);
+ }, []);
const contextValues: QueryBuilderContextType = useMemo(
() => ({
queryBuilderData,
+ initialDataSource,
+ panelType,
resetQueryBuilderData,
+ resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
+ handleSetPanelType,
initQueryBuilderData,
+ setupInitialDataSource,
+ removeEntityByIndex,
+ addNewQuery,
+ addNewFormula,
}),
[
queryBuilderData,
+ initialDataSource,
+ panelType,
resetQueryBuilderData,
+ resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
+ handleSetPanelType,
initQueryBuilderData,
+ setupInitialDataSource,
+ removeEntityByIndex,
+ addNewQuery,
+ addNewFormula,
],
);
diff --git a/frontend/src/store/actions/dashboard/deleteWidget.ts b/frontend/src/store/actions/dashboard/deleteWidget.ts
index ba8e1965e7..d4a29db3f5 100644
--- a/frontend/src/store/actions/dashboard/deleteWidget.ts
+++ b/frontend/src/store/actions/dashboard/deleteWidget.ts
@@ -1,7 +1,7 @@
import updateDashboardApi from 'api/dashboard/update';
import { AxiosError } from 'axios';
import { getPreLayouts, LayoutProps } from 'container/GridGraphLayout';
-import { Dispatch } from 'redux';
+import { Dispatch, SetStateAction } from 'react';
import store from 'store';
import AppActions from 'types/actions';
import { UPDATE_DASHBOARD } from 'types/actions/dashboard';
@@ -66,5 +66,5 @@ export const DeleteWidget = ({
export interface DeleteWidgetProps {
widgetId: Widgets['id'];
- setLayout?: React.Dispatch>;
+ setLayout?: Dispatch>;
}
diff --git a/frontend/src/store/actions/dashboard/getDashboard.ts b/frontend/src/store/actions/dashboard/getDashboard.ts
index 7b8e60ac8e..9005b1beb0 100644
--- a/frontend/src/store/actions/dashboard/getDashboard.ts
+++ b/frontend/src/store/actions/dashboard/getDashboard.ts
@@ -2,8 +2,11 @@ import getDashboard from 'api/dashboard/get';
import {
ClickHouseQueryTemplate,
PromQLQueryTemplate,
- QueryBuilderQueryTemplate,
} from 'constants/dashboard';
+import {
+ initialQueryBuilderFormValues,
+ PANEL_TYPES,
+} from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import GetQueryName from 'lib/query/GetQueryName';
import { Dispatch } from 'redux';
@@ -42,7 +45,7 @@ export const GetDashboard = ({
isStacked: false,
nullZeroValues: 'zero',
opacity: '0',
- panelTypes: graphType || 'TIME_SERIES',
+ panelTypes: graphType || PANEL_TYPES.TIME_SERIES,
timePreferance: 'GLOBAL_TIME',
title: '',
queryType: 0,
@@ -57,26 +60,21 @@ export const GetDashboard = ({
},
query: {
queryType: EQueryType.QUERY_BUILDER,
- promQL: [
+ promql: [
{
name: GetQueryName([]) as string,
...PromQLQueryTemplate,
},
],
- clickHouse: [
+ clickhouse_sql: [
{
name: GetQueryName([]) as string,
...ClickHouseQueryTemplate,
},
],
- metricsBuilder: {
- formulas: [],
- queryBuilder: [
- {
- name: GetQueryName([]) as string,
- ...QueryBuilderQueryTemplate,
- },
- ],
+ builder: {
+ queryFormulas: [],
+ queryData: [initialQueryBuilderFormValues],
},
},
},
diff --git a/frontend/src/store/actions/dashboard/getQueryResults.ts b/frontend/src/store/actions/dashboard/getQueryResults.ts
index 1090c57bea..176b416553 100644
--- a/frontend/src/store/actions/dashboard/getQueryResults.ts
+++ b/frontend/src/store/actions/dashboard/getQueryResults.ts
@@ -6,23 +6,23 @@ import { getMetricsQueryRange } from 'api/metrics/getQueryRange';
import { AxiosError } from 'axios';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
-import { WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME } from 'container/NewWidget/LeftContainer/QuerySection/constants';
-import { EQueryTypeToQueryKeyMapping } from 'container/NewWidget/LeftContainer/QuerySection/types';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import GetMaxMinTime from 'lib/getMaxMinTime';
import GetMinMax from 'lib/getMinMax';
import GetStartAndEndTime from 'lib/getStartAndEndTime';
import getStep from 'lib/getStep';
+import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es';
import { Dispatch } from 'redux';
import store from 'store';
import AppActions from 'types/actions';
import { ErrorResponse, SuccessResponse } from 'types/api';
-import { IDashboardVariable, Query } from 'types/api/dashboard/getAll';
+import { Query } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
-import { EDataSource, EPanelType, EQueryType } from 'types/common/dashboard';
+import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
+import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
export async function GetMetricQueryRange({
query,
@@ -37,55 +37,26 @@ export async function GetMetricQueryRange({
globalSelectedInterval: Time;
variables?: Record;
}): Promise | ErrorResponse> {
- const { queryType } = query;
- const queryKey: Record =
- EQueryTypeToQueryKeyMapping[EQueryType[query.queryType]];
- const queryData = query[queryKey];
- const legendMap: Record = {};
+ const queryData = query[query.queryType];
+ let legendMap: Record = {};
const QueryPayload = {
- dataSource: EDataSource.METRICS,
- compositeMetricQuery: {
- queryType,
- panelType: EPanelType[graphType],
+ compositeQuery: {
+ queryType: query.queryType,
+ panelType: graphType,
},
};
- switch (queryType as EQueryType) {
+
+ switch (query.queryType) {
case EQueryType.QUERY_BUILDER: {
- const builderQueries = {};
- queryData.queryBuilder.map((query) => {
- const generatedQueryPayload = {
- queryName: query.name,
- aggregateOperator: query.aggregateOperator,
- metricName: query.metricName,
- tagFilters: query.tagFilters,
- };
-
- if (graphType === 'TIME_SERIES') {
- generatedQueryPayload.groupBy = query.groupBy;
- }
-
- // Value
- else {
- generatedQueryPayload.reduceTo = query.reduceTo;
- }
-
- generatedQueryPayload.expression = query.name;
- generatedQueryPayload.disabled = query.disabled;
- builderQueries[query.name] = generatedQueryPayload;
- legendMap[query.name] = query.legend || '';
+ const { queryData: data, queryFormulas } = query.builder;
+ const builderQueries = mapQueryDataToApi({
+ queryData: data,
+ queryFormulas,
});
+ legendMap = builderQueries.newLegendMap;
- queryData[WIDGET_QUERY_BUILDER_FORMULA_KEY_NAME].map((formula) => {
- const generatedFormulaPayload = {};
- legendMap[formula.name] = formula.legend || formula.name;
- generatedFormulaPayload.queryName = formula.name;
- generatedFormulaPayload.expression = formula.expression;
- generatedFormulaPayload.disabled = formula.disabled;
- generatedFormulaPayload.legend = formula.legend;
- builderQueries[formula.name] = generatedFormulaPayload;
- });
- QueryPayload.compositeMetricQuery.builderQueries = builderQueries;
+ QueryPayload.compositeQuery.builderQueries = builderQueries.data;
break;
}
case EQueryType.CLICKHOUSE: {
@@ -98,7 +69,7 @@ export async function GetMetricQueryRange({
};
legendMap[query.name] = query.legend;
});
- QueryPayload.compositeMetricQuery.chQueries = chQueries;
+ QueryPayload.compositeQuery.chQueries = chQueries;
break;
}
case EQueryType.PROM: {
@@ -111,7 +82,7 @@ export async function GetMetricQueryRange({
};
legendMap[query.name] = query.legend;
});
- QueryPayload.compositeMetricQuery.promQueries = promQueries;
+ QueryPayload.compositeQuery.promQueries = promQueries;
break;
}
default:
@@ -148,7 +119,12 @@ export async function GetMetricQueryRange({
`API responded with ${response.statusCode} - ${response.error}`,
);
}
+
if (response.payload?.data?.result) {
+ const v2Range = convertNewDataToOld(response.payload);
+
+ response.payload = v2Range;
+
response.payload.data.result = response.payload.data.result.map(
(queryData) => {
const newQueryData = queryData;
@@ -164,6 +140,7 @@ export async function GetMetricQueryRange({
newQueryData.metric[queryData.queryName] = queryData.queryName;
}
}
+
return newQueryData;
},
);
@@ -182,6 +159,7 @@ export const GetQueryResults = (
errorMessage: '',
widgetId: props.widgetId,
errorBoolean: false,
+ isLoadingQueryResult: true,
},
});
const response = await GetMetricQueryRange(props);
@@ -194,6 +172,7 @@ export const GetQueryResults = (
payload: {
errorMessage: isError || '',
widgetId: props.widgetId,
+ isLoadingQueryResult: false,
},
});
return;
@@ -217,6 +196,7 @@ export const GetQueryResults = (
errorMessage: (error as AxiosError).toString(),
widgetId: props.widgetId,
errorBoolean: true,
+ isLoadingQueryResult: false,
},
});
}
diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts
index 94ccbc9687..9e0db3cd6f 100644
--- a/frontend/src/store/reducers/app.ts
+++ b/frontend/src/store/reducers/app.ts
@@ -9,7 +9,7 @@ import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
- UPDATE_FEATURE_FLAGS,
+ UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
UPDATE_ORG,
@@ -47,7 +47,10 @@ const InitialValue: InitialValueTypes = {
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
currentVersion: '',
latestVersion: '',
- featureFlags: {},
+ featureResponse: {
+ data: null,
+ refetch: Promise.resolve,
+ },
isCurrentVersionError: false,
isLatestVersionError: false,
user: getInitialUser(),
@@ -80,10 +83,13 @@ const appReducer = (
};
}
- case UPDATE_FEATURE_FLAGS: {
+ case UPDATE_FEATURE_FLAG_RESPONSE: {
return {
...state,
- featureFlags: { ...action.payload },
+ featureResponse: {
+ data: action.payload.featureFlag,
+ refetch: action.payload.refetch,
+ },
};
}
diff --git a/frontend/src/store/reducers/dashboard.ts b/frontend/src/store/reducers/dashboard.ts
index 0ef474ea32..227d34c7d3 100644
--- a/frontend/src/store/reducers/dashboard.ts
+++ b/frontend/src/store/reducers/dashboard.ts
@@ -32,6 +32,7 @@ const InitialValue: InitialValueTypes = {
isEditMode: false,
isQueryFired: false,
isAddWidget: false,
+ isLoadingQueryResult: false,
};
const dashboard = (
@@ -170,7 +171,12 @@ const dashboard = (
}
case QUERY_ERROR: {
- const { widgetId, errorMessage, errorBoolean = true } = action.payload;
+ const {
+ widgetId,
+ errorMessage,
+ errorBoolean = true,
+ isLoadingQueryResult = false,
+ } = action.payload;
const [selectedDashboard] = state.dashboards;
const { data } = selectedDashboard;
@@ -210,6 +216,7 @@ const dashboard = (
},
],
isQueryFired: true,
+ isLoadingQueryResult,
};
}
@@ -255,6 +262,7 @@ const dashboard = (
},
],
isQueryFired: true,
+ isLoadingQueryResult: false,
};
}
diff --git a/frontend/src/store/reducers/metric.ts b/frontend/src/store/reducers/metric.ts
index 08e9ceebae..103244cc96 100644
--- a/frontend/src/store/reducers/metric.ts
+++ b/frontend/src/store/reducers/metric.ts
@@ -10,7 +10,7 @@ import {
} from 'types/actions/metrics';
import InitialValueTypes from 'types/reducer/metrics';
-const InitialValue: InitialValueTypes = {
+export const InitialValue: InitialValueTypes = {
error: false,
errorMessage: '',
loading: true,
diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts
index 9efd43c45e..78a5da72ad 100644
--- a/frontend/src/types/actions/app.ts
+++ b/frontend/src/types/actions/app.ts
@@ -1,3 +1,4 @@
+import { QueryObserverBaseResult } from 'react-query';
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
import {
Organization,
@@ -22,9 +23,9 @@ export const UPDATE_USER_ORG_ROLE = 'UPDATE_USER_ORG_ROLE';
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 const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG';
+export const UPDATE_FEATURE_FLAG_RESPONSE = 'UPDATE_FEATURE_FLAG_RESPONSE';
export interface LoggedInUser {
type: typeof LOGGED_IN;
@@ -38,10 +39,6 @@ export interface SideBarCollapse {
payload: boolean;
}
-export interface UpdateFeatureFlags {
- type: typeof UPDATE_FEATURE_FLAGS;
- payload: null | FeatureFlagPayload;
-}
export interface UpdateAppVersion {
type: typeof UPDATE_CURRENT_VERSION;
payload: {
@@ -130,6 +127,14 @@ export interface UpdateConfigs {
};
}
+export interface UpdateFeatureFlag {
+ type: typeof UPDATE_FEATURE_FLAG_RESPONSE;
+ payload: {
+ featureFlag: FeatureFlagPayload;
+ refetch: QueryObserverBaseResult['refetch'];
+ };
+}
+
export type AppAction =
| LoggedInUser
| SideBarCollapse
@@ -142,6 +147,6 @@ export type AppAction =
| UpdateUser
| UpdateOrgName
| UpdateOrg
- | UpdateFeatureFlags
| UpdateConfigs
- | UpdateUserFlag;
+ | UpdateUserFlag
+ | UpdateFeatureFlag;
diff --git a/frontend/src/types/actions/dashboard.ts b/frontend/src/types/actions/dashboard.ts
index 4745771cc0..68f2697139 100644
--- a/frontend/src/types/actions/dashboard.ts
+++ b/frontend/src/types/actions/dashboard.ts
@@ -152,6 +152,7 @@ interface QueryError {
errorMessage: string;
widgetId: string;
errorBoolean?: boolean;
+ isLoadingQueryResult?: boolean;
};
}
diff --git a/frontend/src/types/actions/metrics.ts b/frontend/src/types/actions/metrics.ts
index c350e0e265..5cf2a54f41 100644
--- a/frontend/src/types/actions/metrics.ts
+++ b/frontend/src/types/actions/metrics.ts
@@ -1,6 +1,6 @@
+import { TopOperationList } from 'container/MetricsApplication/TopOperationsTable';
import { ServicesList } from 'types/api/metrics/getService';
import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
-import { TopOperations } from 'types/api/metrics/getTopOperations';
export const GET_SERVICE_LIST_SUCCESS = 'GET_SERVICE_LIST_SUCCESS';
export const GET_SERVICE_LIST_LOADING_START = 'GET_SERVICE_LIST_LOADING_START';
@@ -32,7 +32,7 @@ export interface GetServiceListError {
export interface GetInitialApplicationData {
type: typeof GET_INTIAL_APPLICATION_DATA;
payload: {
- topOperations: TopOperations[];
+ topOperations: TopOperationList[];
// dbOverView: DBOverView[];
// externalService: ExternalService[];
// externalAverageDuration: ExternalAverageDuration[];
diff --git a/frontend/src/types/api/alerts/alertTypes.ts b/frontend/src/types/api/alerts/alertTypes.ts
index b8ec9f38ef..47e43b377f 100644
--- a/frontend/src/types/api/alerts/alertTypes.ts
+++ b/frontend/src/types/api/alerts/alertTypes.ts
@@ -1,6 +1,5 @@
// this list must exactly match with the backend
export enum AlertTypes {
- NONE = 'NONE',
METRICS_BASED_ALERT = 'METRIC_BASED_ALERT',
LOGS_BASED_ALERT = 'LOGS_BASED_ALERT',
TRACES_BASED_ALERT = 'TRACES_BASED_ALERT',
diff --git a/frontend/src/types/api/alerts/compositeQuery.ts b/frontend/src/types/api/alerts/compositeQuery.ts
index 864e4aa163..de7d249ba4 100644
--- a/frontend/src/types/api/alerts/compositeQuery.ts
+++ b/frontend/src/types/api/alerts/compositeQuery.ts
@@ -1,17 +1,14 @@
-import {
- IClickHouseQuery,
- IMetricsBuilderFormula,
- IMetricsBuilderQuery,
- IPromQLQuery,
- IQueryBuilderTagFilters,
-} from 'types/api/dashboard/getAll';
-import { EAggregateOperator, EQueryType } from 'types/common/dashboard';
+import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
+import { IClickHouseQuery, IPromQLQuery } from 'types/api/dashboard/getAll';
+import { EQueryType } from 'types/common/dashboard';
+import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export interface ICompositeMetricQuery {
- builderQueries: IBuilderQueries;
+ builderQueries: QueryDataResourse;
promQueries: IPromQueries;
chQueries: IChQueries;
queryType: EQueryType;
+ panelType: GRAPH_TYPES;
}
export interface IChQueries {
@@ -29,44 +26,3 @@ export interface IPromQuery extends IPromQLQuery {
export interface IPromQueries {
[key: string]: IPromQuery;
}
-export interface IBuilderQueries {
- [key: string]: IBuilderQuery;
-}
-
-// IBuilderQuery combines IMetricQuery and IFormulaQuery
-// for api calls
-export interface IBuilderQuery
- extends Omit<
- IMetricQuery,
- 'aggregateOperator' | 'legend' | 'metricName' | 'tagFilters'
- > {
- aggregateOperator: EAggregateOperator | undefined;
- disabled: boolean;
- name: string;
- legend?: string;
- metricName: string | null;
- groupBy?: string[];
- expression?: string;
- tagFilters?: IQueryBuilderTagFilters;
- toggleDisable?: boolean;
- toggleDelete?: boolean;
-}
-
-export interface IFormulaQueries {
- [key: string]: IFormulaQuery;
-}
-
-export interface IFormulaQuery extends IMetricsBuilderFormula {
- formulaOnly: boolean;
- queryName: string;
-}
-
-export interface IMetricQueries {
- [key: string]: IMetricQuery;
-}
-
-export interface IMetricQuery extends IMetricsBuilderQuery {
- formulaOnly: boolean;
- expression?: string;
- queryName: string;
-}
diff --git a/frontend/src/types/api/alerts/def.ts b/frontend/src/types/api/alerts/def.ts
index 65b3e64af4..8d5440e7d9 100644
--- a/frontend/src/types/api/alerts/def.ts
+++ b/frontend/src/types/api/alerts/def.ts
@@ -24,7 +24,7 @@ export interface AlertDef {
}
export interface RuleCondition {
- compositeMetricQuery: ICompositeMetricQuery;
+ compositeQuery: ICompositeMetricQuery;
op?: string | undefined;
target?: number | undefined;
matchType?: string | undefined;
diff --git a/frontend/src/types/api/alerts/queryType.ts b/frontend/src/types/api/alerts/queryType.ts
deleted file mode 100644
index 277d6f0703..0000000000
--- a/frontend/src/types/api/alerts/queryType.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export type QueryType = 1 | 2 | 3;
-
-export const QUERY_BUILDER: QueryType = 1;
-export const PROMQL: QueryType = 3;
-
-export const resolveQueryCategoryName = (s: number): string => {
- switch (s) {
- case 1:
- return 'Query Builder';
- case 2:
- return 'Clickhouse Query';
- case 3:
- return 'PromQL';
- default:
- return '';
- }
-};
diff --git a/frontend/src/types/api/dashboard/getAll.ts b/frontend/src/types/api/dashboard/getAll.ts
index 680cbb5377..894e643e0f 100644
--- a/frontend/src/types/api/dashboard/getAll.ts
+++ b/frontend/src/types/api/dashboard/getAll.ts
@@ -1,11 +1,8 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Layout } from 'react-grid-layout';
-import {
- EAggregateOperator,
- EQueryType,
- EReduceOperator,
-} from 'types/common/dashboard';
+import { EQueryType } from 'types/common/dashboard';
+import { QueryBuilderData } from 'types/common/queryBuilder';
import { QueryData } from '../widgets/getQuery';
@@ -91,34 +88,9 @@ export interface PromQLWidgets extends IBaseWidget {
}
export interface Query {
queryType: EQueryType;
- promQL: IPromQLQuery[];
- metricsBuilder: {
- formulas: IMetricsBuilderFormula[];
- queryBuilder: IMetricsBuilderQuery[];
- };
- clickHouse: IClickHouseQuery[];
-}
-
-export interface IMetricsBuilderFormula {
- expression: string;
- disabled: boolean;
- name: string;
- legend: string;
-}
-export interface IMetricsBuilderQuery {
- aggregateOperator: EAggregateOperator;
- disabled: boolean;
- name: string;
- legend: string;
- metricName: string | null;
- groupBy?: string[];
- tagFilters: IQueryBuilderTagFilters;
- reduceTo?: EReduceOperator;
-}
-
-export interface IQueryBuilderTagFilters {
- op: string;
- items: IQueryBuilderTagFilterItems[] | [];
+ promql: IPromQLQuery[];
+ builder: QueryBuilderData;
+ clickhouse_sql: IClickHouseQuery[];
}
export interface IClickHouseQuery {
diff --git a/frontend/src/types/api/dashboard/shared.ts b/frontend/src/types/api/dashboard/shared.ts
deleted file mode 100644
index 31778e79bc..0000000000
--- a/frontend/src/types/api/dashboard/shared.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum EQueryTypeToQueryKeyMapping {
- QUERY_BUILDER = 'metricsBuilder',
- CLICKHOUSE = 'clickHouse',
- PROM = 'promQL',
-}
diff --git a/frontend/src/types/api/features/getFeatures.ts b/frontend/src/types/api/features/getFeatures.ts
deleted file mode 100644
index f1af4d6abe..0000000000
--- a/frontend/src/types/api/features/getFeatures.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface PayloadProps {
- [key: string]: boolean;
-}
diff --git a/frontend/src/types/api/features/getFeaturesFlags.ts b/frontend/src/types/api/features/getFeaturesFlags.ts
index f1af4d6abe..3d711256c0 100644
--- a/frontend/src/types/api/features/getFeaturesFlags.ts
+++ b/frontend/src/types/api/features/getFeaturesFlags.ts
@@ -1,3 +1,19 @@
-export interface PayloadProps {
- [key: string]: boolean;
+export type FeaturesFlag =
+ | 'DurationSort'
+ | 'TimestampSort'
+ | 'SMART_TRACE_DETAIL'
+ | 'CUSTOM_METRICS_FUNCTION'
+ | 'QUERY_BUILDER_PANELS'
+ | 'QUERY_BUILDER_ALERTS'
+ | 'DISABLE_UPSELL'
+ | 'SSO';
+
+interface FeatureFlagProps {
+ name: FeaturesFlag;
+ active: boolean;
+ usage: number;
+ usage_limit: number;
+ route: string;
}
+
+export type PayloadProps = FeatureFlagProps[];
diff --git a/frontend/src/types/api/metrics/getQueryRange.ts b/frontend/src/types/api/metrics/getQueryRange.ts
index bbe9c697a9..f8c32c29a3 100644
--- a/frontend/src/types/api/metrics/getQueryRange.ts
+++ b/frontend/src/types/api/metrics/getQueryRange.ts
@@ -1,10 +1,16 @@
-import { QueryData } from '../widgets/getQuery';
+import { QueryData, QueryDataV3 } from '../widgets/getQuery';
export type MetricsRangeProps = never;
export interface MetricRangePayloadProps {
data: {
result: QueryData[];
resultType: string;
- variables: Record;
+ };
+}
+
+export interface MetricRangePayloadV3 {
+ data: {
+ result: QueryDataV3[];
+ resultType: string;
};
}
diff --git a/frontend/src/types/api/metrics/getTopOperations.ts b/frontend/src/types/api/metrics/getTopOperations.ts
index f30c01251f..050a2e7399 100644
--- a/frontend/src/types/api/metrics/getTopOperations.ts
+++ b/frontend/src/types/api/metrics/getTopOperations.ts
@@ -1,13 +1,6 @@
+import { TopOperationList } from 'container/MetricsApplication/TopOperationsTable';
import { Tags } from 'types/reducer/trace';
-export interface TopOperations {
- name: string;
- numCalls: number;
- p50: number;
- p95: number;
- p99: number;
-}
-
export interface Props {
service: string;
start: number;
@@ -15,4 +8,4 @@ export interface Props {
selectedTags: Tags[];
}
-export type PayloadProps = TopOperations[];
+export type PayloadProps = TopOperationList[];
diff --git a/frontend/src/types/api/queryBuilder/getAttributeKeys.ts b/frontend/src/types/api/queryBuilder/getAttributeKeys.ts
index e28623cad1..c3062e2562 100644
--- a/frontend/src/types/api/queryBuilder/getAttributeKeys.ts
+++ b/frontend/src/types/api/queryBuilder/getAttributeKeys.ts
@@ -1,8 +1,11 @@
import { DataSource } from 'types/common/queryBuilder';
+import { BaseAutocompleteData } from './queryAutocompleteResponse';
+
export interface IGetAttributeKeysPayload {
aggregateOperator: string;
dataSource: DataSource;
searchText: string;
aggregateAttribute: string;
+ tagType?: BaseAutocompleteData['type'];
}
diff --git a/frontend/src/types/api/queryBuilder/getAttributesValues.ts b/frontend/src/types/api/queryBuilder/getAttributesValues.ts
new file mode 100644
index 0000000000..738f82e16d
--- /dev/null
+++ b/frontend/src/types/api/queryBuilder/getAttributesValues.ts
@@ -0,0 +1,19 @@
+import { DataSource } from 'types/common/queryBuilder';
+
+import { BaseAutocompleteData } from './queryAutocompleteResponse';
+
+export interface IGetAttributeValuesPayload {
+ dataSource: DataSource;
+ aggregateOperator: string;
+ aggregateAttribute: string;
+ searchText: string;
+ attributeKey: string;
+ filterAttributeKeyDataType: BaseAutocompleteData['dataType'];
+ tagType: BaseAutocompleteData['type'];
+}
+
+export interface IAttributeValuesResponse {
+ boolAttributeValues: null | string[];
+ numberAttributeValues: null | string[];
+ stringAttributeValues: null | string[];
+}
diff --git a/frontend/src/types/api/queryBuilder/queryAutocompleteResponse.ts b/frontend/src/types/api/queryBuilder/queryAutocompleteResponse.ts
index 847fb6bcd3..935467120a 100644
--- a/frontend/src/types/api/queryBuilder/queryAutocompleteResponse.ts
+++ b/frontend/src/types/api/queryBuilder/queryAutocompleteResponse.ts
@@ -1,10 +1,17 @@
-export interface AutocompleteData {
- dataType: 'number' | 'string' | 'boolean' | null;
+export type LocalDataType = 'number' | 'string' | 'bool';
+
+export type DataType = 'int64' | 'float64' | 'string' | 'bool';
+
+export type AutocompleteType = 'tag' | 'resource';
+
+export interface BaseAutocompleteData {
+ id?: string;
+ dataType: DataType | null;
isColumn: boolean | null;
key: string;
- type: 'tag' | 'resource' | null;
+ type: AutocompleteType | null;
}
export interface IQueryAutocompleteResponse {
- attributeKeys: AutocompleteData[];
+ attributeKeys: BaseAutocompleteData[] | null;
}
diff --git a/frontend/src/types/api/queryBuilder/queryBuilderData.ts b/frontend/src/types/api/queryBuilder/queryBuilderData.ts
index afddce2814..0dcd686f50 100644
--- a/frontend/src/types/api/queryBuilder/queryBuilderData.ts
+++ b/frontend/src/types/api/queryBuilder/queryBuilderData.ts
@@ -1,44 +1,57 @@
-import { DataSource } from 'types/common/queryBuilder';
+import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
-import { AutocompleteData } from './queryAutocompleteResponse';
+import { BaseAutocompleteData } from './queryAutocompleteResponse';
// Type for Formula
export interface IBuilderFormula {
expression: string;
disabled: boolean;
- label: string;
+ queryName: string;
+ dataSource?: DataSource;
legend: string;
}
export interface TagFilterItem {
- key: string;
- // TODO: type it in the future
+ id: string;
+ key?: BaseAutocompleteData;
op: string;
- value: string[];
+ value: string[] | string;
}
export interface TagFilter {
items: TagFilterItem[];
- // TODO: type it in the future
op: string;
}
+export type Having = {
+ columnName: string;
+ op: string;
+ value: number | number[];
+};
+
+export type HavingForm = Omit & {
+ value: string[];
+};
+
+export type OrderByPayload = {
+ columnName: string;
+ order: string;
+};
+
// Type for query builder
export type IBuilderQuery = {
queryName: string;
dataSource: DataSource;
aggregateOperator: string;
- aggregateAttribute: string;
- tagFilters: TagFilter[];
- groupBy: string[];
+ aggregateAttribute: BaseAutocompleteData;
+ filters: TagFilter;
+ groupBy: BaseAutocompleteData[];
expression: string;
disabled: boolean;
- having?: string;
- limit?: number;
- orderBy?: string[];
- reduceTo?: string;
-};
-
-export type IBuilderQueryForm = Omit & {
- aggregateAttribute: AutocompleteData;
+ having: Having[];
+ limit: number | null;
+ stepInterval: number;
+ orderBy: OrderByPayload[];
+ reduceTo: ReduceOperators;
+ legend: string;
};
diff --git a/frontend/src/types/api/widgets/getQuery.ts b/frontend/src/types/api/widgets/getQuery.ts
index 3a2eb1cda5..60d679c36e 100644
--- a/frontend/src/types/api/widgets/getQuery.ts
+++ b/frontend/src/types/api/widgets/getQuery.ts
@@ -4,8 +4,7 @@ export interface PayloadProps {
}
export interface QueryData {
- metric?: {
- __name__: string;
+ metric: {
[key: string]: string;
};
queryName: string;
@@ -13,6 +12,20 @@ export interface QueryData {
values: [number, string][];
}
+export interface SeriesItem {
+ labels: {
+ [key: string]: string;
+ };
+ values: { timestamp: number; value: string }[];
+}
+
+export interface QueryDataV3 {
+ list: null;
+ queryName: string;
+ legend?: string;
+ series: SeriesItem[];
+}
+
export interface Props {
query: string;
step: string;
diff --git a/frontend/src/types/common/dashboard.ts b/frontend/src/types/common/dashboard.ts
index 9a778e7488..8d08c22f80 100644
--- a/frontend/src/types/common/dashboard.ts
+++ b/frontend/src/types/common/dashboard.ts
@@ -1,55 +1,5 @@
-export enum EDataSource {
- METRICS = 1,
- TRACES,
- LOGS,
-}
-
export enum EQueryType {
- QUERY_BUILDER = 1,
- CLICKHOUSE,
- PROM,
-}
-
-export enum EAggregateOperator {
- NOOP = 1,
- COUNT = 2,
- COUNT_DISTINCT = 3,
- SUM = 4,
- AVG = 5,
- MAX = 6,
- MIN = 7,
- P05 = 8,
- P10 = 9,
- P20 = 10,
- P25 = 11,
- P50 = 12,
- P75 = 13,
- P90 = 14,
- P95 = 15,
- P99 = 16,
- RATE = 17,
- SUM_RATE = 18,
- // leaving gap for possible future {X}_RATE
- RATE_SUM = 22,
- RATE_AVG = 23,
- RATE_MAX = 24,
- RATE_MIN = 25,
- HIST_QUANTILE_50 = 26,
- HIST_QUANTILE_75 = 27,
- HIST_QUANTILE_90 = 28,
- HIST_QUANTILE_95 = 29,
- HIST_QUANTILE_99 = 30,
-}
-
-export enum EPanelType {
- TIME_SERIES = 1,
- VALUE,
-}
-
-export enum EReduceOperator {
- 'Latest of values in timeframe' = 1, // LAST
- 'Sum of values in timeframe', // SUM
- 'Average of values in timeframe', // AVG
- 'Max of values in timeframe', // MAX
- 'Min of values in timeframe', // MIN
+ QUERY_BUILDER = 'builder',
+ CLICKHOUSE = 'clickhouse_sql',
+ PROM = 'promql',
}
diff --git a/frontend/src/types/common/operations.types.ts b/frontend/src/types/common/operations.types.ts
new file mode 100644
index 0000000000..409f66cc5c
--- /dev/null
+++ b/frontend/src/types/common/operations.types.ts
@@ -0,0 +1,29 @@
+import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
+import { DataSource } from 'types/common/queryBuilder';
+
+import { SelectOption } from './select';
+
+type UseQueryOperationsParams = Pick;
+
+export type HandleChangeQueryData = <
+ Key extends keyof IBuilderQuery,
+ Value extends IBuilderQuery[Key]
+>(
+ key: Key,
+ value: Value,
+) => void;
+
+export type UseQueryOperations = (
+ params: UseQueryOperationsParams,
+) => {
+ isMetricsDataSource: boolean;
+ operators: SelectOption[];
+ listOfAdditionalFilters: string[];
+ handleChangeOperator: (value: string) => void;
+ handleChangeAggregatorAttribute: (value: BaseAutocompleteData) => void;
+ handleChangeDataSource: (newSource: DataSource) => void;
+ handleDeleteQuery: () => void;
+ handleChangeQueryData: HandleChangeQueryData;
+};
diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts
index 608c36ae21..c8f3fa2e30 100644
--- a/frontend/src/types/common/queryBuilder.ts
+++ b/frontend/src/types/common/queryBuilder.ts
@@ -1,6 +1,7 @@
+import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import {
IBuilderFormula,
- IBuilderQueryForm,
+ IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
export enum DataSource {
@@ -9,6 +10,48 @@ export enum DataSource {
LOGS = 'logs',
}
+export enum StringOperators {
+ NOOP = 'noop',
+ COUNT = 'count',
+ COUNT_DISTINCT = 'count_distinct',
+}
+
+export enum NumberOperators {
+ SUM = 'sum',
+ AVG = 'avg',
+ MAX = 'max',
+ MIN = 'min',
+ P05 = 'p05',
+ P10 = 'p10',
+ P20 = 'p20',
+ P25 = 'p25',
+ P50 = 'p50',
+ P75 = 'p75',
+ P90 = 'p90',
+ P95 = 'p95',
+ P99 = 'p99',
+ RATE = 'rate',
+ SUM_RATE = 'sum_rate',
+ AVG_RATE = 'avg_rate',
+ MAX_RATE = 'max_rate',
+ MIN_RATE = 'min_rate',
+ RATE_SUM = 'rate_sum',
+ RATE_AVG = 'rate_avg',
+ RATE_MIN = 'rate_min',
+ RATE_MAX = 'rate_max',
+ HIST_QUANTILE_50 = 'hist_quantile_50',
+ HIST_QUANTILE_75 = 'hist_quantile_75',
+ HIST_QUANTILE_90 = 'hist_quantile_90',
+ HIST_QUANTILE_95 = 'hist_quantile_95',
+ HIST_QUANTILE_99 = 'hist_quantile_99',
+}
+
+export enum BoolOperators {
+ NOOP = 'noop',
+ COUNT = 'count',
+ COUNT_DISTINCT = 'count_distinct',
+}
+
export enum MetricAggregateOperator {
NOOP = 'noop',
COUNT = 'count',
@@ -60,6 +103,10 @@ export enum TracesAggregatorOperator {
P95 = 'p95',
P99 = 'p99',
RATE = 'rate',
+ RATE_SUM = 'rate_sum',
+ RATE_AVG = 'rate_avg',
+ RATE_MIN = 'rate_min',
+ RATE_MAX = 'rate_max',
}
export enum LogsAggregatorOperator {
@@ -80,21 +127,43 @@ export enum LogsAggregatorOperator {
P95 = 'p95',
P99 = 'p99',
RATE = 'rate',
+ RATE_SUM = 'rate_sum',
+ RATE_AVG = 'rate_avg',
+ RATE_MIN = 'rate_min',
+ RATE_MAX = 'rate_max',
}
+export type PanelTypeKeys =
+ | 'TIME_SERIES'
+ | 'VALUE'
+ | 'TABLE'
+ | 'LIST'
+ | 'EMPTY_WIDGET';
+
+export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min';
+
export type QueryBuilderData = {
- queryData: IBuilderQueryForm[];
+ queryData: IBuilderQuery[];
queryFormulas: IBuilderFormula[];
};
-// ** TODO: temporary types for context, fix it during development
export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData;
+ initialDataSource: DataSource | null;
+ panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void;
- handleSetQueryData: (
- index: number,
- queryData: Partial,
- ) => void;
+ resetQueryBuilderInfo: () => void;
+ handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
+ handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
+ setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
+ removeEntityByIndex: (type: keyof QueryBuilderData, index: number) => void;
+ addNewQuery: () => void;
+ addNewFormula: () => void;
+};
+
+export type QueryAdditionalFilter = {
+ field: keyof IBuilderQuery;
+ text: string;
};
diff --git a/frontend/src/types/common/queryBuilderMappers.types.ts b/frontend/src/types/common/queryBuilderMappers.types.ts
new file mode 100644
index 0000000000..9203b6dc9d
--- /dev/null
+++ b/frontend/src/types/common/queryBuilderMappers.types.ts
@@ -0,0 +1,14 @@
+import {
+ IBuilderFormula,
+ IBuilderQuery,
+} from 'types/api/queryBuilder/queryBuilderData';
+
+export type MapQuery = Record;
+export type MapFormula = Record;
+
+export type QueryDataResourse = Record