Compare commits
138 Commits
feat/anoma
...
v0.58.0-gi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
946f30b08a | ||
|
|
831540eaf0 | ||
|
|
22c10f9479 | ||
|
|
e748fb0655 | ||
|
|
fdc54a62a9 | ||
|
|
abe0ab69b0 | ||
|
|
e623c92615 | ||
|
|
dc5917db01 | ||
|
|
d6a7f0b6f4 | ||
|
|
471803115e | ||
|
|
8403a3362d | ||
|
|
64d46bc855 | ||
|
|
c9fee27604 | ||
|
|
f1b6b2d3d8 | ||
|
|
468f056530 | ||
|
|
7086470ce2 | ||
|
|
352296c6cd | ||
|
|
975307a8b8 | ||
|
|
12377be809 | ||
|
|
9d90b8d19c | ||
|
|
5005923ef4 | ||
|
|
db4338be42 | ||
|
|
c7d0598ec0 | ||
|
|
4978fb9599 | ||
|
|
7b18c3ba06 | ||
|
|
92cdb36879 | ||
|
|
580f0b816e | ||
|
|
b770fc2457 | ||
|
|
c177230cce | ||
|
|
2112047a02 | ||
|
|
03c193d5a1 | ||
|
|
b83b295318 | ||
|
|
fbe75cd057 | ||
|
|
860145fb1d | ||
|
|
2fe75e74cd | ||
|
|
8e19c346a4 | ||
|
|
1b33efe4cc | ||
|
|
2642338672 | ||
|
|
845dc00568 | ||
|
|
a1090bfdc5 | ||
|
|
44f41c55f9 | ||
|
|
42ac9ab6fe | ||
|
|
5c02250aae | ||
|
|
c49a9dac1a | ||
|
|
abc2ec2155 | ||
|
|
4dc5615d2f | ||
|
|
6c350f30aa | ||
|
|
6664e1bc02 | ||
|
|
438cbcef87 | ||
|
|
829e1f0920 | ||
|
|
68d25a8989 | ||
|
|
cc90321ac0 | ||
|
|
bdcae62bf9 | ||
|
|
4e26189778 | ||
|
|
952ab58023 | ||
|
|
3ca2fff5c5 | ||
|
|
ef3a9adb48 | ||
|
|
975f141604 | ||
|
|
c206f4fa5c | ||
|
|
e88e24e434 | ||
|
|
94e0423479 | ||
|
|
5891fbc229 | ||
|
|
8137ec54ba | ||
|
|
f7b80524a5 | ||
|
|
4be0508dd2 | ||
|
|
a31c4b8339 | ||
|
|
d7846338ce | ||
|
|
5dac1ad20a | ||
|
|
8d704c331c | ||
|
|
f8e47496fa | ||
|
|
6fef9d9676 | ||
|
|
190767fd0a | ||
|
|
1e78786cae | ||
|
|
6448fb17e7 | ||
|
|
f2e33d7ca9 | ||
|
|
6c7167a224 | ||
|
|
00421235b0 | ||
|
|
0e2b67059b | ||
|
|
910c44cefc | ||
|
|
8bad036423 | ||
|
|
a21830132f | ||
|
|
9419f56e95 | ||
|
|
347868c18b | ||
|
|
17e20e7f41 | ||
|
|
2b0da82f94 | ||
|
|
911362cecf | ||
|
|
481f9620d3 | ||
|
|
e5be431f18 | ||
|
|
503ed45a99 | ||
|
|
28818fbaac | ||
|
|
c0e40614bf | ||
|
|
2d732ae4a9 | ||
|
|
8466e31e02 | ||
|
|
efdaf7ee43 | ||
|
|
0dec94a5c6 | ||
|
|
204728ff60 | ||
|
|
e51f4d986d | ||
|
|
337a941d0d | ||
|
|
fc4b55cb34 | ||
|
|
96cb8053df | ||
|
|
5651d69485 | ||
|
|
a6e492880d | ||
|
|
80b3c3e256 | ||
|
|
0806420dd7 | ||
|
|
18e240e3d1 | ||
|
|
d0965a24c5 | ||
|
|
7ed689693f | ||
|
|
90ae55264a | ||
|
|
bf4c792cdb | ||
|
|
dd097821d1 | ||
|
|
701b8803ac | ||
|
|
2728ddd255 | ||
|
|
5187ed58a0 | ||
|
|
78d1e19e60 | ||
|
|
5588c7dd3f | ||
|
|
b70d50f2b3 | ||
|
|
728f699051 | ||
|
|
87499d1ead | ||
|
|
5fa8686fcf | ||
|
|
dc2db524c7 | ||
|
|
b3545b767a | ||
|
|
08f3b089f4 | ||
|
|
1d8e5b6c0f | ||
|
|
0dcded59e5 | ||
|
|
262beef8f9 | ||
|
|
43cc6dea92 | ||
|
|
6684640abe | ||
|
|
0a146910d6 | ||
|
|
690ed0f7f1 | ||
|
|
5bcf7de440 | ||
|
|
703983a5f9 | ||
|
|
766a2123c5 | ||
|
|
a476c68f7e | ||
|
|
fc15aa6f1c | ||
|
|
4192fd573d | ||
|
|
ca13d80205 | ||
|
|
8d84ce8f06 | ||
|
|
09ea7b9eb5 |
83
.github/workflows/docs.yml
vendored
Normal file
83
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
name: "Update PR labels and Block PR until related docs are shipped for the feature"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
types: [opened, edited, labeled, unlabeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs_label_check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check PR Title and Manage Labels
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const prTitle = context.payload.pull_request.title;
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
// Fetch the current PR details to get labels
|
||||||
|
const pr = await github.rest.pulls.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const labels = pr.data.labels.map(label => label.name);
|
||||||
|
|
||||||
|
if (prTitle.startsWith('feat:')) {
|
||||||
|
const hasDocsRequired = labels.includes('docs required');
|
||||||
|
const hasDocsShipped = labels.includes('docs shipped');
|
||||||
|
const hasDocsNotRequired = labels.includes('docs not required');
|
||||||
|
|
||||||
|
// If "docs not required" is present, skip the checks
|
||||||
|
if (hasDocsNotRequired && !hasDocsRequired) {
|
||||||
|
console.log("Skipping checks due to 'docs not required' label.");
|
||||||
|
return; // Exit the script early
|
||||||
|
}
|
||||||
|
|
||||||
|
// If "docs shipped" is present, remove "docs required" if it exists
|
||||||
|
if (hasDocsShipped && hasDocsRequired) {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
name: 'docs required'
|
||||||
|
});
|
||||||
|
console.log("Removed 'docs required' label.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "docs required" label if neither "docs shipped" nor "docs required" are present
|
||||||
|
if (!hasDocsRequired && !hasDocsShipped) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
labels: ['docs required']
|
||||||
|
});
|
||||||
|
console.log("Added 'docs required' label.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the updated labels after any changes
|
||||||
|
const updatedPr = await github.rest.pulls.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedLabels = updatedPr.data.labels.map(label => label.name);
|
||||||
|
const updatedHasDocsRequired = updatedLabels.includes('docs required');
|
||||||
|
const updatedHasDocsShipped = updatedLabels.includes('docs shipped');
|
||||||
|
|
||||||
|
// Block PR if "docs required" is still present and "docs shipped" is missing
|
||||||
|
if (updatedHasDocsRequired && !updatedHasDocsShipped) {
|
||||||
|
core.setFailed("This PR requires documentation. Please remove the 'docs required' label and add the 'docs shipped' label to proceed.");
|
||||||
|
}
|
||||||
1
.github/workflows/staging-deployment.yaml
vendored
1
.github/workflows/staging-deployment.yaml
vendored
@@ -38,6 +38,7 @@ jobs:
|
|||||||
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
|
||||||
export OTELCOL_TAG="main"
|
export OTELCOL_TAG="main"
|
||||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||||
|
export KAFKA_SPAN_EVAL="true"
|
||||||
docker system prune --force
|
docker system prune --force
|
||||||
docker pull signoz/signoz-otel-collector:main
|
docker pull signoz/signoz-otel-collector:main
|
||||||
docker pull signoz/signoz-schema-migrator:main
|
docker pull signoz/signoz-schema-migrator:main
|
||||||
|
|||||||
13
Makefile
13
Makefile
@@ -79,7 +79,7 @@ build-query-service-static:
|
|||||||
@if [ $(DEV_BUILD) != "" ]; then \
|
@if [ $(DEV_BUILD) != "" ]; then \
|
||||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||||
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
||||||
else \
|
else \
|
||||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||||
@@ -188,13 +188,4 @@ check-no-ee-references:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./pkg/query-service/app/metrics/...
|
go test ./pkg/query-service/...
|
||||||
go test ./pkg/query-service/cache/...
|
|
||||||
go test ./pkg/query-service/app/...
|
|
||||||
go test ./pkg/query-service/app/querier/...
|
|
||||||
go test ./pkg/query-service/converter/...
|
|
||||||
go test ./pkg/query-service/formatter/...
|
|
||||||
go test ./pkg/query-service/tests/integration/...
|
|
||||||
go test ./pkg/query-service/rules/...
|
|
||||||
go test ./pkg/query-service/collectorsimulator/...
|
|
||||||
go test ./pkg/query-service/postprocess/...
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ services:
|
|||||||
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.23.5
|
image: signoz/alertmanager:0.23.7
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
command:
|
command:
|
||||||
@@ -146,7 +146,7 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.55.0
|
image: signoz/query-service:0.56.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.55.0
|
image: signoz/frontend:0.56.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.102.10
|
image: signoz/signoz-otel-collector:0.111.5
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@@ -214,7 +214,6 @@ services:
|
|||||||
- /:/hostfs:ro
|
- /:/hostfs:ro
|
||||||
environment:
|
environment:
|
||||||
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
|
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
|
||||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
|
||||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||||
ports:
|
ports:
|
||||||
# - "1777:1777" # pprof extension
|
# - "1777:1777" # pprof extension
|
||||||
@@ -238,7 +237,7 @@ services:
|
|||||||
- query-service
|
- query-service
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:0.102.10
|
image: signoz/signoz-schema-migrator:0.111.5
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|||||||
@@ -131,8 +131,7 @@ processors:
|
|||||||
exporters:
|
exporters:
|
||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
@@ -142,7 +141,6 @@ exporters:
|
|||||||
# logging: {}
|
# logging: {}
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/signoz_logs
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
use_new_schema: true
|
use_new_schema: true
|
||||||
extensions:
|
extensions:
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ services:
|
|||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
container_name: signoz-alertmanager
|
container_name: signoz-alertmanager
|
||||||
image: signoz/alertmanager:0.23.5
|
image: signoz/alertmanager:0.23.7
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -69,7 +69,7 @@ services:
|
|||||||
- --storage.path=/data
|
- --storage.path=/data
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@@ -84,7 +84,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.102.10
|
image: signoz/signoz-otel-collector:0.111.5
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ x-db-depend: &db-depend
|
|||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
otel-collector-migrator:
|
otel-collector-migrator-sync:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
# clickhouse-2:
|
# clickhouse-2:
|
||||||
# condition: service_healthy
|
# condition: service_healthy
|
||||||
@@ -147,7 +147,7 @@ services:
|
|||||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.5}
|
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.7}
|
||||||
container_name: signoz-alertmanager
|
container_name: signoz-alertmanager
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
@@ -162,7 +162,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.55.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@@ -201,7 +201,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.55.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -212,11 +212,13 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ../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-sync:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.10}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator-sync
|
||||||
command:
|
command:
|
||||||
|
- "sync"
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
|
- "--up="
|
||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -225,9 +227,25 @@ services:
|
|||||||
# clickhouse-3:
|
# clickhouse-3:
|
||||||
# condition: service_healthy
|
# condition: service_healthy
|
||||||
|
|
||||||
|
otel-collector-migrator-async:
|
||||||
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
|
container_name: otel-migrator-async
|
||||||
|
command:
|
||||||
|
- "async"
|
||||||
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
|
- "--up="
|
||||||
|
depends_on:
|
||||||
|
clickhouse:
|
||||||
|
condition: service_healthy
|
||||||
|
otel-collector-migrator-sync:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
# clickhouse-2:
|
||||||
|
# condition: service_healthy
|
||||||
|
# clickhouse-3:
|
||||||
|
# condition: service_healthy
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@@ -244,7 +262,6 @@ services:
|
|||||||
- /:/hostfs:ro
|
- /:/hostfs:ro
|
||||||
environment:
|
environment:
|
||||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
|
||||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||||
ports:
|
ports:
|
||||||
# - "1777:1777" # pprof extension
|
# - "1777:1777" # pprof extension
|
||||||
@@ -262,7 +279,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
otel-collector-migrator:
|
otel-collector-migrator-sync:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
query-service:
|
query-service:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ services:
|
|||||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.5}
|
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.7}
|
||||||
container_name: signoz-alertmanager
|
container_name: signoz-alertmanager
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
@@ -167,7 +167,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.55.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@@ -191,6 +191,7 @@ services:
|
|||||||
- GODEBUG=netdns=go
|
- GODEBUG=netdns=go
|
||||||
- TELEMETRY_ENABLED=true
|
- TELEMETRY_ENABLED=true
|
||||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||||
|
- KAFKA_SPAN_EVAL=${KAFKA_SPAN_EVAL:-false}
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
@@ -207,7 +208,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.55.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -219,7 +220,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.102.10}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@@ -233,7 +234,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.10}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@@ -250,7 +251,6 @@ services:
|
|||||||
- /:/hostfs:ro
|
- /:/hostfs:ro
|
||||||
environment:
|
environment:
|
||||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
|
||||||
- DOCKER_MULTI_NODE_CLUSTER=false
|
|
||||||
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
- LOW_CARDINAL_EXCEPTION_GROUPING=false
|
||||||
ports:
|
ports:
|
||||||
# - "1777:1777" # pprof extension
|
# - "1777:1777" # pprof extension
|
||||||
|
|||||||
@@ -142,8 +142,7 @@ extensions:
|
|||||||
exporters:
|
exporters:
|
||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/signoz_traces
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
low_cardinal_exception_grouping: ${LOW_CARDINAL_EXCEPTION_GROUPING}
|
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
@@ -152,7 +151,6 @@ exporters:
|
|||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/signoz_logs
|
||||||
docker_multi_node_cluster: ${DOCKER_MULTI_NODE_CLUSTER}
|
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
use_new_schema: true
|
use_new_schema: true
|
||||||
# logging: {}
|
# logging: {}
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ import (
|
|||||||
|
|
||||||
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
if !strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+gateway.AllowedPrefix) {
|
validPath := false
|
||||||
|
for _, allowedPrefix := range gateway.AllowedPrefix {
|
||||||
|
if strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+allowedPrefix) {
|
||||||
|
validPath = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validPath {
|
||||||
rw.WriteHeader(http.StatusNotFound)
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
|||||||
if anomalyQueryExists {
|
if anomalyQueryExists {
|
||||||
// ensure all queries have metric data source, and there should be only one anomaly query
|
// ensure all queries have metric data source, and there should be only one anomaly query
|
||||||
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
||||||
if query.DataSource != v3.DataSourceMetrics {
|
// What is query.QueryName == query.Expression doing here?
|
||||||
|
// In the current implementation, the way to recognize if a query is a formula is by
|
||||||
|
// checking if the expression is the same as the query name. if the expression is different
|
||||||
|
// then it is a formula. otherwise, it is simple builder query.
|
||||||
|
if query.DataSource != v3.DataSourceMetrics && query.QueryName == query.Expression {
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("all queries must have metric data source")}, nil)
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("all queries must have metric data source")}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -100,6 +104,13 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
|||||||
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
|
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
|
||||||
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
|
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
|
||||||
)
|
)
|
||||||
|
default:
|
||||||
|
provider = anomaly.NewDailyProvider(
|
||||||
|
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
||||||
|
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
||||||
|
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
||||||
|
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})
|
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import (
|
|||||||
"go.signoz.io/signoz/ee/query-service/rules"
|
"go.signoz.io/signoz/ee/query-service/rules"
|
||||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
|
||||||
licensepkg "go.signoz.io/signoz/ee/query-service/license"
|
licensepkg "go.signoz.io/signoz/ee/query-service/license"
|
||||||
@@ -348,7 +347,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.User.OrgId == "" {
|
if user.User.OrgId == "" {
|
||||||
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
return nil, basemodel.UnauthorizedError(errors.New("orgId is missing in the claims"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
@@ -765,8 +764,9 @@ func makeRulesManager(
|
|||||||
Cache: cache,
|
Cache: cache,
|
||||||
EvalDelay: baseconst.GetEvalDelay(),
|
EvalDelay: baseconst.GetEvalDelay(),
|
||||||
|
|
||||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||||
UseLogsNewSchema: useLogsNewSchema,
|
PrepareTestRuleFunc: rules.TestNotification,
|
||||||
|
UseLogsNewSchema: useLogsNewSchema,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create Manager
|
// create Manager
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
RoutePrefix string = "/api/gateway"
|
RoutePrefix string = "/api/gateway"
|
||||||
AllowedPrefix string = "/v1/workspaces/me"
|
AllowedPrefix []string = []string{"/v1/workspaces/me", "/v2/profiles/me", "/v2/deployments/me"}
|
||||||
)
|
)
|
||||||
|
|
||||||
type proxy struct {
|
type proxy struct {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,6 +135,13 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: basemodel.HostsInfraMonitoring,
|
||||||
|
Active: constants.EnableHostsInfraMonitoring(),
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ProPlan = basemodel.FeatureSet{
|
var ProPlan = basemodel.FeatureSet{
|
||||||
@@ -249,6 +257,13 @@ var ProPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: basemodel.HostsInfraMonitoring,
|
||||||
|
Active: constants.EnableHostsInfraMonitoring(),
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnterprisePlan = basemodel.FeatureSet{
|
var EnterprisePlan = basemodel.FeatureSet{
|
||||||
@@ -378,4 +393,11 @@ var EnterprisePlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: basemodel.HostsInfraMonitoring,
|
||||||
|
Active: constants.EnableHostsInfraMonitoring(),
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
baserules "go.signoz.io/signoz/pkg/query-service/rules"
|
baserules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils/labels"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
|
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
|
||||||
@@ -79,6 +84,106 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
|||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNotification prepares a dummy rule for given rule parameters and
|
||||||
|
// sends a test notification. returns alert count and error (if any)
|
||||||
|
func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.ApiError) {
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if opts.Rule == nil {
|
||||||
|
return 0, basemodel.BadRequest(fmt.Errorf("rule is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedRule := opts.Rule
|
||||||
|
var alertname = parsedRule.AlertName
|
||||||
|
if alertname == "" {
|
||||||
|
// alertname is not mandatory for testing, so picking
|
||||||
|
// a random string here
|
||||||
|
alertname = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// append name to indicate this is test alert
|
||||||
|
parsedRule.AlertName = fmt.Sprintf("%s%s", alertname, baserules.TestAlertPostFix)
|
||||||
|
|
||||||
|
var rule baserules.Rule
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if parsedRule.RuleType == baserules.RuleTypeThreshold {
|
||||||
|
|
||||||
|
// add special labels for test alerts
|
||||||
|
parsedRule.Annotations[labels.AlertSummaryLabel] = fmt.Sprintf("The rule threshold is set to %.4f, and the observed metric value is {{$value}}.", *parsedRule.RuleCondition.Target)
|
||||||
|
parsedRule.Labels[labels.RuleSourceLabel] = ""
|
||||||
|
parsedRule.Labels[labels.AlertRuleIdLabel] = ""
|
||||||
|
|
||||||
|
// create a threshold rule
|
||||||
|
rule, err = baserules.NewThresholdRule(
|
||||||
|
alertname,
|
||||||
|
parsedRule,
|
||||||
|
opts.FF,
|
||||||
|
opts.Reader,
|
||||||
|
opts.UseLogsNewSchema,
|
||||||
|
baserules.WithSendAlways(),
|
||||||
|
baserules.WithSendUnmatched(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to prepare a new threshold rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||||
|
return 0, basemodel.BadRequest(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if parsedRule.RuleType == baserules.RuleTypeProm {
|
||||||
|
|
||||||
|
// create promql rule
|
||||||
|
rule, err = baserules.NewPromRule(
|
||||||
|
alertname,
|
||||||
|
parsedRule,
|
||||||
|
opts.Logger,
|
||||||
|
opts.Reader,
|
||||||
|
opts.ManagerOpts.PqlEngine,
|
||||||
|
baserules.WithSendAlways(),
|
||||||
|
baserules.WithSendUnmatched(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to prepare a new promql rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||||
|
return 0, basemodel.BadRequest(err)
|
||||||
|
}
|
||||||
|
} else if parsedRule.RuleType == baserules.RuleTypeAnomaly {
|
||||||
|
// create anomaly rule
|
||||||
|
rule, err = NewAnomalyRule(
|
||||||
|
alertname,
|
||||||
|
parsedRule,
|
||||||
|
opts.FF,
|
||||||
|
opts.Reader,
|
||||||
|
opts.Cache,
|
||||||
|
baserules.WithSendAlways(),
|
||||||
|
baserules.WithSendUnmatched(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", rule.Name()), zap.Error(err))
|
||||||
|
return 0, basemodel.BadRequest(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0, basemodel.BadRequest(fmt.Errorf("failed to derive ruletype with given information"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// set timestamp to current utc time
|
||||||
|
ts := time.Now().UTC()
|
||||||
|
|
||||||
|
count, err := rule.Eval(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("evaluating rule failed", zap.String("rule", rule.Name()), zap.Error(err))
|
||||||
|
return 0, basemodel.InternalError(fmt.Errorf("rule evaluation failed"))
|
||||||
|
}
|
||||||
|
alertsFound, ok := count.(int)
|
||||||
|
if !ok {
|
||||||
|
return 0, basemodel.InternalError(fmt.Errorf("something went wrong"))
|
||||||
|
}
|
||||||
|
rule.SendAlerts(ctx, ts, 0, time.Duration(1*time.Minute), opts.NotifyFunc)
|
||||||
|
|
||||||
|
return alertsFound, nil
|
||||||
|
}
|
||||||
|
|
||||||
// newTask returns an appropriate group for
|
// newTask returns an appropriate group for
|
||||||
// rule type
|
// rule type
|
||||||
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, ruleDB baserules.RuleDB) baserules.Task {
|
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, ruleDB baserules.RuleDB) baserules.Task {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"@dnd-kit/core": "6.1.0",
|
"@dnd-kit/core": "6.1.0",
|
||||||
"@dnd-kit/modifiers": "7.0.0",
|
"@dnd-kit/modifiers": "7.0.0",
|
||||||
"@dnd-kit/sortable": "8.0.0",
|
"@dnd-kit/sortable": "8.0.0",
|
||||||
"@grafana/data": "^9.5.2",
|
"@grafana/data": "^11.2.3",
|
||||||
"@mdx-js/loader": "2.3.0",
|
"@mdx-js/loader": "2.3.0",
|
||||||
"@mdx-js/react": "2.3.0",
|
"@mdx-js/react": "2.3.0",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"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.7.4",
|
"axios": "1.7.7",
|
||||||
"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",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"fontfaceobserver": "2.3.0",
|
"fontfaceobserver": "2.3.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.7",
|
||||||
"i18next": "^21.6.12",
|
"i18next": "^21.6.12",
|
||||||
"i18next-browser-languagedetector": "^6.1.3",
|
"i18next-browser-languagedetector": "^6.1.3",
|
||||||
"i18next-http-backend": "^1.3.2",
|
"i18next-http-backend": "^1.3.2",
|
||||||
@@ -87,6 +87,8 @@
|
|||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "0.379.0",
|
"lucide-react": "0.379.0",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"mini-css-extract-plugin": "2.4.5",
|
||||||
|
"overlayscrollbars": "^2.8.1",
|
||||||
|
"overlayscrollbars-react": "^0.5.6",
|
||||||
"papaparse": "5.4.1",
|
"papaparse": "5.4.1",
|
||||||
"posthog-js": "1.160.3",
|
"posthog-js": "1.160.3",
|
||||||
"rc-tween-one": "3.0.6",
|
"rc-tween-one": "3.0.6",
|
||||||
@@ -107,11 +109,10 @@
|
|||||||
"react-query": "3.39.3",
|
"react-query": "3.39.3",
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-router-dom-v5-compat": "6.27.0",
|
||||||
"react-syntax-highlighter": "15.5.0",
|
"react-syntax-highlighter": "15.5.0",
|
||||||
"react-use": "^17.3.2",
|
"react-use": "^17.3.2",
|
||||||
"react-virtuoso": "4.0.3",
|
"react-virtuoso": "4.0.3",
|
||||||
"overlayscrollbars-react": "^0.5.6",
|
|
||||||
"overlayscrollbars": "^2.8.1",
|
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"rehype-raw": "7.0.0",
|
"rehype-raw": "7.0.0",
|
||||||
@@ -123,10 +124,10 @@
|
|||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||||
"typescript": "^4.0.5",
|
"typescript": "^4.0.5",
|
||||||
"uplot": "1.6.26",
|
"uplot": "1.6.31",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"web-vitals": "^0.2.4",
|
"web-vitals": "^0.2.4",
|
||||||
"webpack": "5.88.2",
|
"webpack": "5.94.0",
|
||||||
"webpack-dev-server": "^4.15.1",
|
"webpack-dev-server": "^4.15.1",
|
||||||
"webpack-retry-chunk-load-plugin": "3.1.1",
|
"webpack-retry-chunk-load-plugin": "3.1.1",
|
||||||
"xstate": "^4.31.0"
|
"xstate": "^4.31.0"
|
||||||
|
|||||||
BIN
frontend/public/Images/signoz-hero-image.webp
Normal file
BIN
frontend/public/Images/signoz-hero-image.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
1
frontend/public/css/uPlot.min.css
vendored
Normal file
1
frontend/public/css/uPlot.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
|
||||||
BIN
frontend/public/fonts/FiraCode-VariableFont_wght.ttf
Normal file
BIN
frontend/public/fonts/FiraCode-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/Inter-VariableFont_opsz,wght.ttf
Normal file
BIN
frontend/public/fonts/Inter-VariableFont_opsz,wght.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/SpaceMono-Regular.ttf
Normal file
BIN
frontend/public/fonts/SpaceMono-Regular.ttf
Normal file
Binary file not shown.
BIN
frontend/public/fonts/WorkSans-VariableFont_wght.ttf
Normal file
BIN
frontend/public/fonts/WorkSans-VariableFont_wght.ttf
Normal file
Binary file not shown.
@@ -43,11 +43,18 @@
|
|||||||
"option_15min": "15 mins",
|
"option_15min": "15 mins",
|
||||||
"option_30min": "30 mins",
|
"option_30min": "30 mins",
|
||||||
"option_60min": "60 mins",
|
"option_60min": "60 mins",
|
||||||
"option_4hours": "4 hours",
|
"option_2hours": "2 hours",
|
||||||
"option_3hours": "3 hours",
|
"option_3hours": "3 hours",
|
||||||
|
"option_4hours": "4 hours",
|
||||||
|
"option_5hours": "5 hours",
|
||||||
"option_6hours": "6 hours",
|
"option_6hours": "6 hours",
|
||||||
|
"option_8hours": "8 hours",
|
||||||
|
"option_10hours": "10 hours",
|
||||||
"option_12hours": "12 hours",
|
"option_12hours": "12 hours",
|
||||||
"option_24hours": "24 hours",
|
"option_24hours": "24 hours",
|
||||||
|
"option_48hours": "48 hours",
|
||||||
|
"option_72hours": "72 hours",
|
||||||
|
"option_1week": "1 week",
|
||||||
"field_threshold": "Alert Threshold",
|
"field_threshold": "Alert Threshold",
|
||||||
"option_allthetimes": "all the times",
|
"option_allthetimes": "all the times",
|
||||||
"option_atleastonce": "at least once",
|
"option_atleastonce": "at least once",
|
||||||
|
|||||||
@@ -29,12 +29,24 @@
|
|||||||
"text_condition1": "Send a notification when",
|
"text_condition1": "Send a notification when",
|
||||||
"text_condition2": "the threshold",
|
"text_condition2": "the threshold",
|
||||||
"text_condition3": "during the last",
|
"text_condition3": "during the last",
|
||||||
|
"option_1min": "1 min",
|
||||||
"option_5min": "5 mins",
|
"option_5min": "5 mins",
|
||||||
"option_10min": "10 mins",
|
"option_10min": "10 mins",
|
||||||
"option_15min": "15 mins",
|
"option_15min": "15 mins",
|
||||||
|
"option_30min": "30 mins",
|
||||||
"option_60min": "60 mins",
|
"option_60min": "60 mins",
|
||||||
|
"option_2hours": "2 hours",
|
||||||
|
"option_3hours": "3 hours",
|
||||||
"option_4hours": "4 hours",
|
"option_4hours": "4 hours",
|
||||||
|
"option_5hours": "5 hours",
|
||||||
|
"option_6hours": "6 hours",
|
||||||
|
"option_8hours": "8 hours",
|
||||||
|
"option_10hours": "10 hours",
|
||||||
|
"option_12hours": "12 hours",
|
||||||
"option_24hours": "24 hours",
|
"option_24hours": "24 hours",
|
||||||
|
"option_48hours": "48 hours",
|
||||||
|
"option_72hours": "72 hours",
|
||||||
|
"option_1week": "1 week",
|
||||||
"field_threshold": "Alert Threshold",
|
"field_threshold": "Alert Threshold",
|
||||||
"option_allthetimes": "all the times",
|
"option_allthetimes": "all the times",
|
||||||
"option_atleastonce": "at least once",
|
"option_atleastonce": "at least once",
|
||||||
|
|||||||
@@ -47,11 +47,18 @@
|
|||||||
"option_15min": "15 mins",
|
"option_15min": "15 mins",
|
||||||
"option_30min": "30 mins",
|
"option_30min": "30 mins",
|
||||||
"option_60min": "60 mins",
|
"option_60min": "60 mins",
|
||||||
|
"option_2hours": "2 hours",
|
||||||
"option_3hours": "3 hours",
|
"option_3hours": "3 hours",
|
||||||
"option_4hours": "4 hours",
|
"option_4hours": "4 hours",
|
||||||
|
"option_5hours": "5 hours",
|
||||||
"option_6hours": "6 hours",
|
"option_6hours": "6 hours",
|
||||||
|
"option_8hours": "8 hours",
|
||||||
|
"option_10hours": "10 hours",
|
||||||
"option_12hours": "12 hours",
|
"option_12hours": "12 hours",
|
||||||
"option_24hours": "24 hours",
|
"option_24hours": "24 hours",
|
||||||
|
"option_48hours": "48 hours",
|
||||||
|
"option_72hours": "72 hours",
|
||||||
|
"option_1week": "1 week",
|
||||||
"field_threshold": "Alert Threshold",
|
"field_threshold": "Alert Threshold",
|
||||||
"option_allthetimes": "all the times",
|
"option_allthetimes": "all the times",
|
||||||
"option_atleastonce": "at least once",
|
"option_atleastonce": "at least once",
|
||||||
|
|||||||
@@ -1,30 +1,50 @@
|
|||||||
{
|
{
|
||||||
"breadcrumb": "Messaging Queues",
|
"breadcrumb": "Messaging Queues",
|
||||||
"header": "Kafka / Overview",
|
"header": "Kafka / Overview",
|
||||||
"overview": {
|
"overview": {
|
||||||
"title": "Start sending data in as little as 20 minutes",
|
"title": "Start sending data in as little as 20 minutes",
|
||||||
"subtitle": "Connect and Monitor Your Data Streams"
|
"subtitle": "Connect and Monitor Your Data Streams"
|
||||||
},
|
},
|
||||||
"configureConsumer": {
|
"configureConsumer": {
|
||||||
"title": "Configure Consumer",
|
"title": "Configure Consumer",
|
||||||
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
"description": "Add consumer data sources to gain insights and enhance monitoring.",
|
||||||
"button": "Get Started"
|
"button": "Get Started"
|
||||||
},
|
},
|
||||||
"configureProducer": {
|
"configureProducer": {
|
||||||
"title": "Configure Producer",
|
"title": "Configure Producer",
|
||||||
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
"description": "Add producer data sources to gain insights and enhance monitoring.",
|
||||||
"button": "Get Started"
|
"button": "Get Started"
|
||||||
},
|
},
|
||||||
"monitorKafka": {
|
"monitorKafka": {
|
||||||
"title": "Monitor kafka",
|
"title": "Monitor kafka",
|
||||||
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
"description": "Add your Kafka source to gain insights and enhance activity tracking.",
|
||||||
"button": "Get Started"
|
"button": "Get Started"
|
||||||
},
|
},
|
||||||
"summarySection": {
|
"summarySection": {
|
||||||
"viewDetailsButton": "View Details"
|
"viewDetailsButton": "View Details",
|
||||||
},
|
"consumer": {
|
||||||
"confirmModal": {
|
"title": "Consumer lag view",
|
||||||
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
"description": "Connect and Monitor Your Data Streams"
|
||||||
"okText": "Proceed"
|
},
|
||||||
}
|
"producer": {
|
||||||
}
|
"title": "Producer latency view",
|
||||||
|
"description": "Connect and Monitor Your Data Streams"
|
||||||
|
},
|
||||||
|
"partition": {
|
||||||
|
"title": "Partition Latency view",
|
||||||
|
"description": "Connect and Monitor Your Data Streams"
|
||||||
|
},
|
||||||
|
"dropRate": {
|
||||||
|
"title": "Drop Rate view",
|
||||||
|
"description": "Connect and Monitor Your Data Streams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirmModal": {
|
||||||
|
"content": "Before navigating to the details page, please make sure you have configured all the required setup to ensure correct data monitoring.",
|
||||||
|
"okText": "Proceed"
|
||||||
|
},
|
||||||
|
"overviewSummarySection": {
|
||||||
|
"title": "Monitor Your Data Streams",
|
||||||
|
"subtitle": "Monitor key Kafka metrics like consumer lag and latency to ensure efficient data flow and troubleshoot in real time."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,12 +29,24 @@
|
|||||||
"text_condition1": "Send a notification when",
|
"text_condition1": "Send a notification when",
|
||||||
"text_condition2": "the threshold",
|
"text_condition2": "the threshold",
|
||||||
"text_condition3": "during the last",
|
"text_condition3": "during the last",
|
||||||
|
"option_1min": "1 min",
|
||||||
"option_5min": "5 mins",
|
"option_5min": "5 mins",
|
||||||
"option_10min": "10 mins",
|
"option_10min": "10 mins",
|
||||||
"option_15min": "15 mins",
|
"option_15min": "15 mins",
|
||||||
|
"option_30min": "30 mins",
|
||||||
"option_60min": "60 mins",
|
"option_60min": "60 mins",
|
||||||
|
"option_2hours": "2 hours",
|
||||||
|
"option_3hours": "3 hours",
|
||||||
"option_4hours": "4 hours",
|
"option_4hours": "4 hours",
|
||||||
|
"option_5hours": "5 hours",
|
||||||
|
"option_6hours": "6 hours",
|
||||||
|
"option_8hours": "8 hours",
|
||||||
|
"option_10hours": "10 hours",
|
||||||
|
"option_12hours": "12 hours",
|
||||||
"option_24hours": "24 hours",
|
"option_24hours": "24 hours",
|
||||||
|
"option_48hours": "48 hours",
|
||||||
|
"option_72hours": "72 hours",
|
||||||
|
"option_1week": "1 week",
|
||||||
"field_threshold": "Alert Threshold",
|
"field_threshold": "Alert Threshold",
|
||||||
"option_allthetimes": "all the times",
|
"option_allthetimes": "all the times",
|
||||||
"option_atleastonce": "at least once",
|
"option_atleastonce": "at least once",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||||
"SERVICE_MAP": "SigNoz | Service Map",
|
"SERVICE_MAP": "SigNoz | Service Map",
|
||||||
"GET_STARTED": "SigNoz | Get Started",
|
"GET_STARTED": "SigNoz | Get Started",
|
||||||
|
"ONBOARDING": "SigNoz | Get Started",
|
||||||
"GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM",
|
"GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM",
|
||||||
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
|
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
|
||||||
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
|
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||||
|
import getOrgUser from 'api/user/getOrgUser';
|
||||||
import loginApi from 'api/user/login';
|
import loginApi from 'api/user/login';
|
||||||
import { Logout } from 'api/utils';
|
import { Logout } from 'api/utils';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
@@ -8,8 +9,10 @@ import ROUTES from 'constants/routes';
|
|||||||
import useLicense from 'hooks/useLicense';
|
import useLicense from 'hooks/useLicense';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { ReactChild, useEffect, useMemo } from 'react';
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
|
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { matchPath, Redirect, useLocation } from 'react-router-dom';
|
import { matchPath, Redirect, useLocation } from 'react-router-dom';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
@@ -17,6 +20,7 @@ import { AppState } from 'store/reducers';
|
|||||||
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||||
|
import { Organization } from 'types/api/user/getOrganization';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { routePermission } from 'utils/permission';
|
import { routePermission } from 'utils/permission';
|
||||||
|
|
||||||
@@ -31,6 +35,19 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const {
|
||||||
|
org,
|
||||||
|
orgPreferences,
|
||||||
|
user,
|
||||||
|
role,
|
||||||
|
isUserFetching,
|
||||||
|
isUserFetchingError,
|
||||||
|
isLoggedIn: isLoggedInState,
|
||||||
|
isFetchingOrgPreferences,
|
||||||
|
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const mapRoutes = useMemo(
|
const mapRoutes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Map(
|
new Map(
|
||||||
@@ -44,18 +61,21 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
[pathname],
|
[pathname],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isOnboardingComplete = useMemo(
|
||||||
|
() =>
|
||||||
|
orgPreferences?.find(
|
||||||
|
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
||||||
|
)?.value,
|
||||||
|
[orgPreferences],
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: licensesData,
|
data: licensesData,
|
||||||
isFetching: isFetchingLicensesData,
|
isFetching: isFetchingLicensesData,
|
||||||
} = useLicense();
|
} = useLicense();
|
||||||
|
|
||||||
const {
|
|
||||||
isUserFetching,
|
|
||||||
isUserFetchingError,
|
|
||||||
isLoggedIn: isLoggedInState,
|
|
||||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
|
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
@@ -66,6 +86,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
||||||
|
|
||||||
|
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
||||||
|
|
||||||
const isLocalStorageLoggedIn =
|
const isLocalStorageLoggedIn =
|
||||||
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
|
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
|
||||||
|
|
||||||
@@ -81,6 +103,63 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
|
||||||
|
queryFn: () => {
|
||||||
|
if (orgData && orgData.id !== undefined) {
|
||||||
|
return getOrgUser({
|
||||||
|
orgId: orgData.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
queryKey: ['getOrgUser'],
|
||||||
|
enabled: !isEmpty(orgData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkFirstTimeUser = (): boolean => {
|
||||||
|
const users = orgUsers?.payload || [];
|
||||||
|
|
||||||
|
const remainingUsers = users.filter(
|
||||||
|
(user) => user.email !== 'admin@signoz.cloud',
|
||||||
|
);
|
||||||
|
|
||||||
|
return remainingUsers.length === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
|
||||||
|
const shouldShowOnboarding = (): boolean => {
|
||||||
|
// Only run this effect if the org users and preferences are loaded
|
||||||
|
|
||||||
|
if (!isLoadingOrgUsers && !isFetchingOrgPreferences) {
|
||||||
|
const isFirstUser = checkFirstTimeUser();
|
||||||
|
|
||||||
|
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||||
|
return isFirstUser && !isOnboardingComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRedirectForOrgOnboarding = (key: string): void => {
|
||||||
|
if (
|
||||||
|
isLoggedInState &&
|
||||||
|
!isFetchingOrgPreferences &&
|
||||||
|
!isLoadingOrgUsers &&
|
||||||
|
!isEmpty(orgUsers?.payload) &&
|
||||||
|
!isNull(orgPreferences)
|
||||||
|
) {
|
||||||
|
if (key === 'ONBOARDING' && isOnboardingComplete) {
|
||||||
|
history.push(ROUTES.APPLICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirstTimeUser = checkFirstTimeUser();
|
||||||
|
|
||||||
|
if (isFirstTimeUser && !isOnboardingComplete) {
|
||||||
|
history.push(ROUTES.ONBOARDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserLoginIfTokenPresent = async (
|
const handleUserLoginIfTokenPresent = async (
|
||||||
key: keyof typeof ROUTES,
|
key: keyof typeof ROUTES,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@@ -102,6 +181,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
response.payload.refreshJwt,
|
response.payload.refreshJwt,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
handleRedirectForOrgOnboarding(key);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userResponse &&
|
userResponse &&
|
||||||
route &&
|
route &&
|
||||||
@@ -129,7 +210,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
) {
|
) {
|
||||||
handleUserLoginIfTokenPresent(key);
|
handleUserLoginIfTokenPresent(key);
|
||||||
} else {
|
} else {
|
||||||
// user does have localstorage values
|
handleRedirectForOrgOnboarding(key);
|
||||||
|
|
||||||
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
|
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
|
||||||
}
|
}
|
||||||
@@ -160,6 +241,45 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [isFetchingLicensesData]);
|
}, [isFetchingLicensesData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||||
|
setOrgData(org[0]);
|
||||||
|
}
|
||||||
|
}, [org]);
|
||||||
|
|
||||||
|
const handleRouting = (): void => {
|
||||||
|
const showOrgOnboarding = shouldShowOnboarding();
|
||||||
|
|
||||||
|
if (showOrgOnboarding && !isOnboardingComplete) {
|
||||||
|
history.push(ROUTES.ONBOARDING);
|
||||||
|
} else {
|
||||||
|
history.push(ROUTES.APPLICATION);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { isPrivate } = currentRoute || {
|
||||||
|
isPrivate: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoggedInState && role && role !== 'ADMIN') {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPrivate) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isEmpty(user) &&
|
||||||
|
!isFetchingOrgPreferences &&
|
||||||
|
!isEmpty(orgUsers?.payload) &&
|
||||||
|
!isNull(orgPreferences)
|
||||||
|
) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [currentRoute, user, role, orgUsers, orgPreferences]);
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
@@ -181,9 +301,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
handlePrivateRoutes(key);
|
handlePrivateRoutes(key);
|
||||||
} else {
|
} else {
|
||||||
// no need to fetch the user and make user fetching false
|
// no need to fetch the user and make user fetching false
|
||||||
|
|
||||||
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
||||||
history.push(ROUTES.APPLICATION);
|
handleRouting();
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UPDATE_USER_IS_FETCH,
|
type: UPDATE_USER_IS_FETCH,
|
||||||
@@ -195,7 +314,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
} else if (pathname === ROUTES.HOME_PAGE) {
|
} else if (pathname === ROUTES.HOME_PAGE) {
|
||||||
// routing to application page over root page
|
// routing to application page over root page
|
||||||
if (isLoggedInState) {
|
if (isLoggedInState) {
|
||||||
history.push(ROUTES.APPLICATION);
|
handleRouting();
|
||||||
} else {
|
} else {
|
||||||
navigateToLoginIfNotLoggedIn();
|
navigateToLoginIfNotLoggedIn();
|
||||||
}
|
}
|
||||||
@@ -208,13 +327,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [dispatch, isLoggedInState, currentRoute, licensesData]);
|
}, [
|
||||||
|
dispatch,
|
||||||
|
isLoggedInState,
|
||||||
|
currentRoute,
|
||||||
|
licensesData,
|
||||||
|
orgUsers,
|
||||||
|
orgPreferences,
|
||||||
|
]);
|
||||||
|
|
||||||
if (isUserFetchingError) {
|
if (isUserFetchingError) {
|
||||||
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserFetching) {
|
if (isUserFetching || isLoading) {
|
||||||
return <Spinner tip="Loading..." />;
|
return <Spinner tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ConfigProvider } from 'antd';
|
|||||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||||
import NotFound from 'components/NotFound';
|
import NotFound from 'components/NotFound';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
@@ -24,13 +25,20 @@ import AlertRuleProvider from 'providers/Alert';
|
|||||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useEffect, useState } from 'react';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { Route, Router, Switch } from 'react-router-dom';
|
import { Route, Router, Switch } from 'react-router-dom';
|
||||||
|
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
|
import {
|
||||||
|
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
|
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||||
|
UPDATE_ORG_PREFERENCES,
|
||||||
|
} from 'types/actions/app';
|
||||||
import AppReducer, { User } from 'types/reducer/app';
|
import AppReducer, { User } from 'types/reducer/app';
|
||||||
|
import { USER_ROLES } from 'types/roles';
|
||||||
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
|
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
|
||||||
|
|
||||||
import PrivateRoute from './Private';
|
import PrivateRoute from './Private';
|
||||||
@@ -65,6 +73,41 @@ function App(): JSX.Element {
|
|||||||
const isPremiumSupportEnabled =
|
const isPremiumSupportEnabled =
|
||||||
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||||
|
|
||||||
|
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
||||||
|
queryFn: () => getAllOrgPreferences(),
|
||||||
|
queryKey: ['getOrgPreferences'],
|
||||||
|
enabled: isLoggedInState && role === USER_ROLES.ADMIN,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (orgPreferences && !isLoadingOrgPreferences) {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||||
|
payload: {
|
||||||
|
isFetchingOrgPreferences: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_ORG_PREFERENCES,
|
||||||
|
payload: {
|
||||||
|
orgPreferences: orgPreferences.payload?.data || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedInState && role !== USER_ROLES.ADMIN) {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||||
|
payload: {
|
||||||
|
isFetchingOrgPreferences: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isLoggedInState, role, dispatch]);
|
||||||
|
|
||||||
const featureResponse = useGetFeatureFlag((allFlags) => {
|
const featureResponse = useGetFeatureFlag((allFlags) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
@@ -182,6 +225,16 @@ function App(): JSX.Element {
|
|||||||
}, [isLoggedInState, isOnBasicPlan, user]);
|
}, [isLoggedInState, isOnBasicPlan, user]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (pathname === ROUTES.ONBOARDING) {
|
||||||
|
window.Intercom('update', {
|
||||||
|
hide_default_launcher: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.Intercom('update', {
|
||||||
|
hide_default_launcher: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
trackPageView(pathname);
|
trackPageView(pathname);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
@@ -204,6 +257,7 @@ function App(): JSX.Element {
|
|||||||
user,
|
user,
|
||||||
licenseData,
|
licenseData,
|
||||||
isPremiumSupportEnabled,
|
isPremiumSupportEnabled,
|
||||||
|
pathname,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -239,36 +293,38 @@ function App(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<ConfigProvider theme={themeConfig}>
|
<ConfigProvider theme={themeConfig}>
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<NotificationProvider>
|
<CompatRouter>
|
||||||
<PrivateRoute>
|
<NotificationProvider>
|
||||||
<ResourceProvider>
|
<PrivateRoute>
|
||||||
<QueryBuilderProvider>
|
<ResourceProvider>
|
||||||
<DashboardProvider>
|
<QueryBuilderProvider>
|
||||||
<KeyboardHotkeysProvider>
|
<DashboardProvider>
|
||||||
<AlertRuleProvider>
|
<KeyboardHotkeysProvider>
|
||||||
<AppLayout>
|
<AlertRuleProvider>
|
||||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
<AppLayout>
|
||||||
<Switch>
|
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||||
{routes.map(({ path, component, exact }) => (
|
<Switch>
|
||||||
<Route
|
{routes.map(({ path, component, exact }) => (
|
||||||
key={`${path}`}
|
<Route
|
||||||
exact={exact}
|
key={`${path}`}
|
||||||
path={path}
|
exact={exact}
|
||||||
component={component}
|
path={path}
|
||||||
/>
|
component={component}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</AlertRuleProvider>
|
</AlertRuleProvider>
|
||||||
</KeyboardHotkeysProvider>
|
</KeyboardHotkeysProvider>
|
||||||
</DashboardProvider>
|
</DashboardProvider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
</ResourceProvider>
|
</ResourceProvider>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
|
</CompatRouter>
|
||||||
</Router>
|
</Router>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ export const Onboarding = Loadable(
|
|||||||
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
|
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const OrgOnboarding = Loadable(
|
||||||
|
() => import(/* webpackChunkName: "OrgOnboarding" */ 'pages/OrgOnboarding'),
|
||||||
|
);
|
||||||
|
|
||||||
export const DashboardPage = Loadable(
|
export const DashboardPage = Loadable(
|
||||||
() =>
|
() =>
|
||||||
import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardsListPage'),
|
import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardsListPage'),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
OldLogsExplorer,
|
OldLogsExplorer,
|
||||||
Onboarding,
|
Onboarding,
|
||||||
OrganizationSettings,
|
OrganizationSettings,
|
||||||
|
OrgOnboarding,
|
||||||
PasswordReset,
|
PasswordReset,
|
||||||
PipelinePage,
|
PipelinePage,
|
||||||
ServiceMapPage,
|
ServiceMapPage,
|
||||||
@@ -68,6 +69,13 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'GET_STARTED',
|
key: 'GET_STARTED',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.ONBOARDING,
|
||||||
|
exact: false,
|
||||||
|
component: OrgOnboarding,
|
||||||
|
isPrivate: true,
|
||||||
|
key: 'ONBOARDING',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: LogsIndexToFields,
|
component: LogsIndexToFields,
|
||||||
path: ROUTES.LOGS_INDEX_FIELDS,
|
path: ROUTES.LOGS_INDEX_FIELDS,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const apiV2 = '/api/v2/';
|
|||||||
export const apiV3 = '/api/v3/';
|
export const apiV3 = '/api/v3/';
|
||||||
export const apiV4 = '/api/v4/';
|
export const apiV4 = '/api/v4/';
|
||||||
export const gatewayApiV1 = '/api/gateway/v1/';
|
export const gatewayApiV1 = '/api/gateway/v1/';
|
||||||
|
export const gatewayApiV2 = '/api/gateway/v2/';
|
||||||
export const apiAlertManager = '/api/alertmanager/';
|
export const apiAlertManager = '/api/alertmanager/';
|
||||||
|
|
||||||
export default apiV1;
|
export default apiV1;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import apiV1, {
|
|||||||
apiV3,
|
apiV3,
|
||||||
apiV4,
|
apiV4,
|
||||||
gatewayApiV1,
|
gatewayApiV1,
|
||||||
|
gatewayApiV2,
|
||||||
} from './apiV1';
|
} from './apiV1';
|
||||||
import { Logout } from './utils';
|
import { Logout } from './utils';
|
||||||
|
|
||||||
@@ -169,6 +170,19 @@ GatewayApiV1Instance.interceptors.response.use(
|
|||||||
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// gateway Api V2
|
||||||
|
export const GatewayApiV2Instance = axios.create({
|
||||||
|
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV2}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
GatewayApiV2Instance.interceptors.response.use(
|
||||||
|
interceptorsResponse,
|
||||||
|
interceptorRejected,
|
||||||
|
);
|
||||||
|
|
||||||
|
GatewayApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||||
|
//
|
||||||
|
|
||||||
AxiosAlertManagerInstance.interceptors.response.use(
|
AxiosAlertManagerInstance.interceptors.response.use(
|
||||||
interceptorsResponse,
|
interceptorsResponse,
|
||||||
interceptorRejected,
|
interceptorRejected,
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { ApiBaseInstance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
|
||||||
|
export interface OnboardingStatusResponse {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
attribute?: string;
|
||||||
|
error_message?: string;
|
||||||
|
status?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOnboardingStatus = async (props: {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
endpointService?: string;
|
||||||
|
}): Promise<SuccessResponse<OnboardingStatusResponse> | ErrorResponse> => {
|
||||||
|
const { endpointService, ...rest } = props;
|
||||||
|
try {
|
||||||
|
const response = await ApiBaseInstance.post(
|
||||||
|
`/messaging-queues/kafka/onboarding/${endpointService || 'consumers'}`,
|
||||||
|
rest,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getOnboardingStatus;
|
||||||
20
frontend/src/api/onboarding/updateProfile.ts
Normal file
20
frontend/src/api/onboarding/updateProfile.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { GatewayApiV2Instance } from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { UpdateProfileProps } from 'types/api/onboarding/types';
|
||||||
|
|
||||||
|
const updateProfile = async (
|
||||||
|
props: UpdateProfileProps,
|
||||||
|
): Promise<SuccessResponse<UpdateProfileProps> | ErrorResponse> => {
|
||||||
|
const response = await GatewayApiV2Instance.put('/profiles/me', {
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateProfile;
|
||||||
18
frontend/src/api/preferences/getAllOrgPreferences.ts
Normal file
18
frontend/src/api/preferences/getAllOrgPreferences.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { GetAllOrgPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const getAllOrgPreferences = async (): Promise<
|
||||||
|
SuccessResponse<GetAllOrgPreferencesResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
const response = await axios.get(`/org/preferences`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAllOrgPreferences;
|
||||||
18
frontend/src/api/preferences/getAllUserPreference.ts
Normal file
18
frontend/src/api/preferences/getAllUserPreference.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { GetAllUserPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const getAllUserPreferences = async (): Promise<
|
||||||
|
SuccessResponse<GetAllUserPreferencesResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
const response = await axios.get(`/user/preferences`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAllUserPreferences;
|
||||||
20
frontend/src/api/preferences/getOrgPreference.ts
Normal file
20
frontend/src/api/preferences/getOrgPreference.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { GetOrgPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const getOrgPreference = async ({
|
||||||
|
preferenceID,
|
||||||
|
}: {
|
||||||
|
preferenceID: string;
|
||||||
|
}): Promise<SuccessResponse<GetOrgPreferenceResponseProps> | ErrorResponse> => {
|
||||||
|
const response = await axios.get(`/org/preferences/${preferenceID}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getOrgPreference;
|
||||||
22
frontend/src/api/preferences/getUserPreference.ts
Normal file
22
frontend/src/api/preferences/getUserPreference.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { GetUserPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const getUserPreference = async ({
|
||||||
|
preferenceID,
|
||||||
|
}: {
|
||||||
|
preferenceID: string;
|
||||||
|
}): Promise<
|
||||||
|
SuccessResponse<GetUserPreferenceResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
const response = await axios.get(`/user/preferences/${preferenceID}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUserPreference;
|
||||||
28
frontend/src/api/preferences/updateOrgPreference.ts
Normal file
28
frontend/src/api/preferences/updateOrgPreference.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
UpdateOrgPreferenceProps,
|
||||||
|
UpdateOrgPreferenceResponseProps,
|
||||||
|
} from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const updateOrgPreference = async (
|
||||||
|
preferencePayload: UpdateOrgPreferenceProps,
|
||||||
|
): Promise<
|
||||||
|
SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
const response = await axios.put(
|
||||||
|
`/org/preferences/${preferencePayload.preferenceID}`,
|
||||||
|
{
|
||||||
|
preference_value: preferencePayload.value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateOrgPreference;
|
||||||
25
frontend/src/api/preferences/updateUserPreference.ts
Normal file
25
frontend/src/api/preferences/updateUserPreference.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
UpdateUserPreferenceProps,
|
||||||
|
UpdateUserPreferenceResponseProps,
|
||||||
|
} from 'types/api/preferences/userOrgPreferences';
|
||||||
|
|
||||||
|
const updateUserPreference = async (
|
||||||
|
preferencePayload: UpdateUserPreferenceProps,
|
||||||
|
): Promise<
|
||||||
|
SuccessResponse<UpdateUserPreferenceResponseProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
const response = await axios.put(`/user/preferences`, {
|
||||||
|
preference_value: preferencePayload.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateUserPreference;
|
||||||
18
frontend/src/api/user/inviteUsers.ts
Normal file
18
frontend/src/api/user/inviteUsers.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers';
|
||||||
|
|
||||||
|
const inviteUsers = async (
|
||||||
|
users: UsersProps,
|
||||||
|
): Promise<SuccessResponse<InviteUsersResponse>> => {
|
||||||
|
const response = await axios.post(`/invite/bulk`, users);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default inviteUsers;
|
||||||
@@ -1,46 +1,3 @@
|
|||||||
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 onboardingHelpMessage = (
|
export const onboardingHelpMessage = (
|
||||||
dataSourceName: string,
|
dataSourceName: string,
|
||||||
moduleId: string,
|
moduleId: string,
|
||||||
@@ -55,35 +12,3 @@ Module: ${moduleId}
|
|||||||
|
|
||||||
Thanks
|
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`;
|
|
||||||
|
|
||||||
export const integrationsListMessage = `Hi Team,
|
|
||||||
|
|
||||||
I need help with Integrations.
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|
||||||
export const integrationDetailMessage = (
|
|
||||||
selectedIntegration: string,
|
|
||||||
): string => `
|
|
||||||
Hi Team,
|
|
||||||
|
|
||||||
I need help in configuring this integration.
|
|
||||||
|
|
||||||
Integration Id: ${selectedIntegration}
|
|
||||||
|
|
||||||
Thanks`;
|
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ function LogDetail({
|
|||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
width="60%"
|
width="60%"
|
||||||
|
maskStyle={{ background: 'none' }}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||||
|
|||||||
@@ -195,21 +195,20 @@ function ListLogView({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container
|
<Container
|
||||||
$isActiveLog={isHighlighted}
|
$isActiveLog={
|
||||||
|
isHighlighted ||
|
||||||
|
activeLog?.id === logData.id ||
|
||||||
|
activeContextLog?.id === logData.id
|
||||||
|
}
|
||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
|
$logType={logType}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
onClick={handleDetailedView}
|
onClick={handleDetailedView}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
<div className="log-line">
|
<div className="log-line">
|
||||||
<LogStateIndicator
|
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||||
type={logType}
|
|
||||||
isActive={
|
|
||||||
activeLog?.id === logData.id || activeContextLog?.id === logData.id
|
|
||||||
}
|
|
||||||
fontSize={fontSize}
|
|
||||||
/>
|
|
||||||
<div>
|
<div>
|
||||||
<LogContainer fontSize={fontSize}>
|
<LogContainer fontSize={fontSize}>
|
||||||
<LogGeneralField
|
<LogGeneralField
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Typography } from 'antd';
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { getActiveLogBackground } from 'utils/logs';
|
||||||
|
|
||||||
interface LogTextProps {
|
interface LogTextProps {
|
||||||
linesPerRow?: number;
|
linesPerRow?: number;
|
||||||
@@ -15,6 +15,7 @@ interface LogContainerProps {
|
|||||||
export const Container = styled(Card)<{
|
export const Container = styled(Card)<{
|
||||||
$isActiveLog: boolean;
|
$isActiveLog: boolean;
|
||||||
$isDarkMode: boolean;
|
$isDarkMode: boolean;
|
||||||
|
$logType: string;
|
||||||
fontSize: FontSize;
|
fontSize: FontSize;
|
||||||
}>`
|
}>`
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -41,13 +42,8 @@ export const Container = styled(Card)<{
|
|||||||
? `padding:0.3rem 0.6rem;`
|
? `padding:0.3rem 0.6rem;`
|
||||||
: ``}
|
: ``}
|
||||||
|
|
||||||
${({ $isActiveLog, $isDarkMode }): string =>
|
${({ $isActiveLog, $isDarkMode, $logType }): string =>
|
||||||
$isActiveLog
|
getActiveLogBackground($isActiveLog, $isDarkMode, $logType)}
|
||||||
? `background-color: ${
|
|
||||||
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
|
||||||
} !important`
|
|
||||||
: ''}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Text = styled(Typography.Text)`
|
export const Text = styled(Typography.Text)`
|
||||||
|
|||||||
@@ -41,10 +41,4 @@
|
|||||||
background-color: var(--bg-sakura-500);
|
background-color: var(--bg-sakura-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.isActive {
|
|
||||||
.line {
|
|
||||||
background-color: var(--bg-robin-400, #7190f9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ describe('LogStateIndicator', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly when isActive is true', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<LogStateIndicator type="INFO" isActive fontSize={FontSize.MEDIUM} />,
|
|
||||||
);
|
|
||||||
const indicator = container.firstChild as HTMLElement;
|
|
||||||
expect(indicator.classList.contains('isActive')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders correctly with different types', () => {
|
it('renders correctly with different types', () => {
|
||||||
const { container: containerInfo } = render(
|
const { container: containerInfo } = render(
|
||||||
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
|
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
|
||||||
|
|||||||
@@ -44,22 +44,16 @@ export const LogType = {
|
|||||||
|
|
||||||
function LogStateIndicator({
|
function LogStateIndicator({
|
||||||
type,
|
type,
|
||||||
isActive,
|
|
||||||
fontSize,
|
fontSize,
|
||||||
}: {
|
}: {
|
||||||
type: string;
|
type: string;
|
||||||
fontSize: FontSize;
|
fontSize: FontSize;
|
||||||
isActive?: boolean;
|
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={cx('log-state-indicator', isActive ? 'isActive' : '')}>
|
<div className="log-state-indicator">
|
||||||
<div className={cx('line', type, fontSize)}> </div>
|
<div className={cx('line', type, fontSize)}> </div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogStateIndicator.defaultProps = {
|
|
||||||
isActive: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LogStateIndicator;
|
export default LogStateIndicator;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ describe('getLogIndicatorType', () => {
|
|||||||
body: 'Sample log Message',
|
body: 'Sample log Message',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
|
scope_string: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
attributesFloat: {},
|
attributesFloat: {},
|
||||||
@@ -40,6 +41,7 @@ describe('getLogIndicatorType', () => {
|
|||||||
body: 'Sample log Message',
|
body: 'Sample log Message',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
|
scope_string: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
attributesFloat: {},
|
attributesFloat: {},
|
||||||
@@ -62,6 +64,7 @@ describe('getLogIndicatorType', () => {
|
|||||||
body: 'Sample log Message',
|
body: 'Sample log Message',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
|
scope_string: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
attributesFloat: {},
|
attributesFloat: {},
|
||||||
@@ -83,6 +86,7 @@ describe('getLogIndicatorType', () => {
|
|||||||
body: 'Sample log',
|
body: 'Sample log',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
|
scope_string: {},
|
||||||
attributes_string: {
|
attributes_string: {
|
||||||
log_level: 'INFO' as never,
|
log_level: 'INFO' as never,
|
||||||
},
|
},
|
||||||
@@ -112,6 +116,7 @@ describe('getLogIndicatorTypeForTable', () => {
|
|||||||
attributesString: {},
|
attributesString: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
|
scope_string: {},
|
||||||
attributesFloat: {},
|
attributesFloat: {},
|
||||||
severity_text: 'WARN',
|
severity_text: 'WARN',
|
||||||
};
|
};
|
||||||
@@ -130,6 +135,7 @@ describe('getLogIndicatorTypeForTable', () => {
|
|||||||
severity_number: 0,
|
severity_number: 0,
|
||||||
body: 'Sample log message',
|
body: 'Sample log message',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
|
scope_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
@@ -166,6 +172,7 @@ describe('logIndicatorBySeverityNumber', () => {
|
|||||||
body: 'Sample log Message',
|
body: 'Sample log Message',
|
||||||
resources_string: {},
|
resources_string: {},
|
||||||
attributesString: {},
|
attributesString: {},
|
||||||
|
scope_string: {},
|
||||||
attributes_string: {},
|
attributes_string: {},
|
||||||
attributesInt: {},
|
attributesInt: {},
|
||||||
attributesFloat: {},
|
attributesFloat: {},
|
||||||
|
|||||||
@@ -162,20 +162,15 @@ function RawLogView({
|
|||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
$isReadOnly={isReadOnly}
|
$isReadOnly={isReadOnly}
|
||||||
$isHightlightedLog={isHighlighted}
|
$isHightlightedLog={isHighlighted}
|
||||||
$isActiveLog={isActiveLog}
|
$isActiveLog={
|
||||||
|
activeLog?.id === data.id || activeContextLog?.id === data.id || isActiveLog
|
||||||
|
}
|
||||||
|
$logType={logType}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
<LogStateIndicator
|
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||||
type={logType}
|
|
||||||
isActive={
|
|
||||||
activeLog?.id === data.id ||
|
|
||||||
activeContextLog?.id === data.id ||
|
|
||||||
isActiveLog
|
|
||||||
}
|
|
||||||
fontSize={fontSize}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RawLogContent
|
<RawLogContent
|
||||||
$isReadOnly={isReadOnly}
|
$isReadOnly={isReadOnly}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const RawLogViewContainer = styled(Row)<{
|
|||||||
$isReadOnly?: boolean;
|
$isReadOnly?: boolean;
|
||||||
$isActiveLog?: boolean;
|
$isActiveLog?: boolean;
|
||||||
$isHightlightedLog: boolean;
|
$isHightlightedLog: boolean;
|
||||||
|
$logType: string;
|
||||||
fontSize: FontSize;
|
fontSize: FontSize;
|
||||||
}>`
|
}>`
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -34,11 +35,12 @@ export const RawLogViewContainer = styled(Row)<{
|
|||||||
: `margin: 2px 0;`}
|
: `margin: 2px 0;`}
|
||||||
}
|
}
|
||||||
|
|
||||||
${({ $isActiveLog }): string => getActiveLogBackground($isActiveLog)}
|
${({ $isActiveLog, $logType }): string =>
|
||||||
|
getActiveLogBackground($isActiveLog, true, $logType)}
|
||||||
|
|
||||||
${({ $isReadOnly, $isActiveLog, $isDarkMode }): string =>
|
${({ $isReadOnly, $isActiveLog, $isDarkMode, $logType }): string =>
|
||||||
$isActiveLog
|
$isActiveLog
|
||||||
? getActiveLogBackground($isActiveLog, $isDarkMode)
|
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
|
||||||
: getDefaultLogBackground($isReadOnly, $isDarkMode)}
|
: getDefaultLogBackground($isReadOnly, $isDarkMode)}
|
||||||
|
|
||||||
${({ $isHightlightedLog, $isDarkMode }): string =>
|
${({ $isHightlightedLog, $isDarkMode }): string =>
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
linesPerRow,
|
linesPerRow,
|
||||||
fontSize,
|
fontSize,
|
||||||
appendTo = 'center',
|
appendTo = 'center',
|
||||||
activeContextLog,
|
|
||||||
activeLog,
|
|
||||||
isListViewPanel,
|
isListViewPanel,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -90,9 +88,6 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
<div className="table-timestamp">
|
<div className="table-timestamp">
|
||||||
<LogStateIndicator
|
<LogStateIndicator
|
||||||
type={getLogIndicatorTypeForTable(item)}
|
type={getLogIndicatorTypeForTable(item)}
|
||||||
isActive={
|
|
||||||
activeLog?.id === item.id || activeContextLog?.id === item.id
|
|
||||||
}
|
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||||
@@ -130,16 +125,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
},
|
},
|
||||||
...(appendTo === 'end' ? fieldColumns : []),
|
...(appendTo === 'end' ? fieldColumns : []),
|
||||||
];
|
];
|
||||||
}, [
|
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
|
||||||
fields,
|
|
||||||
isListViewPanel,
|
|
||||||
appendTo,
|
|
||||||
isDarkMode,
|
|
||||||
linesPerRow,
|
|
||||||
activeLog?.id,
|
|
||||||
activeContextLog?.id,
|
|
||||||
fontSize,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { columns, dataSource: flattenLogData };
|
return { columns, dataSource: flattenLogData };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ function DynamicColumnTable({
|
|||||||
className="dynamicColumnTable-button filter-btn"
|
className="dynamicColumnTable-button filter-btn"
|
||||||
size="middle"
|
size="middle"
|
||||||
icon={<SlidersHorizontal size={14} />}
|
icon={<SlidersHorizontal size={14} />}
|
||||||
|
data-testid="additional-filters-button"
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,4 +37,8 @@ export enum QueryParams {
|
|||||||
partition = 'partition',
|
partition = 'partition',
|
||||||
selectedTimelineQuery = 'selectedTimelineQuery',
|
selectedTimelineQuery = 'selectedTimelineQuery',
|
||||||
ruleType = 'ruleType',
|
ruleType = 'ruleType',
|
||||||
|
configDetail = 'configDetail',
|
||||||
|
getStartedSource = 'getStartedSource',
|
||||||
|
getStartedSourceService = 'getStartedSourceService',
|
||||||
|
mqServiceView = 'mqServiceView',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ import { QueryFunctionsTypes } from 'types/common/queryBuilder';
|
|||||||
import { SelectOption } from 'types/common/select';
|
import { SelectOption } from 'types/common/select';
|
||||||
|
|
||||||
export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||||
{
|
|
||||||
value: QueryFunctionsTypes.ANOMALY,
|
|
||||||
label: 'Anomaly',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: QueryFunctionsTypes.CUTOFF_MIN,
|
value: QueryFunctionsTypes.CUTOFF_MIN,
|
||||||
label: 'Cut Off Min',
|
label: 'Cut Off Min',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const ROUTES = {
|
|||||||
TRACE_DETAIL: '/trace/:id',
|
TRACE_DETAIL: '/trace/:id',
|
||||||
TRACES_EXPLORER: '/traces-explorer',
|
TRACES_EXPLORER: '/traces-explorer',
|
||||||
GET_STARTED: '/get-started',
|
GET_STARTED: '/get-started',
|
||||||
|
ONBOARDING: '/onboarding',
|
||||||
GET_STARTED_APPLICATION_MONITORING: '/get-started/application-monitoring',
|
GET_STARTED_APPLICATION_MONITORING: '/get-started/application-monitoring',
|
||||||
GET_STARTED_LOGS_MANAGEMENT: '/get-started/logs-management',
|
GET_STARTED_LOGS_MANAGEMENT: '/get-started/logs-management',
|
||||||
GET_STARTED_INFRASTRUCTURE_MONITORING:
|
GET_STARTED_INFRASTRUCTURE_MONITORING:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 240px;
|
width: 240px;
|
||||||
padding: 8px;
|
padding: 0px 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.anomaly-alert-evaluation-view-series-list {
|
.anomaly-alert-evaluation-view-series-list {
|
||||||
@@ -41,6 +41,10 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
.anomaly-alert-evaluation-view-series-list-search {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.anomaly-alert-evaluation-view-series-list-title {
|
.anomaly-alert-evaluation-view-series-list-title {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
font-size: 13px !important;
|
font-size: 13px !important;
|
||||||
@@ -59,8 +63,32 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
.anomaly-alert-evaluation-view-series-list-item-color {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0.1rem;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(136, 136, 136);
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,3 +118,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip {
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
color: #ddd;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
max-height: 500px;
|
||||||
|
width: 280px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: none; /* Hide tooltip by default */
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0.3rem;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-corner {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(136, 136, 136);
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip-title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip-series {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 0px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip-series-name {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip-band {
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uplot-tooltip-marker {
|
||||||
|
display: inline-block;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import 'uplot/dist/uPlot.min.css';
|
|||||||
import './AnomalyAlertEvaluationView.styles.scss';
|
import './AnomalyAlertEvaluationView.styles.scss';
|
||||||
|
|
||||||
import { Checkbox, Typography } from 'antd';
|
import { Checkbox, Typography } from 'antd';
|
||||||
|
import Search from 'antd/es/input/Search';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
import { useResizeObserver } from 'hooks/useDimensions';
|
||||||
import getAxes from 'lib/uPlotLib/utils/getAxes';
|
import getAxes from 'lib/uPlotLib/utils/getAxes';
|
||||||
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
|
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||||
@@ -11,6 +13,8 @@ import { LineChart } from 'lucide-react';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import uPlot from 'uplot';
|
import uPlot from 'uplot';
|
||||||
|
|
||||||
|
import tooltipPlugin from './tooltipPlugin';
|
||||||
|
|
||||||
function UplotChart({
|
function UplotChart({
|
||||||
data,
|
data,
|
||||||
options,
|
options,
|
||||||
@@ -63,13 +67,20 @@ function AnomalyAlertEvaluationView({
|
|||||||
const [seriesData, setSeriesData] = useState<any>({});
|
const [seriesData, setSeriesData] = useState<any>({});
|
||||||
const [selectedSeries, setSelectedSeries] = useState<string | null>(null);
|
const [selectedSeries, setSelectedSeries] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [filteredSeriesKeys, setFilteredSeriesKeys] = useState<string[]>([]);
|
||||||
|
const [allSeries, setAllSeries] = useState<string[]>([]);
|
||||||
|
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
const graphRef = useRef<HTMLDivElement>(null);
|
||||||
const dimensions = useResizeObserver(graphRef);
|
const dimensions = useResizeObserver(graphRef);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const chartData = getUplotChartDataForAnomalyDetection(data);
|
const chartData = getUplotChartDataForAnomalyDetection(data, isDarkMode);
|
||||||
setSeriesData(chartData);
|
setSeriesData(chartData);
|
||||||
}, [data]);
|
|
||||||
|
setAllSeries(Object.keys(chartData));
|
||||||
|
|
||||||
|
setFilteredSeriesKeys(Object.keys(chartData));
|
||||||
|
}, [data, isDarkMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const seriesKeys = Object.keys(seriesData);
|
const seriesKeys = Object.keys(seriesData);
|
||||||
@@ -130,8 +141,6 @@ function AnomalyAlertEvaluationView({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const allSeries = Object.keys(seriesData);
|
|
||||||
|
|
||||||
const initialData = allSeries.length
|
const initialData = allSeries.length
|
||||||
? [
|
? [
|
||||||
seriesData[allSeries[0]].data[0], // Shared X-axis
|
seriesData[allSeries[0]].data[0], // Shared X-axis
|
||||||
@@ -142,10 +151,38 @@ function AnomalyAlertEvaluationView({
|
|||||||
const options = {
|
const options = {
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height - 36,
|
height: dimensions.height - 36,
|
||||||
plugins: [bandsPlugin],
|
plugins: [bandsPlugin, tooltipPlugin(isDarkMode)],
|
||||||
focus: {
|
focus: {
|
||||||
alpha: 0.3,
|
alpha: 0.3,
|
||||||
},
|
},
|
||||||
|
legend: {
|
||||||
|
show: true,
|
||||||
|
live: false,
|
||||||
|
isolate: true,
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
lock: false,
|
||||||
|
focus: {
|
||||||
|
prox: 1e6,
|
||||||
|
bias: 1,
|
||||||
|
},
|
||||||
|
points: {
|
||||||
|
size: (
|
||||||
|
u: { series: { [x: string]: { points: { size: number } } } },
|
||||||
|
seriesIdx: string | number,
|
||||||
|
): number => u.series[seriesIdx].points.size * 3,
|
||||||
|
width: (u: any, seriesIdx: any, size: number): number => size / 4,
|
||||||
|
stroke: (
|
||||||
|
u: {
|
||||||
|
series: {
|
||||||
|
[x: string]: { points: { stroke: (arg0: any, arg1: any) => any } };
|
||||||
|
};
|
||||||
|
},
|
||||||
|
seriesIdx: string | number,
|
||||||
|
): string => `${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`,
|
||||||
|
fill: (): string => '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
label: 'Time',
|
label: 'Time',
|
||||||
@@ -158,6 +195,7 @@ function AnomalyAlertEvaluationView({
|
|||||||
width: 2,
|
width: 2,
|
||||||
show: true,
|
show: true,
|
||||||
paths: _spline,
|
paths: _spline,
|
||||||
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Predicted Value`,
|
label: `Predicted Value`,
|
||||||
@@ -166,18 +204,29 @@ function AnomalyAlertEvaluationView({
|
|||||||
dash: [2, 2],
|
dash: [2, 2],
|
||||||
show: true,
|
show: true,
|
||||||
paths: _spline,
|
paths: _spline,
|
||||||
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Upper Band`,
|
label: `Upper Band`,
|
||||||
stroke: 'transparent',
|
stroke: 'transparent',
|
||||||
show: false,
|
show: true,
|
||||||
paths: _spline,
|
paths: _spline,
|
||||||
|
spanGaps: true,
|
||||||
|
points: {
|
||||||
|
show: false,
|
||||||
|
size: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Lower Band`,
|
label: `Lower Band`,
|
||||||
stroke: 'transparent',
|
stroke: 'transparent',
|
||||||
show: false,
|
show: true,
|
||||||
paths: _spline,
|
paths: _spline,
|
||||||
|
spanGaps: true,
|
||||||
|
points: {
|
||||||
|
show: false,
|
||||||
|
size: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: allSeries.map((seriesKey) => ({
|
: allSeries.map((seriesKey) => ({
|
||||||
@@ -186,11 +235,13 @@ function AnomalyAlertEvaluationView({
|
|||||||
width: 2,
|
width: 2,
|
||||||
show: true,
|
show: true,
|
||||||
paths: _spline,
|
paths: _spline,
|
||||||
|
spanGaps: true,
|
||||||
}))),
|
}))),
|
||||||
],
|
],
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
time: true,
|
time: true,
|
||||||
|
spanGaps: true,
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
...getYAxisScaleForAnomalyDetection({
|
...getYAxisScaleForAnomalyDetection({
|
||||||
@@ -204,12 +255,30 @@ function AnomalyAlertEvaluationView({
|
|||||||
grid: {
|
grid: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
legend: {
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
axes: getAxes(isDarkMode, yAxisUnit),
|
axes: getAxes(isDarkMode, yAxisUnit),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearch = (searchText: string): void => {
|
||||||
|
if (!searchText || searchText.length === 0) {
|
||||||
|
setFilteredSeriesKeys(allSeries);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredSeries = allSeries.filter((series) =>
|
||||||
|
series.toLowerCase().includes(searchText.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
setFilteredSeriesKeys(filteredSeries);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchValueChange = useDebouncedFn((event): void => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const value = event?.target?.value || '';
|
||||||
|
|
||||||
|
handleSearch(value);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="anomaly-alert-evaluation-view">
|
<div className="anomaly-alert-evaluation-view">
|
||||||
<div
|
<div
|
||||||
@@ -237,37 +306,51 @@ function AnomalyAlertEvaluationView({
|
|||||||
<div className="anomaly-alert-evaluation-view-series-selection">
|
<div className="anomaly-alert-evaluation-view-series-selection">
|
||||||
{allSeries.length > 1 && (
|
{allSeries.length > 1 && (
|
||||||
<div className="anomaly-alert-evaluation-view-series-list">
|
<div className="anomaly-alert-evaluation-view-series-list">
|
||||||
<Typography.Title
|
<Search
|
||||||
level={5}
|
className="anomaly-alert-evaluation-view-series-list-search"
|
||||||
className="anomaly-alert-evaluation-view-series-list-title"
|
placeholder="Search a series"
|
||||||
>
|
allowClear
|
||||||
Select a series
|
onChange={handleSearchValueChange}
|
||||||
</Typography.Title>
|
/>
|
||||||
<div className="anomaly-alert-evaluation-view-series-list-items">
|
|
||||||
<Checkbox
|
|
||||||
className="anomaly-alert-evaluation-view-series-list-item"
|
|
||||||
type="checkbox"
|
|
||||||
name="series"
|
|
||||||
value="all"
|
|
||||||
checked={selectedSeries === null}
|
|
||||||
onChange={(): void => handleSeriesChange(null)}
|
|
||||||
>
|
|
||||||
Show All
|
|
||||||
</Checkbox>
|
|
||||||
|
|
||||||
{allSeries.map((seriesKey) => (
|
<div className="anomaly-alert-evaluation-view-series-list-items">
|
||||||
|
{filteredSeriesKeys.length > 0 && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="anomaly-alert-evaluation-view-series-list-item"
|
className="anomaly-alert-evaluation-view-series-list-item"
|
||||||
key={seriesKey}
|
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="series"
|
name="series"
|
||||||
value={seriesKey}
|
value="all"
|
||||||
checked={selectedSeries === seriesKey}
|
checked={selectedSeries === null}
|
||||||
onChange={(): void => handleSeriesChange(seriesKey)}
|
onChange={(): void => handleSeriesChange(null)}
|
||||||
>
|
>
|
||||||
{seriesKey}
|
Show All
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filteredSeriesKeys.map((seriesKey) => (
|
||||||
|
<div key={seriesKey}>
|
||||||
|
<Checkbox
|
||||||
|
className="anomaly-alert-evaluation-view-series-list-item"
|
||||||
|
key={seriesKey}
|
||||||
|
type="checkbox"
|
||||||
|
name="series"
|
||||||
|
value={seriesKey}
|
||||||
|
checked={selectedSeries === seriesKey}
|
||||||
|
onChange={(): void => handleSeriesChange(seriesKey)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="anomaly-alert-evaluation-view-series-list-item-color"
|
||||||
|
style={{ backgroundColor: seriesData[seriesKey].color }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{seriesKey}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{filteredSeriesKeys.length === 0 && (
|
||||||
|
<Typography>No series found</Typography>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||||
|
|
||||||
|
const tooltipPlugin = (
|
||||||
|
isDarkMode: boolean,
|
||||||
|
): { hooks: { init: (u: any) => void } } => {
|
||||||
|
let tooltip: HTMLDivElement;
|
||||||
|
const tooltipLeftOffset = 10;
|
||||||
|
const tooltipTopOffset = 10;
|
||||||
|
let isMouseOverPlot = false;
|
||||||
|
|
||||||
|
function formatValue(value: string | number | Date): string | number | Date {
|
||||||
|
if (typeof value === 'string' && !Number.isNaN(parseFloat(value))) {
|
||||||
|
return parseFloat(value).toFixed(3);
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value.toFixed(3);
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toLocaleString();
|
||||||
|
}
|
||||||
|
if (value == null) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTooltip(u: any, left: number, top: number): void {
|
||||||
|
const idx = u.posToIdx(left);
|
||||||
|
const xVal = u.data[0][idx];
|
||||||
|
|
||||||
|
if (xVal == null) {
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xDate = new Date(xVal * 1000);
|
||||||
|
const formattedXDate = formatValue(xDate);
|
||||||
|
|
||||||
|
let tooltipContent = `<div class="uplot-tooltip-title">Time: ${formattedXDate}</div>`;
|
||||||
|
|
||||||
|
let mainValue;
|
||||||
|
let upperBand;
|
||||||
|
let lowerBand;
|
||||||
|
|
||||||
|
let color = null;
|
||||||
|
|
||||||
|
// Loop through all series (excluding the x-axis series)
|
||||||
|
for (let i = 1; i < u.series.length; i++) {
|
||||||
|
const series = u.series[i];
|
||||||
|
|
||||||
|
const yVal = u.data[i][idx];
|
||||||
|
const formattedYVal = formatValue(yVal);
|
||||||
|
|
||||||
|
color = generateColor(
|
||||||
|
series.label,
|
||||||
|
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the round marker for the series
|
||||||
|
const marker = `<span class="uplot-tooltip-marker" style="background-color: ${color};"></span>`;
|
||||||
|
|
||||||
|
if (series.label.toLowerCase().includes('upper band')) {
|
||||||
|
upperBand = formattedYVal;
|
||||||
|
} else if (series.label.toLowerCase().includes('lower band')) {
|
||||||
|
lowerBand = formattedYVal;
|
||||||
|
} else if (series.label.toLowerCase().includes('main series')) {
|
||||||
|
mainValue = formattedYVal;
|
||||||
|
} else {
|
||||||
|
tooltipContent += `
|
||||||
|
<div class="uplot-tooltip-series">
|
||||||
|
${marker}
|
||||||
|
<span class="uplot-tooltip-series-name">${series.label}:</span>
|
||||||
|
<span class="uplot-tooltip-series-value">${formattedYVal}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add main value, upper band, and lower band to the tooltip
|
||||||
|
if (mainValue !== undefined) {
|
||||||
|
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||||
|
tooltipContent += `
|
||||||
|
<div class="uplot-tooltip-series">
|
||||||
|
${marker}
|
||||||
|
<span class="uplot-tooltip-series-name">Main Series:</span>
|
||||||
|
<span class="uplot-tooltip-series-value">${mainValue}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (upperBand !== undefined) {
|
||||||
|
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||||
|
tooltipContent += `
|
||||||
|
<div class="uplot-tooltip-series">
|
||||||
|
${marker}
|
||||||
|
<span class="uplot-tooltip-series-name">Upper Band:</span>
|
||||||
|
<span class="uplot-tooltip-series-value">${upperBand}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (lowerBand !== undefined) {
|
||||||
|
const marker = `<span class="uplot-tooltip-marker"></span>`;
|
||||||
|
tooltipContent += `
|
||||||
|
<div class="uplot-tooltip-series">
|
||||||
|
${marker}
|
||||||
|
<span class="uplot-tooltip-series-name">Lower Band:</span>
|
||||||
|
<span class="uplot-tooltip-series-value">${lowerBand}</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip.innerHTML = tooltipContent;
|
||||||
|
tooltip.style.display = 'block';
|
||||||
|
tooltip.style.left = `${left + tooltipLeftOffset}px`;
|
||||||
|
tooltip.style.top = `${top + tooltipTopOffset}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(u: any): void {
|
||||||
|
tooltip = document.createElement('div');
|
||||||
|
tooltip.className = 'uplot-tooltip';
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
u.over.appendChild(tooltip);
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
u.over.addEventListener('mouseenter', () => {
|
||||||
|
isMouseOverPlot = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
u.over.addEventListener('mouseleave', () => {
|
||||||
|
isMouseOverPlot = false;
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
u.over.addEventListener('mousemove', (e: MouseEvent) => {
|
||||||
|
if (isMouseOverPlot) {
|
||||||
|
const rect = u.over.getBoundingClientRect();
|
||||||
|
const left = e.clientX - rect.left;
|
||||||
|
const top = e.clientY - rect.top;
|
||||||
|
updateTooltip(u, left, top);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hooks: {
|
||||||
|
init,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tooltipPlugin;
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
width: calc(100% - 64px);
|
width: calc(100% - 64px);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
.content-container {
|
.content-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 1rem;
|
margin: 0 1rem;
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const pageTitle = t(routeKey);
|
const pageTitle = t(routeKey);
|
||||||
const renderFullScreen =
|
const renderFullScreen =
|
||||||
pathname === ROUTES.GET_STARTED ||
|
pathname === ROUTES.GET_STARTED ||
|
||||||
|
pathname === ROUTES.ONBOARDING ||
|
||||||
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
|
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
|
||||||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
|
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
|
||||||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
|
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
|
||||||
@@ -211,6 +212,13 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [licenseData, isFetching]);
|
}, [licenseData, isFetching]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// after logging out hide the trial expiry banner
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
setShowTrialExpiryBanner(false);
|
||||||
|
}
|
||||||
|
}, [isLoggedIn]);
|
||||||
|
|
||||||
const handleUpgrade = (): void => {
|
const handleUpgrade = (): void => {
|
||||||
if (role === 'ADMIN') {
|
if (role === 'ADMIN') {
|
||||||
history.push(ROUTES.BILLING);
|
history.push(ROUTES.BILLING);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
initialQueryPromQLData,
|
initialQueryPromQLData,
|
||||||
PANEL_TYPES,
|
PANEL_TYPES,
|
||||||
} from 'constants/queryBuilder';
|
} from 'constants/queryBuilder';
|
||||||
|
import { AlertDetectionTypes } from 'container/FormAlertRules';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import {
|
import {
|
||||||
AlertDef,
|
AlertDef,
|
||||||
@@ -58,8 +59,53 @@ export const alertDefaults: AlertDef = {
|
|||||||
evalWindow: defaultEvalWindow,
|
evalWindow: defaultEvalWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const anamolyAlertDefaults: AlertDef = {
|
||||||
|
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||||
|
version: ENTITY_VERSION_V4,
|
||||||
|
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||||
|
condition: {
|
||||||
|
compositeQuery: {
|
||||||
|
builderQueries: {
|
||||||
|
A: {
|
||||||
|
...initialQueryBuilderFormValuesMap.metrics,
|
||||||
|
functions: [
|
||||||
|
{
|
||||||
|
name: 'anomaly',
|
||||||
|
args: [],
|
||||||
|
namedArgs: { z_score_threshold: 3 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
promQueries: { A: initialQueryPromQLData },
|
||||||
|
chQueries: {
|
||||||
|
A: {
|
||||||
|
name: 'A',
|
||||||
|
query: ``,
|
||||||
|
legend: '',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
panelType: PANEL_TYPES.TIME_SERIES,
|
||||||
|
unit: undefined,
|
||||||
|
},
|
||||||
|
op: defaultCompareOp,
|
||||||
|
matchType: defaultMatchType,
|
||||||
|
algorithm: defaultAlgorithm,
|
||||||
|
seasonality: defaultSeasonality,
|
||||||
|
target: 3,
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
severity: 'warning',
|
||||||
|
},
|
||||||
|
annotations: defaultAnnotations,
|
||||||
|
evalWindow: defaultEvalWindow,
|
||||||
|
};
|
||||||
|
|
||||||
export const logAlertDefaults: AlertDef = {
|
export const logAlertDefaults: AlertDef = {
|
||||||
alertType: AlertTypes.LOGS_BASED_ALERT,
|
alertType: AlertTypes.LOGS_BASED_ALERT,
|
||||||
|
version: ENTITY_VERSION_V4,
|
||||||
condition: {
|
condition: {
|
||||||
compositeQuery: {
|
compositeQuery: {
|
||||||
builderQueries: {
|
builderQueries: {
|
||||||
@@ -90,6 +136,7 @@ export const logAlertDefaults: AlertDef = {
|
|||||||
|
|
||||||
export const traceAlertDefaults: AlertDef = {
|
export const traceAlertDefaults: AlertDef = {
|
||||||
alertType: AlertTypes.TRACES_BASED_ALERT,
|
alertType: AlertTypes.TRACES_BASED_ALERT,
|
||||||
|
version: ENTITY_VERSION_V4,
|
||||||
condition: {
|
condition: {
|
||||||
compositeQuery: {
|
compositeQuery: {
|
||||||
builderQueries: {
|
builderQueries: {
|
||||||
@@ -120,6 +167,7 @@ export const traceAlertDefaults: AlertDef = {
|
|||||||
|
|
||||||
export const exceptionAlertDefaults: AlertDef = {
|
export const exceptionAlertDefaults: AlertDef = {
|
||||||
alertType: AlertTypes.EXCEPTIONS_BASED_ALERT,
|
alertType: AlertTypes.EXCEPTIONS_BASED_ALERT,
|
||||||
|
version: ENTITY_VERSION_V4,
|
||||||
condition: {
|
condition: {
|
||||||
compositeQuery: {
|
compositeQuery: {
|
||||||
builderQueries: {
|
builderQueries: {
|
||||||
@@ -149,7 +197,7 @@ export const exceptionAlertDefaults: AlertDef = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
|
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
|
||||||
[AlertTypes.ANOMALY_BASED_ALERT]: alertDefaults,
|
[AlertTypes.ANOMALY_BASED_ALERT]: anamolyAlertDefaults,
|
||||||
[AlertTypes.METRICS_BASED_ALERT]: alertDefaults,
|
[AlertTypes.METRICS_BASED_ALERT]: alertDefaults,
|
||||||
[AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults,
|
[AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults,
|
||||||
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
|
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { AlertDef } from 'types/api/alerts/def';
|
|||||||
import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
|
import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
|
||||||
import {
|
import {
|
||||||
alertDefaults,
|
alertDefaults,
|
||||||
|
anamolyAlertDefaults,
|
||||||
exceptionAlertDefaults,
|
exceptionAlertDefaults,
|
||||||
logAlertDefaults,
|
logAlertDefaults,
|
||||||
traceAlertDefaults,
|
traceAlertDefaults,
|
||||||
@@ -24,8 +25,12 @@ function CreateRules(): JSX.Element {
|
|||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
const alertTypeFromURL = queryParams.get(QueryParams.ruleType);
|
||||||
const version = queryParams.get('version');
|
const version = queryParams.get('version');
|
||||||
const alertTypeFromParams = queryParams.get(QueryParams.alertType);
|
const alertTypeFromParams =
|
||||||
|
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||||
|
? AlertTypes.ANOMALY_BASED_ALERT
|
||||||
|
: queryParams.get(QueryParams.alertType);
|
||||||
|
|
||||||
const compositeQuery = useGetCompositeQueryParam();
|
const compositeQuery = useGetCompositeQueryParam();
|
||||||
function getAlertTypeFromDataSource(): AlertTypes | null {
|
function getAlertTypeFromDataSource(): AlertTypes | null {
|
||||||
@@ -58,7 +63,7 @@ function CreateRules(): JSX.Element {
|
|||||||
break;
|
break;
|
||||||
case AlertTypes.ANOMALY_BASED_ALERT:
|
case AlertTypes.ANOMALY_BASED_ALERT:
|
||||||
setInitValues({
|
setInitValues({
|
||||||
...alertDefaults,
|
...anamolyAlertDefaults,
|
||||||
version: version || ENTITY_VERSION_V4,
|
version: version || ENTITY_VERSION_V4,
|
||||||
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
ruleType: AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||||
});
|
});
|
||||||
@@ -78,7 +83,10 @@ function CreateRules(): JSX.Element {
|
|||||||
: typ,
|
: typ,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typ === AlertTypes.ANOMALY_BASED_ALERT) {
|
if (
|
||||||
|
typ === AlertTypes.ANOMALY_BASED_ALERT ||
|
||||||
|
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||||
|
) {
|
||||||
queryParams.set(
|
queryParams.set(
|
||||||
QueryParams.ruleType,
|
QueryParams.ruleType,
|
||||||
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
AlertDetectionTypes.ANOMALY_DETECTION_ALERT,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 10px 12px;
|
padding: 10px 10px;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
border: 1px solid var(--bg-slate-400);
|
border: 1px solid var(--bg-slate-400);
|
||||||
background: rgba(22, 24, 29, 0.6);
|
background: rgba(22, 24, 29, 0.6);
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
border: 1px solid var(--bg-slate-400);
|
border: 1px solid var(--bg-slate-400);
|
||||||
background: var(--bg-slate-500);
|
background: var(--bg-slate-500);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import {
|
|||||||
PanelBottomClose,
|
PanelBottomClose,
|
||||||
Plus,
|
Plus,
|
||||||
X,
|
X,
|
||||||
XCircle,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
CSSProperties,
|
CSSProperties,
|
||||||
@@ -515,7 +514,11 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="explorer-options-container">
|
<div className="explorer-options-container">
|
||||||
{isQueryUpdated && !isExplorerOptionHidden && (
|
{
|
||||||
|
// if a viewName is selected and the explorer options are not hidden then
|
||||||
|
// always show the clear option
|
||||||
|
}
|
||||||
|
{!isExplorerOptionHidden && viewName && (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
isEditDeleteSupported ? '' : 'hide-update',
|
isEditDeleteSupported ? '' : 'hide-update',
|
||||||
@@ -529,18 +532,25 @@ function ExplorerOptions({
|
|||||||
icon={<X size={14} />}
|
icon={<X size={14} />}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Divider
|
{
|
||||||
type="vertical"
|
// only show the update view option when the query is updated
|
||||||
className={isEditDeleteSupported ? '' : 'hidden'}
|
}
|
||||||
/>
|
{isQueryUpdated && (
|
||||||
<Tooltip title="Update this view" placement="top">
|
<>
|
||||||
<Button
|
<Divider
|
||||||
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
|
type="vertical"
|
||||||
disabled={isViewUpdating}
|
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||||
onClick={onUpdateQueryHandler}
|
/>
|
||||||
icon={<Disc3 size={14} />}
|
<Tooltip title="Update this view" placement="top">
|
||||||
/>
|
<Button
|
||||||
</Tooltip>
|
className={cx('action-icon', isEditDeleteSupported ? ' ' : 'hidden')}
|
||||||
|
disabled={isViewUpdating}
|
||||||
|
onClick={onUpdateQueryHandler}
|
||||||
|
icon={<Disc3 size={14} />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isExplorerOptionHidden && (
|
{!isExplorerOptionHidden && (
|
||||||
@@ -564,10 +574,7 @@ function ExplorerOptions({
|
|||||||
}}
|
}}
|
||||||
dropdownStyle={dropdownStyle}
|
dropdownStyle={dropdownStyle}
|
||||||
className="views-dropdown"
|
className="views-dropdown"
|
||||||
allowClear={{
|
allowClear={false}
|
||||||
clearIcon: <XCircle size={16} style={{ marginTop: '-3px' }} />,
|
|
||||||
}}
|
|
||||||
onClear={handleClearSelect}
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{viewsData?.data?.data?.map((view) => {
|
{viewsData?.data?.data?.map((view) => {
|
||||||
@@ -662,8 +669,8 @@ function ExplorerOptions({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ExplorerOptionsHideArea
|
<ExplorerOptionsHideArea
|
||||||
|
viewName={viewName}
|
||||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||||
sourcepage={sourcepage}
|
sourcepage={sourcepage}
|
||||||
@@ -672,7 +679,6 @@ function ExplorerOptions({
|
|||||||
onUpdateQueryHandler={onUpdateQueryHandler}
|
onUpdateQueryHandler={onUpdateQueryHandler}
|
||||||
isEditDeleteSupported={isEditDeleteSupported}
|
isEditDeleteSupported={isEditDeleteSupported}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
className="save-view-modal"
|
className="save-view-modal"
|
||||||
title={<span className="title">Save this view</span>}
|
title={<span className="title">Save this view</span>}
|
||||||
@@ -705,7 +711,6 @@ function ExplorerOptions({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
footer={null}
|
footer={null}
|
||||||
onOk={onCancel(false)}
|
onOk={onCancel(false)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
import { setExplorerToolBarVisibility } from './utils';
|
import { setExplorerToolBarVisibility } from './utils';
|
||||||
|
|
||||||
interface DroppableAreaProps {
|
interface DroppableAreaProps {
|
||||||
|
viewName: string;
|
||||||
isQueryUpdated: boolean;
|
isQueryUpdated: boolean;
|
||||||
isExplorerOptionHidden?: boolean;
|
isExplorerOptionHidden?: boolean;
|
||||||
sourcepage: DataSource;
|
sourcepage: DataSource;
|
||||||
@@ -20,6 +21,7 @@ interface DroppableAreaProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ExplorerOptionsHideArea({
|
function ExplorerOptionsHideArea({
|
||||||
|
viewName,
|
||||||
isQueryUpdated,
|
isQueryUpdated,
|
||||||
isExplorerOptionHidden,
|
isExplorerOptionHidden,
|
||||||
sourcepage,
|
sourcepage,
|
||||||
@@ -39,7 +41,7 @@ function ExplorerOptionsHideArea({
|
|||||||
<div className="explorer-option-droppable-container">
|
<div className="explorer-option-droppable-container">
|
||||||
{isExplorerOptionHidden && (
|
{isExplorerOptionHidden && (
|
||||||
<>
|
<>
|
||||||
{isQueryUpdated && (
|
{viewName && (
|
||||||
<div className="explorer-actions-btn">
|
<div className="explorer-actions-btn">
|
||||||
<Tooltip title="Clear this view">
|
<Tooltip title="Clear this view">
|
||||||
<Button
|
<Button
|
||||||
@@ -49,7 +51,7 @@ function ExplorerOptionsHideArea({
|
|||||||
icon={<X size={14} color={Color.BG_INK_500} />}
|
icon={<X size={14} color={Color.BG_INK_500} />}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{isEditDeleteSupported && (
|
{isEditDeleteSupported && isQueryUpdated && (
|
||||||
<Tooltip title="Update this View">
|
<Tooltip title="Update this View">
|
||||||
<Button
|
<Button
|
||||||
onClick={onUpdateQueryHandler}
|
onClick={onUpdateQueryHandler}
|
||||||
|
|||||||
@@ -160,6 +160,15 @@ function RuleOptions({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeDeviation = (value: number): void => {
|
||||||
|
const target = value || alertDef.condition.target || 3;
|
||||||
|
|
||||||
|
setAlertDef({
|
||||||
|
...alertDef,
|
||||||
|
condition: { ...alertDef.condition, target: Number(target) },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const renderEvalWindows = (): JSX.Element => (
|
const renderEvalWindows = (): JSX.Element => (
|
||||||
<InlineSelect
|
<InlineSelect
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
@@ -172,8 +181,18 @@ function RuleOptions({
|
|||||||
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
|
||||||
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
|
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
|
||||||
<Select.Option value="1h0m0s">{t('option_60min')}</Select.Option>
|
<Select.Option value="1h0m0s">{t('option_60min')}</Select.Option>
|
||||||
|
<Select.Option value="2h0m0s">{t('option_2hours')}</Select.Option>
|
||||||
|
<Select.Option value="3h0m0s">{t('option_3hours')}</Select.Option>
|
||||||
<Select.Option value="4h0m0s">{t('option_4hours')}</Select.Option>
|
<Select.Option value="4h0m0s">{t('option_4hours')}</Select.Option>
|
||||||
|
<Select.Option value="5h0m0s">{t('option_5hours')}</Select.Option>
|
||||||
|
<Select.Option value="6h0m0s">{t('option_6hours')}</Select.Option>
|
||||||
|
<Select.Option value="8h0m0s">{t('option_8hours')}</Select.Option>
|
||||||
|
<Select.Option value="10h0m0s">{t('option_10hours')}</Select.Option>
|
||||||
|
<Select.Option value="12h0m0s">{t('option_12hours')}</Select.Option>
|
||||||
<Select.Option value="24h0m0s">{t('option_24hours')}</Select.Option>
|
<Select.Option value="24h0m0s">{t('option_24hours')}</Select.Option>
|
||||||
|
<Select.Option value="48h0m0s">{t('option_48hours')}</Select.Option>
|
||||||
|
<Select.Option value="72h0m0s">{t('option_72hours')}</Select.Option>
|
||||||
|
<Select.Option value="168h0m0s">{t('option_1week')}</Select.Option>
|
||||||
</InlineSelect>
|
</InlineSelect>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -203,6 +222,28 @@ function RuleOptions({
|
|||||||
</InlineSelect>
|
</InlineSelect>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderDeviationOpts = (): JSX.Element => (
|
||||||
|
<InlineSelect
|
||||||
|
getPopupContainer={popupContainer}
|
||||||
|
defaultValue={3}
|
||||||
|
style={{ minWidth: '120px' }}
|
||||||
|
value={alertDef.condition.target}
|
||||||
|
onChange={(value: number | unknown): void => {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
onChangeDeviation(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value={1}>1</Select.Option>
|
||||||
|
<Select.Option value={2}>2</Select.Option>
|
||||||
|
<Select.Option value={3}>3</Select.Option>
|
||||||
|
<Select.Option value={4}>4</Select.Option>
|
||||||
|
<Select.Option value={5}>5</Select.Option>
|
||||||
|
<Select.Option value={6}>6</Select.Option>
|
||||||
|
<Select.Option value={7}>7</Select.Option>
|
||||||
|
</InlineSelect>
|
||||||
|
);
|
||||||
|
|
||||||
const renderSeasonality = (): JSX.Element => (
|
const renderSeasonality = (): JSX.Element => (
|
||||||
<InlineSelect
|
<InlineSelect
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
@@ -237,39 +278,6 @@ function RuleOptions({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderAnomalyRuleOpts = (
|
|
||||||
onChange: InputNumberProps['onChange'],
|
|
||||||
): JSX.Element => (
|
|
||||||
<Form.Item>
|
|
||||||
<Typography.Text className="rule-definition">
|
|
||||||
{t('text_condition1_anomaly')}
|
|
||||||
<InlineSelect
|
|
||||||
getPopupContainer={popupContainer}
|
|
||||||
allowClear
|
|
||||||
showSearch
|
|
||||||
options={queryOptions}
|
|
||||||
placeholder={t('selected_query_placeholder')}
|
|
||||||
value={alertDef.condition.selectedQueryName}
|
|
||||||
onChange={onChangeSelectedQueryName}
|
|
||||||
/>
|
|
||||||
{t('text_condition3')} {renderEvalWindows()}
|
|
||||||
<Typography.Text>is</Typography.Text>
|
|
||||||
<InputNumber
|
|
||||||
value={alertDef?.condition?.target}
|
|
||||||
onChange={onChange}
|
|
||||||
type="number"
|
|
||||||
onWheel={(e): void => e.currentTarget.blur()}
|
|
||||||
/>
|
|
||||||
<Typography.Text>deviations</Typography.Text>
|
|
||||||
{renderCompareOps()}
|
|
||||||
<Typography.Text>the predicted data</Typography.Text>
|
|
||||||
{renderMatchOpts()}
|
|
||||||
using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '}
|
|
||||||
seasonality
|
|
||||||
</Typography.Text>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderPromRuleOptions = (): JSX.Element => (
|
const renderPromRuleOptions = (): JSX.Element => (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
@@ -320,6 +328,32 @@ function RuleOptions({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderAnomalyRuleOpts = (): JSX.Element => (
|
||||||
|
<Form.Item>
|
||||||
|
<Typography.Text className="rule-definition">
|
||||||
|
{t('text_condition1_anomaly')}
|
||||||
|
<InlineSelect
|
||||||
|
getPopupContainer={popupContainer}
|
||||||
|
allowClear
|
||||||
|
showSearch
|
||||||
|
options={queryOptions}
|
||||||
|
placeholder={t('selected_query_placeholder')}
|
||||||
|
value={alertDef.condition.selectedQueryName}
|
||||||
|
onChange={onChangeSelectedQueryName}
|
||||||
|
/>
|
||||||
|
{t('text_condition3')} {renderEvalWindows()}
|
||||||
|
<Typography.Text>is</Typography.Text>
|
||||||
|
{renderDeviationOpts()}
|
||||||
|
<Typography.Text>deviations</Typography.Text>
|
||||||
|
{renderCompareOps()}
|
||||||
|
<Typography.Text>the predicted data</Typography.Text>
|
||||||
|
{renderMatchOpts()}
|
||||||
|
using the {renderAlgorithms()} algorithm with {renderSeasonality()}{' '}
|
||||||
|
seasonality
|
||||||
|
</Typography.Text>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
|
||||||
const renderFrequency = (): JSX.Element => (
|
const renderFrequency = (): JSX.Element => (
|
||||||
<InlineSelect
|
<InlineSelect
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
@@ -354,7 +388,7 @@ function RuleOptions({
|
|||||||
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
|
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
|
||||||
{queryCategory !== EQueryType.PROM &&
|
{queryCategory !== EQueryType.PROM &&
|
||||||
ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||||
<>{renderAnomalyRuleOpts(onChange)}</>
|
<>{renderAnomalyRuleOpts()}</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{queryCategory !== EQueryType.PROM &&
|
{queryCategory !== EQueryType.PROM &&
|
||||||
@@ -362,32 +396,31 @@ function RuleOptions({
|
|||||||
renderThresholdRuleOpts()}
|
renderThresholdRuleOpts()}
|
||||||
|
|
||||||
<Space direction="vertical" size="large">
|
<Space direction="vertical" size="large">
|
||||||
{queryCategory !== EQueryType.PROM &&
|
{ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
||||||
ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
|
<Space direction="horizontal" align="center">
|
||||||
<Space direction="horizontal" align="center">
|
<Form.Item noStyle name={['condition', 'target']}>
|
||||||
<Form.Item noStyle name={['condition', 'target']}>
|
<InputNumber
|
||||||
<InputNumber
|
addonBefore={t('field_threshold')}
|
||||||
addonBefore={t('field_threshold')}
|
value={alertDef?.condition?.target}
|
||||||
value={alertDef?.condition?.target}
|
onChange={onChange}
|
||||||
onChange={onChange}
|
type="number"
|
||||||
type="number"
|
onWheel={(e): void => e.currentTarget.blur()}
|
||||||
onWheel={(e): void => e.currentTarget.blur()}
|
/>
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item noStyle>
|
<Form.Item noStyle>
|
||||||
<Select
|
<Select
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
allowClear
|
allowClear
|
||||||
showSearch
|
showSearch
|
||||||
options={categorySelectOptions}
|
options={categorySelectOptions}
|
||||||
placeholder={t('field_unit')}
|
placeholder={t('field_unit')}
|
||||||
value={alertDef.condition.targetUnit}
|
value={alertDef.condition.targetUnit}
|
||||||
onChange={onChangeAlertUnit}
|
onChange={onChangeAlertUnit}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Collapse>
|
<Collapse>
|
||||||
<Collapse.Panel header={t('More options')} key="1">
|
<Collapse.Panel header={t('More options')} key="1">
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import {
|
import {
|
||||||
@@ -72,6 +73,19 @@ export enum AlertDetectionTypes {
|
|||||||
ANOMALY_DETECTION_ALERT = 'anomaly_rule',
|
ANOMALY_DETECTION_ALERT = 'anomaly_rule',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ALERT_SETUP_GUIDE_URLS: Record<AlertTypes, string> = {
|
||||||
|
[AlertTypes.METRICS_BASED_ALERT]:
|
||||||
|
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
[AlertTypes.LOGS_BASED_ALERT]:
|
||||||
|
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
[AlertTypes.TRACES_BASED_ALERT]:
|
||||||
|
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
[AlertTypes.EXCEPTIONS_BASED_ALERT]:
|
||||||
|
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
[AlertTypes.ANOMALY_BASED_ALERT]:
|
||||||
|
'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page',
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
function FormAlertRules({
|
function FormAlertRules({
|
||||||
alertType,
|
alertType,
|
||||||
@@ -88,6 +102,8 @@ function FormAlertRules({
|
|||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
|
const location = useLocation();
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
|
||||||
// In case of alert the panel types should always be "Graph" only
|
// In case of alert the panel types should always be "Graph" only
|
||||||
const panelType = PANEL_TYPES.TIME_SERIES;
|
const panelType = PANEL_TYPES.TIME_SERIES;
|
||||||
@@ -120,9 +136,7 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const alertTypeFromURL = urlQuery.get(QueryParams.ruleType);
|
const alertTypeFromURL = urlQuery.get(QueryParams.ruleType);
|
||||||
|
|
||||||
const [detectionMethod, setDetectionMethod] = useState<string>(
|
const [detectionMethod, setDetectionMethod] = useState<string | null>(null);
|
||||||
AlertDetectionTypes.THRESHOLD_ALERT,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEqual(currentQuery.unit, yAxisUnit)) {
|
if (!isEqual(currentQuery.unit, yAxisUnit)) {
|
||||||
@@ -154,11 +168,24 @@ function FormAlertRules({
|
|||||||
|
|
||||||
useShareBuilderUrl(sq);
|
useShareBuilderUrl(sq);
|
||||||
|
|
||||||
|
const handleDetectionMethodChange = (value: string): void => {
|
||||||
|
setAlertDef((def) => ({
|
||||||
|
...def,
|
||||||
|
ruleType: value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
logEvent(`Alert: Detection method changed`, {
|
||||||
|
detectionMethod: value,
|
||||||
|
});
|
||||||
|
|
||||||
|
setDetectionMethod(value);
|
||||||
|
};
|
||||||
|
|
||||||
const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => {
|
const updateFunctions = (data: IBuilderQuery): QueryFunctionProps[] => {
|
||||||
const anomalyFunction = {
|
const anomalyFunction = {
|
||||||
name: 'anomaly',
|
name: 'anomaly',
|
||||||
args: [],
|
args: [],
|
||||||
namedArgs: { z_score_threshold: 9 },
|
namedArgs: { z_score_threshold: alertDef.condition.target || 3 },
|
||||||
};
|
};
|
||||||
const functions = data.functions || [];
|
const functions = data.functions || [];
|
||||||
|
|
||||||
@@ -166,6 +193,19 @@ function FormAlertRules({
|
|||||||
// Add anomaly if not already present
|
// Add anomaly if not already present
|
||||||
if (!functions.some((func) => func.name === 'anomaly')) {
|
if (!functions.some((func) => func.name === 'anomaly')) {
|
||||||
functions.push(anomalyFunction);
|
functions.push(anomalyFunction);
|
||||||
|
} else {
|
||||||
|
const anomalyFuncIndex = functions.findIndex(
|
||||||
|
(func) => func.name === 'anomaly',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (anomalyFuncIndex !== -1) {
|
||||||
|
const anomalyFunc = {
|
||||||
|
...functions[anomalyFuncIndex],
|
||||||
|
namedArgs: { z_score_threshold: alertDef.condition.target || 3 },
|
||||||
|
};
|
||||||
|
functions.splice(anomalyFuncIndex, 1);
|
||||||
|
functions.push(anomalyFunc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Remove anomaly if present
|
// Remove anomaly if present
|
||||||
@@ -178,6 +218,20 @@ function FormAlertRules({
|
|||||||
return functions;
|
return functions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ruleType =
|
||||||
|
detectionMethod === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||||
|
? AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||||
|
: AlertDetectionTypes.THRESHOLD_ALERT;
|
||||||
|
|
||||||
|
queryParams.set(QueryParams.ruleType, ruleType);
|
||||||
|
|
||||||
|
const generatedUrl = `${location.pathname}?${queryParams.toString()}`;
|
||||||
|
|
||||||
|
history.replace(generatedUrl);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [detectionMethod]);
|
||||||
|
|
||||||
const updateFunctionsBasedOnAlertType = (): void => {
|
const updateFunctionsBasedOnAlertType = (): void => {
|
||||||
for (let index = 0; index < currentQuery.builder.queryData.length; index++) {
|
for (let index = 0; index < currentQuery.builder.queryData.length; index++) {
|
||||||
const queryData = currentQuery.builder.queryData[index];
|
const queryData = currentQuery.builder.queryData[index];
|
||||||
@@ -191,7 +245,11 @@ function FormAlertRules({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateFunctionsBasedOnAlertType();
|
updateFunctionsBasedOnAlertType();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [detectionMethod, alertDef, currentQuery.builder.queryData.length]);
|
}, [
|
||||||
|
detectionMethod,
|
||||||
|
alertDef.condition.target,
|
||||||
|
currentQuery.builder.queryData.length,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const broadcastToSpecificChannels =
|
const broadcastToSpecificChannels =
|
||||||
@@ -215,7 +273,8 @@ function FormAlertRules({
|
|||||||
});
|
});
|
||||||
|
|
||||||
setDetectionMethod(ruleType);
|
setDetectionMethod(ruleType);
|
||||||
}, [initialValue, isNewRule, alertTypeFromURL]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [initialValue, isNewRule]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Set selectedQueryName based on the length of queryOptions
|
// Set selectedQueryName based on the length of queryOptions
|
||||||
@@ -335,7 +394,11 @@ function FormAlertRules({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alertDef.condition?.target !== 0 && !alertDef.condition?.target) {
|
if (
|
||||||
|
alertDef.ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT &&
|
||||||
|
alertDef.condition?.target !== 0 &&
|
||||||
|
!alertDef.condition?.target
|
||||||
|
) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: t('target_missing'),
|
description: t('target_missing'),
|
||||||
@@ -493,6 +556,7 @@ function FormAlertRules({
|
|||||||
queryType: currentQuery.queryType,
|
queryType: currentQuery.queryType,
|
||||||
alertId: postableAlert?.id,
|
alertId: postableAlert?.id,
|
||||||
alertName: postableAlert?.alert,
|
alertName: postableAlert?.alert,
|
||||||
|
ruleType: postableAlert?.ruleType,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
@@ -577,6 +641,7 @@ function FormAlertRules({
|
|||||||
queryType: currentQuery.queryType,
|
queryType: currentQuery.queryType,
|
||||||
status: statusResponse.status,
|
status: statusResponse.status,
|
||||||
statusMessage: statusResponse.message,
|
statusMessage: statusResponse.message,
|
||||||
|
ruleType: postableAlert.ruleType,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
}, [t, isFormValid, memoizedPreparePostData, notifications]);
|
||||||
@@ -650,6 +715,29 @@ function FormAlertRules({
|
|||||||
|
|
||||||
const isRuleCreated = !ruleId || ruleId === 0;
|
const isRuleCreated = !ruleId || ruleId === 0;
|
||||||
|
|
||||||
|
function handleRedirection(option: AlertTypes): void {
|
||||||
|
let url;
|
||||||
|
if (
|
||||||
|
option === AlertTypes.METRICS_BASED_ALERT &&
|
||||||
|
alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT
|
||||||
|
) {
|
||||||
|
url = ALERT_SETUP_GUIDE_URLS[AlertTypes.ANOMALY_BASED_ALERT];
|
||||||
|
} else {
|
||||||
|
url = ALERT_SETUP_GUIDE_URLS[option];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
logEvent('Alert: Check example alert clicked', {
|
||||||
|
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||||
|
isNewRule: !ruleId || ruleId === 0,
|
||||||
|
ruleId,
|
||||||
|
queryType: currentQuery.queryType,
|
||||||
|
link: url,
|
||||||
|
});
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isRuleCreated) {
|
if (!isRuleCreated) {
|
||||||
logEvent('Alert: Edit page visited', {
|
logEvent('Alert: Edit page visited', {
|
||||||
@@ -672,15 +760,6 @@ function FormAlertRules({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleDetectionMethodChange = (value: any): void => {
|
|
||||||
setAlertDef((def) => ({
|
|
||||||
...def,
|
|
||||||
ruleType: value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
setDetectionMethod(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAnomalyDetectionEnabled =
|
const isAnomalyDetectionEnabled =
|
||||||
useFeatureFlag(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
useFeatureFlag(FeatureKeys.ANOMALY_DETECTION)?.active || false;
|
||||||
|
|
||||||
@@ -709,7 +788,11 @@ function FormAlertRules({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="periscope-btn" icon={<ExternalLink size={14} />}>
|
<Button
|
||||||
|
className="periscope-btn"
|
||||||
|
onClick={(): void => handleRedirection(alertDef.alertType as AlertTypes)}
|
||||||
|
icon={<ExternalLink size={14} />}
|
||||||
|
>
|
||||||
Alert Setup Guide
|
Alert Setup Guide
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -745,7 +828,7 @@ function FormAlertRules({
|
|||||||
<Tabs2
|
<Tabs2
|
||||||
key={detectionMethod}
|
key={detectionMethod}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
initialSelectedTab={detectionMethod}
|
initialSelectedTab={detectionMethod || ''}
|
||||||
onSelectTab={handleDetectionMethodChange}
|
onSelectTab={handleDetectionMethodChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ function LabelSelect({
|
|||||||
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
|
if (e.key === 'Enter' || e.code === 'Enter' || e.key === ':') {
|
||||||
send('NEXT');
|
send('NEXT');
|
||||||
}
|
}
|
||||||
|
if (state.value === 'Idle') {
|
||||||
|
send('NEXT');
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
value={currentVal as never}
|
value={currentVal as never}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { QueryParams } from 'constants/query';
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
@@ -72,16 +73,18 @@ function WidgetHeader({
|
|||||||
tableProcessedDataRef,
|
tableProcessedDataRef,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
}: IWidgetHeaderProps): JSX.Element | null {
|
}: IWidgetHeaderProps): JSX.Element | null {
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
const onEditHandler = useCallback((): void => {
|
const onEditHandler = useCallback((): void => {
|
||||||
const widgetId = widget.id;
|
const widgetId = widget.id;
|
||||||
history.push(
|
urlQuery.set(QueryParams.widgetId, widgetId);
|
||||||
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
|
urlQuery.set(QueryParams.graphType, widget.panelTypes);
|
||||||
widget.panelTypes
|
urlQuery.set(
|
||||||
}&${QueryParams.compositeQuery}=${encodeURIComponent(
|
QueryParams.compositeQuery,
|
||||||
JSON.stringify(widget.query),
|
encodeURIComponent(JSON.stringify(widget.query)),
|
||||||
)}`,
|
|
||||||
);
|
);
|
||||||
}, [widget.id, widget.panelTypes, widget.query]);
|
const generatedUrl = `${window.location.pathname}/new?${urlQuery}`;
|
||||||
|
history.push(generatedUrl);
|
||||||
|
}, [urlQuery, widget.id, widget.panelTypes, widget.query]);
|
||||||
|
|
||||||
const onCreateAlertsHandler = useCreateAlerts(widget, 'dashboardView');
|
const onCreateAlertsHandler = useCreateAlerts(widget, 'dashboardView');
|
||||||
|
|
||||||
|
|||||||
@@ -97,13 +97,19 @@ function GridTableComponent({
|
|||||||
|
|
||||||
const newColumnData = columns.map((e) => ({
|
const newColumnData = columns.map((e) => ({
|
||||||
...e,
|
...e,
|
||||||
render: (text: string): ReactNode => {
|
render: (text: string, ...rest: any): ReactNode => {
|
||||||
const isNumber = !Number.isNaN(Number(text));
|
let textForThreshold = text;
|
||||||
|
if (columnUnits && columnUnits?.[e.title as string]) {
|
||||||
|
textForThreshold = rest[0][`${e.title}_without_unit`];
|
||||||
|
}
|
||||||
|
const isNumber = !Number.isNaN(Number(textForThreshold));
|
||||||
|
|
||||||
if (thresholds && isNumber) {
|
if (thresholds && isNumber) {
|
||||||
const { hasMultipleMatches, threshold } = findMatchingThreshold(
|
const { hasMultipleMatches, threshold } = findMatchingThreshold(
|
||||||
thresholds,
|
thresholds,
|
||||||
e.title as string,
|
e.title as string,
|
||||||
Number(text),
|
Number(textForThreshold),
|
||||||
|
columnUnits?.[e.title as string],
|
||||||
);
|
);
|
||||||
|
|
||||||
const idx = thresholds.findIndex(
|
const idx = thresholds.findIndex(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||||
|
import { convertUnit } from 'container/NewWidget/RightContainer/dataFormatCategories';
|
||||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||||
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
|
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
|
||||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||||
@@ -30,10 +31,39 @@ function evaluateCondition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates whether a given value meets a specified threshold condition.
|
||||||
|
* It first converts the value to the appropriate unit if a threshold unit is provided,
|
||||||
|
* and then checks the condition using the specified operator.
|
||||||
|
*
|
||||||
|
* @param value - The value to be evaluated.
|
||||||
|
* @param thresholdValue - The threshold value to compare against.
|
||||||
|
* @param thresholdOperator - The operator used for comparison (e.g., '>', '<', '==').
|
||||||
|
* @param thresholdUnit - The unit to which the value should be converted.
|
||||||
|
* @param columnUnit - The current unit of the value.
|
||||||
|
* @returns A boolean indicating whether the value meets the threshold condition.
|
||||||
|
*/
|
||||||
|
function evaluateThresholdWithConvertedValue(
|
||||||
|
value: number,
|
||||||
|
thresholdValue: number,
|
||||||
|
thresholdOperator?: string,
|
||||||
|
thresholdUnit?: string,
|
||||||
|
columnUnit?: string,
|
||||||
|
): boolean {
|
||||||
|
const convertedValue = convertUnit(value, columnUnit, thresholdUnit);
|
||||||
|
|
||||||
|
if (convertedValue) {
|
||||||
|
return evaluateCondition(thresholdOperator, convertedValue, thresholdValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluateCondition(thresholdOperator, value, thresholdValue);
|
||||||
|
}
|
||||||
|
|
||||||
export function findMatchingThreshold(
|
export function findMatchingThreshold(
|
||||||
thresholds: ThresholdProps[],
|
thresholds: ThresholdProps[],
|
||||||
label: string,
|
label: string,
|
||||||
value: number,
|
value: number,
|
||||||
|
columnUnit?: string,
|
||||||
): {
|
): {
|
||||||
threshold: ThresholdProps;
|
threshold: ThresholdProps;
|
||||||
hasMultipleMatches: boolean;
|
hasMultipleMatches: boolean;
|
||||||
@@ -45,10 +75,12 @@ export function findMatchingThreshold(
|
|||||||
if (
|
if (
|
||||||
threshold.thresholdValue !== undefined &&
|
threshold.thresholdValue !== undefined &&
|
||||||
threshold.thresholdTableOptions === label &&
|
threshold.thresholdTableOptions === label &&
|
||||||
evaluateCondition(
|
evaluateThresholdWithConvertedValue(
|
||||||
threshold.thresholdOperator,
|
|
||||||
value,
|
value,
|
||||||
threshold.thresholdValue,
|
threshold?.thresholdValue,
|
||||||
|
threshold.thresholdOperator,
|
||||||
|
threshold.thresholdUnit,
|
||||||
|
columnUnit,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
matchingThresholds.push(threshold);
|
matchingThresholds.push(threshold);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { ColumnsType } from 'antd/es/table/interface';
|
|||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import DropDown from 'components/DropDown/DropDown';
|
import DropDown from 'components/DropDown/DropDown';
|
||||||
import { listAlertMessage } from 'components/LaunchChatSupport/util';
|
|
||||||
import {
|
import {
|
||||||
DynamicColumnsKey,
|
DynamicColumnsKey,
|
||||||
TableDataSource,
|
TableDataSource,
|
||||||
@@ -397,15 +396,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
dynamicColumns={dynamicColumns}
|
dynamicColumns={dynamicColumns}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
pagination={paginationConfig}
|
pagination={paginationConfig}
|
||||||
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',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,17 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
// overridding the request integration style to fix the spacing for dashboard list
|
||||||
|
.request-entity-container {
|
||||||
|
margin-bottom: 16px !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integrations-content {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboards-list-view-content {
|
.dashboards-list-view-content {
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
max-width: 836px;
|
max-width: 836px;
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import logEvent from 'api/common/logEvent';
|
|||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
|
||||||
import { dashboardListMessage } from 'components/LaunchChatSupport/util';
|
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
|
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
|
||||||
@@ -79,6 +77,7 @@ import { isCloudUser } from 'utils/app';
|
|||||||
|
|
||||||
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
|
||||||
import ImportJSON from './ImportJSON';
|
import ImportJSON from './ImportJSON';
|
||||||
|
import { RequestDashboardBtn } from './RequestDashboardBtn';
|
||||||
import { DeleteButton } from './TableComponents/DeleteButton';
|
import { DeleteButton } from './TableComponents/DeleteButton';
|
||||||
import {
|
import {
|
||||||
DashboardDynamicColumns,
|
DashboardDynamicColumns,
|
||||||
@@ -693,17 +692,14 @@ function DashboardsList(): JSX.Element {
|
|||||||
<Typography.Text className="subtitle">
|
<Typography.Text className="subtitle">
|
||||||
Create and manage dashboards for your workspace.
|
Create and manage dashboards for your workspace.
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<LaunchChatSupport
|
|
||||||
attributes={{
|
|
||||||
screen: 'Dashboard list page',
|
|
||||||
}}
|
|
||||||
eventName="Dashboard: Facing Issues in dashboard"
|
|
||||||
message={dashboardListMessage}
|
|
||||||
buttonText="Need help with dashboards?"
|
|
||||||
onHoverText="Click here to get help with dashboards"
|
|
||||||
intercomMessageDisabled
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
{isCloudUser() && (
|
||||||
|
<div className="integrations-container">
|
||||||
|
<div className="integrations-content">
|
||||||
|
<RequestDashboardBtn />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isDashboardListLoading ||
|
{isDashboardListLoading ||
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ function ImportJSON({
|
|||||||
|
|
||||||
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
||||||
|
|
||||||
|
// Add validation for uuid
|
||||||
|
if (dashboardData.uuid !== undefined && dashboardData.uuid.trim() === '') {
|
||||||
|
// silently remove uuid if it is empty
|
||||||
|
delete dashboardData.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
if (dashboardData?.layout) {
|
if (dashboardData?.layout) {
|
||||||
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
||||||
} else {
|
} else {
|
||||||
@@ -123,11 +129,14 @@ function ImportJSON({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
} catch {
|
} catch (error) {
|
||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
setIsFeatureAlert(false);
|
setIsFeatureAlert(false);
|
||||||
|
|
||||||
setIsCreateDashboardError(true);
|
setIsCreateDashboardError(true);
|
||||||
|
notifications.error({
|
||||||
|
message: error instanceof Error ? error.message : t('error_loading_json'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import '../../pages/Integrations/Integrations.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Input, Space, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function RequestDashboardBtn(): JSX.Element {
|
||||||
|
const [
|
||||||
|
isSubmittingRequestForDashboard,
|
||||||
|
setIsSubmittingRequestForDashboard,
|
||||||
|
] = useState(false);
|
||||||
|
|
||||||
|
const [requestedDashboardName, setRequestedDashboardName] = useState('');
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
|
const handleRequestDashboardSubmit = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
setIsSubmittingRequestForDashboard(true);
|
||||||
|
const response = await logEvent('Dashboard Requested', {
|
||||||
|
screen: 'Dashboard list page',
|
||||||
|
dashboard: requestedDashboardName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
notifications.success({
|
||||||
|
message: 'Dashboard Request Submitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsSubmittingRequestForDashboard(false);
|
||||||
|
} else {
|
||||||
|
notifications.error({
|
||||||
|
message:
|
||||||
|
response.error ||
|
||||||
|
t('something_went_wrong', {
|
||||||
|
ns: 'common',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsSubmittingRequestForDashboard(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: t('something_went_wrong', {
|
||||||
|
ns: 'common',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsSubmittingRequestForDashboard(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="request-entity-container">
|
||||||
|
<Typography.Text>
|
||||||
|
Can't find the dashboard you need? Request a new Dashboard.
|
||||||
|
</Typography.Text>
|
||||||
|
|
||||||
|
<div className="form-section">
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter dashboard name..."
|
||||||
|
style={{ width: 300, marginBottom: 0 }}
|
||||||
|
value={requestedDashboardName}
|
||||||
|
onChange={(e): void => setRequestedDashboardName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="periscope-btn primary"
|
||||||
|
icon={
|
||||||
|
isSubmittingRequestForDashboard ? (
|
||||||
|
<LoadingOutlined />
|
||||||
|
) : (
|
||||||
|
<Check size={12} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type="primary"
|
||||||
|
onClick={handleRequestDashboardSubmit}
|
||||||
|
disabled={
|
||||||
|
isSubmittingRequestForDashboard ||
|
||||||
|
!requestedDashboardName ||
|
||||||
|
requestedDashboardName?.trim().length === 0
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Space.Compact>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -157,6 +157,11 @@ export const getFieldAttributes = (field: string): IFieldAttributes => {
|
|||||||
const stringWithoutPrefix = field.slice('resources_'.length);
|
const stringWithoutPrefix = field.slice('resources_'.length);
|
||||||
const parts = splitOnce(stringWithoutPrefix, '.');
|
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||||
[dataType, newField] = parts;
|
[dataType, newField] = parts;
|
||||||
|
} else if (field.startsWith('scope_string')) {
|
||||||
|
logType = MetricsType.Scope;
|
||||||
|
const stringWithoutPrefix = field.slice('scope_'.length);
|
||||||
|
const parts = splitOnce(stringWithoutPrefix, '.');
|
||||||
|
[dataType, newField] = parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { dataType, newField, logType };
|
return { dataType, newField, logType };
|
||||||
@@ -187,6 +192,7 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
|
|||||||
traceId: logData.traceId,
|
traceId: logData.traceId,
|
||||||
attributes: {},
|
attributes: {},
|
||||||
resources: {},
|
resources: {},
|
||||||
|
scope: {},
|
||||||
severity_text: logData.severity_text,
|
severity_text: logData.severity_text,
|
||||||
severity_number: logData.severity_number,
|
severity_number: logData.severity_number,
|
||||||
};
|
};
|
||||||
@@ -198,6 +204,9 @@ export const aggregateAttributesResourcesToString = (logData: ILog): string => {
|
|||||||
} else if (key.startsWith('resources_')) {
|
} else if (key.startsWith('resources_')) {
|
||||||
outputJson.resources = outputJson.resources || {};
|
outputJson.resources = outputJson.resources || {};
|
||||||
Object.assign(outputJson.resources, logData[key as keyof ILog]);
|
Object.assign(outputJson.resources, logData[key as keyof ILog]);
|
||||||
|
} else if (key.startsWith('scope_string')) {
|
||||||
|
outputJson.scope = outputJson.scope || {};
|
||||||
|
Object.assign(outputJson.scope, logData[key as keyof ILog]);
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -2,3 +2,25 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-row-backdrop {
|
||||||
|
&.INFO {
|
||||||
|
background-color: var(--bg-robin-500) 10;
|
||||||
|
}
|
||||||
|
&.WARNING,
|
||||||
|
&.WARN {
|
||||||
|
background-color: var(--bg-amber-500) 10;
|
||||||
|
}
|
||||||
|
&.ERROR {
|
||||||
|
background-color: var(--bg-cherry-500) 10;
|
||||||
|
}
|
||||||
|
&.TRACE {
|
||||||
|
background-color: var(--bg-forest-400) 10;
|
||||||
|
}
|
||||||
|
&.DEBUG {
|
||||||
|
background-color: var(--bg-aqua-500) 10;
|
||||||
|
}
|
||||||
|
&.FATAL {
|
||||||
|
background-color: var(--bg-sakura-500) 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import LogDetail from 'components/LogDetail';
|
import LogDetail from 'components/LogDetail';
|
||||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||||
|
import { getLogIndicatorType } from 'components/Logs/LogStateIndicator/utils';
|
||||||
import { useTableView } from 'components/Logs/TableView/useTableView';
|
import { useTableView } from 'components/Logs/TableView/useTableView';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
@@ -21,6 +22,11 @@ import { TableHeaderCellStyled, TableRowStyled } from './styles';
|
|||||||
import TableRow from './TableRow';
|
import TableRow from './TableRow';
|
||||||
import { InfinityTableProps } from './types';
|
import { InfinityTableProps } from './types';
|
||||||
|
|
||||||
|
interface CustomTableRowProps {
|
||||||
|
activeContextLogId: string;
|
||||||
|
activeLogId: string;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
// eslint-disable-next-line react/function-component-definition
|
||||||
const CustomTableRow: TableComponents<ILog>['TableRow'] = ({
|
const CustomTableRow: TableComponents<ILog>['TableRow'] = ({
|
||||||
children,
|
children,
|
||||||
@@ -31,10 +37,17 @@ const CustomTableRow: TableComponents<ILog>['TableRow'] = ({
|
|||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
|
const logType = getLogIndicatorType(props.item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowStyled
|
<TableRowStyled
|
||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
$isActiveLog={isHighlighted}
|
$isActiveLog={
|
||||||
|
isHighlighted ||
|
||||||
|
(context as CustomTableRowProps).activeContextLogId === props.item.id ||
|
||||||
|
(context as CustomTableRowProps).activeLogId === props.item.id
|
||||||
|
}
|
||||||
|
$logType={logType}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -66,8 +79,6 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
...tableViewProps,
|
...tableViewProps,
|
||||||
onClickExpand: onSetActiveLog,
|
onClickExpand: onSetActiveLog,
|
||||||
onOpenLogsContext: handleSetActiveContextLog,
|
onOpenLogsContext: handleSetActiveContextLog,
|
||||||
activeLog,
|
|
||||||
activeContextLog,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { draggedColumns, onDragColumns } = useDragColumns<
|
const { draggedColumns, onDragColumns } = useDragColumns<
|
||||||
@@ -153,7 +164,14 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
// TODO: fix it in the future
|
// TODO: fix it in the future
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
TableRow: CustomTableRow,
|
TableRow: (props): any =>
|
||||||
|
CustomTableRow({
|
||||||
|
...props,
|
||||||
|
context: {
|
||||||
|
activeContextLogId: activeContextLog?.id,
|
||||||
|
activeLogId: activeLog?.id,
|
||||||
|
},
|
||||||
|
} as any),
|
||||||
}}
|
}}
|
||||||
itemContent={itemContent}
|
itemContent={itemContent}
|
||||||
fixedHeaderContent={tableHeader}
|
fixedHeaderContent={tableHeader}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -37,13 +36,12 @@ export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
|||||||
export const TableRowStyled = styled.tr<{
|
export const TableRowStyled = styled.tr<{
|
||||||
$isActiveLog: boolean;
|
$isActiveLog: boolean;
|
||||||
$isDarkMode: boolean;
|
$isDarkMode: boolean;
|
||||||
|
$logType: string;
|
||||||
}>`
|
}>`
|
||||||
td {
|
td {
|
||||||
${({ $isActiveLog, $isDarkMode }): string =>
|
${({ $isActiveLog, $isDarkMode, $logType }): string =>
|
||||||
$isActiveLog
|
$isActiveLog
|
||||||
? `background-color: ${
|
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
|
||||||
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
|
||||||
} !important`
|
|
||||||
: ''};
|
: ''};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export enum KeyOperationTableHeader {
|
|||||||
export enum MetricsType {
|
export enum MetricsType {
|
||||||
Tag = 'tag',
|
Tag = 'tag',
|
||||||
Resource = 'resource',
|
Resource = 'resource',
|
||||||
|
Scope = 'scope',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum WidgetKeys {
|
export enum WidgetKeys {
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
|
||||||
import { dashboardHelpMessage } from 'components/LaunchChatSupport/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_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
@@ -47,7 +45,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard, DashboardData } from 'types/api/dashboard/getAll';
|
import {
|
||||||
|
Dashboard,
|
||||||
|
DashboardData,
|
||||||
|
IDashboardVariable,
|
||||||
|
} 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';
|
||||||
@@ -63,6 +65,30 @@ interface DashboardDescriptionProps {
|
|||||||
handle: FullScreenHandle;
|
handle: FullScreenHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeDashboardData(
|
||||||
|
selectedData: DashboardData,
|
||||||
|
): Omit<DashboardData, 'uuid'> {
|
||||||
|
if (!selectedData?.variables) {
|
||||||
|
const { uuid, ...rest } = selectedData;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedVariables = Object.entries(selectedData.variables).reduce(
|
||||||
|
(acc, [key, value]) => {
|
||||||
|
const { selectedValue, ...rest } = value;
|
||||||
|
acc[key] = rest;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, IDashboardVariable>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { uuid, ...restData } = selectedData;
|
||||||
|
return {
|
||||||
|
...restData,
|
||||||
|
variables: updatedVariables,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||||
const { handle } = props;
|
const { handle } = props;
|
||||||
@@ -328,18 +354,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
{isDashboardLocked && <LockKeyhole size={14} />}
|
{isDashboardLocked && <LockKeyhole size={14} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="right-section">
|
<div className="right-section">
|
||||||
<LaunchChatSupport
|
|
||||||
attributes={{
|
|
||||||
uuid: selectedDashboard?.uuid,
|
|
||||||
title: updatedTitle,
|
|
||||||
screen: 'Dashboard Details',
|
|
||||||
}}
|
|
||||||
eventName="Dashboard: Facing Issues in dashboard"
|
|
||||||
message={dashboardHelpMessage(selectedDashboard?.data, selectedDashboard)}
|
|
||||||
buttonText="Need help with this dashboard?"
|
|
||||||
onHoverText="Click here to get help with dashboard"
|
|
||||||
intercomMessageDisabled
|
|
||||||
/>
|
|
||||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
|
<DateTimeSelectionV2 showAutoRefresh hideShareModal />
|
||||||
<Popover
|
<Popover
|
||||||
open={isDashboardSettingsOpen}
|
open={isDashboardSettingsOpen}
|
||||||
@@ -407,7 +421,10 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<FileJson size={14} />}
|
icon={<FileJson size={14} />}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
downloadObjectAsJson(selectedData, selectedData.title);
|
downloadObjectAsJson(
|
||||||
|
sanitizeDashboardData(selectedData),
|
||||||
|
selectedData.title,
|
||||||
|
);
|
||||||
setIsDashbordSettingsOpen(false);
|
setIsDashbordSettingsOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -417,7 +434,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<ClipboardCopy size={14} />}
|
icon={<ClipboardCopy size={14} />}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setCopy(JSON.stringify(selectedData, null, 2));
|
setCopy(
|
||||||
|
JSON.stringify(sanitizeDashboardData(selectedData), null, 2),
|
||||||
|
);
|
||||||
setIsDashbordSettingsOpen(false);
|
setIsDashbordSettingsOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -257,8 +257,7 @@ function VariableItem({
|
|||||||
if (variableData.name) {
|
if (variableData.name) {
|
||||||
if (
|
if (
|
||||||
value === ALL_SELECT_VALUE ||
|
value === ALL_SELECT_VALUE ||
|
||||||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
|
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE))
|
||||||
(Array.isArray(value) && value.length === 0)
|
|
||||||
) {
|
) {
|
||||||
onValueUpdate(variableData.name, variableData.id, optionsData, true);
|
onValueUpdate(variableData.name, variableData.id, optionsData, true);
|
||||||
} else {
|
} else {
|
||||||
@@ -324,10 +323,6 @@ function VariableItem({
|
|||||||
Array.isArray(selectedValueStringified) &&
|
Array.isArray(selectedValueStringified) &&
|
||||||
selectedValueStringified.includes(option.toString())
|
selectedValueStringified.includes(option.toString())
|
||||||
) {
|
) {
|
||||||
if (newSelectedValue.length === 0) {
|
|
||||||
handleChange(ALL_SELECT_VALUE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (newSelectedValue.length === 1) {
|
if (newSelectedValue.length === 1) {
|
||||||
handleChange(newSelectedValue[0].toString());
|
handleChange(newSelectedValue[0].toString());
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Color } from '@signozhq/design-tokens';
|
|||||||
import { Button, Tabs, Typography } from 'antd';
|
import { Button, Tabs, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||||
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
|
||||||
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';
|
||||||
@@ -235,21 +234,6 @@ function QuerySection({
|
|||||||
onChange={handleQueryCategoryChange}
|
onChange={handleQueryCategoryChange}
|
||||||
tabBarExtraContent={
|
tabBarExtraContent={
|
||||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||||
<LaunchChatSupport
|
|
||||||
attributes={{
|
|
||||||
uuid: selectedDashboard?.uuid,
|
|
||||||
title: selectedDashboard?.data.title,
|
|
||||||
screen: 'Dashboard widget',
|
|
||||||
panelType: selectedGraph,
|
|
||||||
widgetId: query.id,
|
|
||||||
queryType: currentQuery.queryType,
|
|
||||||
}}
|
|
||||||
eventName="Dashboard: Facing Issues in dashboard"
|
|
||||||
buttonText="Need help with this chart?"
|
|
||||||
// message={chartHelpMessage(selectedDashboard, graphType)}
|
|
||||||
onHoverText="Click here to get help with this dashboard widget"
|
|
||||||
intercomMessageDisabled
|
|
||||||
/>
|
|
||||||
<TextToolTip
|
<TextToolTip
|
||||||
text="This will temporarily save the current query and graph state. This will persist across tab change"
|
text="This will temporarily save the current query and graph state. This will persist across tab change"
|
||||||
url="https://signoz.io/docs/userguide/query-builder?utm_source=product&utm_medium=query-builder"
|
url="https://signoz.io/docs/userguide/query-builder?utm_source=product&utm_medium=query-builder"
|
||||||
|
|||||||
@@ -297,6 +297,16 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invalid-unit {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Giest Mono';
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: 0.48px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.threshold-card-container:hover {
|
.threshold-card-container:hover {
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ import './Threshold.styles.scss';
|
|||||||
|
|
||||||
import { Button, Input, InputNumber, Select, Space, Typography } from 'antd';
|
import { Button, Input, InputNumber, Select, Space, Typography } from 'antd';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { unitOptions } from 'container/NewWidget/utils';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { Check, Pencil, Trash2, X } from 'lucide-react';
|
import { Check, Pencil, Trash2, X } from 'lucide-react';
|
||||||
import { useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
operatorOptions,
|
operatorOptions,
|
||||||
panelTypeVsDragAndDrop,
|
panelTypeVsDragAndDrop,
|
||||||
showAsOptions,
|
showAsOptions,
|
||||||
unitOptions,
|
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
import { convertUnit } from '../dataFormatCategories';
|
||||||
import ColorSelector from './ColorSelector';
|
import ColorSelector from './ColorSelector';
|
||||||
import CustomColor from './CustomColor';
|
import CustomColor from './CustomColor';
|
||||||
import ShowCaseValue from './ShowCaseValue';
|
import ShowCaseValue from './ShowCaseValue';
|
||||||
@@ -40,6 +41,7 @@ function Threshold({
|
|||||||
thresholdLabel = '',
|
thresholdLabel = '',
|
||||||
tableOptions,
|
tableOptions,
|
||||||
thresholdTableOptions = '',
|
thresholdTableOptions = '',
|
||||||
|
columnUnits,
|
||||||
}: ThresholdProps): JSX.Element {
|
}: ThresholdProps): JSX.Element {
|
||||||
const [isEditMode, setIsEditMode] = useState<boolean>(isEditEnabled);
|
const [isEditMode, setIsEditMode] = useState<boolean>(isEditEnabled);
|
||||||
const [operator, setOperator] = useState<string | number>(
|
const [operator, setOperator] = useState<string | number>(
|
||||||
@@ -192,6 +194,13 @@ function Threshold({
|
|||||||
|
|
||||||
const allowDragAndDrop = panelTypeVsDragAndDrop[selectedGraph];
|
const allowDragAndDrop = panelTypeVsDragAndDrop[selectedGraph];
|
||||||
|
|
||||||
|
const isInvalidUnitComparison = useMemo(
|
||||||
|
() =>
|
||||||
|
unit !== 'none' &&
|
||||||
|
convertUnit(value, unit, columnUnits?.[tableSelectedOption]) === null,
|
||||||
|
[unit, value, columnUnits, tableSelectedOption],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={allowDragAndDrop ? ref : null}
|
ref={allowDragAndDrop ? ref : null}
|
||||||
@@ -303,7 +312,7 @@ function Threshold({
|
|||||||
{isEditMode ? (
|
{isEditMode ? (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={unit}
|
defaultValue={unit}
|
||||||
options={unitOptions}
|
options={unitOptions(columnUnits?.[tableSelectedOption] || '')}
|
||||||
onChange={handleUnitChange}
|
onChange={handleUnitChange}
|
||||||
showSearch
|
showSearch
|
||||||
className="unit-selection"
|
className="unit-selection"
|
||||||
@@ -339,6 +348,12 @@ function Threshold({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{isInvalidUnitComparison && (
|
||||||
|
<Typography.Text className="invalid-unit">
|
||||||
|
Threshold unit ({unit}) is not valid in comparison with the column unit (
|
||||||
|
{columnUnits?.[tableSelectedOption] || 'none'})
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
{isEditMode && (
|
{isEditMode && (
|
||||||
<div className="threshold-action-button">
|
<div className="threshold-action-button">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
import './ThresholdSelector.styles.scss';
|
import './ThresholdSelector.styles.scss';
|
||||||
|
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { Events } from 'constants/events';
|
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|
||||||
import { Antenna, Plus } from 'lucide-react';
|
import { Antenna, Plus } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import { eventEmitter } from 'utils/getEventEmitter';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import Threshold from './Threshold';
|
import Threshold from './Threshold';
|
||||||
@@ -21,22 +19,23 @@ function ThresholdSelector({
|
|||||||
setThresholds,
|
setThresholds,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
|
columnUnits,
|
||||||
}: ThresholdSelectorProps): JSX.Element {
|
}: ThresholdSelectorProps): JSX.Element {
|
||||||
const [tableOptions, setTableOptions] = useState<
|
const { currentQuery } = useQueryBuilder();
|
||||||
Array<{ value: string; label: string }>
|
|
||||||
>([]);
|
function getAggregateColumnsNamesAndLabels(): string[] {
|
||||||
useEffect(() => {
|
if (currentQuery.queryType === EQueryType.QUERY_BUILDER) {
|
||||||
eventEmitter.on(
|
const queries = currentQuery.builder.queryData.map((q) => q.queryName);
|
||||||
Events.TABLE_COLUMNS_DATA,
|
const formulas = currentQuery.builder.queryFormulas.map((q) => q.queryName);
|
||||||
(data: { columns: ColumnsType<RowData>; dataSource: RowData[] }) => {
|
return [...queries, ...formulas];
|
||||||
const newTableOptions = data.columns.map((e) => ({
|
}
|
||||||
value: e.title as string,
|
if (currentQuery.queryType === EQueryType.CLICKHOUSE) {
|
||||||
label: e.title as string,
|
return currentQuery.clickhouse_sql.map((q) => q.name);
|
||||||
}));
|
}
|
||||||
setTableOptions([...newTableOptions]);
|
return currentQuery.promql.map((q) => q.name);
|
||||||
},
|
}
|
||||||
);
|
|
||||||
}, []);
|
const aggregationQueries = getAggregateColumnsNamesAndLabels();
|
||||||
|
|
||||||
const moveThreshold = useCallback(
|
const moveThreshold = useCallback(
|
||||||
(dragIndex: number, hoverIndex: number) => {
|
(dragIndex: number, hoverIndex: number) => {
|
||||||
@@ -66,7 +65,7 @@ function ThresholdSelector({
|
|||||||
moveThreshold,
|
moveThreshold,
|
||||||
keyIndex: thresholds.length,
|
keyIndex: thresholds.length,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
thresholdTableOptions: tableOptions[0]?.value || '',
|
thresholdTableOptions: aggregationQueries[0] || '',
|
||||||
},
|
},
|
||||||
...thresholds,
|
...thresholds,
|
||||||
]);
|
]);
|
||||||
@@ -105,8 +104,12 @@ function ThresholdSelector({
|
|||||||
moveThreshold={moveThreshold}
|
moveThreshold={moveThreshold}
|
||||||
selectedGraph={selectedGraph}
|
selectedGraph={selectedGraph}
|
||||||
thresholdLabel={threshold.thresholdLabel}
|
thresholdLabel={threshold.thresholdLabel}
|
||||||
tableOptions={tableOptions}
|
tableOptions={aggregationQueries.map((query) => ({
|
||||||
|
value: query,
|
||||||
|
label: query,
|
||||||
|
}))}
|
||||||
thresholdTableOptions={threshold.thresholdTableOptions}
|
thresholdTableOptions={threshold.thresholdTableOptions}
|
||||||
|
columnUnits={columnUnits}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||||
|
import { ColumnUnit } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export type ThresholdOperators = '>' | '<' | '>=' | '<=' | '=';
|
export type ThresholdOperators = '>' | '<' | '>=' | '<=' | '=';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export type ThresholdProps = {
|
|||||||
moveThreshold: (dragIndex: number, hoverIndex: number) => void;
|
moveThreshold: (dragIndex: number, hoverIndex: number) => void;
|
||||||
selectedGraph: PANEL_TYPES;
|
selectedGraph: PANEL_TYPES;
|
||||||
tableOptions?: Array<{ value: string; label: string }>;
|
tableOptions?: Array<{ value: string; label: string }>;
|
||||||
|
columnUnits?: ColumnUnit;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShowCaseValueProps = {
|
export type ShowCaseValueProps = {
|
||||||
@@ -36,4 +38,5 @@ export type ThresholdSelectorProps = {
|
|||||||
thresholds: ThresholdProps[];
|
thresholds: ThresholdProps[];
|
||||||
setThresholds: Dispatch<SetStateAction<ThresholdProps[]>>;
|
setThresholds: Dispatch<SetStateAction<ThresholdProps[]>>;
|
||||||
selectedGraph: PANEL_TYPES;
|
selectedGraph: PANEL_TYPES;
|
||||||
|
columnUnits: ColumnUnit;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { DefaultOptionType } from 'antd/es/select';
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
|
||||||
|
|
||||||
import { getCategorySelectOptionByName } from './alertFomatCategories';
|
|
||||||
|
|
||||||
export const operatorOptions: DefaultOptionType[] = [
|
export const operatorOptions: DefaultOptionType[] = [
|
||||||
{ value: '>', label: '>' },
|
{ value: '>', label: '>' },
|
||||||
@@ -11,11 +8,6 @@ export const operatorOptions: DefaultOptionType[] = [
|
|||||||
{ value: '<=', label: '<=' },
|
{ value: '<=', label: '<=' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const unitOptions = categoryToSupport.map((category) => ({
|
|
||||||
label: category,
|
|
||||||
options: getCategorySelectOptionByName(category),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const showAsOptions: DefaultOptionType[] = [
|
export const showAsOptions: DefaultOptionType[] = [
|
||||||
{ value: 'Text', label: 'Text' },
|
{ value: 'Text', label: 'Text' },
|
||||||
{ value: 'Background', label: 'Background' },
|
{ value: 'Background', label: 'Background' },
|
||||||
|
|||||||
@@ -438,3 +438,168 @@ export const dataTypeCategories: DataTypeCategories = [
|
|||||||
export const flattenedCategories = flattenDeep(
|
export const flattenedCategories = flattenDeep(
|
||||||
dataTypeCategories.map((category) => category.formats),
|
dataTypeCategories.map((category) => category.formats),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type ConversionFactors = {
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: number | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Object containing conversion factors for various categories and formats
|
||||||
|
const conversionFactors: ConversionFactors = {
|
||||||
|
[CategoryNames.Time]: {
|
||||||
|
[TimeFormats.Hertz]: 1,
|
||||||
|
[TimeFormats.Nanoseconds]: 1e-9,
|
||||||
|
[TimeFormats.Microseconds]: 1e-6,
|
||||||
|
[TimeFormats.Milliseconds]: 1e-3,
|
||||||
|
[TimeFormats.Seconds]: 1,
|
||||||
|
[TimeFormats.Minutes]: 60,
|
||||||
|
[TimeFormats.Hours]: 3600,
|
||||||
|
[TimeFormats.Days]: 86400,
|
||||||
|
[TimeFormats.DurationMs]: 1e-3,
|
||||||
|
[TimeFormats.DurationS]: 1,
|
||||||
|
[TimeFormats.DurationHms]: null, // Requires special handling
|
||||||
|
[TimeFormats.DurationDhms]: null, // Requires special handling
|
||||||
|
[TimeFormats.Timeticks]: null, // Requires special handling
|
||||||
|
[TimeFormats.ClockMs]: 1e-3,
|
||||||
|
[TimeFormats.ClockS]: 1,
|
||||||
|
},
|
||||||
|
[CategoryNames.Throughput]: {
|
||||||
|
[ThroughputFormats.CountsPerSec]: 1,
|
||||||
|
[ThroughputFormats.OpsPerSec]: 1,
|
||||||
|
[ThroughputFormats.RequestsPerSec]: 1,
|
||||||
|
[ThroughputFormats.ReadsPerSec]: 1,
|
||||||
|
[ThroughputFormats.WritesPerSec]: 1,
|
||||||
|
[ThroughputFormats.IOOpsPerSec]: 1,
|
||||||
|
[ThroughputFormats.CountsPerMin]: 1 / 60,
|
||||||
|
[ThroughputFormats.OpsPerMin]: 1 / 60,
|
||||||
|
[ThroughputFormats.ReadsPerMin]: 1 / 60,
|
||||||
|
[ThroughputFormats.WritesPerMin]: 1 / 60,
|
||||||
|
},
|
||||||
|
[CategoryNames.Data]: {
|
||||||
|
[DataFormats.BytesIEC]: 1,
|
||||||
|
[DataFormats.BytesSI]: 1,
|
||||||
|
[DataFormats.BitsIEC]: 0.125,
|
||||||
|
[DataFormats.BitsSI]: 0.125,
|
||||||
|
[DataFormats.KibiBytes]: 1024,
|
||||||
|
[DataFormats.KiloBytes]: 1000,
|
||||||
|
[DataFormats.MebiBytes]: 1048576,
|
||||||
|
[DataFormats.MegaBytes]: 1000000,
|
||||||
|
[DataFormats.GibiBytes]: 1073741824,
|
||||||
|
[DataFormats.GigaBytes]: 1000000000,
|
||||||
|
[DataFormats.TebiBytes]: 1099511627776,
|
||||||
|
[DataFormats.TeraBytes]: 1000000000000,
|
||||||
|
[DataFormats.PebiBytes]: 1125899906842624,
|
||||||
|
[DataFormats.PetaBytes]: 1000000000000000,
|
||||||
|
},
|
||||||
|
[CategoryNames.DataRate]: {
|
||||||
|
[DataRateFormats.PacketsPerSec]: null, // Cannot convert directly to other data rates
|
||||||
|
[DataRateFormats.BytesPerSecIEC]: 1,
|
||||||
|
[DataRateFormats.BytesPerSecSI]: 1,
|
||||||
|
[DataRateFormats.BitsPerSecIEC]: 0.125,
|
||||||
|
[DataRateFormats.BitsPerSecSI]: 0.125,
|
||||||
|
[DataRateFormats.KibiBytesPerSec]: 1024,
|
||||||
|
[DataRateFormats.KibiBitsPerSec]: 128,
|
||||||
|
[DataRateFormats.KiloBytesPerSec]: 1000,
|
||||||
|
[DataRateFormats.KiloBitsPerSec]: 125,
|
||||||
|
[DataRateFormats.MebiBytesPerSec]: 1048576,
|
||||||
|
[DataRateFormats.MebiBitsPerSec]: 131072,
|
||||||
|
[DataRateFormats.MegaBytesPerSec]: 1000000,
|
||||||
|
[DataRateFormats.MegaBitsPerSec]: 125000,
|
||||||
|
[DataRateFormats.GibiBytesPerSec]: 1073741824,
|
||||||
|
[DataRateFormats.GibiBitsPerSec]: 134217728,
|
||||||
|
[DataRateFormats.GigaBytesPerSec]: 1000000000,
|
||||||
|
[DataRateFormats.GigaBitsPerSec]: 125000000,
|
||||||
|
[DataRateFormats.TebiBytesPerSec]: 1099511627776,
|
||||||
|
[DataRateFormats.TebiBitsPerSec]: 137438953472,
|
||||||
|
[DataRateFormats.TeraBytesPerSec]: 1000000000000,
|
||||||
|
[DataRateFormats.TeraBitsPerSec]: 125000000000,
|
||||||
|
[DataRateFormats.PebiBytesPerSec]: 1125899906842624,
|
||||||
|
[DataRateFormats.PebiBitsPerSec]: 140737488355328,
|
||||||
|
[DataRateFormats.PetaBytesPerSec]: 1000000000000000,
|
||||||
|
[DataRateFormats.PetaBitsPerSec]: 125000000000000,
|
||||||
|
},
|
||||||
|
[CategoryNames.Miscellaneous]: {
|
||||||
|
[MiscellaneousFormats.None]: null,
|
||||||
|
[MiscellaneousFormats.String]: null,
|
||||||
|
[MiscellaneousFormats.Short]: null,
|
||||||
|
[MiscellaneousFormats.Percent]: 1,
|
||||||
|
[MiscellaneousFormats.PercentUnit]: 100,
|
||||||
|
[MiscellaneousFormats.Humidity]: 1,
|
||||||
|
[MiscellaneousFormats.Decibel]: null,
|
||||||
|
[MiscellaneousFormats.Hexadecimal0x]: null,
|
||||||
|
[MiscellaneousFormats.Hexadecimal]: null,
|
||||||
|
[MiscellaneousFormats.ScientificNotation]: null,
|
||||||
|
[MiscellaneousFormats.LocaleFormat]: null,
|
||||||
|
[MiscellaneousFormats.Pixels]: null,
|
||||||
|
},
|
||||||
|
[CategoryNames.Boolean]: {
|
||||||
|
[BooleanFormats.TRUE_FALSE]: null, // Not convertible
|
||||||
|
[BooleanFormats.YES_NO]: null, // Not convertible
|
||||||
|
[BooleanFormats.ON_OFF]: null, // Not convertible
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to get the conversion factor between two units in a specific category
|
||||||
|
function getConversionFactor(
|
||||||
|
fromUnit: string,
|
||||||
|
toUnit: string,
|
||||||
|
category: CategoryNames,
|
||||||
|
): number | null {
|
||||||
|
// Retrieves the conversion factors for the specified category
|
||||||
|
const categoryFactors = conversionFactors[category];
|
||||||
|
if (!categoryFactors) {
|
||||||
|
return null; // Returns null if the category does not exist
|
||||||
|
}
|
||||||
|
const fromFactor = categoryFactors[fromUnit];
|
||||||
|
const toFactor = categoryFactors[toUnit];
|
||||||
|
if (
|
||||||
|
fromFactor === undefined ||
|
||||||
|
toFactor === undefined ||
|
||||||
|
fromFactor === null ||
|
||||||
|
toFactor === null
|
||||||
|
) {
|
||||||
|
return null; // Returns null if either unit does not exist or is not convertible
|
||||||
|
}
|
||||||
|
return fromFactor / toFactor; // Returns the conversion factor ratio
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to convert a value from one unit to another
|
||||||
|
export function convertUnit(
|
||||||
|
value: number,
|
||||||
|
fromUnitId?: string,
|
||||||
|
toUnitId?: string,
|
||||||
|
): number | null {
|
||||||
|
let fromUnit: string | undefined;
|
||||||
|
let toUnit: string | undefined;
|
||||||
|
|
||||||
|
// Finds the category that contains the specified units and extracts fromUnit and toUnit using array methods
|
||||||
|
const category = dataTypeCategories.find((category) =>
|
||||||
|
category.formats.some((format) => {
|
||||||
|
if (format.id === fromUnitId) fromUnit = format.id;
|
||||||
|
if (format.id === toUnitId) toUnit = format.id;
|
||||||
|
return fromUnit && toUnit; // Break out early if both units are found
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!category || !fromUnit || !toUnit) return null; // Return null if category or units are not found
|
||||||
|
|
||||||
|
// Gets the conversion factor for the specified units
|
||||||
|
const conversionFactor = getConversionFactor(
|
||||||
|
fromUnit,
|
||||||
|
toUnit,
|
||||||
|
category.name as any,
|
||||||
|
);
|
||||||
|
if (conversionFactor === null) return null; // Return null if conversion is not possible
|
||||||
|
|
||||||
|
return value * conversionFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get the category name for a given unit ID
|
||||||
|
export const getCategoryName = (unitId: string): CategoryNames | null => {
|
||||||
|
// Finds the category that contains the specified unit ID
|
||||||
|
const foundCategory = dataTypeCategories.find((category) =>
|
||||||
|
category.formats.some((format) => format.id === unitId),
|
||||||
|
);
|
||||||
|
return foundCategory ? (foundCategory.name as CategoryNames) : null;
|
||||||
|
};
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ function RightContainer({
|
|||||||
setThresholds={setThresholds}
|
setThresholds={setThresholds}
|
||||||
yAxisUnit={yAxisUnit}
|
yAxisUnit={yAxisUnit}
|
||||||
selectedGraph={selectedGraph}
|
selectedGraph={selectedGraph}
|
||||||
|
columnUnits={columnUnits}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
||||||
import {
|
import {
|
||||||
initialQueryBuilderFormValuesMap,
|
initialQueryBuilderFormValuesMap,
|
||||||
@@ -8,12 +9,19 @@ import {
|
|||||||
listViewInitialTraceQuery,
|
listViewInitialTraceQuery,
|
||||||
PANEL_TYPES_INITIAL_QUERY,
|
PANEL_TYPES_INITIAL_QUERY,
|
||||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
} from 'container/NewDashboard/ComponentsSlider/constants';
|
||||||
import { cloneDeep, isEqual, set, unset } from 'lodash-es';
|
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||||
|
import { cloneDeep, isEmpty, isEqual, set, unset } from 'lodash-es';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import {
|
||||||
|
dataTypeCategories,
|
||||||
|
getCategoryName,
|
||||||
|
} from './RightContainer/dataFormatCategories';
|
||||||
|
import { CategoryNames } from './RightContainer/types';
|
||||||
|
|
||||||
export const getIsQueryModified = (
|
export const getIsQueryModified = (
|
||||||
currentQuery: Query,
|
currentQuery: Query,
|
||||||
stagedQuery: Query | null,
|
stagedQuery: Query | null,
|
||||||
@@ -529,3 +537,41 @@ export const PANEL_TYPE_TO_QUERY_TYPES: Record<PANEL_TYPES, EQueryType[]> = {
|
|||||||
EQueryType.PROM,
|
EQueryType.PROM,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of category select options based on the provided category name.
|
||||||
|
* If the category is found, it maps the formats to an array of objects containing
|
||||||
|
* the label and value for each format.
|
||||||
|
*/
|
||||||
|
export const getCategorySelectOptionByName = (
|
||||||
|
name?: CategoryNames | string,
|
||||||
|
): DefaultOptionType[] =>
|
||||||
|
dataTypeCategories
|
||||||
|
.find((category) => category.name === name)
|
||||||
|
?.formats.map((format) => ({
|
||||||
|
label: format.name,
|
||||||
|
value: format.id,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates unit options based on the provided column unit.
|
||||||
|
* It first retrieves the category name associated with the column unit.
|
||||||
|
* If the category is empty, it maps all supported categories to their respective
|
||||||
|
* select options. If a valid category is found, it filters the supported categories
|
||||||
|
* to return only the options for the matched category.
|
||||||
|
*/
|
||||||
|
export const unitOptions = (columnUnit: string): DefaultOptionType[] => {
|
||||||
|
const category = getCategoryName(columnUnit);
|
||||||
|
if (isEmpty(category)) {
|
||||||
|
return categoryToSupport.map((category) => ({
|
||||||
|
label: category,
|
||||||
|
options: getCategorySelectOptionByName(category),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return categoryToSupport
|
||||||
|
.filter((supportedCategory) => supportedCategory === category)
|
||||||
|
.map((filteredCategory) => ({
|
||||||
|
label: filteredCategory,
|
||||||
|
options: getCategorySelectOptionByName(filteredCategory),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Once you are done instrumenting your Java application, you can run it using the below commands
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
- Ensure you have Java and Maven installed. Compile your Java consumer applications: Ensure your consumer apps are compiled and ready to run.
|
||||||
|
|
||||||
|
**Run Consumer App with Java Agent:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -javaagent:/path/to/opentelemetry-javaagent.jar \
|
||||||
|
-Dotel.service.name=consumer-svc \
|
||||||
|
-Dotel.traces.exporter=otlp \
|
||||||
|
-Dotel.metrics.exporter=otlp \
|
||||||
|
-Dotel.logs.exporter=otlp \
|
||||||
|
-Dotel.instrumentation.kafka.producer-propagation.enabled=true \
|
||||||
|
-Dotel.instrumentation.kafka.experimental-span-attributes=true \
|
||||||
|
-Dotel.instrumentation.kafka.metric-reporter.enabled=true \
|
||||||
|
-jar /path/to/your/consumer.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
<path> - update it to the path where you downloaded the Java JAR agent in previous step
|
||||||
|
<my-app> - Jar file of your application
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Once you are done instrumenting your Java application, you can run it using the below commands
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
- Ensure you have Java and Maven installed. Compile your Java producer applications: Ensure your producer apps are compiled and ready to run.
|
||||||
|
|
||||||
|
**Run Producer App with Java Agent:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
java -javaagent:/path/to/opentelemetry-javaagent.jar \
|
||||||
|
-Dotel.service.name=producer-svc \
|
||||||
|
-Dotel.traces.exporter=otlp \
|
||||||
|
-Dotel.metrics.exporter=otlp \
|
||||||
|
-Dotel.logs.exporter=otlp \
|
||||||
|
-jar /path/to/your/producer.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
<path> - update it to the path where you downloaded the Java JAR agent in previous step
|
||||||
|
<my-app> - Jar file of your application
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance.
|
||||||
@@ -60,7 +60,7 @@ This is a **sample cURL request** which can be used as a template:
|
|||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl --location 'https://ingest.{{REGION}}.signoz.cloud:443/logs/json/' \
|
curl --location 'https://ingest.{{REGION}}.signoz.cloud:443/logs/json' \
|
||||||
--header 'Content-Type: application/json' \
|
--header 'Content-Type: application/json' \
|
||||||
--header 'signoz-access-token: {{SIGNOZ_INGESTION_KEY}}' \
|
--header 'signoz-access-token: {{SIGNOZ_INGESTION_KEY}}' \
|
||||||
--data '[
|
--data '[
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
|
|||||||
<div
|
<div
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
logEvent('Onboarding V2: Skip Button Clicked', {});
|
logEvent('Onboarding V2: Skip Button Clicked', {});
|
||||||
history.push('/');
|
history.push(ROUTES.APPLICATION);
|
||||||
}}
|
}}
|
||||||
className="skip-to-console"
|
className="skip-to-console"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import {
|
|||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { QueryParams } from 'constants/query';
|
||||||
import Header from 'container/OnboardingContainer/common/Header/Header';
|
import Header from 'container/OnboardingContainer/common/Header/Header';
|
||||||
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
||||||
|
import { useOnboardingStatus } from 'hooks/messagingQueue / onboarding/useOnboardingStatus';
|
||||||
import { useQueryService } from 'hooks/useQueryService';
|
import { useQueryService } from 'hooks/useQueryService';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import MessagingQueueHealthCheck from 'pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck';
|
||||||
|
import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@@ -27,6 +32,12 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
const getStartedSource = urlQuery.get(QueryParams.getStartedSource);
|
||||||
|
const getStartedSourceService = urlQuery.get(
|
||||||
|
QueryParams.getStartedSourceService,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
serviceName,
|
serviceName,
|
||||||
selectedDataSource,
|
selectedDataSource,
|
||||||
@@ -57,8 +68,69 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
maxTime,
|
maxTime,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
selectedTags,
|
selectedTags,
|
||||||
|
options: {
|
||||||
|
enabled: getStartedSource !== 'kafka',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [pollInterval, setPollInterval] = useState<number | false>(10000);
|
||||||
|
const {
|
||||||
|
data: onbData,
|
||||||
|
error: onbErr,
|
||||||
|
isFetching: onbFetching,
|
||||||
|
} = useOnboardingStatus(
|
||||||
|
{
|
||||||
|
enabled: getStartedSource === 'kafka',
|
||||||
|
refetchInterval: pollInterval,
|
||||||
|
},
|
||||||
|
getStartedSourceService || '',
|
||||||
|
'query-key-onboarding-status',
|
||||||
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
shouldRetryOnboardingCall,
|
||||||
|
setShouldRetryOnboardingCall,
|
||||||
|
] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// runs only when the caller is coming from 'kafka' i.e. coming from Messaging Queues - setup helper
|
||||||
|
if (getStartedSource === 'kafka') {
|
||||||
|
if (onbData?.statusCode !== 200) {
|
||||||
|
setShouldRetryOnboardingCall(true);
|
||||||
|
} else if (onbData?.payload?.status === 'success') {
|
||||||
|
const attributeData = getAttributeDataFromOnboardingStatus(
|
||||||
|
onbData?.payload,
|
||||||
|
);
|
||||||
|
if (attributeData.overallStatus === 'success') {
|
||||||
|
setLoading(false);
|
||||||
|
setIsReceivingData(true);
|
||||||
|
setPollInterval(false);
|
||||||
|
} else {
|
||||||
|
setShouldRetryOnboardingCall(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
shouldRetryOnboardingCall,
|
||||||
|
onbData,
|
||||||
|
onbErr,
|
||||||
|
onbFetching,
|
||||||
|
getStartedSource,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (retryCount < 0 && getStartedSource === 'kafka') {
|
||||||
|
setPollInterval(false);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [retryCount, getStartedSource]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getStartedSource === 'kafka' && !onbFetching) {
|
||||||
|
setRetryCount((prevCount) => prevCount - 1);
|
||||||
|
}
|
||||||
|
}, [getStartedSource, onbData, onbFetching]);
|
||||||
|
|
||||||
const renderDocsReference = (): JSX.Element => {
|
const renderDocsReference = (): JSX.Element => {
|
||||||
switch (selectedDataSource?.name) {
|
switch (selectedDataSource?.name) {
|
||||||
case 'java':
|
case 'java':
|
||||||
@@ -192,25 +264,27 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let pollingTimer: string | number | NodeJS.Timer | undefined;
|
let pollingTimer: string | number | NodeJS.Timer | undefined;
|
||||||
|
|
||||||
if (loading) {
|
if (getStartedSource !== 'kafka') {
|
||||||
pollingTimer = setInterval(() => {
|
if (loading) {
|
||||||
// Trigger a refetch with the updated parameters
|
pollingTimer = setInterval(() => {
|
||||||
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
// Trigger a refetch with the updated parameters
|
||||||
const updatedMaxTime = Date.now() * 1000000;
|
const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
||||||
|
const updatedMaxTime = Date.now() * 1000000;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
maxTime: updatedMaxTime,
|
maxTime: updatedMaxTime,
|
||||||
minTime: updatedMinTime,
|
minTime: updatedMinTime,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UPDATE_TIME_INTERVAL,
|
type: UPDATE_TIME_INTERVAL,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
}, pollingInterval); // Same interval as pollingInterval
|
}, pollingInterval); // Same interval as pollingInterval
|
||||||
} else if (!loading && pollingTimer) {
|
} else if (!loading && pollingTimer) {
|
||||||
clearInterval(pollingTimer);
|
clearInterval(pollingTimer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the interval when the component unmounts
|
// Clean up the interval when the component unmounts
|
||||||
@@ -221,15 +295,24 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
}, [refetch, selectedTags, selectedTime, loading]);
|
}, [refetch, selectedTags, selectedTime, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
verifyApplicationData(data);
|
if (getStartedSource !== 'kafka') {
|
||||||
|
verifyApplicationData(data);
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isServiceLoading, data, error, isError]);
|
}, [isServiceLoading, data, error, isError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetch();
|
if (getStartedSource !== 'kafka') {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isQueryServiceLoading = useMemo(
|
||||||
|
() => isServiceLoading || loading || onbFetching,
|
||||||
|
[isServiceLoading, loading, onbFetching],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="connection-status-container">
|
<div className="connection-status-container">
|
||||||
<div className="full-docs-link">{renderDocsReference()}</div>
|
<div className="full-docs-link">{renderDocsReference()}</div>
|
||||||
@@ -250,30 +333,42 @@ export default function ConnectionStatus(): JSX.Element {
|
|||||||
<div className="label"> Status </div>
|
<div className="label"> Status </div>
|
||||||
|
|
||||||
<div className="status">
|
<div className="status">
|
||||||
{(loading || isServiceLoading) && <LoadingOutlined />}
|
{isQueryServiceLoading && <LoadingOutlined />}
|
||||||
{!(loading || isServiceLoading) && isReceivingData && (
|
{!isQueryServiceLoading &&
|
||||||
<>
|
isReceivingData &&
|
||||||
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
(getStartedSource !== 'kafka' ? (
|
||||||
<span> Success </span>
|
<>
|
||||||
</>
|
<CheckCircleTwoTone twoToneColor="#52c41a" />
|
||||||
)}
|
<span> Success </span>
|
||||||
{!(loading || isServiceLoading) && !isReceivingData && (
|
</>
|
||||||
<>
|
) : (
|
||||||
<CloseCircleTwoTone twoToneColor="#e84749" />
|
<MessagingQueueHealthCheck
|
||||||
<span> Failed </span>
|
serviceToInclude={[getStartedSourceService || '']}
|
||||||
</>
|
/>
|
||||||
)}
|
))}
|
||||||
|
{!isQueryServiceLoading &&
|
||||||
|
!isReceivingData &&
|
||||||
|
(getStartedSource !== 'kafka' ? (
|
||||||
|
<>
|
||||||
|
<CloseCircleTwoTone twoToneColor="#e84749" />
|
||||||
|
<span> Failed </span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<MessagingQueueHealthCheck
|
||||||
|
serviceToInclude={[getStartedSourceService || '']}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="details-info">
|
<div className="details-info">
|
||||||
<div className="label"> Details </div>
|
<div className="label"> Details </div>
|
||||||
|
|
||||||
<div className="details">
|
<div className="details">
|
||||||
{(loading || isServiceLoading) && <div> Waiting for Update </div>}
|
{isQueryServiceLoading && <div> Waiting for Update </div>}
|
||||||
{!(loading || isServiceLoading) && isReceivingData && (
|
{!isQueryServiceLoading && isReceivingData && (
|
||||||
<div> Received data from the application successfully. </div>
|
<div> Received data from the application successfully. </div>
|
||||||
)}
|
)}
|
||||||
{!(loading || isServiceLoading) && !isReceivingData && (
|
{!isQueryServiceLoading && !isReceivingData && (
|
||||||
<div> Could not detect the install </div>
|
<div> Could not detect the install </div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user