Compare commits
14 Commits
v0.45.0-04
...
v0.42.0-qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8d28fbad | ||
|
|
f6cafd42e2 | ||
|
|
392ab00afa | ||
|
|
496ec5046a | ||
|
|
610b6976ff | ||
|
|
729419c6b1 | ||
|
|
5aad24356f | ||
|
|
1e7053aa2a | ||
|
|
98b07d5f4a | ||
|
|
20e71b92e3 | ||
|
|
c99a112dc7 | ||
|
|
e4d9c4e239 | ||
|
|
781732f25a | ||
|
|
77e55a0ec9 |
31
.github/workflows/jest-coverage-changes.yml
vendored
31
.github/workflows/jest-coverage-changes.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
name: Jest Coverage - changed files
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: develop
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: "refs/heads/develop"
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }} # Provide the GitHub token for authentication
|
|
||||||
|
|
||||||
- name: Fetch branch
|
|
||||||
run: git fetch origin ${{ github.event.pull_request.head.ref }}
|
|
||||||
|
|
||||||
- run: |
|
|
||||||
git checkout ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: cd frontend && npm install -g yarn && yarn
|
|
||||||
|
|
||||||
- name: npm run test:changedsince
|
|
||||||
run: cd frontend && npm run i18n:generate-hash && npm run test:changedsince
|
|
||||||
32
.github/workflows/staging-deployment.yaml
vendored
32
.github/workflows/staging-deployment.yaml
vendored
@@ -9,29 +9,19 @@ jobs:
|
|||||||
name: Deploy latest develop branch to staging
|
name: Deploy latest develop branch to staging
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: staging
|
environment: staging
|
||||||
permissions:
|
|
||||||
contents: 'read'
|
|
||||||
id-token: 'write'
|
|
||||||
steps:
|
steps:
|
||||||
- id: 'auth'
|
- name: Executing remote ssh commands using ssh key
|
||||||
uses: 'google-github-actions/auth@v2'
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
with:
|
|
||||||
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
|
||||||
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
|
|
||||||
|
|
||||||
- name: 'sdk'
|
|
||||||
uses: 'google-github-actions/setup-gcloud@v2'
|
|
||||||
|
|
||||||
- name: 'ssh'
|
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
|
GITHUB_BRANCH: develop
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
|
with:
|
||||||
GCP_ZONE: ${{ secrets.GCP_ZONE }}
|
host: ${{ secrets.HOST_DNS }}
|
||||||
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
|
username: ${{ secrets.USERNAME }}
|
||||||
run: |
|
key: ${{ secrets.SSH_KEY }}
|
||||||
read -r -d '' COMMAND <<EOF || true
|
envs: GITHUB_BRANCH,GITHUB_SHA
|
||||||
|
command_timeout: 60m
|
||||||
|
script: |
|
||||||
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
||||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||||
@@ -50,5 +40,3 @@ jobs:
|
|||||||
make build-ee-query-service-amd64
|
make build-ee-query-service-amd64
|
||||||
make build-frontend-amd64
|
make build-frontend-amd64
|
||||||
make run-signoz
|
make run-signoz
|
||||||
EOF
|
|
||||||
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
|
|
||||||
|
|||||||
30
.github/workflows/testing-deployment.yaml
vendored
30
.github/workflows/testing-deployment.yaml
vendored
@@ -9,29 +9,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: testing
|
environment: testing
|
||||||
if: ${{ github.event.label.name == 'testing-deploy' }}
|
if: ${{ github.event.label.name == 'testing-deploy' }}
|
||||||
permissions:
|
|
||||||
contents: 'read'
|
|
||||||
id-token: 'write'
|
|
||||||
steps:
|
steps:
|
||||||
- id: 'auth'
|
- name: Executing remote ssh commands using ssh key
|
||||||
uses: 'google-github-actions/auth@v2'
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
with:
|
|
||||||
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
|
||||||
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
|
|
||||||
|
|
||||||
- name: 'sdk'
|
|
||||||
uses: 'google-github-actions/setup-gcloud@v2'
|
|
||||||
|
|
||||||
- name: 'ssh'
|
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
|
GITHUB_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||||
GITHUB_SHA: ${{ github.sha }}
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
|
with:
|
||||||
GCP_ZONE: ${{ secrets.GCP_ZONE }}
|
host: ${{ secrets.HOST_DNS }}
|
||||||
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
|
username: ${{ secrets.USERNAME }}
|
||||||
run: |
|
key: ${{ secrets.SSH_KEY }}
|
||||||
read -r -d '' COMMAND <<EOF || true
|
envs: GITHUB_BRANCH,GITHUB_SHA
|
||||||
|
command_timeout: 60m
|
||||||
|
script: |
|
||||||
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
|
||||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||||
@@ -51,5 +41,3 @@ jobs:
|
|||||||
make build-ee-query-service-amd64
|
make build-ee-query-service-amd64
|
||||||
make build-frontend-amd64
|
make build-frontend-amd64
|
||||||
make run-signoz
|
make run-signoz
|
||||||
EOF
|
|
||||||
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
|||||||
"wget",
|
"wget",
|
||||||
"--spider",
|
"--spider",
|
||||||
"-q",
|
"-q",
|
||||||
"0.0.0.0:8123/ping"
|
"localhost:8123/ping"
|
||||||
]
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
@@ -146,7 +146,7 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.44.0
|
image: signoz/query-service:0.42.0
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
@@ -186,7 +186,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.44.0
|
image: signoz/frontend:0.42.0
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
@@ -199,7 +199,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:0.88.21
|
image: signoz/signoz-otel-collector:0.88.17
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@@ -237,7 +237,7 @@ services:
|
|||||||
- query-service
|
- query-service
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:0.88.21
|
image: signoz/signoz-schema-migrator:0.88.17
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|||||||
@@ -111,18 +111,18 @@ processors:
|
|||||||
|
|
||||||
exporters:
|
exporters:
|
||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
enabled: true
|
enabled: true
|
||||||
clickhousemetricswrite/prometheus:
|
clickhousemetricswrite/prometheus:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
# logging: {}
|
# logging: {}
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
extensions:
|
extensions:
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ rule_files:
|
|||||||
scrape_configs: []
|
scrape_configs: []
|
||||||
|
|
||||||
remote_read:
|
remote_read:
|
||||||
- url: tcp://clickhouse:9000/signoz_metrics
|
- url: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ services:
|
|||||||
"wget",
|
"wget",
|
||||||
"--spider",
|
"--spider",
|
||||||
"-q",
|
"-q",
|
||||||
"0.0.0.0:8123/ping"
|
"localhost:8123/ping"
|
||||||
]
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
@@ -66,7 +66,7 @@ services:
|
|||||||
- --storage.path=/data
|
- --storage.path=/data
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.21}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.17}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@@ -81,7 +81,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# 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:
|
otel-collector:
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
image: signoz/signoz-otel-collector:0.88.21
|
image: signoz/signoz-otel-collector:0.88.17
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ x-clickhouse-defaults: &clickhouse-defaults
|
|||||||
"wget",
|
"wget",
|
||||||
"--spider",
|
"--spider",
|
||||||
"-q",
|
"-q",
|
||||||
"0.0.0.0:8123/ping"
|
"localhost:8123/ping"
|
||||||
]
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
@@ -164,7 +164,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# 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:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.44.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.42.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@@ -203,7 +203,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.44.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.42.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -215,7 +215,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.21}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.17}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@@ -229,7 +229,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.21}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.17}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -122,20 +122,21 @@ extensions:
|
|||||||
|
|
||||||
exporters:
|
exporters:
|
||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
enabled: true
|
enabled: true
|
||||||
clickhousemetricswrite/prometheus:
|
clickhousemetricswrite/prometheus:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
|
# logging: {}
|
||||||
|
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
# logging: {}
|
|
||||||
|
|
||||||
service:
|
service:
|
||||||
telemetry:
|
telemetry:
|
||||||
|
|||||||
@@ -22,4 +22,4 @@ rule_files:
|
|||||||
scrape_configs: []
|
scrape_configs: []
|
||||||
|
|
||||||
remote_read:
|
remote_read:
|
||||||
- url: tcp://clickhouse:9000/signoz_metrics
|
- url: tcp://clickhouse:9000/?database=signoz_metrics
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
|||||||
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost)
|
||||||
|
|
||||||
// PAT APIs
|
// PAT APIs
|
||||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||||
|
|||||||
236
ee/query-service/app/api/metrics.go
Normal file
236
ee/query-service/app/api/metrics.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/parser"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !ah.CheckFeature(basemodel.CustomMetricsFunction) {
|
||||||
|
zap.L().Info("CustomMetricsFunction feature is not enabled in this plan")
|
||||||
|
ah.APIHandler.QueryRangeMetricsV2(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metricsQueryRangeParams, apiErrorObj := parser.ParseMetricQueryRangeParams(r)
|
||||||
|
|
||||||
|
if apiErrorObj != nil {
|
||||||
|
zap.L().Error("Error in parsing metric query params", zap.Error(apiErrorObj.Err))
|
||||||
|
RespondError(w, apiErrorObj, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prometheus instant query needs same timestamp
|
||||||
|
if metricsQueryRangeParams.CompositeMetricQuery.PanelType == basemodel.QUERY_VALUE &&
|
||||||
|
metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.PROM {
|
||||||
|
metricsQueryRangeParams.Start = metricsQueryRangeParams.End
|
||||||
|
}
|
||||||
|
|
||||||
|
// round up the end to nearest multiple
|
||||||
|
if metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.QUERY_BUILDER {
|
||||||
|
end := (metricsQueryRangeParams.End) / 1000
|
||||||
|
step := metricsQueryRangeParams.Step
|
||||||
|
metricsQueryRangeParams.End = (end / step * step) * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelResult struct {
|
||||||
|
Series []*basemodel.Series
|
||||||
|
TableName string
|
||||||
|
Err error
|
||||||
|
Name string
|
||||||
|
Query string
|
||||||
|
}
|
||||||
|
|
||||||
|
execClickHouseQueries := func(queries map[string]string) ([]*basemodel.Series, []string, error, map[string]string) {
|
||||||
|
var seriesList []*basemodel.Series
|
||||||
|
var tableName []string
|
||||||
|
ch := make(chan channelResult, len(queries))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for name, query := range queries {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(name, query string) {
|
||||||
|
defer wg.Done()
|
||||||
|
seriesList, tableName, err := ah.opts.DataConnector.GetMetricResultEE(r.Context(), query)
|
||||||
|
for _, series := range seriesList {
|
||||||
|
series.QueryName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- channelResult{Series: seriesList, TableName: tableName}
|
||||||
|
}(name, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
errQuriesByName := make(map[string]string)
|
||||||
|
// read values from the channel
|
||||||
|
for r := range ch {
|
||||||
|
if r.Err != nil {
|
||||||
|
errs = append(errs, r.Err)
|
||||||
|
errQuriesByName[r.Name] = r.Query
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seriesList = append(seriesList, r.Series...)
|
||||||
|
tableName = append(tableName, r.TableName)
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return nil, nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName
|
||||||
|
}
|
||||||
|
return seriesList, tableName, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
execPromQueries := func(metricsQueryRangeParams *basemodel.QueryRangeParamsV2) ([]*basemodel.Series, error, map[string]string) {
|
||||||
|
var seriesList []*basemodel.Series
|
||||||
|
ch := make(chan channelResult, len(metricsQueryRangeParams.CompositeMetricQuery.PromQueries))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for name, query := range metricsQueryRangeParams.CompositeMetricQuery.PromQueries {
|
||||||
|
if query.Disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(name string, query *basemodel.PromQuery) {
|
||||||
|
var seriesList []*basemodel.Series
|
||||||
|
defer wg.Done()
|
||||||
|
tmpl := template.New("promql-query")
|
||||||
|
tmpl, tmplErr := tmpl.Parse(query.Query)
|
||||||
|
if tmplErr != nil {
|
||||||
|
ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var queryBuf bytes.Buffer
|
||||||
|
tmplErr = tmpl.Execute(&queryBuf, metricsQueryRangeParams.Variables)
|
||||||
|
if tmplErr != nil {
|
||||||
|
ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query.Query = queryBuf.String()
|
||||||
|
queryModel := basemodel.QueryRangeParams{
|
||||||
|
Start: time.UnixMilli(metricsQueryRangeParams.Start),
|
||||||
|
End: time.UnixMilli(metricsQueryRangeParams.End),
|
||||||
|
Step: time.Duration(metricsQueryRangeParams.Step * int64(time.Second)),
|
||||||
|
Query: query.Query,
|
||||||
|
}
|
||||||
|
promResult, _, err := ah.opts.DataConnector.GetQueryRangeResult(r.Context(), &queryModel)
|
||||||
|
if err != nil {
|
||||||
|
ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query.Query}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matrix, _ := promResult.Matrix()
|
||||||
|
for _, v := range matrix {
|
||||||
|
var s basemodel.Series
|
||||||
|
s.QueryName = name
|
||||||
|
s.Labels = v.Metric.Copy().Map()
|
||||||
|
for _, p := range v.Floats {
|
||||||
|
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.F})
|
||||||
|
}
|
||||||
|
seriesList = append(seriesList, &s)
|
||||||
|
}
|
||||||
|
ch <- channelResult{Series: seriesList}
|
||||||
|
}(name, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
errQuriesByName := make(map[string]string)
|
||||||
|
// read values from the channel
|
||||||
|
for r := range ch {
|
||||||
|
if r.Err != nil {
|
||||||
|
errs = append(errs, r.Err)
|
||||||
|
errQuriesByName[r.Name] = r.Query
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seriesList = append(seriesList, r.Series...)
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName
|
||||||
|
}
|
||||||
|
return seriesList, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var seriesList []*basemodel.Series
|
||||||
|
var tableName []string
|
||||||
|
var err error
|
||||||
|
var errQuriesByName map[string]string
|
||||||
|
switch metricsQueryRangeParams.CompositeMetricQuery.QueryType {
|
||||||
|
case basemodel.QUERY_BUILDER:
|
||||||
|
runQueries := metrics.PrepareBuilderMetricQueries(metricsQueryRangeParams, constants.SIGNOZ_TIMESERIES_TABLENAME)
|
||||||
|
if runQueries.Err != nil {
|
||||||
|
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: runQueries.Err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seriesList, tableName, err, errQuriesByName = execClickHouseQueries(runQueries.Queries)
|
||||||
|
|
||||||
|
case basemodel.CLICKHOUSE:
|
||||||
|
queries := make(map[string]string)
|
||||||
|
|
||||||
|
for name, chQuery := range metricsQueryRangeParams.CompositeMetricQuery.ClickHouseQueries {
|
||||||
|
if chQuery.Disabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpl := template.New("clickhouse-query")
|
||||||
|
tmpl, err := tmpl.Parse(chQuery.Query)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var query bytes.Buffer
|
||||||
|
|
||||||
|
// replace go template variables
|
||||||
|
querytemplate.AssignReservedVars(metricsQueryRangeParams)
|
||||||
|
|
||||||
|
err = tmpl.Execute(&query, metricsQueryRangeParams.Variables)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queries[name] = query.String()
|
||||||
|
}
|
||||||
|
seriesList, tableName, err, errQuriesByName = execClickHouseQueries(queries)
|
||||||
|
case basemodel.PROM:
|
||||||
|
seriesList, err, errQuriesByName = execPromQueries(metricsQueryRangeParams)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("invalid query type")
|
||||||
|
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, errQuriesByName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
apiErrObj := &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}
|
||||||
|
RespondError(w, apiErrObj, errQuriesByName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if metricsQueryRangeParams.CompositeMetricQuery.PanelType == basemodel.QUERY_VALUE &&
|
||||||
|
len(seriesList) > 1 &&
|
||||||
|
(metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.QUERY_BUILDER ||
|
||||||
|
metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.CLICKHOUSE) {
|
||||||
|
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: fmt.Errorf("invalid: query resulted in more than one series for value type")}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseFormat struct {
|
||||||
|
ResultType string `json:"resultType"`
|
||||||
|
Result []*basemodel.Series `json:"result"`
|
||||||
|
TableName []string `json:"tableName"`
|
||||||
|
}
|
||||||
|
resp := ResponseFormat{ResultType: "matrix", Result: seriesList, TableName: tableName}
|
||||||
|
ah.Respond(w, resp)
|
||||||
|
}
|
||||||
@@ -329,6 +329,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
r.Use(loggingMiddleware)
|
r.Use(loggingMiddleware)
|
||||||
|
|
||||||
apiHandler.RegisterRoutes(r, am)
|
apiHandler.RegisterRoutes(r, am)
|
||||||
|
apiHandler.RegisterMetricsRoutes(r, am)
|
||||||
apiHandler.RegisterLogsRoutes(r, am)
|
apiHandler.RegisterLogsRoutes(r, am)
|
||||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import (
|
|||||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
"go.signoz.io/signoz/ee/query-service/app"
|
"go.signoz.io/signoz/ee/query-service/app"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
|
||||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/version"
|
"go.signoz.io/signoz/pkg/query-service/version"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
@@ -145,12 +143,6 @@ func main() {
|
|||||||
zap.L().Info("JWT secret key set successfully.")
|
zap.L().Info("JWT secret key set successfully.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := migrate.Migrate(constants.RELATIONAL_DATASOURCE_PATH); err != nil {
|
|
||||||
zap.L().Error("Failed to migrate", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
zap.L().Info("Migration successful")
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := app.NewServer(serverOptions)
|
server, err := app.NewServer(serverOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("Failed to create server", zap.Error(err))
|
zap.L().Fatal("Failed to create server", zap.Error(err))
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
Name: basemodel.QueryBuilderPanels,
|
Name: basemodel.QueryBuilderPanels,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: 20,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
basemodel.Feature{
|
||||||
Name: basemodel.QueryBuilderAlerts,
|
Name: basemodel.QueryBuilderAlerts,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: 10,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
basemodel.Feature{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM nginx:1.26-alpine
|
FROM nginx:1.25.2-alpine
|
||||||
|
|
||||||
# Add Maintainer Info
|
# Add Maintainer Info
|
||||||
LABEL maintainer="signoz"
|
LABEL maintainer="signoz"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const config: Config.InitialOptions = {
|
|||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
coverageDirectory: 'coverage',
|
coverageDirectory: 'coverage',
|
||||||
coverageReporters: ['text', 'cobertura', 'html', 'json-summary'],
|
coverageReporters: ['text', 'cobertura', 'html', 'json-summary'],
|
||||||
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
|
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||||
modulePathIgnorePatterns: ['dist'],
|
modulePathIgnorePatterns: ['dist'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
@@ -36,14 +35,6 @@ const config: Config.InitialOptions = {
|
|||||||
browsers: ['chromium', 'firefox', 'webkit'],
|
browsers: ['chromium', 'firefox', 'webkit'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
statements: 80,
|
|
||||||
branches: 65,
|
|
||||||
functions: 80,
|
|
||||||
lines: 80,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -21,9 +21,7 @@
|
|||||||
"playwright:codegen:local": "playwright codegen http://localhost:3301",
|
"playwright:codegen:local": "playwright codegen http://localhost:3301",
|
||||||
"playwright:codegen:local:auth": "yarn playwright:codegen:local --load-storage=tests/auth.json",
|
"playwright:codegen:local:auth": "yarn playwright:codegen:local --load-storage=tests/auth.json",
|
||||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||||
"commitlint": "commitlint --edit $1",
|
"commitlint": "commitlint --edit $1"
|
||||||
"test": "jest --coverage",
|
|
||||||
"test:changedsince": "jest --changedSince=develop --coverage --silent"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.15.0"
|
"node": ">=16.15.0"
|
||||||
@@ -46,14 +44,11 @@
|
|||||||
"@sentry/webpack-plugin": "2.16.0",
|
"@sentry/webpack-plugin": "2.16.0",
|
||||||
"@signozhq/design-tokens": "0.0.8",
|
"@signozhq/design-tokens": "0.0.8",
|
||||||
"@uiw/react-md-editor": "3.23.5",
|
"@uiw/react-md-editor": "3.23.5",
|
||||||
"@visx/group": "3.3.0",
|
|
||||||
"@visx/shape": "3.5.0",
|
|
||||||
"@visx/tooltip": "3.3.0",
|
|
||||||
"@xstate/react": "^3.0.0",
|
"@xstate/react": "^3.0.0",
|
||||||
"ansi-to-html": "0.7.2",
|
"ansi-to-html": "0.7.2",
|
||||||
"antd": "5.11.0",
|
"antd": "5.11.0",
|
||||||
"antd-table-saveas-excel": "2.2.1",
|
"antd-table-saveas-excel": "2.2.1",
|
||||||
"axios": "1.6.4",
|
"axios": "1.6.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-jest": "^29.6.4",
|
"babel-jest": "^29.6.4",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
"button_test_channel": "Test",
|
"button_test_channel": "Test",
|
||||||
"button_return": "Back",
|
"button_return": "Back",
|
||||||
"field_channel_name": "Name",
|
"field_channel_name": "Name",
|
||||||
"field_send_resolved": "Send resolved alerts",
|
|
||||||
"field_channel_type": "Type",
|
"field_channel_type": "Type",
|
||||||
"field_webhook_url": "Webhook URL",
|
"field_webhook_url": "Webhook URL",
|
||||||
"field_slack_recipient": "Recipient",
|
"field_slack_recipient": "Recipient",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"new_dashboard_title": "Sample Title",
|
"new_dashboard_title": "Sample Title",
|
||||||
"layout_saved_successfully": "Layout saved successfully",
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
"add_panel": "Add Panel",
|
"add_panel": "Add Panel",
|
||||||
"add_row": "Add Row",
|
|
||||||
"save_layout": "Save Layout",
|
"save_layout": "Save Layout",
|
||||||
"variable_updated_successfully": "Variable updated successfully",
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
"error_while_updating_variable": "Error while updating variable",
|
"error_while_updating_variable": "Error while updating variable",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
"button_test_channel": "Test",
|
"button_test_channel": "Test",
|
||||||
"button_return": "Back",
|
"button_return": "Back",
|
||||||
"field_channel_name": "Name",
|
"field_channel_name": "Name",
|
||||||
"field_send_resolved": "Send resolved alerts",
|
|
||||||
"field_channel_type": "Type",
|
"field_channel_type": "Type",
|
||||||
"field_webhook_url": "Webhook URL",
|
"field_webhook_url": "Webhook URL",
|
||||||
"field_slack_recipient": "Recipient",
|
"field_slack_recipient": "Recipient",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
"new_dashboard_title": "Sample Title",
|
"new_dashboard_title": "Sample Title",
|
||||||
"layout_saved_successfully": "Layout saved successfully",
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
"add_panel": "Add Panel",
|
"add_panel": "Add Panel",
|
||||||
"add_row": "Add Row",
|
|
||||||
"save_layout": "Save Layout",
|
"save_layout": "Save Layout",
|
||||||
"full_view": "Full Screen View",
|
"full_view": "Full Screen View",
|
||||||
"variable_updated_successfully": "Variable updated successfully",
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const create = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
email_configs: [
|
email_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
to: props.to,
|
to: props.to,
|
||||||
html: props.html,
|
html: props.html,
|
||||||
headers: props.headers,
|
headers: props.headers,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const create = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
msteams_configs: [
|
msteams_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
webhook_url: props.webhook_url,
|
webhook_url: props.webhook_url,
|
||||||
title: props.title,
|
title: props.title,
|
||||||
text: props.text,
|
text: props.text,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const create = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
pagerduty_configs: [
|
pagerduty_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
routing_key: props.routing_key,
|
routing_key: props.routing_key,
|
||||||
client: props.client,
|
client: props.client,
|
||||||
client_url: props.client_url,
|
client_url: props.client_url,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const create = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
slack_configs: [
|
slack_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
api_url: props.api_url,
|
api_url: props.api_url,
|
||||||
channel: props.channel,
|
channel: props.channel,
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const create = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
webhook_configs: [
|
webhook_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
url: props.api_url,
|
url: props.api_url,
|
||||||
http_config: httpConfig,
|
http_config: httpConfig,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const editEmail = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
email_configs: [
|
email_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
to: props.to,
|
to: props.to,
|
||||||
html: props.html,
|
html: props.html,
|
||||||
headers: props.headers,
|
headers: props.headers,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const editMsTeams = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
msteams_configs: [
|
msteams_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
webhook_url: props.webhook_url,
|
webhook_url: props.webhook_url,
|
||||||
title: props.title,
|
title: props.title,
|
||||||
text: props.text,
|
text: props.text,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const editOpsgenie = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
opsgenie_configs: [
|
opsgenie_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
api_key: props.api_key,
|
api_key: props.api_key,
|
||||||
description: props.description,
|
description: props.description,
|
||||||
priority: props.priority,
|
priority: props.priority,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const editPager = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
pagerduty_configs: [
|
pagerduty_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
routing_key: props.routing_key,
|
routing_key: props.routing_key,
|
||||||
client: props.client,
|
client: props.client,
|
||||||
client_url: props.client_url,
|
client_url: props.client_url,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const editSlack = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
slack_configs: [
|
slack_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
api_url: props.api_url,
|
api_url: props.api_url,
|
||||||
channel: props.channel,
|
channel: props.channel,
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const editWebhook = async (
|
|||||||
name: props.name,
|
name: props.name,
|
||||||
webhook_configs: [
|
webhook_configs: [
|
||||||
{
|
{
|
||||||
send_resolved: props.send_resolved,
|
send_resolved: true,
|
||||||
url: props.api_url,
|
url: props.api_url,
|
||||||
http_config: httpConfig,
|
http_config: httpConfig,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiV4Instance } from 'api';
|
import axios from 'api';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||||
|
|
||||||
@@ -6,6 +6,4 @@ export const getMetricMeta = (
|
|||||||
metricName: string,
|
metricName: string,
|
||||||
servicename: string,
|
servicename: string,
|
||||||
): Promise<AxiosResponse<MetricMetaProps>> =>
|
): Promise<AxiosResponse<MetricMetaProps>> =>
|
||||||
ApiV4Instance.get(
|
axios.get(`/metric_meta?metricName=${metricName}&serviceName=${servicename}`);
|
||||||
`/metric/metric_metadata?metricName=${metricName}&serviceName=${servicename}`,
|
|
||||||
);
|
|
||||||
|
|||||||
27
frontend/src/api/metrics/getMetricName.ts
Normal file
27
frontend/src/api/metrics/getMetricName.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ApiV2Instance as axios } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
MetricNameProps,
|
||||||
|
MetricNamesPayloadProps,
|
||||||
|
} from 'types/api/metrics/getMetricName';
|
||||||
|
|
||||||
|
export const getMetricName = async (
|
||||||
|
props: MetricNameProps,
|
||||||
|
): Promise<SuccessResponse<MetricNamesPayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/metrics/autocomplete/list?match=${props || ''}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ApiV3Instance as axios } from 'api';
|
import { ApiV2Instance as axios } from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import {
|
import {
|
||||||
TagKeyProps,
|
TagKeyProps,
|
||||||
@@ -9,19 +8,15 @@ import {
|
|||||||
TagValueProps,
|
TagValueProps,
|
||||||
TagValuesPayloadProps,
|
TagValuesPayloadProps,
|
||||||
} from 'types/api/metrics/getResourceAttributes';
|
} from 'types/api/metrics/getResourceAttributes';
|
||||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
|
||||||
|
|
||||||
export const getResourceAttributesTagKeys = async (
|
export const getResourceAttributesTagKeys = async (
|
||||||
props: TagKeyProps,
|
props: TagKeyProps,
|
||||||
): Promise<SuccessResponse<TagKeysPayloadProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<TagKeysPayloadProps> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`/autocomplete/attribute_keys?${createQueryParams({
|
`/metrics/autocomplete/tagKey?metricName=${props.metricName}${
|
||||||
aggregateOperator: MetricAggregateOperator.RATE,
|
props.match ? `&match=${props.match}` : ''
|
||||||
searchText: props.match,
|
}`,
|
||||||
dataSource: DataSource.METRICS,
|
|
||||||
aggregateAttribute: props.metricName,
|
|
||||||
})}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -40,13 +35,7 @@ export const getResourceAttributesTagValues = async (
|
|||||||
): Promise<SuccessResponse<TagValuesPayloadProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<TagValuesPayloadProps> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`/autocomplete/attribute_values?${createQueryParams({
|
`/metrics/autocomplete/tagValue?metricName=${props.metricName}&tagKey=${props.tagKey}`,
|
||||||
aggregateOperator: MetricAggregateOperator.RATE,
|
|
||||||
dataSource: DataSource.METRICS,
|
|
||||||
aggregateAttribute: props.metricName,
|
|
||||||
attributeKey: props.tagKey,
|
|
||||||
searchText: '',
|
|
||||||
})}`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ function ListLogView({
|
|||||||
const timestampValue = useMemo(
|
const timestampValue = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof flattenLogData.timestamp === 'string'
|
typeof flattenLogData.timestamp === 'string'
|
||||||
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(flattenLogData.timestamp).format()
|
||||||
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
|
: dayjs(flattenLogData.timestamp / 1e6).format(),
|
||||||
[flattenLogData.timestamp],
|
[flattenLogData.timestamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -90,12 +90,12 @@ function RawLogView({
|
|||||||
const text = useMemo(
|
const text = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof data.timestamp === 'string'
|
typeof data.timestamp === 'string'
|
||||||
? `${dayjs(data.timestamp).format(
|
? `${dayjs(data.timestamp).format()} | ${attributesText} ${severityText} ${
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
data.body
|
||||||
)} | ${attributesText} ${severityText} ${data.body}`
|
}`
|
||||||
: `${dayjs(data.timestamp / 1e6).format(
|
: `${dayjs(
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
data.timestamp / 1e6,
|
||||||
)} | ${attributesText} ${severityText} ${data.body}`,
|
).format()} | ${attributesText} ${severityText} ${data.body}`,
|
||||||
[data.timestamp, data.body, severityText, attributesText],
|
[data.timestamp, data.body, severityText, attributesText],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
||||||
const date =
|
const date =
|
||||||
typeof field === 'string'
|
typeof field === 'string'
|
||||||
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(field).format()
|
||||||
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
: dayjs(field / 1e6).format();
|
||||||
return {
|
return {
|
||||||
children: (
|
children: (
|
||||||
<div className="table-timestamp">
|
<div className="table-timestamp">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
import './DynamicColumnTable.syles.scss';
|
import './DynamicColumnTable.syles.scss';
|
||||||
|
|
||||||
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
|
import { Button, Dropdown, MenuProps, Switch } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
|
||||||
import { SlidersHorizontal } from 'lucide-react';
|
import { SlidersHorizontal } from 'lucide-react';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
@@ -21,7 +20,6 @@ function DynamicColumnTable({
|
|||||||
columns,
|
columns,
|
||||||
dynamicColumns,
|
dynamicColumns,
|
||||||
onDragColumn,
|
onDragColumn,
|
||||||
facingIssueBtn,
|
|
||||||
...restProps
|
...restProps
|
||||||
}: DynamicColumnTableProps): JSX.Element {
|
}: DynamicColumnTableProps): JSX.Element {
|
||||||
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
|
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
|
||||||
@@ -85,8 +83,6 @@ function DynamicColumnTable({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DynamicColumnTable">
|
<div className="DynamicColumnTable">
|
||||||
<Flex justify="flex-end" align="center" gap={8}>
|
|
||||||
{facingIssueBtn && <FacingIssueBtn {...facingIssueBtn} />}
|
|
||||||
{dynamicColumns && (
|
{dynamicColumns && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
@@ -100,7 +96,6 @@ function DynamicColumnTable({
|
|||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
columns={columnsData}
|
columns={columnsData}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { TableProps } from 'antd';
|
import { TableProps } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
|
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
|
||||||
import { FacingIssueBtnProps } from 'components/facingIssueBtn/FacingIssueBtn';
|
|
||||||
|
|
||||||
import { TableDataSource } from './contants';
|
import { TableDataSource } from './contants';
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ export interface DynamicColumnTableProps extends TableProps<any> {
|
|||||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||||
dynamicColumns: TableProps<any>['columns'];
|
dynamicColumns: TableProps<any>['columns'];
|
||||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||||
facingIssueBtn?: FacingIssueBtnProps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetVisibleColumnsFunction = (
|
export type GetVisibleColumnsFunction = (
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
.facing-issue-button {
|
|
||||||
color: var(--bg-amber-500);
|
|
||||||
border-color: var(--bg-amber-500);
|
|
||||||
|
|
||||||
.ant-btn:hover {
|
|
||||||
color: var(--bg-amber-400) !important;
|
|
||||||
border-color: var(--bg-amber-300) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import './FacingIssueBtn.style.scss';
|
|
||||||
|
|
||||||
import { Button, Tooltip } from 'antd';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import { FeatureKeys } from 'constants/features';
|
|
||||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
|
||||||
import { defaultTo } from 'lodash-es';
|
|
||||||
import { HelpCircle } from 'lucide-react';
|
|
||||||
import { isCloudUser } from 'utils/app';
|
|
||||||
|
|
||||||
export interface FacingIssueBtnProps {
|
|
||||||
eventName: string;
|
|
||||||
attributes: Record<string, unknown>;
|
|
||||||
message?: string;
|
|
||||||
buttonText?: string;
|
|
||||||
className?: string;
|
|
||||||
onHoverText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FacingIssueBtn({
|
|
||||||
attributes,
|
|
||||||
eventName,
|
|
||||||
message = '',
|
|
||||||
buttonText = '',
|
|
||||||
className = '',
|
|
||||||
onHoverText = '',
|
|
||||||
}: FacingIssueBtnProps): JSX.Element | null {
|
|
||||||
const handleFacingIssuesClick = (): void => {
|
|
||||||
logEvent(eventName, attributes);
|
|
||||||
|
|
||||||
if (window.Intercom) {
|
|
||||||
window.Intercom('showNewMessage', defaultTo(message, ''));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isChatSupportEnabled = useFeatureFlags(FeatureKeys.CHAT_SUPPORT)?.active;
|
|
||||||
const isCloudUserVal = isCloudUser();
|
|
||||||
|
|
||||||
return isCloudUserVal && isChatSupportEnabled ? ( // Note: we would need to move this condition to license based in future
|
|
||||||
<div className="facing-issue-button">
|
|
||||||
<Tooltip title={onHoverText} autoAdjustOverflow>
|
|
||||||
<Button
|
|
||||||
className={cx('periscope-btn', 'facing-issue-button', className)}
|
|
||||||
onClick={handleFacingIssuesClick}
|
|
||||||
icon={<HelpCircle size={14} />}
|
|
||||||
>
|
|
||||||
{buttonText || 'Facing issues?'}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
FacingIssueBtn.defaultProps = {
|
|
||||||
message: '',
|
|
||||||
buttonText: '',
|
|
||||||
className: '',
|
|
||||||
onHoverText: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FacingIssueBtn;
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { AlertDef } from 'types/api/alerts/def';
|
|
||||||
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
|
|
||||||
|
|
||||||
export const chartHelpMessage = (
|
|
||||||
selectedDashboard: Dashboard | undefined,
|
|
||||||
graphType: PANEL_TYPES,
|
|
||||||
): string => `
|
|
||||||
Hi Team,
|
|
||||||
|
|
||||||
I need help in creating this chart. Here are my dashboard details
|
|
||||||
|
|
||||||
Name: ${selectedDashboard?.data.title || ''}
|
|
||||||
Panel type: ${graphType}
|
|
||||||
Dashboard Id: ${selectedDashboard?.uuid || ''}
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|
||||||
export const dashboardHelpMessage = (
|
|
||||||
data: DashboardData | undefined,
|
|
||||||
selectedDashboard: Dashboard | undefined,
|
|
||||||
): string => `
|
|
||||||
Hi Team,
|
|
||||||
|
|
||||||
I need help with this dashboard. Here are my dashboard details
|
|
||||||
|
|
||||||
Name: ${data?.title || ''}
|
|
||||||
Dashboard Id: ${selectedDashboard?.uuid || ''}
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|
||||||
export const dashboardListMessage = `Hi Team,
|
|
||||||
|
|
||||||
I need help with dashboards.
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|
||||||
export const listAlertMessage = `Hi Team,
|
|
||||||
|
|
||||||
I need help with managing alerts.
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|
||||||
export const alertHelpMessage = (
|
|
||||||
alertDef: AlertDef,
|
|
||||||
ruleId: number,
|
|
||||||
): string => `
|
|
||||||
Hi Team,
|
|
||||||
|
|
||||||
I need help in configuring this alert. Here are my alert rule details
|
|
||||||
|
|
||||||
Name: ${alertDef?.alert || ''}
|
|
||||||
Alert Type: ${alertDef?.alertType || ''}
|
|
||||||
State: ${(alertDef as any)?.state || ''}
|
|
||||||
Alert Id: ${ruleId}
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
@@ -29,7 +29,6 @@ export const getComponentForPanelType = (
|
|||||||
[PANEL_TYPES.LIST]:
|
[PANEL_TYPES.LIST]:
|
||||||
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
|
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
|
||||||
[PANEL_TYPES.BAR]: Uplot,
|
[PANEL_TYPES.BAR]: Uplot,
|
||||||
[PANEL_TYPES.PIE]: null,
|
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,5 +30,4 @@ export enum QueryParams {
|
|||||||
integration = 'integration',
|
integration = 'integration',
|
||||||
pagination = 'pagination',
|
pagination = 'pagination',
|
||||||
relativeTime = 'relativeTime',
|
relativeTime = 'relativeTime',
|
||||||
alertType = 'alertType',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,15 +285,9 @@ export enum PANEL_TYPES {
|
|||||||
LIST = 'list',
|
LIST = 'list',
|
||||||
TRACE = 'trace',
|
TRACE = 'trace',
|
||||||
BAR = 'bar',
|
BAR = 'bar',
|
||||||
PIE = 'pie',
|
|
||||||
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
export enum PANEL_GROUP_TYPES {
|
|
||||||
ROW = 'row',
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export enum ATTRIBUTE_TYPES {
|
export enum ATTRIBUTE_TYPES {
|
||||||
SUM = 'Sum',
|
SUM = 'Sum',
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
className={cx(
|
className={cx(
|
||||||
'app-layout',
|
'app-layout',
|
||||||
isDarkMode ? 'darkMode' : 'lightMode',
|
isDarkMode ? 'darkMode' : 'lightMode',
|
||||||
!collapsed && !renderFullScreen ? 'docked' : '',
|
!collapsed ? 'docked' : '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isToDisplayLayout && !renderFullScreen && (
|
{isToDisplayLayout && !renderFullScreen && (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.billing-container {
|
.billing-container {
|
||||||
margin-bottom: 40px;
|
|
||||||
padding-top: 36px;
|
padding-top: 36px;
|
||||||
width: 65%;
|
width: 65%;
|
||||||
|
|
||||||
|
|||||||
@@ -53,14 +53,13 @@ function CreateAlertChannels({
|
|||||||
EmailChannel
|
EmailChannel
|
||||||
>
|
>
|
||||||
>({
|
>({
|
||||||
send_resolved: true,
|
|
||||||
text: `{{ range .Alerts -}}
|
text: `{{ range .Alerts -}}
|
||||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||||
|
|
||||||
*Summary:* {{ .Annotations.summary }}
|
*Summary:* {{ .Annotations.summary }}
|
||||||
*Description:* {{ .Annotations.description }}
|
*Description:* {{ .Annotations.description }}
|
||||||
*RelatedLogs:* {{ if gt (len .Annotations.related_logs) 0 -}} View in <{{ .Annotations.related_logs }}|logs explorer> {{- end}}
|
*RelatedLogs:* {{ .Annotations.related_logs }}
|
||||||
*RelatedTraces:* {{ if gt (len .Annotations.related_traces) 0 -}} View in <{{ .Annotations.related_traces }}|traces explorer> {{- end}}
|
*RelatedTraces:* {{ .Annotations.related_traces }}
|
||||||
|
|
||||||
*Details:*
|
*Details:*
|
||||||
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
|
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }}
|
||||||
@@ -120,7 +119,7 @@ function CreateAlertChannels({
|
|||||||
api_url: selectedConfig?.api_url || '',
|
api_url: selectedConfig?.api_url || '',
|
||||||
channel: selectedConfig?.channel || '',
|
channel: selectedConfig?.channel || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
text: selectedConfig?.text || '',
|
text: selectedConfig?.text || '',
|
||||||
title: selectedConfig?.title || '',
|
title: selectedConfig?.title || '',
|
||||||
}),
|
}),
|
||||||
@@ -159,7 +158,7 @@ function CreateAlertChannels({
|
|||||||
let request: WebhookChannel = {
|
let request: WebhookChannel = {
|
||||||
api_url: selectedConfig?.api_url || '',
|
api_url: selectedConfig?.api_url || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selectedConfig?.username !== '' || selectedConfig?.password !== '') {
|
if (selectedConfig?.username !== '' || selectedConfig?.password !== '') {
|
||||||
@@ -227,7 +226,7 @@ function CreateAlertChannels({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
routing_key: selectedConfig?.routing_key || '',
|
routing_key: selectedConfig?.routing_key || '',
|
||||||
client: selectedConfig?.client || '',
|
client: selectedConfig?.client || '',
|
||||||
client_url: selectedConfig?.client_url || '',
|
client_url: selectedConfig?.client_url || '',
|
||||||
@@ -275,7 +274,7 @@ function CreateAlertChannels({
|
|||||||
() => ({
|
() => ({
|
||||||
api_key: selectedConfig?.api_key || '',
|
api_key: selectedConfig?.api_key || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
description: selectedConfig?.description || '',
|
description: selectedConfig?.description || '',
|
||||||
message: selectedConfig?.message || '',
|
message: selectedConfig?.message || '',
|
||||||
priority: selectedConfig?.priority || '',
|
priority: selectedConfig?.priority || '',
|
||||||
@@ -313,7 +312,7 @@ function CreateAlertChannels({
|
|||||||
const prepareEmailRequest = useCallback(
|
const prepareEmailRequest = useCallback(
|
||||||
() => ({
|
() => ({
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
to: selectedConfig?.to || '',
|
to: selectedConfig?.to || '',
|
||||||
html: selectedConfig?.html || '',
|
html: selectedConfig?.html || '',
|
||||||
headers: selectedConfig?.headers || {},
|
headers: selectedConfig?.headers || {},
|
||||||
@@ -351,7 +350,7 @@ function CreateAlertChannels({
|
|||||||
() => ({
|
() => ({
|
||||||
webhook_url: selectedConfig?.webhook_url || '',
|
webhook_url: selectedConfig?.webhook_url || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
text: selectedConfig?.text || '',
|
text: selectedConfig?.text || '',
|
||||||
title: selectedConfig?.title || '',
|
title: selectedConfig?.title || '',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { Form, Row } from 'antd';
|
import { Form, Row } from 'antd';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import { QueryParams } from 'constants/query';
|
|
||||||
import FormAlertRules from 'container/FormAlertRules';
|
import FormAlertRules from 'container/FormAlertRules';
|
||||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||||
import history from 'lib/history';
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
@@ -20,25 +18,15 @@ import SelectAlertType from './SelectAlertType';
|
|||||||
|
|
||||||
function CreateRules(): JSX.Element {
|
function CreateRules(): JSX.Element {
|
||||||
const [initValues, setInitValues] = useState<AlertDef | null>(null);
|
const [initValues, setInitValues] = useState<AlertDef | null>(null);
|
||||||
|
const [alertType, setAlertType] = useState<AlertTypes>(
|
||||||
|
AlertTypes.METRICS_BASED_ALERT,
|
||||||
|
);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const version = queryParams.get('version');
|
const version = queryParams.get('version');
|
||||||
const alertTypeFromParams = queryParams.get(QueryParams.alertType);
|
|
||||||
|
|
||||||
const compositeQuery = useGetCompositeQueryParam();
|
const compositeQuery = useGetCompositeQueryParam();
|
||||||
function getAlertTypeFromDataSource(): AlertTypes | null {
|
|
||||||
if (!compositeQuery) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource;
|
|
||||||
|
|
||||||
return ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
|
|
||||||
}
|
|
||||||
|
|
||||||
const [alertType, setAlertType] = useState<AlertTypes>(
|
|
||||||
(alertTypeFromParams as AlertTypes) || getAlertTypeFromDataSource(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
|
|
||||||
@@ -60,17 +48,21 @@ function CreateRules(): JSX.Element {
|
|||||||
version: version || ENTITY_VERSION_V4,
|
version: version || ENTITY_VERSION_V4,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
queryParams.set(QueryParams.alertType, typ);
|
|
||||||
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
|
|
||||||
history.replace(generatedUrl);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!compositeQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataSource = compositeQuery?.builder?.queryData[0]?.dataSource;
|
||||||
|
|
||||||
|
const alertType = ALERT_TYPE_VS_SOURCE_MAPPING[dataSource];
|
||||||
|
|
||||||
if (alertType) {
|
if (alertType) {
|
||||||
onSelectType(alertType);
|
onSelectType(alertType);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [alertType]);
|
}, [compositeQuery]);
|
||||||
|
|
||||||
if (!initValues) {
|
if (!initValues) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function EditAlertChannels({
|
|||||||
api_url: selectedConfig?.api_url || '',
|
api_url: selectedConfig?.api_url || '',
|
||||||
channel: selectedConfig?.channel || '',
|
channel: selectedConfig?.channel || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
text: selectedConfig?.text || '',
|
text: selectedConfig?.text || '',
|
||||||
title: selectedConfig?.title || '',
|
title: selectedConfig?.title || '',
|
||||||
id,
|
id,
|
||||||
@@ -115,7 +115,7 @@ function EditAlertChannels({
|
|||||||
return {
|
return {
|
||||||
api_url: selectedConfig?.api_url || '',
|
api_url: selectedConfig?.api_url || '',
|
||||||
name: name || '',
|
name: name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
id,
|
id,
|
||||||
@@ -284,7 +284,7 @@ function EditAlertChannels({
|
|||||||
() => ({
|
() => ({
|
||||||
webhook_url: selectedConfig?.webhook_url || '',
|
webhook_url: selectedConfig?.webhook_url || '',
|
||||||
name: selectedConfig?.name || '',
|
name: selectedConfig?.name || '',
|
||||||
send_resolved: selectedConfig?.send_resolved || false,
|
send_resolved: true,
|
||||||
text: selectedConfig?.text || '',
|
text: selectedConfig?.text || '',
|
||||||
title: selectedConfig?.title || '',
|
title: selectedConfig?.title || '',
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
|
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from 'antd/lib/form/interface';
|
||||||
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
|
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
@@ -95,22 +95,6 @@ function FormAlertChannels({
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={t('field_send_resolved')}
|
|
||||||
labelAlign="left"
|
|
||||||
name="send_resolved"
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
defaultChecked={initialValue?.send_resolved}
|
|
||||||
onChange={(value): void => {
|
|
||||||
setSelectedConfig((state) => ({
|
|
||||||
...state,
|
|
||||||
send_resolved: value,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
||||||
<Select.Option value="slack" key="slack">
|
<Select.Option value="slack" key="slack">
|
||||||
|
|||||||
@@ -43,8 +43,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.facing-issue-btn {
|
|
||||||
margin-top: 20px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import testAlertApi from 'api/alerts/testAlert';
|
import testAlertApi from 'api/alerts/testAlert';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
|
||||||
import { alertHelpMessage } from 'components/facingIssueBtn/util';
|
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
@@ -140,11 +138,6 @@ function FormAlertRules({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Set selectedQueryName based on the length of queryOptions
|
// Set selectedQueryName based on the length of queryOptions
|
||||||
const selectedQueryName = alertDef?.condition?.selectedQueryName;
|
|
||||||
if (
|
|
||||||
!selectedQueryName ||
|
|
||||||
!queryOptions.some((option) => option.value === selectedQueryName)
|
|
||||||
) {
|
|
||||||
setAlertDef((def) => ({
|
setAlertDef((def) => ({
|
||||||
...def,
|
...def,
|
||||||
condition: {
|
condition: {
|
||||||
@@ -153,8 +146,7 @@ function FormAlertRules({
|
|||||||
queryOptions.length > 0 ? String(queryOptions[0].value) : undefined,
|
queryOptions.length > 0 ? String(queryOptions[0].value) : undefined,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}, [currentQuery?.queryType, queryOptions]);
|
||||||
}, [alertDef, currentQuery?.queryType, queryOptions]);
|
|
||||||
|
|
||||||
const onCancelHandler = useCallback(() => {
|
const onCancelHandler = useCallback(() => {
|
||||||
history.replace(ROUTES.LIST_ALL_ALERT);
|
history.replace(ROUTES.LIST_ALL_ALERT);
|
||||||
@@ -490,8 +482,6 @@ function FormAlertRules({
|
|||||||
alertDef?.broadcastToAll ||
|
alertDef?.broadcastToAll ||
|
||||||
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0);
|
(alertDef.preferredChannels && alertDef.preferredChannels.length > 0);
|
||||||
|
|
||||||
const isRuleCreated = !ruleId || ruleId === 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Element}
|
{Element}
|
||||||
@@ -524,7 +514,6 @@ function FormAlertRules({
|
|||||||
runQuery={handleRunQuery}
|
runQuery={handleRunQuery}
|
||||||
alertDef={alertDef}
|
alertDef={alertDef}
|
||||||
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
panelType={panelType || PANEL_TYPES.TIME_SERIES}
|
||||||
key={currentQuery.queryType}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RuleOptions
|
<RuleOptions
|
||||||
@@ -574,22 +563,6 @@ function FormAlertRules({
|
|||||||
</StyledLeftContainer>
|
</StyledLeftContainer>
|
||||||
<Col flex="1 1 300px">
|
<Col flex="1 1 300px">
|
||||||
<UserGuide queryType={currentQuery.queryType} />
|
<UserGuide queryType={currentQuery.queryType} />
|
||||||
<FacingIssueBtn
|
|
||||||
attributes={{
|
|
||||||
alert: alertDef?.alert,
|
|
||||||
alertType: alertDef?.alertType,
|
|
||||||
id: ruleId,
|
|
||||||
ruleType: alertDef?.ruleType,
|
|
||||||
state: (alertDef as any)?.state,
|
|
||||||
panelType,
|
|
||||||
screen: isRuleCreated ? 'Edit Alert' : 'New Alert',
|
|
||||||
}}
|
|
||||||
className="facing-issue-btn"
|
|
||||||
eventName="Alert: Facing Issues in alert"
|
|
||||||
buttonText="Need help with this alert?"
|
|
||||||
message={alertHelpMessage(alertDef, ruleId)}
|
|
||||||
onHoverText="Click here to get help with this alert"
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -26,6 +26,5 @@ export const PANEL_TYPES_VS_FULL_VIEW_TABLE: PanelTypeAndGraphManagerVisibilityP
|
|||||||
LIST: false,
|
LIST: false,
|
||||||
TRACE: false,
|
TRACE: false,
|
||||||
BAR: true,
|
BAR: true,
|
||||||
PIE: false,
|
|
||||||
EMPTY_WIDGET: false,
|
EMPTY_WIDGET: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function WidgetGraphComponent({
|
|||||||
|
|
||||||
const lineChartRef = useRef<ToggleGraphProps>();
|
const lineChartRef = useRef<ToggleGraphProps>();
|
||||||
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
|
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
|
||||||
Array(queryResponse.data?.payload?.data?.result?.length || 0).fill(true),
|
Array(queryResponse.data?.payload?.data.result.length || 0).fill(true),
|
||||||
);
|
);
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
const graphRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ function WidgetGraphComponent({
|
|||||||
i: uuid,
|
i: uuid,
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 6,
|
h: 3,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -35,11 +35,7 @@ function GridCardGraph({
|
|||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
const {
|
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||||
toScrollWidgetId,
|
|
||||||
setToScrollWidgetId,
|
|
||||||
variablesToGetUpdated,
|
|
||||||
} = useDashboard();
|
|
||||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
GlobalReducer
|
GlobalReducer
|
||||||
@@ -94,11 +90,7 @@ function GridCardGraph({
|
|||||||
const isEmptyWidget =
|
const isEmptyWidget =
|
||||||
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
||||||
|
|
||||||
const queryEnabledCondition =
|
const queryEnabledCondition = isVisible && !isEmptyWidget && isQueryEnabled;
|
||||||
isVisible &&
|
|
||||||
!isEmptyWidget &&
|
|
||||||
isQueryEnabled &&
|
|
||||||
isEmpty(variablesToGetUpdated);
|
|
||||||
|
|
||||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||||
if (widget.panelTypes !== PANEL_TYPES.LIST) {
|
if (widget.panelTypes !== PANEL_TYPES.LIST) {
|
||||||
@@ -174,8 +166,7 @@ function GridCardGraph({
|
|||||||
|
|
||||||
const menuList =
|
const menuList =
|
||||||
widget.panelTypes === PANEL_TYPES.TABLE ||
|
widget.panelTypes === PANEL_TYPES.TABLE ||
|
||||||
widget.panelTypes === PANEL_TYPES.LIST ||
|
widget.panelTypes === PANEL_TYPES.LIST
|
||||||
widget.panelTypes === PANEL_TYPES.PIE
|
|
||||||
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
|
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
|
||||||
: headerMenuList;
|
: headerMenuList;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
import './GridCardLayout.styles.scss';
|
import './GridCardLayout.styles.scss';
|
||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Flex, Form, Input, Modal, Tooltip, Typography } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { useForm } from 'antd/es/form/Form';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
|
||||||
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
|
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
@@ -16,21 +12,12 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
|||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { defaultTo } from 'lodash-es';
|
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import {
|
import { FullscreenIcon } from 'lucide-react';
|
||||||
FullscreenIcon,
|
|
||||||
GripVertical,
|
|
||||||
MoveDown,
|
|
||||||
MoveUp,
|
|
||||||
Settings,
|
|
||||||
Trash2,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { sortLayout } from 'providers/Dashboard/util';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
||||||
import { ItemCallback, Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@@ -40,7 +27,6 @@ import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { ROLES, USER_ROLES } from 'types/roles';
|
import { ROLES, USER_ROLES } from 'types/roles';
|
||||||
import { ComponentTypes } from 'utils/permission';
|
import { ComponentTypes } from 'utils/permission';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
import { EditMenuAction, ViewMenuAction } from './config';
|
import { EditMenuAction, ViewMenuAction } from './config';
|
||||||
import GridCard from './GridCard';
|
import GridCard from './GridCard';
|
||||||
@@ -59,8 +45,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
layouts,
|
layouts,
|
||||||
setLayouts,
|
setLayouts,
|
||||||
panelMap,
|
|
||||||
setPanelMap,
|
|
||||||
setSelectedDashboard,
|
setSelectedDashboard,
|
||||||
isDashboardLocked,
|
isDashboardLocked,
|
||||||
} = useDashboard();
|
} = useDashboard();
|
||||||
@@ -81,26 +65,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
|
|
||||||
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
||||||
|
|
||||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [currentSelectRowId, setCurrentSelectRowId] = useState<string | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [currentPanelMap, setCurrentPanelMap] = useState<
|
|
||||||
Record<string, { widgets: Layout[]; collapsed: boolean }>
|
|
||||||
>({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentPanelMap(panelMap);
|
|
||||||
}, [panelMap]);
|
|
||||||
|
|
||||||
const [form] = useForm<{
|
|
||||||
title: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const updateDashboardMutation = useUpdateDashboard();
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
@@ -123,7 +87,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDashboardLayout(sortLayout(layouts));
|
setDashboardLayout(layouts);
|
||||||
}, [layouts]);
|
}, [layouts]);
|
||||||
|
|
||||||
const onSaveHandler = (): void => {
|
const onSaveHandler = (): void => {
|
||||||
@@ -133,7 +97,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
...selectedDashboard,
|
...selectedDashboard,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
panelMap: { ...currentPanelMap },
|
|
||||||
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
uuid: selectedDashboard.uuid,
|
||||||
@@ -143,9 +106,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.payload) {
|
||||||
if (updatedDashboard.payload.data.layout)
|
if (updatedDashboard.payload.data.layout)
|
||||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
setLayouts(updatedDashboard.payload.data.layout);
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
featureResponse.refetch();
|
featureResponse.refetch();
|
||||||
@@ -168,8 +130,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
dashboardLayout,
|
dashboardLayout,
|
||||||
);
|
);
|
||||||
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
||||||
const updatedLayout = sortLayout(layout);
|
setDashboardLayout(layout);
|
||||||
setDashboardLayout(updatedLayout);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,297 +167,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dashboardLayout]);
|
}, [dashboardLayout]);
|
||||||
|
|
||||||
function handleAddRow(): void {
|
|
||||||
if (!selectedDashboard) return;
|
|
||||||
const id = uuid();
|
|
||||||
|
|
||||||
const newRowWidgetMap: { widgets: Layout[]; collapsed: boolean } = {
|
|
||||||
widgets: [],
|
|
||||||
collapsed: false,
|
|
||||||
};
|
|
||||||
const currentRowIdx = 0;
|
|
||||||
for (let j = currentRowIdx; j < dashboardLayout.length; j++) {
|
|
||||||
if (!currentPanelMap[dashboardLayout[j].i]) {
|
|
||||||
newRowWidgetMap.widgets.push(dashboardLayout[j]);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
...selectedDashboard.data,
|
|
||||||
layout: [
|
|
||||||
{
|
|
||||||
i: id,
|
|
||||||
w: 12,
|
|
||||||
minW: 12,
|
|
||||||
minH: 1,
|
|
||||||
maxH: 1,
|
|
||||||
x: 0,
|
|
||||||
h: 1,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
...dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
|
||||||
],
|
|
||||||
panelMap: { ...currentPanelMap, [id]: newRowWidgetMap },
|
|
||||||
widgets: [
|
|
||||||
...(selectedDashboard.data.widgets || []),
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
title: 'Sample Row',
|
|
||||||
description: '',
|
|
||||||
panelTypes: PANEL_GROUP_TYPES.ROW,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateDashboardMutation.mutate(updatedDashboard, {
|
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onSuccess: (updatedDashboard) => {
|
|
||||||
if (updatedDashboard.payload) {
|
|
||||||
if (updatedDashboard.payload.data.layout)
|
|
||||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
featureResponse.refetch();
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRowSettingsClick = (id: string): void => {
|
|
||||||
setIsSettingsModalOpen(true);
|
|
||||||
setCurrentSelectRowId(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSettingsModalSubmit = (): void => {
|
|
||||||
const newTitle = form.getFieldValue('title');
|
|
||||||
if (!selectedDashboard) return;
|
|
||||||
|
|
||||||
if (!currentSelectRowId) return;
|
|
||||||
|
|
||||||
const currentWidget = selectedDashboard?.data?.widgets?.find(
|
|
||||||
(e) => e.id === currentSelectRowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentWidget) return;
|
|
||||||
|
|
||||||
currentWidget.title = newTitle;
|
|
||||||
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
|
||||||
(e) => e.id !== currentSelectRowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedWidgets?.push(currentWidget);
|
|
||||||
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
...selectedDashboard.data,
|
|
||||||
widgets: updatedWidgets,
|
|
||||||
},
|
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
|
||||||
onSuccess: (updatedDashboard) => {
|
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
|
||||||
}
|
|
||||||
if (setPanelMap)
|
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
form.setFieldValue('title', '');
|
|
||||||
setIsSettingsModalOpen(false);
|
|
||||||
setCurrentSelectRowId(null);
|
|
||||||
featureResponse.refetch();
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
const handleRowCollapse = (id: string): void => {
|
|
||||||
if (!selectedDashboard) return;
|
|
||||||
const rowProperties = { ...currentPanelMap[id] };
|
|
||||||
const updatedPanelMap = { ...currentPanelMap };
|
|
||||||
|
|
||||||
let updatedDashboardLayout = [...dashboardLayout];
|
|
||||||
if (rowProperties.collapsed === true) {
|
|
||||||
rowProperties.collapsed = false;
|
|
||||||
const widgetsInsideTheRow = rowProperties.widgets;
|
|
||||||
let maxY = 0;
|
|
||||||
widgetsInsideTheRow.forEach((w) => {
|
|
||||||
maxY = Math.max(maxY, w.y + w.h);
|
|
||||||
});
|
|
||||||
const currentRowWidget = dashboardLayout.find((w) => w.i === id);
|
|
||||||
if (currentRowWidget && widgetsInsideTheRow.length) {
|
|
||||||
maxY -= currentRowWidget.h + currentRowWidget.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
const idxCurrentRow = dashboardLayout.findIndex((w) => w.i === id);
|
|
||||||
|
|
||||||
for (let j = idxCurrentRow + 1; j < dashboardLayout.length; j++) {
|
|
||||||
updatedDashboardLayout[j].y += maxY;
|
|
||||||
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
|
||||||
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
|
||||||
updatedDashboardLayout[j].i
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
||||||
].widgets.map((w) => ({
|
|
||||||
...w,
|
|
||||||
y: w.y + maxY,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatedDashboardLayout = [...updatedDashboardLayout, ...widgetsInsideTheRow];
|
|
||||||
} else {
|
|
||||||
rowProperties.collapsed = true;
|
|
||||||
const currentIdx = dashboardLayout.findIndex((w) => w.i === id);
|
|
||||||
|
|
||||||
let widgetsInsideTheRow: Layout[] = [];
|
|
||||||
let isPanelMapUpdated = false;
|
|
||||||
for (let j = currentIdx + 1; j < dashboardLayout.length; j++) {
|
|
||||||
if (currentPanelMap[dashboardLayout[j].i]) {
|
|
||||||
rowProperties.widgets = widgetsInsideTheRow;
|
|
||||||
widgetsInsideTheRow = [];
|
|
||||||
isPanelMapUpdated = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
widgetsInsideTheRow.push(dashboardLayout[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isPanelMapUpdated) {
|
|
||||||
rowProperties.widgets = widgetsInsideTheRow;
|
|
||||||
}
|
|
||||||
let maxY = 0;
|
|
||||||
widgetsInsideTheRow.forEach((w) => {
|
|
||||||
maxY = Math.max(maxY, w.y + w.h);
|
|
||||||
});
|
|
||||||
const currentRowWidget = dashboardLayout[currentIdx];
|
|
||||||
if (currentRowWidget && widgetsInsideTheRow.length) {
|
|
||||||
maxY -= currentRowWidget.h + currentRowWidget.y;
|
|
||||||
}
|
|
||||||
for (let j = currentIdx + 1; j < updatedDashboardLayout.length; j++) {
|
|
||||||
updatedDashboardLayout[j].y += maxY;
|
|
||||||
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
|
||||||
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
|
||||||
updatedDashboardLayout[j].i
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
||||||
].widgets.map((w) => ({
|
|
||||||
...w,
|
|
||||||
y: w.y + maxY,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedDashboardLayout = updatedDashboardLayout.filter(
|
|
||||||
(widget) => !rowProperties.widgets.some((w: Layout) => w.i === widget.i),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setCurrentPanelMap((prev) => ({
|
|
||||||
...prev,
|
|
||||||
...updatedPanelMap,
|
|
||||||
[id]: {
|
|
||||||
...rowProperties,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
setDashboardLayout(sortLayout(updatedDashboardLayout));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {
|
|
||||||
if (currentPanelMap[oldItem.i]) {
|
|
||||||
const differenceY = newItem.y - oldItem.y;
|
|
||||||
const widgetsInsideRow = currentPanelMap[oldItem.i].widgets.map((w) => ({
|
|
||||||
...w,
|
|
||||||
y: w.y + differenceY,
|
|
||||||
}));
|
|
||||||
setCurrentPanelMap((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[oldItem.i]: {
|
|
||||||
...prev[oldItem.i],
|
|
||||||
widgets: widgetsInsideRow,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowDelete = (): void => {
|
|
||||||
if (!selectedDashboard) return;
|
|
||||||
|
|
||||||
if (!currentSelectRowId) return;
|
|
||||||
|
|
||||||
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
|
||||||
(e) => e.id !== currentSelectRowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedLayout =
|
|
||||||
selectedDashboard.data.layout?.filter((e) => e.i !== currentSelectRowId) ||
|
|
||||||
[];
|
|
||||||
|
|
||||||
const updatedPanelMap = { ...currentPanelMap };
|
|
||||||
delete updatedPanelMap[currentSelectRowId];
|
|
||||||
|
|
||||||
const updatedSelectedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
|
||||||
data: {
|
|
||||||
...selectedDashboard.data,
|
|
||||||
widgets: updatedWidgets,
|
|
||||||
layout: updatedLayout,
|
|
||||||
panelMap: updatedPanelMap,
|
|
||||||
},
|
|
||||||
uuid: selectedDashboard.uuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
|
||||||
onSuccess: (updatedDashboard) => {
|
|
||||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
|
||||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
|
||||||
}
|
|
||||||
if (setPanelMap)
|
|
||||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
setCurrentSelectRowId(null);
|
|
||||||
featureResponse.refetch();
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
||||||
onError: () => {
|
|
||||||
notifications.error({
|
|
||||||
message: SOMETHING_WENT_WRONG,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex justify="flex-end" gap={8} align="center">
|
|
||||||
<FacingIssueBtn
|
|
||||||
attributes={{
|
|
||||||
uuid: selectedDashboard?.uuid,
|
|
||||||
title: data?.title,
|
|
||||||
screen: 'Dashboard Details',
|
|
||||||
}}
|
|
||||||
eventName="Dashboard: Facing Issues in dashboard"
|
|
||||||
buttonText="Need help with this dashboard?"
|
|
||||||
message={dashboardHelpMessage(data, selectedDashboard)}
|
|
||||||
onHoverText="Click here to get help for this dashboard"
|
|
||||||
/>
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<Tooltip title="Open in Full Screen">
|
<Tooltip title="Open in Full Screen">
|
||||||
<Button
|
<Button
|
||||||
@@ -518,23 +190,12 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
{t('dashboard:add_panel')}
|
{t('dashboard:add_panel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isDashboardLocked && addPanelPermission && (
|
|
||||||
<Button
|
|
||||||
className="periscope-btn"
|
|
||||||
onClick={(): void => handleAddRow()}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
data-testid="add-row"
|
|
||||||
>
|
|
||||||
{t('dashboard:add_row')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<FullScreen handle={handle} className="fullscreen-grid-container">
|
<FullScreen handle={handle} className="fullscreen-grid-container">
|
||||||
<ReactGridLayout
|
<ReactGridLayout
|
||||||
cols={12}
|
cols={12}
|
||||||
rowHeight={45}
|
rowHeight={100}
|
||||||
autoSize
|
autoSize
|
||||||
width={100}
|
width={100}
|
||||||
useCSSTransforms
|
useCSSTransforms
|
||||||
@@ -543,7 +204,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
isResizable={!isDashboardLocked && addPanelPermission}
|
isResizable={!isDashboardLocked && addPanelPermission}
|
||||||
allowOverlap={false}
|
allowOverlap={false}
|
||||||
onLayoutChange={handleLayoutChange}
|
onLayoutChange={handleLayoutChange}
|
||||||
onDragStop={handleDragStop}
|
|
||||||
draggableHandle=".drag-handle"
|
draggableHandle=".drag-handle"
|
||||||
layout={dashboardLayout}
|
layout={dashboardLayout}
|
||||||
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
||||||
@@ -552,58 +212,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
const { i: id } = layout;
|
const { i: id } = layout;
|
||||||
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
||||||
|
|
||||||
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
|
|
||||||
const rowWidgetProperties = currentPanelMap[id] || {};
|
|
||||||
return (
|
|
||||||
<CardContainer
|
|
||||||
className="row-card"
|
|
||||||
isDarkMode={isDarkMode}
|
|
||||||
key={id}
|
|
||||||
data-grid={JSON.stringify(currentWidget)}
|
|
||||||
>
|
|
||||||
<div className={cx('row-panel')}>
|
|
||||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
|
||||||
<Button
|
|
||||||
disabled={updateDashboardMutation.isLoading}
|
|
||||||
icon={
|
|
||||||
rowWidgetProperties.collapsed ? (
|
|
||||||
<MoveDown size={14} />
|
|
||||||
) : (
|
|
||||||
<MoveUp size={14} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
onClick={(): void => handleRowCollapse(id)}
|
|
||||||
/>
|
|
||||||
<Typography.Text>{currentWidget.title}</Typography.Text>
|
|
||||||
<Button
|
|
||||||
icon={<Settings size={14} />}
|
|
||||||
type="text"
|
|
||||||
onClick={(): void => handleRowSettingsClick(id)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{rowWidgetProperties.collapsed && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<GripVertical size={14} />}
|
|
||||||
className="drag-handle"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!rowWidgetProperties.collapsed && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<Trash2 size={14} />}
|
|
||||||
onClick={(): void => {
|
|
||||||
setIsDeleteModalOpen(true);
|
|
||||||
setCurrentSelectRowId(id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer
|
<CardContainer
|
||||||
className={isDashboardLocked ? '' : 'enable-resize'}
|
className={isDashboardLocked ? '' : 'enable-resize'}
|
||||||
@@ -616,7 +224,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
||||||
>
|
>
|
||||||
<GridCard
|
<GridCard
|
||||||
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
|
widget={currentWidget || ({ id, query: {} } as Widgets)}
|
||||||
headerMenuList={widgetActions}
|
headerMenuList={widgetActions}
|
||||||
variables={variables}
|
variables={variables}
|
||||||
version={selectedDashboard?.data?.version}
|
version={selectedDashboard?.data?.version}
|
||||||
@@ -627,46 +235,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ReactGridLayout>
|
</ReactGridLayout>
|
||||||
<Modal
|
|
||||||
open={isSettingsModalOpen}
|
|
||||||
title="Row Options"
|
|
||||||
destroyOnClose
|
|
||||||
footer={null}
|
|
||||||
onCancel={(): void => {
|
|
||||||
setIsSettingsModalOpen(false);
|
|
||||||
setCurrentSelectRowId(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
|
|
||||||
<Form.Item required name={['title']}>
|
|
||||||
<Input
|
|
||||||
placeholder="Enter row name here..."
|
|
||||||
defaultValue={defaultTo(
|
|
||||||
widgets?.find((widget) => widget.id === currentSelectRowId)
|
|
||||||
?.title as string,
|
|
||||||
'Sample Title',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item>
|
|
||||||
<Button type="primary" htmlType="submit">
|
|
||||||
Apply Changes
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={isDeleteModalOpen}
|
|
||||||
title="Delete Row"
|
|
||||||
destroyOnClose
|
|
||||||
onCancel={(): void => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
setCurrentSelectRowId(null);
|
|
||||||
}}
|
|
||||||
onOk={(): void => handleRowDelete()}
|
|
||||||
>
|
|
||||||
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
|
|
||||||
</Modal>
|
|
||||||
</FullScreen>
|
</FullScreen>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ export const EMPTY_WIDGET_LAYOUT = {
|
|||||||
i: PANEL_TYPES.EMPTY_WIDGET,
|
i: PANEL_TYPES.EMPTY_WIDGET,
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 6,
|
h: 3,
|
||||||
y: 0,
|
y: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,17 +29,6 @@ interface Props {
|
|||||||
export const CardContainer = styled.div<Props>`
|
export const CardContainer = styled.div<Props>`
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&.row-card {
|
|
||||||
.row-panel {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.enable-resize {
|
&.enable-resize {
|
||||||
:hover {
|
:hover {
|
||||||
.react-resizable-handle {
|
.react-resizable-handle {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ const GridPanelSwitch = forwardRef<
|
|||||||
thresholds,
|
thresholds,
|
||||||
},
|
},
|
||||||
[PANEL_TYPES.LIST]: null,
|
[PANEL_TYPES.LIST]: null,
|
||||||
[PANEL_TYPES.PIE]: null,
|
|
||||||
[PANEL_TYPES.TRACE]: null,
|
[PANEL_TYPES.TRACE]: null,
|
||||||
[PANEL_TYPES.BAR]: {
|
[PANEL_TYPES.BAR]: {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export type PropsTypePropsMap = {
|
|||||||
[PANEL_TYPES.VALUE]: GridValueComponentProps;
|
[PANEL_TYPES.VALUE]: GridValueComponentProps;
|
||||||
[PANEL_TYPES.TABLE]: GridTableComponentProps;
|
[PANEL_TYPES.TABLE]: GridTableComponentProps;
|
||||||
[PANEL_TYPES.TRACE]: null;
|
[PANEL_TYPES.TRACE]: null;
|
||||||
[PANEL_TYPES.PIE]: null;
|
|
||||||
[PANEL_TYPES.LIST]: null;
|
[PANEL_TYPES.LIST]: null;
|
||||||
[PANEL_TYPES.BAR]: UplotProps & {
|
[PANEL_TYPES.BAR]: UplotProps & {
|
||||||
ref: ForwardedRef<ToggleGraphProps | undefined>;
|
ref: ForwardedRef<ToggleGraphProps | undefined>;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Input, Typography } from 'antd';
|
|||||||
import type { ColumnsType } from 'antd/es/table/interface';
|
import type { ColumnsType } from 'antd/es/table/interface';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import DropDown from 'components/DropDown/DropDown';
|
import DropDown from 'components/DropDown/DropDown';
|
||||||
import { listAlertMessage } from 'components/facingIssueBtn/util';
|
|
||||||
import {
|
import {
|
||||||
DynamicColumnsKey,
|
DynamicColumnsKey,
|
||||||
TableDataSource,
|
TableDataSource,
|
||||||
@@ -359,15 +358,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
pagination={{
|
pagination={{
|
||||||
defaultCurrent: Number(paginationParam) || 1,
|
defaultCurrent: Number(paginationParam) || 1,
|
||||||
}}
|
}}
|
||||||
facingIssueBtn={{
|
|
||||||
attributes: {
|
|
||||||
screen: 'Alert list page',
|
|
||||||
},
|
|
||||||
eventName: 'Alert: Facing Issues in alert',
|
|
||||||
buttonText: 'Facing issues with alerts?',
|
|
||||||
message: listAlertMessage,
|
|
||||||
onHoverText: 'Click here to get help with alerts',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Card, Col, Dropdown, Input, Row, TableColumnProps } from 'antd';
|
|||||||
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
import { ItemType } from 'antd/es/menu/hooks/useItems';
|
||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { dashboardListMessage } from 'components/facingIssueBtn/util';
|
|
||||||
import {
|
import {
|
||||||
DynamicColumnsKey,
|
DynamicColumnsKey,
|
||||||
TableDataSource,
|
TableDataSource,
|
||||||
@@ -386,15 +385,6 @@ function DashboardsList(): JSX.Element {
|
|||||||
dataSource={data}
|
dataSource={data}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
showSorterTooltip
|
showSorterTooltip
|
||||||
facingIssueBtn={{
|
|
||||||
attributes: {
|
|
||||||
screen: 'Dashboard list page',
|
|
||||||
},
|
|
||||||
eventName: 'Dashboard: Facing Issues in dashboard',
|
|
||||||
buttonText: 'Facing issues with dashboards?',
|
|
||||||
message: dashboardListMessage,
|
|
||||||
onHoverText: 'Click here to get help with dashboards',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ function LogControls(): JSX.Element | null {
|
|||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
const timestamp =
|
const timestamp =
|
||||||
typeof log.timestamp === 'string'
|
typeof log.timestamp === 'string'
|
||||||
? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(log.timestamp).format()
|
||||||
: dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
: dayjs(log.timestamp / 1e6).format();
|
||||||
|
|
||||||
return FlatLogData({
|
return FlatLogData({
|
||||||
...log,
|
...log,
|
||||||
|
|||||||
@@ -531,8 +531,8 @@ function LogsExplorerViews({
|
|||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
const timestamp =
|
const timestamp =
|
||||||
typeof log.timestamp === 'string'
|
typeof log.timestamp === 'string'
|
||||||
? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(log.timestamp).format()
|
||||||
: dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
: dayjs(log.timestamp / 1e6).format();
|
||||||
|
|
||||||
return FlatLogData({
|
return FlatLogData({
|
||||||
timestamp,
|
timestamp,
|
||||||
@@ -608,7 +608,6 @@ function LogsExplorerViews({
|
|||||||
className="periscope-btn"
|
className="periscope-btn"
|
||||||
onClick={handleToggleShowFormatOptions}
|
onClick={handleToggleShowFormatOptions}
|
||||||
icon={<Sliders size={14} />}
|
icon={<Sliders size={14} />}
|
||||||
data-testid="periscope-btn"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showFormatMenuItems && (
|
{showFormatMenuItems && (
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
import { render, RenderResult } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
|
||||||
import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range';
|
|
||||||
import { server } from 'mocks-server/server';
|
|
||||||
import { rest } from 'msw';
|
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
|
||||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
|
||||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
|
||||||
import i18n from 'ReactI18';
|
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
import LogsExplorerViews from '..';
|
|
||||||
import { logsQueryRangeSuccessNewFormatResponse } from './mock';
|
|
||||||
|
|
||||||
const logExplorerRoute = '/logs/logs-explorer';
|
|
||||||
|
|
||||||
const queryRangeURL = 'http://localhost/api/v3/query_range';
|
|
||||||
|
|
||||||
const lodsQueryServerRequest = (): void =>
|
|
||||||
server.use(
|
|
||||||
rest.post(queryRangeURL, (req, res, ctx) =>
|
|
||||||
res(ctx.status(200), ctx.json(logsQueryRangeSuccessResponse)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// mocking the graph components in this test as this should be handled separately
|
|
||||||
jest.mock(
|
|
||||||
'container/TimeSeriesView/TimeSeriesView',
|
|
||||||
() =>
|
|
||||||
// eslint-disable-next-line func-names, @typescript-eslint/explicit-function-return-type, react/display-name
|
|
||||||
function () {
|
|
||||||
return <div>Time Series Chart</div>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
jest.mock(
|
|
||||||
'container/LogsExplorerChart',
|
|
||||||
() =>
|
|
||||||
// eslint-disable-next-line func-names, @typescript-eslint/explicit-function-return-type, react/display-name
|
|
||||||
function () {
|
|
||||||
return <div>Histogram Chart</div>;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mock('constants/panelTypes', () => ({
|
|
||||||
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('d3-interpolate', () => ({
|
|
||||||
interpolate: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('hooks/queryBuilder/useGetExplorerQueryRange', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
useGetExplorerQueryRange: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Set up the specific behavior for useGetExplorerQueryRange in individual test cases
|
|
||||||
beforeEach(() => {
|
|
||||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
|
||||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderer = (): RenderResult =>
|
|
||||||
render(
|
|
||||||
<MemoryRouter initialEntries={[logExplorerRoute]}>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<MockQueryClientProvider>
|
|
||||||
<QueryBuilderProvider>
|
|
||||||
<VirtuosoMockContext.Provider
|
|
||||||
value={{ viewportHeight: 300, itemHeight: 100 }}
|
|
||||||
>
|
|
||||||
<LogsExplorerViews selectedView={SELECTED_VIEWS.SEARCH} showHistogram />
|
|
||||||
</VirtuosoMockContext.Provider>
|
|
||||||
</QueryBuilderProvider>
|
|
||||||
</MockQueryClientProvider>
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('LogsExplorerViews -', () => {
|
|
||||||
it('render correctly with props - list and table', async () => {
|
|
||||||
lodsQueryServerRequest();
|
|
||||||
const { queryByText, queryByTestId } = renderer();
|
|
||||||
|
|
||||||
expect(queryByTestId('periscope-btn')).toBeInTheDocument();
|
|
||||||
await userEvent.click(queryByTestId('periscope-btn') as HTMLElement);
|
|
||||||
|
|
||||||
expect(document.querySelector('.menu-container')).toBeInTheDocument();
|
|
||||||
|
|
||||||
const menuItems = document.querySelectorAll('.menu-items .item');
|
|
||||||
expect(menuItems.length).toBe(3);
|
|
||||||
|
|
||||||
// switch to table view
|
|
||||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
|
||||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
'{"container_id":"container_id","container_name":"container_name","driver":"driver","eta":"2m0s","location":"frontend","log_level":"INFO","message":"Dispatch successful","service":"frontend","span_id":"span_id","trace_id":"span_id"}',
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('check isLoading state', async () => {
|
|
||||||
lodsQueryServerRequest();
|
|
||||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
|
||||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
|
||||||
isLoading: true,
|
|
||||||
isFetching: false,
|
|
||||||
});
|
|
||||||
const { queryByText, queryByTestId } = renderer();
|
|
||||||
|
|
||||||
// switch to table view
|
|
||||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
|
||||||
expect(
|
|
||||||
queryByText(
|
|
||||||
'Just a bit of patience, just a little bit’s enough ⎯ we’re getting your logs!',
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('check error state', async () => {
|
|
||||||
lodsQueryServerRequest();
|
|
||||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
|
||||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
|
||||||
isLoading: false,
|
|
||||||
isFetching: false,
|
|
||||||
isError: true,
|
|
||||||
});
|
|
||||||
const { queryByText, queryByTestId } = renderer();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText('Something went wrong. Please try again or contact support.'),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
// switch to table view
|
|
||||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
queryByText('Something went wrong. Please try again or contact support.'),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
export const logsQueryRangeSuccessNewFormatResponse = {
|
|
||||||
data: {
|
|
||||||
result: [],
|
|
||||||
resultType: '',
|
|
||||||
newResult: {
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
resultType: '',
|
|
||||||
result: [
|
|
||||||
{
|
|
||||||
queryName: 'A',
|
|
||||||
series: null,
|
|
||||||
list: [
|
|
||||||
{
|
|
||||||
timestamp: '2024-02-15T21:20:22Z',
|
|
||||||
data: {
|
|
||||||
attributes_bool: {},
|
|
||||||
attributes_float64: {},
|
|
||||||
attributes_int64: {},
|
|
||||||
attributes_string: {
|
|
||||||
container_id: 'container_id',
|
|
||||||
container_name: 'container_name',
|
|
||||||
driver: 'driver',
|
|
||||||
eta: '2m0s',
|
|
||||||
location: 'frontend',
|
|
||||||
log_level: 'INFO',
|
|
||||||
message: 'Dispatch successful',
|
|
||||||
service: 'frontend',
|
|
||||||
span_id: 'span_id',
|
|
||||||
trace_id: 'span_id',
|
|
||||||
},
|
|
||||||
body:
|
|
||||||
'2024-02-15T21:20:22.035Z\tINFO\tfrontend\tDispatch successful\t{"service": "frontend", "trace_id": "span_id", "span_id": "span_id", "driver": "driver", "eta": "2m0s"}',
|
|
||||||
id: 'id',
|
|
||||||
resources_string: {
|
|
||||||
'container.name': 'container_name',
|
|
||||||
},
|
|
||||||
severity_number: 0,
|
|
||||||
severity_text: '',
|
|
||||||
span_id: '',
|
|
||||||
trace_flags: 0,
|
|
||||||
trace_id: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable sonarjs/no-duplicate-string */
|
|
||||||
|
|
||||||
import { DownloadOptions } from 'container/Download/Download.types';
|
import { DownloadOptions } from 'container/Download/Download.types';
|
||||||
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
|
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ export enum FORMULA {
|
|||||||
ERROR_PERCENTAGE = 'A*100/B',
|
ERROR_PERCENTAGE = 'A*100/B',
|
||||||
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
||||||
APDEX_TRACES = '((B + C)/2)/A',
|
APDEX_TRACES = '((B + C)/2)/A',
|
||||||
APDEX_DELTA_SPAN_METRICS = '((B + C)/2)/A',
|
APDEX_DELTA_SPAN_METRICS = '(B + C/2)/A',
|
||||||
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
|
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ export const getNearestHighestBucketValue = (
|
|||||||
value: number,
|
value: number,
|
||||||
buckets: number[],
|
buckets: number[],
|
||||||
): string => {
|
): string => {
|
||||||
// sort the buckets
|
|
||||||
buckets.sort((a, b) => a - b);
|
|
||||||
const nearestBucket = buckets.find((bucket) => bucket >= value);
|
const nearestBucket = buckets.find((bucket) => bucket >= value);
|
||||||
return nearestBucket?.toString() || '+Inf';
|
return nearestBucket?.toString() || '+Inf';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const PANEL_TYPES_INITIAL_QUERY = {
|
|||||||
[PANEL_TYPES.LIST]: initialQueriesMap.logs,
|
[PANEL_TYPES.LIST]: initialQueriesMap.logs,
|
||||||
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
||||||
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
||||||
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,99 @@
|
|||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
|
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { PANEL_TYPES_INITIAL_QUERY } from './constants';
|
import {
|
||||||
|
listViewInitialLogQuery,
|
||||||
|
listViewInitialTraceQuery,
|
||||||
|
PANEL_TYPES_INITIAL_QUERY,
|
||||||
|
} from './constants';
|
||||||
import menuItems from './menuItems';
|
import menuItems from './menuItems';
|
||||||
import { Card, Container, Text } from './styles';
|
import { Card, Container, Text } from './styles';
|
||||||
|
|
||||||
function DashboardGraphSlider(): JSX.Element {
|
function DashboardGraphSlider(): JSX.Element {
|
||||||
const { handleToggleDashboardSlider } = useDashboard();
|
const {
|
||||||
|
handleToggleDashboardSlider,
|
||||||
|
layouts,
|
||||||
|
selectedDashboard,
|
||||||
|
} = useDashboard();
|
||||||
|
|
||||||
|
const { data } = selectedDashboard || {};
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const onClickHandler = (name: PANEL_TYPES) => (): void => {
|
const onClickHandler = (name: PANEL_TYPES) => (): void => {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
|
||||||
|
updateDashboardMutation.mutateAsync(
|
||||||
|
{
|
||||||
|
uuid: selectedDashboard?.uuid || '',
|
||||||
|
data: {
|
||||||
|
title: data?.title || '',
|
||||||
|
variables: data?.variables || {},
|
||||||
|
description: data?.description || '',
|
||||||
|
name: data?.name || '',
|
||||||
|
tags: data?.tags || [],
|
||||||
|
version: data?.version || 'v3',
|
||||||
|
layout: [
|
||||||
|
{
|
||||||
|
i: id,
|
||||||
|
w: 6,
|
||||||
|
x: 0,
|
||||||
|
h: 3,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
...(layouts.filter((layout) => layout.i !== PANEL_TYPES.EMPTY_WIDGET) ||
|
||||||
|
[]),
|
||||||
|
],
|
||||||
|
widgets: [
|
||||||
|
...(data?.widgets || []),
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
isStacked: false,
|
||||||
|
nullZeroValues: '',
|
||||||
|
opacity: '',
|
||||||
|
panelTypes: name,
|
||||||
|
query:
|
||||||
|
name === PANEL_TYPES.LIST
|
||||||
|
? listViewInitialLogQuery
|
||||||
|
: PANEL_TYPES_INITIAL_QUERY[name],
|
||||||
|
timePreferance: 'GLOBAL_TIME',
|
||||||
|
softMax: null,
|
||||||
|
softMin: null,
|
||||||
|
selectedLogFields: [
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'body',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: 'string',
|
||||||
|
type: '',
|
||||||
|
name: 'timestamp',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selectedTracesFields: [
|
||||||
|
...listViewInitialTraceQuery.builder.queryData[0].selectColumns,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (data.payload) {
|
||||||
handleToggleDashboardSlider(false);
|
handleToggleDashboardSlider(false);
|
||||||
const queryParamsLog = {
|
const queryParamsLog = {
|
||||||
graphType: name,
|
graphType: name,
|
||||||
@@ -54,6 +132,15 @@ function DashboardGraphSlider(): JSX.Element {
|
|||||||
`${history.location.pathname}/new?${createQueryParams(queryParams)}`,
|
`${history.location.pathname}/new?${createQueryParams(queryParams)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
notifications.success({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import {
|
import { BarChart3, LineChart, List, SigmaSquare, Table } from 'lucide-react';
|
||||||
BarChart3,
|
|
||||||
LineChart,
|
|
||||||
List,
|
|
||||||
PieChart,
|
|
||||||
SigmaSquare,
|
|
||||||
Table,
|
|
||||||
} from 'lucide-react';
|
|
||||||
|
|
||||||
const Items: ItemsProps[] = [
|
const Items: ItemsProps[] = [
|
||||||
{
|
{
|
||||||
@@ -35,11 +28,6 @@ const Items: ItemsProps[] = [
|
|||||||
icon: <BarChart3 size={32} color={Color.BG_ROBIN_400} />,
|
icon: <BarChart3 size={32} color={Color.BG_ROBIN_400} />,
|
||||||
display: 'Bar',
|
display: 'Bar',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: PANEL_TYPES.PIE,
|
|
||||||
icon: <PieChart size={32} color={Color.BG_ROBIN_400} />,
|
|
||||||
display: 'Pie',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface ItemsProps {
|
export interface ItemsProps {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Row } from 'antd';
|
import { Row } from 'antd';
|
||||||
import { isNull } from 'lodash-es';
|
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import { convertVariablesToDbFormat } from './util';
|
||||||
import VariableItem from './VariableItem';
|
import VariableItem from './VariableItem';
|
||||||
|
|
||||||
function DashboardVariableSelection(): JSX.Element | null {
|
function DashboardVariableSelection(): JSX.Element | null {
|
||||||
@@ -11,14 +11,15 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
setSelectedDashboard,
|
setSelectedDashboard,
|
||||||
updateLocalStorageDashboardVariables,
|
updateLocalStorageDashboardVariables,
|
||||||
variablesToGetUpdated,
|
|
||||||
setVariablesToGetUpdated,
|
|
||||||
} = useDashboard();
|
} = useDashboard();
|
||||||
|
|
||||||
const { data } = selectedDashboard || {};
|
const { data } = selectedDashboard || {};
|
||||||
|
|
||||||
const { variables } = data || {};
|
const { variables } = data || {};
|
||||||
|
|
||||||
|
const [update, setUpdate] = useState<boolean>(false);
|
||||||
|
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||||
|
|
||||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,27 +45,8 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
}, [variables]);
|
}, [variables]);
|
||||||
|
|
||||||
const onVarChanged = (name: string): void => {
|
const onVarChanged = (name: string): void => {
|
||||||
/**
|
setLastUpdatedVar(name);
|
||||||
* this function takes care of adding the dependent variables to current update queue and removing
|
setUpdate(!update);
|
||||||
* the updated variable name from the queue
|
|
||||||
*/
|
|
||||||
const dependentVariables = variablesTableData
|
|
||||||
?.map((variable: any) => {
|
|
||||||
if (variable.type === 'QUERY') {
|
|
||||||
const re = new RegExp(`\\{\\{\\s*?\\.${name}\\s*?\\}\\}`); // regex for `{{.var}}`
|
|
||||||
const queryValue = variable.queryValue || '';
|
|
||||||
const dependVarReMatch = queryValue.match(re);
|
|
||||||
if (dependVarReMatch !== null && dependVarReMatch.length > 0) {
|
|
||||||
return variable.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter((val: string | null) => !isNull(val));
|
|
||||||
setVariablesToGetUpdated((prev) => [
|
|
||||||
...prev.filter((v) => v !== name),
|
|
||||||
...dependentVariables,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onValueUpdate = (
|
const onValueUpdate = (
|
||||||
@@ -72,46 +54,39 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
id: string,
|
id: string,
|
||||||
value: IDashboardVariable['selectedValue'],
|
value: IDashboardVariable['selectedValue'],
|
||||||
allSelected: boolean,
|
allSelected: boolean,
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
): void => {
|
): void => {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
const newVariablesArr = variablesTableData.map(
|
||||||
|
(variable: IDashboardVariable) => {
|
||||||
|
const variableCopy = { ...variable };
|
||||||
|
|
||||||
|
if (variableCopy.id === id) {
|
||||||
|
variableCopy.selectedValue = value;
|
||||||
|
variableCopy.allSelected = allSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return variableCopy;
|
||||||
|
},
|
||||||
|
);
|
||||||
updateLocalStorageDashboardVariables(name, value, allSelected);
|
updateLocalStorageDashboardVariables(name, value, allSelected);
|
||||||
|
|
||||||
|
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||||
|
|
||||||
if (selectedDashboard) {
|
if (selectedDashboard) {
|
||||||
setSelectedDashboard((prev) => {
|
setSelectedDashboard({
|
||||||
if (prev) {
|
...selectedDashboard,
|
||||||
const oldVariables = prev?.data.variables;
|
|
||||||
// this is added to handle case where we have two different
|
|
||||||
// schemas for variable response
|
|
||||||
if (oldVariables[id]) {
|
|
||||||
oldVariables[id] = {
|
|
||||||
...oldVariables[id],
|
|
||||||
selectedValue: value,
|
|
||||||
allSelected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (oldVariables[name]) {
|
|
||||||
oldVariables[name] = {
|
|
||||||
...oldVariables[name],
|
|
||||||
selectedValue: value,
|
|
||||||
allSelected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
data: {
|
data: {
|
||||||
...prev?.data,
|
...selectedDashboard?.data,
|
||||||
variables: {
|
variables: {
|
||||||
...oldVariables,
|
...variables,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onVarChanged(name);
|
onVarChanged(name);
|
||||||
|
|
||||||
|
setUpdate(!update);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,12 +107,13 @@ function DashboardVariableSelection(): JSX.Element | null {
|
|||||||
<VariableItem
|
<VariableItem
|
||||||
key={`${variable.name}${variable.id}}${variable.order}`}
|
key={`${variable.name}${variable.id}}${variable.order}`}
|
||||||
existingVariables={variables}
|
existingVariables={variables}
|
||||||
|
lastUpdatedVar={lastUpdatedVar}
|
||||||
variableData={{
|
variableData={{
|
||||||
name: variable.name,
|
name: variable.name,
|
||||||
...variable,
|
...variable,
|
||||||
|
change: update,
|
||||||
}}
|
}}
|
||||||
onValueUpdate={onValueUpdate}
|
onValueUpdate={onValueUpdate}
|
||||||
variablesToGetUpdated={variablesToGetUpdated}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={mockVariableData}
|
variableData={mockVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
@@ -68,7 +68,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={mockVariableData}
|
variableData={mockVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
@@ -82,7 +82,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={mockVariableData}
|
variableData={mockVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
@@ -110,7 +110,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={mockCustomVariableData}
|
variableData={mockCustomVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
@@ -131,7 +131,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={customVariableData}
|
variableData={customVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
@@ -146,7 +146,7 @@ describe('VariableItem', () => {
|
|||||||
variableData={mockCustomVariableData}
|
variableData={mockCustomVariableData}
|
||||||
existingVariables={{}}
|
existingVariables={{}}
|
||||||
onValueUpdate={mockOnValueUpdate}
|
onValueUpdate={mockOnValueUpdate}
|
||||||
variablesToGetUpdated={[]}
|
lastUpdatedVar=""
|
||||||
/>
|
/>
|
||||||
</MockQueryClientProvider>,
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ interface VariableItemProps {
|
|||||||
arg1: IDashboardVariable['selectedValue'],
|
arg1: IDashboardVariable['selectedValue'],
|
||||||
allSelected: boolean,
|
allSelected: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
variablesToGetUpdated: string[];
|
lastUpdatedVar: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSelectValue = (
|
const getSelectValue = (
|
||||||
@@ -49,7 +49,7 @@ function VariableItem({
|
|||||||
variableData,
|
variableData,
|
||||||
existingVariables,
|
existingVariables,
|
||||||
onValueUpdate,
|
onValueUpdate,
|
||||||
variablesToGetUpdated,
|
lastUpdatedVar,
|
||||||
}: VariableItemProps): JSX.Element {
|
}: VariableItemProps): JSX.Element {
|
||||||
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
||||||
[],
|
[],
|
||||||
@@ -108,10 +108,16 @@ function VariableItem({
|
|||||||
|
|
||||||
if (!areArraysEqual(newOptionsData, oldOptionsData)) {
|
if (!areArraysEqual(newOptionsData, oldOptionsData)) {
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
|
const re = new RegExp(`\\{\\{\\s*?\\.${lastUpdatedVar}\\s*?\\}\\}`); // regex for `{{.var}}`
|
||||||
|
// If the variable is dependent on the last updated variable
|
||||||
|
// and contains the last updated variable in its query (of the form `{{.var}}`)
|
||||||
|
// then we need to update the value of the variable
|
||||||
|
const queryValue = variableData.queryValue || '';
|
||||||
|
const dependVarReMatch = queryValue.match(re);
|
||||||
if (
|
if (
|
||||||
variableData.type === 'QUERY' &&
|
variableData.type === 'QUERY' &&
|
||||||
variableData.name &&
|
dependVarReMatch !== null &&
|
||||||
variablesToGetUpdated.includes(variableData.name)
|
dependVarReMatch.length > 0
|
||||||
) {
|
) {
|
||||||
let value = variableData.selectedValue;
|
let value = variableData.selectedValue;
|
||||||
let allSelected = false;
|
let allSelected = false;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Button, Tabs, Tooltip, Typography } from 'antd';
|
|||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
||||||
import { getDefaultWidgetData } from 'container/NewWidget/utils';
|
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
@@ -12,7 +11,6 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|||||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||||
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { defaultTo } from 'lodash-es';
|
|
||||||
import { Atom, Play, Terminal } from 'lucide-react';
|
import { Atom, Play, Terminal } from 'lucide-react';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import {
|
import {
|
||||||
@@ -57,11 +55,8 @@ function QuerySection({
|
|||||||
|
|
||||||
const getWidget = useCallback(() => {
|
const getWidget = useCallback(() => {
|
||||||
const widgetId = urlQuery.get('widgetId');
|
const widgetId = urlQuery.get('widgetId');
|
||||||
return defaultTo(
|
return widgets?.find((e) => e.id === widgetId);
|
||||||
widgets?.find((e) => e.id === widgetId),
|
}, [widgets, urlQuery]);
|
||||||
getDefaultWidgetData(widgetId || '', selectedGraph),
|
|
||||||
);
|
|
||||||
}, [urlQuery, widgets, selectedGraph]);
|
|
||||||
|
|
||||||
const selectedWidget = getWidget() as Widgets;
|
const selectedWidget = getWidget() as Widgets;
|
||||||
|
|
||||||
|
|||||||
@@ -67,13 +67,12 @@ function LeftContainer({
|
|||||||
setRequestData((prev) => ({
|
setRequestData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
selectedTime: selectedTime.enum || prev.selectedTime,
|
selectedTime: selectedTime.enum || prev.selectedTime,
|
||||||
globalSelectedInterval,
|
|
||||||
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
|
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
|
||||||
query: stagedQuery,
|
query: stagedQuery,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [stagedQuery, selectedTime, globalSelectedInterval]);
|
}, [stagedQuery, selectedTime]);
|
||||||
|
|
||||||
const queryResponse = useGetQueryRange(
|
const queryResponse = useGetQueryRange(
|
||||||
requestData,
|
requestData,
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
.facing-issue-btn-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr max-content;
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,6 @@ export const panelTypeVsThreshold: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.VALUE]: true,
|
[PANEL_TYPES.VALUE]: true,
|
||||||
[PANEL_TYPES.TABLE]: true,
|
[PANEL_TYPES.TABLE]: true,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
@@ -37,7 +36,6 @@ export const panelTypeVsSoftMinMax: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.VALUE]: false,
|
[PANEL_TYPES.VALUE]: false,
|
||||||
[PANEL_TYPES.TABLE]: false,
|
[PANEL_TYPES.TABLE]: false,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
@@ -47,7 +45,6 @@ export const panelTypeVsDragAndDrop: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.TIME_SERIES]: false,
|
[PANEL_TYPES.TIME_SERIES]: false,
|
||||||
[PANEL_TYPES.VALUE]: true,
|
[PANEL_TYPES.VALUE]: true,
|
||||||
[PANEL_TYPES.TABLE]: true,
|
[PANEL_TYPES.TABLE]: true,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.BAR]: false,
|
[PANEL_TYPES.BAR]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
@@ -59,7 +56,6 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.VALUE]: false,
|
[PANEL_TYPES.VALUE]: false,
|
||||||
[PANEL_TYPES.TABLE]: false,
|
[PANEL_TYPES.TABLE]: false,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.BAR]: false,
|
[PANEL_TYPES.BAR]: false,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
@@ -70,7 +66,6 @@ export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.VALUE]: true,
|
[PANEL_TYPES.VALUE]: true,
|
||||||
[PANEL_TYPES.TABLE]: true,
|
[PANEL_TYPES.TABLE]: true,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
@@ -81,7 +76,6 @@ export const panelTypeVsCreateAlert: { [key in PANEL_TYPES]: boolean } = {
|
|||||||
[PANEL_TYPES.VALUE]: true,
|
[PANEL_TYPES.VALUE]: true,
|
||||||
[PANEL_TYPES.TABLE]: false,
|
[PANEL_TYPES.TABLE]: false,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: false,
|
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
@@ -94,7 +88,6 @@ export const panelTypeVsPanelTimePreferences: {
|
|||||||
[PANEL_TYPES.VALUE]: true,
|
[PANEL_TYPES.VALUE]: true,
|
||||||
[PANEL_TYPES.TABLE]: true,
|
[PANEL_TYPES.TABLE]: true,
|
||||||
[PANEL_TYPES.LIST]: false,
|
[PANEL_TYPES.LIST]: false,
|
||||||
[PANEL_TYPES.PIE]: true,
|
|
||||||
[PANEL_TYPES.BAR]: true,
|
[PANEL_TYPES.BAR]: true,
|
||||||
[PANEL_TYPES.TRACE]: false,
|
[PANEL_TYPES.TRACE]: false,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import './NewWidget.styles.scss';
|
|
||||||
|
|
||||||
import { LockFilled, WarningOutlined } from '@ant-design/icons';
|
import { LockFilled, WarningOutlined } from '@ant-design/icons';
|
||||||
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
|
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
|
||||||
import { chartHelpMessage } from 'components/facingIssueBtn/util';
|
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
@@ -18,7 +14,6 @@ import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
|
|||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { defaultTo, isUndefined } from 'lodash-es';
|
|
||||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import {
|
import {
|
||||||
@@ -50,11 +45,7 @@ import {
|
|||||||
RightContainerWrapper,
|
RightContainerWrapper,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import { NewWidgetProps } from './types';
|
import { NewWidgetProps } from './types';
|
||||||
import {
|
import { getIsQueryModified, handleQueryChange } from './utils';
|
||||||
getDefaultWidgetData,
|
|
||||||
getIsQueryModified,
|
|
||||||
handleQueryChange,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
@@ -89,26 +80,10 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
|
|
||||||
const { dashboardId } = useParams<DashboardWidgetPageParams>();
|
const { dashboardId } = useParams<DashboardWidgetPageParams>();
|
||||||
|
|
||||||
const [isNewDashboard, setIsNewDashboard] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const widgetId = query.get('widgetId');
|
|
||||||
const selectedWidget = widgets?.find((e) => e.id === widgetId);
|
|
||||||
const isWidgetNotPresent = isUndefined(selectedWidget);
|
|
||||||
if (isWidgetNotPresent) {
|
|
||||||
setIsNewDashboard(true);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getWidget = useCallback(() => {
|
const getWidget = useCallback(() => {
|
||||||
const widgetId = query.get('widgetId');
|
const widgetId = query.get('widgetId');
|
||||||
const selectedWidget = widgets?.find((e) => e.id === widgetId);
|
return widgets?.find((e) => e.id === widgetId);
|
||||||
return defaultTo(
|
}, [query, widgets]);
|
||||||
selectedWidget,
|
|
||||||
getDefaultWidgetData(widgetId || '', selectedGraph),
|
|
||||||
) as Widgets;
|
|
||||||
}, [query, selectedGraph, widgets]);
|
|
||||||
|
|
||||||
const [selectedWidget, setSelectedWidget] = useState(getWidget());
|
const [selectedWidget, setSelectedWidget] = useState(getWidget());
|
||||||
|
|
||||||
@@ -252,48 +227,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const widgetId = query.get('widgetId');
|
|
||||||
let updatedLayout = selectedDashboard.data.layout || [];
|
|
||||||
if (isNewDashboard) {
|
|
||||||
updatedLayout = [
|
|
||||||
{
|
|
||||||
i: widgetId || '',
|
|
||||||
w: 6,
|
|
||||||
x: 0,
|
|
||||||
h: 6,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
...updatedLayout,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const dashboard: Dashboard = {
|
const dashboard: Dashboard = {
|
||||||
...selectedDashboard,
|
...selectedDashboard,
|
||||||
uuid: selectedDashboard.uuid,
|
uuid: selectedDashboard.uuid,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
widgets: isNewDashboard
|
widgets: [
|
||||||
? [
|
|
||||||
...afterWidgets,
|
|
||||||
{
|
|
||||||
...(selectedWidget || ({} as Widgets)),
|
|
||||||
description: selectedWidget?.description || '',
|
|
||||||
timePreferance: selectedTime.enum,
|
|
||||||
isStacked: selectedWidget?.isStacked || false,
|
|
||||||
opacity: selectedWidget?.opacity || '1',
|
|
||||||
nullZeroValues: selectedWidget?.nullZeroValues || 'zero',
|
|
||||||
title: selectedWidget?.title,
|
|
||||||
yAxisUnit: selectedWidget?.yAxisUnit,
|
|
||||||
panelTypes: graphType,
|
|
||||||
query: currentQuery,
|
|
||||||
thresholds: selectedWidget?.thresholds,
|
|
||||||
softMin: selectedWidget?.softMin || 0,
|
|
||||||
softMax: selectedWidget?.softMax || 0,
|
|
||||||
fillSpans: selectedWidget?.fillSpans,
|
|
||||||
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
|
||||||
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
...preWidgets,
|
...preWidgets,
|
||||||
{
|
{
|
||||||
...(selectedWidget || ({} as Widgets)),
|
...(selectedWidget || ({} as Widgets)),
|
||||||
@@ -315,7 +254,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
...afterWidgets,
|
...afterWidgets,
|
||||||
],
|
],
|
||||||
layout: [...updatedLayout],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -336,8 +274,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
query,
|
|
||||||
isNewDashboard,
|
|
||||||
preWidgets,
|
preWidgets,
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
selectedTime.enum,
|
selectedTime.enum,
|
||||||
@@ -427,21 +363,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className="facing-issue-btn-container">
|
|
||||||
<FacingIssueBtn
|
|
||||||
attributes={{
|
|
||||||
uuid: selectedDashboard?.uuid,
|
|
||||||
title: selectedDashboard?.data.title,
|
|
||||||
panelType: graphType,
|
|
||||||
widgetId: query.get('widgetId'),
|
|
||||||
queryType: currentQuery.queryType,
|
|
||||||
screen: 'Dashboard list page',
|
|
||||||
}}
|
|
||||||
eventName="Dashboard: Facing Issues in dashboard"
|
|
||||||
buttonText="Need help with this chart?"
|
|
||||||
message={chartHelpMessage(selectedDashboard, graphType)}
|
|
||||||
onHoverText="Click here to get help in creating chart"
|
|
||||||
/>
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
{isSaveDisabled && (
|
{isSaveDisabled && (
|
||||||
<Tooltip title={MESSAGE.PANEL}>
|
<Tooltip title={MESSAGE.PANEL}>
|
||||||
@@ -469,7 +390,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
)}
|
)}
|
||||||
<Button onClick={onClickDiscardHandler}>Discard Changes</Button>
|
<Button onClick={onClickDiscardHandler}>Discard Changes</Button>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</div>
|
|
||||||
|
|
||||||
<PanelContainer>
|
<PanelContainer>
|
||||||
<LeftContainerWrapper flex={5}>
|
<LeftContainerWrapper flex={5}>
|
||||||
|
|||||||
@@ -3,13 +3,7 @@ import {
|
|||||||
initialQueryBuilderFormValuesMap,
|
initialQueryBuilderFormValuesMap,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
import {
|
|
||||||
listViewInitialLogQuery,
|
|
||||||
listViewInitialTraceQuery,
|
|
||||||
PANEL_TYPES_INITIAL_QUERY,
|
|
||||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
|
||||||
import { isEqual, set, unset } from 'lodash-es';
|
import { isEqual, set, unset } from 'lodash-es';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
|
||||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
@@ -31,7 +25,6 @@ export type PartialPanelTypes = {
|
|||||||
[PANEL_TYPES.TABLE]: 'table';
|
[PANEL_TYPES.TABLE]: 'table';
|
||||||
[PANEL_TYPES.TIME_SERIES]: 'graph';
|
[PANEL_TYPES.TIME_SERIES]: 'graph';
|
||||||
[PANEL_TYPES.VALUE]: 'value';
|
[PANEL_TYPES.VALUE]: 'value';
|
||||||
[PANEL_TYPES.PIE]: 'pie';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const panelTypeDataSourceFormValuesMap: Record<
|
export const panelTypeDataSourceFormValuesMap: Record<
|
||||||
@@ -170,50 +163,6 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[PANEL_TYPES.PIE]: {
|
|
||||||
[DataSource.LOGS]: {
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
|
||||||
'groupBy',
|
|
||||||
'limit',
|
|
||||||
'having',
|
|
||||||
'orderBy',
|
|
||||||
'functions',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[DataSource.METRICS]: {
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
|
||||||
'groupBy',
|
|
||||||
'limit',
|
|
||||||
'having',
|
|
||||||
'orderBy',
|
|
||||||
'functions',
|
|
||||||
'spaceAggregation',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[DataSource.TRACES]: {
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
|
||||||
'groupBy',
|
|
||||||
'limit',
|
|
||||||
'having',
|
|
||||||
'orderBy',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[PANEL_TYPES.LIST]: {
|
[PANEL_TYPES.LIST]: {
|
||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
@@ -308,38 +257,3 @@ export function handleQueryChange(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultWidgetData = (
|
|
||||||
id: string,
|
|
||||||
name: PANEL_TYPES,
|
|
||||||
): Widgets => ({
|
|
||||||
id,
|
|
||||||
title: '',
|
|
||||||
description: '',
|
|
||||||
isStacked: false,
|
|
||||||
nullZeroValues: '',
|
|
||||||
opacity: '',
|
|
||||||
panelTypes: name,
|
|
||||||
query:
|
|
||||||
name === PANEL_TYPES.LIST
|
|
||||||
? listViewInitialLogQuery
|
|
||||||
: PANEL_TYPES_INITIAL_QUERY[name],
|
|
||||||
timePreferance: 'GLOBAL_TIME',
|
|
||||||
softMax: null,
|
|
||||||
softMin: null,
|
|
||||||
selectedLogFields: [
|
|
||||||
{
|
|
||||||
dataType: 'string',
|
|
||||||
type: '',
|
|
||||||
name: 'body',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataType: 'string',
|
|
||||||
type: '',
|
|
||||||
name: 'timestamp',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
selectedTracesFields: [
|
|
||||||
...listViewInitialTraceQuery.builder.queryData[0].selectColumns,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export default function EnvironmentDetails(): JSX.Element {
|
|||||||
|
|
||||||
<div className="request-entity-container">
|
<div className="request-entity-container">
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
Cannot find what you’re looking for? Request an environment
|
Cannot find what you’re looking for? Request a data source
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|
||||||
<div className="form-section">
|
<div className="form-section">
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
LeftCircleOutlined,
|
LeftCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Space, Steps, Typography } from 'antd';
|
import { Button, Space, Steps, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { stepsMap } from 'container/OnboardingContainer/constants/stepsConfig';
|
import { stepsMap } from 'container/OnboardingContainer/constants/stepsConfig';
|
||||||
import { DataSourceType } from 'container/OnboardingContainer/Steps/DataSource/DataSource';
|
import { DataSourceType } from 'container/OnboardingContainer/Steps/DataSource/DataSource';
|
||||||
@@ -18,7 +17,6 @@ import { hasFrameworks } from 'container/OnboardingContainer/utils/dataSourceUti
|
|||||||
import useAnalytics from 'hooks/analytics/useAnalytics';
|
import useAnalytics from 'hooks/analytics/useAnalytics';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { isEmpty, isNull } from 'lodash-es';
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
import { HelpCircle } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { useOnboardingContext } from '../../context/OnboardingContext';
|
import { useOnboardingContext } from '../../context/OnboardingContext';
|
||||||
@@ -381,31 +379,6 @@ export default function ModuleStepsContainer({
|
|||||||
history.push('/');
|
history.push('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFacingIssuesClick = (): void => {
|
|
||||||
logEvent('Onboarding V2: Facing Issues Sending Data to SigNoz', {
|
|
||||||
dataSource: selectedDataSource?.id,
|
|
||||||
framework: selectedFramework,
|
|
||||||
environment: selectedEnvironment,
|
|
||||||
module: activeStep?.module?.id,
|
|
||||||
step: activeStep?.step?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = `Hi Team,
|
|
||||||
|
|
||||||
I am facing issues sending data to SigNoz. Here are my application details
|
|
||||||
|
|
||||||
Data Source: ${selectedDataSource?.name}
|
|
||||||
Framework:
|
|
||||||
Environment:
|
|
||||||
Module: ${activeStep?.module?.id}
|
|
||||||
|
|
||||||
Thanks
|
|
||||||
`;
|
|
||||||
if (window.Intercom) {
|
|
||||||
window.Intercom('showNewMessage', message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="onboarding-module-steps">
|
<div className="onboarding-module-steps">
|
||||||
<div className="steps-container">
|
<div className="steps-container">
|
||||||
@@ -482,15 +455,6 @@ Thanks
|
|||||||
<Button onClick={handleNext} type="primary" icon={<ArrowRightOutlined />}>
|
<Button onClick={handleNext} type="primary" icon={<ArrowRightOutlined />}>
|
||||||
{current < lastStepIndex ? 'Continue to next step' : 'Done'}
|
{current < lastStepIndex ? 'Continue to next step' : 'Done'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
|
||||||
className="periscope-btn"
|
|
||||||
onClick={handleFacingIssuesClick}
|
|
||||||
danger
|
|
||||||
icon={<HelpCircle size={14} />}
|
|
||||||
>
|
|
||||||
Facing issues sending data to SigNoz?
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
.piechart-no-data {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.piechart-container {
|
|
||||||
height: 90%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.piechart-tooltip {
|
|
||||||
|
|
||||||
.piechart-indicator {
|
|
||||||
width: 15px;
|
|
||||||
height: 3px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-value {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.piechart-legend {
|
|
||||||
width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
overflow-y: scroll;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.piechart-legend-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.piechart-legend-label {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
import './PiePanelWrapper.styles.scss';
|
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Group } from '@visx/group';
|
|
||||||
import { Pie } from '@visx/shape';
|
|
||||||
import { useTooltip, useTooltipInPortal } from '@visx/tooltip';
|
|
||||||
import { themeColors } from 'constants/theme';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
|
||||||
import { useRef, useState } from 'react';
|
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
|
|
||||||
import { PanelWrapperProps, TooltipData } from './panelWrapper.types';
|
|
||||||
import { getLabel, lightenColor, tooltipStyles } from './utils';
|
|
||||||
|
|
||||||
// refernce: https://www.youtube.com/watch?v=bL3P9CqQkKw
|
|
||||||
function PiePanelWrapper({
|
|
||||||
queryResponse,
|
|
||||||
widget,
|
|
||||||
}: PanelWrapperProps): JSX.Element {
|
|
||||||
const [active, setActive] = useState<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
color: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const {
|
|
||||||
tooltipOpen,
|
|
||||||
tooltipLeft,
|
|
||||||
tooltipTop,
|
|
||||||
tooltipData,
|
|
||||||
hideTooltip,
|
|
||||||
showTooltip,
|
|
||||||
} = useTooltip<TooltipData>();
|
|
||||||
|
|
||||||
const { containerRef, TooltipInPortal } = useTooltipInPortal({
|
|
||||||
scroll: true,
|
|
||||||
detectBounds: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const panelData =
|
|
||||||
queryResponse.data?.payload?.data.newResult.data.result || [];
|
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
|
|
||||||
const pieChartData: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
color: string;
|
|
||||||
}[] = [].concat(
|
|
||||||
...(panelData
|
|
||||||
.map((d) =>
|
|
||||||
d.series?.map((s) => ({
|
|
||||||
label:
|
|
||||||
d.series?.length === 1
|
|
||||||
? getLabel(Object.values(s.labels)[0], widget.query, d.queryName)
|
|
||||||
: getLabel(Object.values(s.labels)[0], {} as Query, d.queryName, true),
|
|
||||||
value: s.values[0].value,
|
|
||||||
color: generateColor(
|
|
||||||
d.series?.length === 1
|
|
||||||
? getLabel(Object.values(s.labels)[0], widget.query, d.queryName)
|
|
||||||
: getLabel(Object.values(s.labels)[0], {} as Query, d.queryName, true),
|
|
||||||
themeColors.chartcolors,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.filter((d) => d !== undefined) as never[]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let size = 0;
|
|
||||||
let width = 0;
|
|
||||||
let height = 0;
|
|
||||||
|
|
||||||
const chartRef = useRef<HTMLDivElement>(null);
|
|
||||||
if (chartRef.current) {
|
|
||||||
const { offsetWidth, offsetHeight } = chartRef.current;
|
|
||||||
size = Math.min(offsetWidth, offsetHeight);
|
|
||||||
width = offsetWidth;
|
|
||||||
height = offsetHeight;
|
|
||||||
}
|
|
||||||
const half = size / 2;
|
|
||||||
|
|
||||||
const getFillColor = (color: string): string => {
|
|
||||||
if (active === null) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
const lightenedColor = lightenColor(color, 0.4); // Adjust the opacity value (0.7 in this case)
|
|
||||||
return active.color === color ? color : lightenedColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!pieChartData.length && <div className="piechart-no-data">No data</div>}
|
|
||||||
{pieChartData.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div className="piechart-container" ref={chartRef}>
|
|
||||||
<svg width={width} height={height} ref={containerRef}>
|
|
||||||
<Group top={height / 2} left={width / 2}>
|
|
||||||
<Pie
|
|
||||||
data={pieChartData}
|
|
||||||
pieValue={(data: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
color: string;
|
|
||||||
}): number => parseFloat(data.value)}
|
|
||||||
outerRadius={({ data }): number => {
|
|
||||||
if (!active) return half - 3;
|
|
||||||
return data.label === active.label ? half : half - 3;
|
|
||||||
}}
|
|
||||||
padAngle={0.02}
|
|
||||||
cornerRadius={3}
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
||||||
(pie) =>
|
|
||||||
pie.arcs.map((arc, index) => {
|
|
||||||
const { label } = arc.data;
|
|
||||||
const [centroidX, centroidY] = pie.path.centroid(arc);
|
|
||||||
const hasSpaceForLabel = arc.endAngle - arc.startAngle >= 0.6;
|
|
||||||
const arcPath = pie.path(arc);
|
|
||||||
const arcFill = arc.data.color;
|
|
||||||
return (
|
|
||||||
<g
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
key={`arc-${label}-${index}`}
|
|
||||||
onMouseEnter={(): void => {
|
|
||||||
showTooltip({
|
|
||||||
tooltipData: {
|
|
||||||
label,
|
|
||||||
value: arc.data.value,
|
|
||||||
color: arc.data.color,
|
|
||||||
key: label,
|
|
||||||
},
|
|
||||||
tooltipTop: centroidY + height / 2,
|
|
||||||
tooltipLeft: centroidX + width / 2,
|
|
||||||
});
|
|
||||||
setActive(arc.data);
|
|
||||||
}}
|
|
||||||
onMouseLeave={(): void => {
|
|
||||||
hideTooltip();
|
|
||||||
setActive(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<path d={arcPath || ''} fill={getFillColor(arcFill)} />
|
|
||||||
{hasSpaceForLabel && (
|
|
||||||
<text
|
|
||||||
x={centroidX}
|
|
||||||
y={centroidY}
|
|
||||||
dy=".33em"
|
|
||||||
fill="#000"
|
|
||||||
fontSize={10}
|
|
||||||
textAnchor="middle"
|
|
||||||
pointerEvents="none"
|
|
||||||
>
|
|
||||||
{arc.data.label}
|
|
||||||
</text>
|
|
||||||
)}
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Pie>
|
|
||||||
</Group>
|
|
||||||
</svg>
|
|
||||||
{tooltipOpen && tooltipData && (
|
|
||||||
<TooltipInPortal
|
|
||||||
top={tooltipTop}
|
|
||||||
left={tooltipLeft}
|
|
||||||
style={{
|
|
||||||
...tooltipStyles,
|
|
||||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
|
||||||
color: isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_400,
|
|
||||||
}}
|
|
||||||
className="piechart-tooltip"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: tooltipData.color,
|
|
||||||
}}
|
|
||||||
className="piechart-indicator"
|
|
||||||
/>
|
|
||||||
{tooltipData.key}
|
|
||||||
<div className="tooltip-value">{tooltipData.value}</div>
|
|
||||||
</TooltipInPortal>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="piechart-legend">
|
|
||||||
{pieChartData.length > 0 &&
|
|
||||||
pieChartData.map((data) => (
|
|
||||||
<div
|
|
||||||
key={data.label}
|
|
||||||
className="piechart-legend-item"
|
|
||||||
onMouseEnter={(): void => {
|
|
||||||
setActive(data);
|
|
||||||
}}
|
|
||||||
onMouseLeave={(): void => {
|
|
||||||
setActive(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor: getFillColor(data.color),
|
|
||||||
}}
|
|
||||||
className="piechart-legend-label"
|
|
||||||
/>
|
|
||||||
{data.label}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PiePanelWrapper;
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
|
||||||
import ListPanelWrapper from './ListPanelWrapper';
|
import ListPanelWrapper from './ListPanelWrapper';
|
||||||
import PiePanelWrapper from './PiePanelWrapper';
|
|
||||||
import TablePanelWrapper from './TablePanelWrapper';
|
import TablePanelWrapper from './TablePanelWrapper';
|
||||||
import UplotPanelWrapper from './UplotPanelWrapper';
|
import UplotPanelWrapper from './UplotPanelWrapper';
|
||||||
import ValuePanelWrapper from './ValuePanelWrapper';
|
import ValuePanelWrapper from './ValuePanelWrapper';
|
||||||
@@ -13,6 +12,5 @@ export const PanelTypeVsPanelWrapper = {
|
|||||||
[PANEL_TYPES.VALUE]: ValuePanelWrapper,
|
[PANEL_TYPES.VALUE]: ValuePanelWrapper,
|
||||||
[PANEL_TYPES.TRACE]: null,
|
[PANEL_TYPES.TRACE]: null,
|
||||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||||
[PANEL_TYPES.PIE]: PiePanelWrapper,
|
|
||||||
[PANEL_TYPES.BAR]: UplotPanelWrapper,
|
[PANEL_TYPES.BAR]: UplotPanelWrapper,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,3 @@ export type PanelWrapperProps = {
|
|||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
selectedGraph?: PANEL_TYPES;
|
selectedGraph?: PANEL_TYPES;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TooltipData = {
|
|
||||||
label: string;
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
import { defaultStyles } from '@visx/tooltip';
|
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
|
|
||||||
export const tooltipStyles = {
|
|
||||||
...defaultStyles,
|
|
||||||
minWidth: 60,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.9)',
|
|
||||||
color: 'white',
|
|
||||||
zIndex: 9999,
|
|
||||||
display: 'flex',
|
|
||||||
gap: '10px',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '5px 10px',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getLabel = (
|
|
||||||
label: string,
|
|
||||||
query: Query,
|
|
||||||
queryName: string,
|
|
||||||
isQueryContentMultipleResult = false, // If there are more than one aggregation return by the query, this should be set to true. Default is false.
|
|
||||||
): string => {
|
|
||||||
let finalQuery;
|
|
||||||
if (!isQueryContentMultipleResult) {
|
|
||||||
finalQuery = query.builder.queryData.find((q) => q.queryName === queryName);
|
|
||||||
if (!finalQuery) {
|
|
||||||
// If the query is not found in queryData, then check in queryFormulas
|
|
||||||
finalQuery = query.builder.queryFormulas.find(
|
|
||||||
(q) => q.queryName === queryName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (finalQuery) {
|
|
||||||
if (finalQuery.legend !== '') {
|
|
||||||
return finalQuery.legend;
|
|
||||||
}
|
|
||||||
if (label !== undefined) {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
return queryName;
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to convert a hex color to RGB format
|
|
||||||
const hexToRgb = (
|
|
||||||
color: string,
|
|
||||||
): { r: number; g: number; b: number } | null => {
|
|
||||||
const hex = color.replace(
|
|
||||||
/^#?([a-f\d])([a-f\d])([a-f\d])$/i,
|
|
||||||
(m, r, g, b) => r + r + g + g + b + b,
|
|
||||||
);
|
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
||||||
return result
|
|
||||||
? {
|
|
||||||
r: parseInt(result[1], 16),
|
|
||||||
g: parseInt(result[2], 16),
|
|
||||||
b: parseInt(result[3], 16),
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const lightenColor = (color: string, opacity: number): string => {
|
|
||||||
// Convert the hex color to RGB format
|
|
||||||
const rgbColor = hexToRgb(color);
|
|
||||||
if (!rgbColor) return color; // Return the original color if unable to parse
|
|
||||||
|
|
||||||
// Extract the RGB components
|
|
||||||
const { r, g, b } = rgbColor;
|
|
||||||
|
|
||||||
// Create a new RGBA color string with the specified opacity
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
||||||
};
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { render } from '@testing-library/react';
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import i18n from 'ReactI18';
|
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
import ChangeHistory from '../index';
|
|
||||||
import { pipelineData, pipelineDataHistory } from './testUtils';
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ChangeHistory test', () => {
|
|
||||||
it('should render changeHistory correctly', () => {
|
|
||||||
const { getAllByText, getByText } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<ChangeHistory pipelineData={pipelineData} />
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// change History table headers
|
|
||||||
[
|
|
||||||
'Version',
|
|
||||||
'Deployment Stage',
|
|
||||||
'Last Deploy Message',
|
|
||||||
'Last Deployed Time',
|
|
||||||
'Edited by',
|
|
||||||
].forEach((text) => expect(getByText(text)).toBeInTheDocument());
|
|
||||||
|
|
||||||
// table content
|
|
||||||
expect(getAllByText('test-user').length).toBe(2);
|
|
||||||
expect(getAllByText('Deployment was successful').length).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test deployment stage and icon based on history data', () => {
|
|
||||||
const { getByText, container } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<ChangeHistory
|
|
||||||
pipelineData={{
|
|
||||||
...pipelineData,
|
|
||||||
history: pipelineDataHistory,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</QueryClientProvider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// assertion for different deployment stages
|
|
||||||
expect(container.querySelector('[data-icon="loading"]')).toBeInTheDocument();
|
|
||||||
expect(getByText('In Progress')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector('[data-icon="exclamation-circle"]'),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(getByText('Dirty')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector('[data-icon="close-circle"]'),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(getByText('Failed')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
container.querySelector('[data-icon="minus-circle"]'),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(getByText('Unknown')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(container.querySelectorAll('.ant-table-row').length).toBe(5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
/* eslint-disable sonarjs/no-duplicate-string */
|
|
||||||
import { Pipeline } from 'types/api/pipeline/def';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
|
|
||||||
export const pipelineData: Pipeline = {
|
|
||||||
id: 'test-id-1',
|
|
||||||
version: 24,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
is_valid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'DEPLOYED',
|
|
||||||
deployResult: 'Deployment was successful',
|
|
||||||
lastHash: 'log_pipelines:24',
|
|
||||||
lastConf: 'oiwernveroi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
pipelines: [
|
|
||||||
{
|
|
||||||
id: 'test-id-2',
|
|
||||||
orderId: 1,
|
|
||||||
name: 'hotrod logs parser',
|
|
||||||
alias: 'hotrodlogsparser',
|
|
||||||
description: 'Trying to test Logs Pipeline feature',
|
|
||||||
enabled: true,
|
|
||||||
filter: {
|
|
||||||
op: 'AND',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
key: 'container_name',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
id: 'sampleid',
|
|
||||||
value: 'hotrod',
|
|
||||||
op: '=',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
config: [
|
|
||||||
{
|
|
||||||
type: 'regex_parser',
|
|
||||||
id: 'parsetext(regex)',
|
|
||||||
output: 'parseattribsjson',
|
|
||||||
on_error: 'send',
|
|
||||||
orderId: 1,
|
|
||||||
enabled: true,
|
|
||||||
name: 'parse text (regex)',
|
|
||||||
parse_to: 'attributes',
|
|
||||||
regex:
|
|
||||||
'.+\\t+(?P<log_level>.+)\\t+(?P<location>.+)\\t+(?P<message>.+)\\t+(?P<attribs_json>.+)',
|
|
||||||
parse_from: 'body',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'json_parser',
|
|
||||||
id: 'parseattribsjson',
|
|
||||||
output: 'removetempattribs_json',
|
|
||||||
orderId: 2,
|
|
||||||
enabled: true,
|
|
||||||
name: 'parse attribs json',
|
|
||||||
parse_to: 'attributes',
|
|
||||||
parse_from: 'attributes.attribs_json',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'remove',
|
|
||||||
id: 'removetempattribs_json',
|
|
||||||
output: 'c2062723-895e-4614-ba38-29c5d5ee5927',
|
|
||||||
orderId: 3,
|
|
||||||
enabled: true,
|
|
||||||
name: 'remove temp attribs_json',
|
|
||||||
field: 'attributes.attribs_json',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'add',
|
|
||||||
id: 'c2062723-895e-4614-ba38-29c5d5ee5927',
|
|
||||||
orderId: 4,
|
|
||||||
enabled: true,
|
|
||||||
name: 'test add ',
|
|
||||||
field: 'resource["container.name"]',
|
|
||||||
value: 'hotrod',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
createdBy: 'test@email',
|
|
||||||
createdAt: '2024-01-02T13:56:02.858300964Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tes-id-1',
|
|
||||||
orderId: 2,
|
|
||||||
name: 'Logs Parser - test - Customer Service',
|
|
||||||
alias: 'LogsParser-test-CustomerService',
|
|
||||||
description: 'Trying to test Logs Pipeline feature',
|
|
||||||
enabled: true,
|
|
||||||
filter: {
|
|
||||||
op: 'AND',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
key: 'service',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
id: 'sample-test-1',
|
|
||||||
value: 'customer',
|
|
||||||
op: '=',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
config: [
|
|
||||||
{
|
|
||||||
type: 'grok_parser',
|
|
||||||
id: 'Testtest',
|
|
||||||
on_error: 'send',
|
|
||||||
orderId: 1,
|
|
||||||
enabled: true,
|
|
||||||
name: 'Test test',
|
|
||||||
parse_to: 'attributes',
|
|
||||||
pattern:
|
|
||||||
'^%{DATE:date}Z INFO customer/database.go:73 Loading customer {"service": "customer", "component": "mysql", "trace_id": "test-id", "span_id": "1427a3fcad8b1514", "customer_id": "567"}',
|
|
||||||
parse_from: 'body',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
createdBy: 'test@email',
|
|
||||||
createdAt: '2024-01-02T13:56:02.863764227Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
history: [
|
|
||||||
{
|
|
||||||
id: 'test-id-4',
|
|
||||||
version: 24,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'DEPLOYED',
|
|
||||||
deployResult: 'Deployment was successful',
|
|
||||||
lastHash: 'log_pipelines:24',
|
|
||||||
lastConf: 'eovineroiv',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2024-01-02T13:56:02Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'test-4',
|
|
||||||
version: 23,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'DEPLOYED',
|
|
||||||
deployResult: 'Deployment was successful',
|
|
||||||
lastHash: 'log_pipelines:23',
|
|
||||||
lastConf: 'eivrounreovi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2023-12-29T12:59:20Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pipelineDataHistory: Pipeline['history'] = [
|
|
||||||
{
|
|
||||||
id: 'test-id-4',
|
|
||||||
version: 24,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'DEPLOYED',
|
|
||||||
deployResult: 'Deployment was successful',
|
|
||||||
lastHash: 'log_pipelines:24',
|
|
||||||
lastConf: 'eovineroiv',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2024-01-02T13:56:02Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'test-4',
|
|
||||||
version: 23,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'IN_PROGRESS',
|
|
||||||
deployResult: 'Deployment is in progress',
|
|
||||||
lastHash: 'log_pipelines:23',
|
|
||||||
lastConf: 'eivrounreovi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2023-12-29T12:59:20Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'test-4-1',
|
|
||||||
version: 25,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'DIRTY',
|
|
||||||
deployResult: 'Deployment is dirty',
|
|
||||||
lastHash: 'log_pipelines:23',
|
|
||||||
lastConf: 'eivrounreovi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2023-12-29T12:59:20Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'test-4-2',
|
|
||||||
version: 26,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'FAILED',
|
|
||||||
deployResult: 'Deployment failed',
|
|
||||||
lastHash: 'log_pipelines:23',
|
|
||||||
lastConf: 'eivrounreovi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2023-12-29T12:59:20Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'test-4-3',
|
|
||||||
version: 27,
|
|
||||||
elementType: 'log_pipelines',
|
|
||||||
active: false,
|
|
||||||
isValid: false,
|
|
||||||
disabled: false,
|
|
||||||
deployStatus: 'UNKNOWN',
|
|
||||||
deployResult: '',
|
|
||||||
lastHash: 'log_pipelines:23',
|
|
||||||
lastConf: 'eivrounreovi',
|
|
||||||
createdBy: 'test-created-by',
|
|
||||||
createdByName: 'test-user',
|
|
||||||
createdAt: '2023-12-29T12:59:20Z',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
@@ -9,17 +8,8 @@ import store from 'store';
|
|||||||
import CreatePipelineButton from '../Layouts/Pipeline/CreatePipelineButton';
|
import CreatePipelineButton from '../Layouts/Pipeline/CreatePipelineButton';
|
||||||
import { pipelineApiResponseMockData } from '../mocks/pipeline';
|
import { pipelineApiResponseMockData } from '../mocks/pipeline';
|
||||||
|
|
||||||
const trackEventVar = jest.fn();
|
|
||||||
jest.mock('hooks/analytics/useAnalytics', () => ({
|
|
||||||
__esModule: true,
|
|
||||||
default: jest.fn().mockImplementation(() => ({
|
|
||||||
trackEvent: trackEventVar,
|
|
||||||
trackPageView: jest.fn(),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('PipelinePage container test', () => {
|
describe('PipelinePage container test', () => {
|
||||||
it('should render CreatePipelineButton section', async () => {
|
it('should render CreatePipelineButton section', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@@ -36,58 +26,4 @@ describe('PipelinePage container test', () => {
|
|||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CreatePipelineButton - edit mode & tracking', async () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<CreatePipelineButton
|
|
||||||
setActionType={jest.fn()}
|
|
||||||
isActionMode="viewing-mode"
|
|
||||||
setActionMode={jest.fn()}
|
|
||||||
pipelineData={pipelineApiResponseMockData}
|
|
||||||
/>
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// enter_edit_mode click and track event data
|
|
||||||
const editButton = getByText('enter_edit_mode');
|
|
||||||
expect(editButton).toBeInTheDocument();
|
|
||||||
await userEvent.click(editButton);
|
|
||||||
|
|
||||||
expect(trackEventVar).toBeCalledWith('Logs: Pipelines: Entered Edit Mode', {
|
|
||||||
source: 'signoz-ui',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('CreatePipelineButton - add new mode & tracking', async () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<CreatePipelineButton
|
|
||||||
setActionType={jest.fn()}
|
|
||||||
isActionMode="viewing-mode"
|
|
||||||
setActionMode={jest.fn()}
|
|
||||||
pipelineData={{ ...pipelineApiResponseMockData, pipelines: [] }}
|
|
||||||
/>
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
// new_pipeline click and track event data
|
|
||||||
const editButton = getByText('new_pipeline');
|
|
||||||
expect(editButton).toBeInTheDocument();
|
|
||||||
await userEvent.click(editButton);
|
|
||||||
|
|
||||||
expect(trackEventVar).toBeCalledWith(
|
|
||||||
'Logs: Pipelines: Clicked Add New Pipeline',
|
|
||||||
{
|
|
||||||
source: 'signoz-ui',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { fireEvent, render, waitFor } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
@@ -21,43 +20,4 @@ describe('PipelinePage container test', () => {
|
|||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle search', async () => {
|
|
||||||
const setPipelineValue = jest.fn();
|
|
||||||
const { getByPlaceholderText, container } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<Provider store={store}>
|
|
||||||
<I18nextProvider i18n={i18n}>
|
|
||||||
<PipelinesSearchSection setPipelineSearchValue={setPipelineValue} />
|
|
||||||
</I18nextProvider>
|
|
||||||
</Provider>
|
|
||||||
</MemoryRouter>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchInput = getByPlaceholderText('search_pipeline_placeholder');
|
|
||||||
|
|
||||||
// Type into the search input
|
|
||||||
userEvent.type(searchInput, 'sample_pipeline');
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(299);
|
|
||||||
expect(setPipelineValue).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Wait for the debounce delay to pass
|
|
||||||
await waitFor(() => {
|
|
||||||
// Expect the callback to be called after debounce delay
|
|
||||||
expect(setPipelineValue).toHaveBeenCalledWith('sample_pipeline');
|
|
||||||
});
|
|
||||||
|
|
||||||
// clear button
|
|
||||||
fireEvent.click(
|
|
||||||
container.querySelector(
|
|
||||||
'span[class*="ant-input-clear-icon"]',
|
|
||||||
) as HTMLElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait for the debounce delay to pass
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(setPipelineValue).toHaveBeenCalledWith('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ export default function QBEntityOptions({
|
|||||||
onQueryFunctionsUpdates && (
|
onQueryFunctionsUpdates && (
|
||||||
<QueryFunctions
|
<QueryFunctions
|
||||||
query={query}
|
query={query}
|
||||||
queryFunctions={query.functions || []}
|
queryFunctions={query.functions}
|
||||||
key={query.functions?.toString()}
|
key={query.functions.toString()}
|
||||||
onChange={onQueryFunctionsUpdates}
|
onChange={onQueryFunctionsUpdates}
|
||||||
maxFunctions={isLogsDataSource ? 1 : 3}
|
maxFunctions={isLogsDataSource ? 1 : 3}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
|
||||||
|
|
||||||
import LeftToolbarActions from '../LeftToolbarActions';
|
|
||||||
import RightToolbarActions from '../RightToolbarActions';
|
|
||||||
|
|
||||||
describe('ToolbarActions', () => {
|
|
||||||
it('LeftToolbarActions - renders correctly with default props', async () => {
|
|
||||||
const handleChangeSelectedView = jest.fn();
|
|
||||||
const handleToggleShowHistogram = jest.fn();
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<LeftToolbarActions
|
|
||||||
items={{
|
|
||||||
search: {
|
|
||||||
name: 'search',
|
|
||||||
label: 'Search',
|
|
||||||
disabled: false,
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
queryBuilder: {
|
|
||||||
name: 'query-builder',
|
|
||||||
label: 'Query Builder',
|
|
||||||
disabled: false,
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
clickhouse: {
|
|
||||||
name: 'clickhouse',
|
|
||||||
label: 'Clickhouse',
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
selectedView={SELECTED_VIEWS.SEARCH}
|
|
||||||
onChangeSelectedView={handleChangeSelectedView}
|
|
||||||
onToggleHistrogramVisibility={handleToggleShowHistogram}
|
|
||||||
showHistogram
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('search-view')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('query-builder-view')).toBeInTheDocument();
|
|
||||||
|
|
||||||
// clickhouse should not be present as its show: false
|
|
||||||
expect(queryByTestId('clickhouse-view')).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByTestId('search-view'));
|
|
||||||
expect(handleChangeSelectedView).toBeCalled();
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByTestId('query-builder-view'));
|
|
||||||
expect(handleChangeSelectedView).toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders - clickhouse view and test histogram toggle', async () => {
|
|
||||||
const handleChangeSelectedView = jest.fn();
|
|
||||||
const handleToggleShowHistogram = jest.fn();
|
|
||||||
const { queryByTestId, getByRole } = render(
|
|
||||||
<LeftToolbarActions
|
|
||||||
items={{
|
|
||||||
search: {
|
|
||||||
name: 'search',
|
|
||||||
label: 'Search',
|
|
||||||
disabled: false,
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
queryBuilder: {
|
|
||||||
name: 'query-builder',
|
|
||||||
label: 'Query Builder',
|
|
||||||
disabled: false,
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
clickhouse: {
|
|
||||||
name: 'clickhouse',
|
|
||||||
label: 'Clickhouse',
|
|
||||||
disabled: false,
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
selectedView={SELECTED_VIEWS.QUERY_BUILDER}
|
|
||||||
onChangeSelectedView={handleChangeSelectedView}
|
|
||||||
onToggleHistrogramVisibility={handleToggleShowHistogram}
|
|
||||||
showHistogram
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const clickHouseView = queryByTestId('clickhouse-view');
|
|
||||||
expect(clickHouseView).toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(clickHouseView as HTMLElement);
|
|
||||||
expect(handleChangeSelectedView).toBeCalled();
|
|
||||||
|
|
||||||
await userEvent.click(getByRole('switch'));
|
|
||||||
expect(handleToggleShowHistogram).toBeCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('RightToolbarActions - render correctly with props', async () => {
|
|
||||||
const onStageRunQuery = jest.fn();
|
|
||||||
const { queryByText } = render(
|
|
||||||
<RightToolbarActions onStageRunQuery={onStageRunQuery} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
const stageNRunBtn = queryByText('Stage & Run Query');
|
|
||||||
expect(stageNRunBtn).toBeInTheDocument();
|
|
||||||
await userEvent.click(stageNRunBtn as HTMLElement);
|
|
||||||
expect(onStageRunQuery).toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
|
||||||
transition: 0.2s all linear;
|
transition: 0.2s all linear;
|
||||||
|
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ function SideNav({
|
|||||||
}, [isCloudUserVal, isEnterprise, isFetching]);
|
}, [isCloudUserVal, isEnterprise, isFetching]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!(isCloudUserVal || isEECloudUser())) {
|
if (!isCloudUserVal) {
|
||||||
let updatedMenuItems = [...menuItems];
|
let updatedMenuItems = [...menuItems];
|
||||||
updatedMenuItems = updatedMenuItems.filter(
|
updatedMenuItems = updatedMenuItems.filter(
|
||||||
(item) => item.key !== ROUTES.INTEGRATIONS,
|
(item) => item.key !== ROUTES.INTEGRATIONS,
|
||||||
|
|||||||
@@ -13,18 +13,11 @@ function Events({
|
|||||||
return <Typography>No events data in selected span</Typography>;
|
return <Typography>No events data in selected span</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedTraceEvents = events.sort((a, b) => {
|
|
||||||
// Handle undefined names by treating them as empty strings
|
|
||||||
const nameA = a.name || '';
|
|
||||||
const nameB = b.name || '';
|
|
||||||
return nameA.localeCompare(nameB);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorTag
|
<ErrorTag
|
||||||
onToggleHandler={onToggleHandler}
|
onToggleHandler={onToggleHandler}
|
||||||
setText={setText}
|
setText={setText}
|
||||||
event={sortedTraceEvents}
|
event={events}
|
||||||
firstSpanStartTime={firstSpanStartTime}
|
firstSpanStartTime={firstSpanStartTime}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,9 +41,8 @@ function Tags({
|
|||||||
setSearchText(value);
|
setSearchText(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredTags = tags
|
const filteredTags = tags.filter((tag) => tag.key.includes(searchText));
|
||||||
.filter((tag) => tag.key.includes(searchText))
|
|
||||||
.sort((a, b) => a.key.localeCompare(b.key));
|
|
||||||
if (tags.length === 0) {
|
if (tags.length === 0) {
|
||||||
return <Typography>No tags in selected span</Typography>;
|
return <Typography>No tags in selected span</Typography>;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user