mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 11:55:52 +08:00
commit
262beef8f9
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@ -5,6 +5,6 @@
|
|||||||
/frontend/ @YounixM
|
/frontend/ @YounixM
|
||||||
/frontend/src/container/MetricsApplication @srikanthccv
|
/frontend/src/container/MetricsApplication @srikanthccv
|
||||||
/frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv
|
/frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv
|
||||||
/deploy/ @prashant-shahi
|
/deploy/ @SigNoz/devops
|
||||||
/sample-apps/ @prashant-shahi
|
/sample-apps/ @SigNoz/devops
|
||||||
.github @prashant-shahi
|
.github @SigNoz/devops
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -67,3 +67,6 @@ e2e/.auth
|
|||||||
# go
|
# go
|
||||||
vendor/
|
vendor/
|
||||||
**/main/**
|
**/main/**
|
||||||
|
|
||||||
|
# git-town
|
||||||
|
.git-branches.toml
|
||||||
|
@ -649,12 +649,12 @@
|
|||||||
|
|
||||||
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/#creating-replicated-tables
|
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/#creating-replicated-tables
|
||||||
-->
|
-->
|
||||||
<!--
|
|
||||||
<macros>
|
<macros>
|
||||||
<shard>01</shard>
|
<shard>01</shard>
|
||||||
<replica>example01-01-1</replica>
|
<replica>example01-01-1</replica>
|
||||||
</macros>
|
</macros>
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
|
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
|
||||||
|
@ -146,7 +146,7 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.52.0
|
image: signoz/query-service:0.53.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.52.0
|
image: signoz/frontend:0.53.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.4
|
image: signoz/signoz-otel-collector:0.102.7
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@ -238,7 +238,7 @@ services:
|
|||||||
- query-service
|
- query-service
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:0.102.4
|
image: signoz/signoz-schema-migrator:0.102.7
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
@ -649,12 +649,12 @@
|
|||||||
|
|
||||||
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/#creating-replicated-tables
|
See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replication/#creating-replicated-tables
|
||||||
-->
|
-->
|
||||||
<!--
|
|
||||||
<macros>
|
<macros>
|
||||||
<shard>01</shard>
|
<shard>01</shard>
|
||||||
<replica>example01-01-1</replica>
|
<replica>example01-01-1</replica>
|
||||||
</macros>
|
</macros>
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
|
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
|
||||||
|
@ -66,7 +66,7 @@ services:
|
|||||||
- --storage.path=/data
|
- --storage.path=/data
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.4}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.7}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@ -81,7 +81,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
otel-collector:
|
otel-collector:
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
image: signoz/signoz-otel-collector:0.102.4
|
image: signoz/signoz-otel-collector:0.102.7
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
|
@ -164,7 +164,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.52.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.53.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@ -204,7 +204,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.52.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.53.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -216,7 +216,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.4}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.7}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@ -230,7 +230,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.4}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.7}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
@ -164,7 +164,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.52.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.53.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
@ -203,7 +203,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.52.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.53.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -215,7 +215,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.4}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.7}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@ -229,7 +229,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.4}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.7}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 3301;
|
listen 3301;
|
||||||
server_name _;
|
server_name _;
|
||||||
@ -42,6 +47,14 @@ server {
|
|||||||
proxy_read_timeout 600s;
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://query-service:8080/ws;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade "websocket";
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_read_timeout 86400;
|
||||||
|
}
|
||||||
|
|
||||||
# redirect server error pages to the static page /50x.html
|
# redirect server error pages to the static page /50x.html
|
||||||
#
|
#
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
@ -359,6 +359,8 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||||
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
||||||
|
apiHandler.RegisterWebSocketPaths(r, am)
|
||||||
|
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
@ -375,6 +377,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
// loggingMiddleware is used for logging public api calls
|
// loggingMiddleware is used for logging public api calls
|
||||||
func loggingMiddleware(next http.Handler) http.Handler {
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -386,6 +389,7 @@ func loggingMiddleware(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
// loggingMiddlewarePrivate is used for logging private api calls
|
// loggingMiddlewarePrivate is used for logging private api calls
|
||||||
// from internal services like alert manager
|
// from internal services like alert manager
|
||||||
func loggingMiddlewarePrivate(next http.Handler) http.Handler {
|
func loggingMiddlewarePrivate(next http.Handler) http.Handler {
|
||||||
@ -398,27 +402,32 @@ func loggingMiddlewarePrivate(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
type loggingResponseWriter struct {
|
type loggingResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
statusCode int
|
statusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
|
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
|
||||||
// WriteHeader(int) is not called if our response implicitly returns 200 OK, so
|
// WriteHeader(int) is not called if our response implicitly returns 200 OK, so
|
||||||
// we default to that status code.
|
// we default to that status code.
|
||||||
return &loggingResponseWriter{w, http.StatusOK}
|
return &loggingResponseWriter{w, http.StatusOK}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||||
lrw.statusCode = code
|
lrw.statusCode = code
|
||||||
lrw.ResponseWriter.WriteHeader(code)
|
lrw.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
// Flush implements the http.Flush interface.
|
// Flush implements the http.Flush interface.
|
||||||
func (lrw *loggingResponseWriter) Flush() {
|
func (lrw *loggingResponseWriter) Flush() {
|
||||||
lrw.ResponseWriter.(http.Flusher).Flush()
|
lrw.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/logging.go
|
||||||
// Support websockets
|
// Support websockets
|
||||||
func (lrw *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (lrw *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
h, ok := lrw.ResponseWriter.(http.Hijacker)
|
h, ok := lrw.ResponseWriter.(http.Hijacker)
|
||||||
@ -564,6 +573,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(remove): Implemented at pkg/http/middleware/timeout.go
|
||||||
func setTimeoutMiddleware(next http.Handler) http.Handler {
|
func setTimeoutMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
@ -12,6 +12,7 @@ const DisableUpsell = "DISABLE_UPSELL"
|
|||||||
const Onboarding = "ONBOARDING"
|
const Onboarding = "ONBOARDING"
|
||||||
const ChatSupport = "CHAT_SUPPORT"
|
const ChatSupport = "CHAT_SUPPORT"
|
||||||
const Gateway = "GATEWAY"
|
const Gateway = "GATEWAY"
|
||||||
|
const PremiumSupport = "PREMIUM_SUPPORT"
|
||||||
|
|
||||||
var BasicPlan = basemodel.FeatureSet{
|
var BasicPlan = basemodel.FeatureSet{
|
||||||
basemodel.Feature{
|
basemodel.Feature{
|
||||||
@ -119,6 +120,13 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: false,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ProPlan = basemodel.FeatureSet{
|
var ProPlan = basemodel.FeatureSet{
|
||||||
@ -220,6 +228,13 @@ var ProPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: true,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnterprisePlan = basemodel.FeatureSet{
|
var EnterprisePlan = basemodel.FeatureSet{
|
||||||
@ -335,4 +350,11 @@ var EnterprisePlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: true,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
1
frontend/public/Icons/groupBy.svg
Normal file
1
frontend/public/Icons/groupBy.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#prefix__clip0_4344_1236)" stroke="#C0C1C3" stroke-width="1.167" stroke-linecap="round" stroke-linejoin="round"><path d="M4.667 1.167H2.333c-.644 0-1.166.522-1.166 1.166v2.334c0 .644.522 1.166 1.166 1.166h2.334c.644 0 1.166-.522 1.166-1.166V2.333c0-.644-.522-1.166-1.166-1.166zM8.167 1.167a1.17 1.17 0 011.166 1.166v2.334a1.17 1.17 0 01-1.166 1.166M11.667 1.167a1.17 1.17 0 011.166 1.166v2.334a1.17 1.17 0 01-1.166 1.166M5.833 10.5H2.917c-.992 0-1.75-.758-1.75-1.75v-.583"/><path d="M4.083 12.25l1.75-1.75-1.75-1.75M11.667 8.167H9.333c-.644 0-1.166.522-1.166 1.166v2.334c0 .644.522 1.166 1.166 1.166h2.334c.644 0 1.166-.522 1.166-1.166V9.333c0-.644-.522-1.166-1.166-1.166z"/></g><defs><clipPath id="prefix__clip0_4344_1236"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 878 B |
1
frontend/public/Icons/solid-x-circle.svg
Normal file
1
frontend/public/Icons/solid-x-circle.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#prefix__clip0_4062_7291)" stroke-width="1.167" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12.833A5.833 5.833 0 107 1.167a5.833 5.833 0 000 11.666z" fill="#E5484D" stroke="#E5484D"/><path d="M8.75 5.25l-3.5 3.5M5.25 5.25l3.5 3.5" stroke="#121317"/></g><defs><clipPath id="prefix__clip0_4062_7291"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 467 B |
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"create_dashboard": "Create Dashboard",
|
"create_dashboard": "Create Dashboard",
|
||||||
"import_json": "Import Dashboard JSON",
|
"import_json": "Import Dashboard JSON",
|
||||||
|
"view_template": "View templates",
|
||||||
"import_grafana_json": "Import Grafana JSON",
|
"import_grafana_json": "Import Grafana JSON",
|
||||||
"copy_to_clipboard": "Copy To ClipBoard",
|
"copy_to_clipboard": "Copy To ClipBoard",
|
||||||
"download_json": "Download JSON",
|
"download_json": "Download JSON",
|
||||||
|
@ -49,5 +49,6 @@
|
|||||||
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
||||||
"DEFAULT": "Open source Observability Platform | SigNoz",
|
"DEFAULT": "Open source Observability Platform | SigNoz",
|
||||||
"SHORTCUTS": "SigNoz | Shortcuts",
|
"SHORTCUTS": "SigNoz | Shortcuts",
|
||||||
"INTEGRATIONS": "SigNoz | Integrations"
|
"INTEGRATIONS": "SigNoz | Integrations",
|
||||||
|
"MESSAGING_QUEUES": "SigNoz | Messaging Queues"
|
||||||
}
|
}
|
||||||
|
@ -204,3 +204,15 @@ export const InstalledIntegrations = Loadable(
|
|||||||
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
|
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const MessagingQueues = Loadable(
|
||||||
|
() =>
|
||||||
|
import(/* webpackChunkName: "MessagingQueues" */ 'pages/MessagingQueues'),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MQDetailPage = Loadable(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage'
|
||||||
|
),
|
||||||
|
);
|
||||||
|
@ -23,6 +23,8 @@ import {
|
|||||||
LogsExplorer,
|
LogsExplorer,
|
||||||
LogsIndexToFields,
|
LogsIndexToFields,
|
||||||
LogsSaveViews,
|
LogsSaveViews,
|
||||||
|
MessagingQueues,
|
||||||
|
MQDetailPage,
|
||||||
MySettings,
|
MySettings,
|
||||||
NewDashboardPage,
|
NewDashboardPage,
|
||||||
OldLogsExplorer,
|
OldLogsExplorer,
|
||||||
@ -351,6 +353,20 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'INTEGRATIONS',
|
key: 'INTEGRATIONS',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.MESSAGING_QUEUES,
|
||||||
|
exact: true,
|
||||||
|
component: MessagingQueues,
|
||||||
|
key: 'MESSAGING_QUEUES',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.MESSAGING_QUEUES_DETAIL,
|
||||||
|
exact: true,
|
||||||
|
component: MQDetailPage,
|
||||||
|
key: 'MESSAGING_QUEUES_DETAIL',
|
||||||
|
isPrivate: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SUPPORT_ROUTE: AppRoutes = {
|
export const SUPPORT_ROUTE: AppRoutes = {
|
||||||
|
62
frontend/src/api/common/getQueryStats.ts
Normal file
62
frontend/src/api/common/getQueryStats.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||||
|
import { ENVIRONMENT } from 'constants/env';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
|
export interface WsDataEvent {
|
||||||
|
read_rows: number;
|
||||||
|
read_bytes: number;
|
||||||
|
elapsed_ms: number;
|
||||||
|
}
|
||||||
|
interface GetQueryStatsProps {
|
||||||
|
queryId: string;
|
||||||
|
setData: React.Dispatch<React.SetStateAction<WsDataEvent | undefined>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getURL(baseURL: string, queryId: string): URL | string {
|
||||||
|
if (baseURL && !isEmpty(baseURL)) {
|
||||||
|
return `${baseURL}/ws/query_progress?q=${queryId}`;
|
||||||
|
}
|
||||||
|
const url = new URL(`/ws/query_progress?q=${queryId}`, window.location.href);
|
||||||
|
|
||||||
|
if (window.location.protocol === 'http:') {
|
||||||
|
url.protocol = 'ws';
|
||||||
|
} else {
|
||||||
|
url.protocol = 'wss';
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQueryStats(props: GetQueryStatsProps): void {
|
||||||
|
const { queryId, setData } = props;
|
||||||
|
|
||||||
|
const token = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || '';
|
||||||
|
|
||||||
|
// https://github.com/whatwg/websockets/issues/20 reason for not using the relative URLs
|
||||||
|
const url = getURL(ENVIRONMENT.wsURL, queryId);
|
||||||
|
|
||||||
|
const socket = new WebSocket(url, token);
|
||||||
|
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(event?.data);
|
||||||
|
setData(parsedData);
|
||||||
|
} catch {
|
||||||
|
setData(event?.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('error', (event) => {
|
||||||
|
console.error(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('close', (event) => {
|
||||||
|
// 1000 is a normal closure status code
|
||||||
|
if (event.code !== 1000) {
|
||||||
|
console.error('WebSocket closed with error:', event);
|
||||||
|
} else {
|
||||||
|
console.error('WebSocket closed normally.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import { ApiV2Instance as axios } from 'api';
|
import { ApiV2Instance as axios } from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||||
|
import store from 'store';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import {
|
import {
|
||||||
Props,
|
Props,
|
||||||
@ -11,7 +13,26 @@ const dashboardVariablesQuery = async (
|
|||||||
props: Props,
|
props: Props,
|
||||||
): Promise<SuccessResponse<VariableResponseProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<VariableResponseProps> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`/variables/query`, props);
|
const { globalTime } = store.getState();
|
||||||
|
const { start, end } = getStartEndRangeTime({
|
||||||
|
type: 'GLOBAL_TIME',
|
||||||
|
interval: globalTime.selectedTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeVariables: Record<string, number> = {
|
||||||
|
start_timestamp_ms: parseInt(start, 10) * 1e3,
|
||||||
|
end_timestamp_ms: parseInt(end, 10) * 1e3,
|
||||||
|
start_timestamp_nano: parseInt(start, 10) * 1e9,
|
||||||
|
end_timestamp_nano: parseInt(end, 10) * 1e9,
|
||||||
|
start_timestamp: parseInt(start, 10),
|
||||||
|
end_timestamp: parseInt(end, 10),
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = { ...props };
|
||||||
|
|
||||||
|
payload.variables = { ...payload.variables, ...timeVariables };
|
||||||
|
|
||||||
|
const response = await axios.post(`/variables/query`, payload);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -12,10 +12,13 @@ export const getMetricsQueryRange = async (
|
|||||||
props: QueryRangePayload,
|
props: QueryRangePayload,
|
||||||
version: string,
|
version: string,
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
if (version && version === ENTITY_VERSION_V4) {
|
if (version && version === ENTITY_VERSION_V4) {
|
||||||
const response = await ApiV4Instance.post('/query_range', props, { signal });
|
const response = await ApiV4Instance.post('/query_range', props, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
@ -26,7 +29,10 @@ export const getMetricsQueryRange = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await ApiV3Instance.post('/query_range', props, { signal });
|
const response = await ApiV3Instance.post('/query_range', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { ApiV3Instance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
||||||
|
import { encode } from 'js-base64';
|
||||||
|
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IGetAttributeSuggestionsPayload,
|
||||||
|
IGetAttributeSuggestionsSuccessResponse,
|
||||||
|
} from 'types/api/queryBuilder/getAttributeSuggestions';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const getAttributeSuggestions = async ({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
filters,
|
||||||
|
}: IGetAttributeSuggestionsPayload): Promise<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
let base64EncodedFiltersString;
|
||||||
|
try {
|
||||||
|
// the replace function is to remove the padding at the end of base64 encoded string which is auto added to make it a multiple of 4
|
||||||
|
// why ? because the current working of qs doesn't work well with padding
|
||||||
|
base64EncodedFiltersString = encode(JSON.stringify(filters)).replace(
|
||||||
|
/=+$/,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// default base64 encoded string for empty filters object
|
||||||
|
base64EncodedFiltersString = 'eyJpdGVtcyI6W10sIm9wIjoiQU5EIn0';
|
||||||
|
}
|
||||||
|
const response: AxiosResponse<{
|
||||||
|
data: IGetAttributeSuggestionsSuccessResponse;
|
||||||
|
}> = await ApiV3Instance.get(
|
||||||
|
`/filter_suggestions?${createQueryParams({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
existingFilter: base64EncodedFiltersString,
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload: BaseAutocompleteData[] =
|
||||||
|
response.data.data.attributes?.map(({ id: _, ...item }) => ({
|
||||||
|
...item,
|
||||||
|
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.statusText,
|
||||||
|
payload: {
|
||||||
|
attributes: payload,
|
||||||
|
example_queries: response.data.data.example_queries,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return ErrorResponseHandler(e as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
27
frontend/src/assets/CustomIcons/GroupByIcon.tsx
Normal file
27
frontend/src/assets/CustomIcons/GroupByIcon.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
|
||||||
|
function GroupByIcon(): JSX.Element {
|
||||||
|
const isDarkMode = useIsDarkMode();
|
||||||
|
return (
|
||||||
|
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g
|
||||||
|
clipPath="url(#prefix__clip0_4344_1236)"
|
||||||
|
stroke={isDarkMode ? Color.BG_VANILLA_100 : Color.BG_INK_500}
|
||||||
|
strokeWidth="1.167"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M4.667 1.167H2.333c-.644 0-1.166.522-1.166 1.166v2.334c0 .644.522 1.166 1.166 1.166h2.334c.644 0 1.166-.522 1.166-1.166V2.333c0-.644-.522-1.166-1.166-1.166zM8.167 1.167a1.17 1.17 0 011.166 1.166v2.334a1.17 1.17 0 01-1.166 1.166M11.667 1.167a1.17 1.17 0 011.166 1.166v2.334a1.17 1.17 0 01-1.166 1.166M5.833 10.5H2.917c-.992 0-1.75-.758-1.75-1.75v-.583" />
|
||||||
|
<path d="M4.083 12.25l1.75-1.75-1.75-1.75M11.667 8.167H9.333c-.644 0-1.166.522-1.166 1.166v2.334c0 .644.522 1.166 1.166 1.166h2.334c.644 0 1.166-.522 1.166-1.166V9.333c0-.644-.522-1.166-1.166-1.166z" />
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="prefix__clip0_4344_1236">
|
||||||
|
<path fill="#fff" d="M0 0h14v14H0z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupByIcon;
|
@ -7,6 +7,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
|||||||
import { CreditCard, X } from 'lucide-react';
|
import { CreditCard, X } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
import { License } from 'types/api/licenses/def';
|
import { License } from 'types/api/licenses/def';
|
||||||
@ -57,11 +58,11 @@ export default function ChatSupportGateway(): JSX.Element {
|
|||||||
onError: handleBillingOnError,
|
onError: handleBillingOnError,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const { pathname } = useLocation();
|
||||||
const handleAddCreditCard = (): void => {
|
const handleAddCreditCard = (): void => {
|
||||||
logEvent('Add Credit card modal: Clicked', {
|
logEvent('Add Credit card modal: Clicked', {
|
||||||
source: `intercom icon`,
|
source: `intercom icon`,
|
||||||
page: '',
|
page: pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateCreditCard({
|
updateCreditCard({
|
||||||
@ -79,7 +80,7 @@ export default function ChatSupportGateway(): JSX.Element {
|
|||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
logEvent('Disabled Chat Support: Clicked', {
|
logEvent('Disabled Chat Support: Clicked', {
|
||||||
source: `intercom icon`,
|
source: `intercom icon`,
|
||||||
page: '',
|
page: pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsAddCreditCardModalOpen(true);
|
setIsAddCreditCardModalOpen(true);
|
||||||
|
@ -13,6 +13,7 @@ import { defaultTo } from 'lodash-es';
|
|||||||
import { CreditCard, HelpCircle, X } from 'lucide-react';
|
import { CreditCard, HelpCircle, X } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
import { License } from 'types/api/licenses/def';
|
import { License } from 'types/api/licenses/def';
|
||||||
@ -47,6 +48,7 @@ function LaunchChatSupport({
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { pathname } = useLocation();
|
||||||
const isPremiumChatSupportEnabled =
|
const isPremiumChatSupportEnabled =
|
||||||
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||||
|
|
||||||
@ -65,6 +67,11 @@ function LaunchChatSupport({
|
|||||||
|
|
||||||
const handleFacingIssuesClick = (): void => {
|
const handleFacingIssuesClick = (): void => {
|
||||||
if (showAddCreditCardModal) {
|
if (showAddCreditCardModal) {
|
||||||
|
logEvent('Disabled Chat Support: Clicked', {
|
||||||
|
source: `facing issues button`,
|
||||||
|
page: pathname,
|
||||||
|
...attributes,
|
||||||
|
});
|
||||||
setIsAddCreditCardModalOpen(true);
|
setIsAddCreditCardModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
logEvent(eventName, attributes);
|
logEvent(eventName, attributes);
|
||||||
@ -105,7 +112,7 @@ function LaunchChatSupport({
|
|||||||
const handleAddCreditCard = (): void => {
|
const handleAddCreditCard = (): void => {
|
||||||
logEvent('Add Credit card modal: Clicked', {
|
logEvent('Add Credit card modal: Clicked', {
|
||||||
source: `facing issues button`,
|
source: `facing issues button`,
|
||||||
page: '',
|
page: pathname,
|
||||||
...attributes,
|
...attributes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -41,6 +41,21 @@ I need help with managing alerts.
|
|||||||
|
|
||||||
Thanks`;
|
Thanks`;
|
||||||
|
|
||||||
|
export const onboardingHelpMessage = (
|
||||||
|
dataSourceName: string,
|
||||||
|
moduleId: string,
|
||||||
|
): string => `Hi Team,
|
||||||
|
|
||||||
|
I am facing issues sending data to SigNoz. Here are my application details
|
||||||
|
|
||||||
|
Data Source: ${dataSourceName}
|
||||||
|
Framework:
|
||||||
|
Environment:
|
||||||
|
Module: ${moduleId}
|
||||||
|
|
||||||
|
Thanks
|
||||||
|
`;
|
||||||
|
|
||||||
export const alertHelpMessage = (
|
export const alertHelpMessage = (
|
||||||
alertDef: AlertDef,
|
alertDef: AlertDef,
|
||||||
ruleId: number,
|
ruleId: number,
|
||||||
|
@ -3,12 +3,18 @@ import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
|||||||
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
import { VIEWS } from './constants';
|
import { VIEWS } from './constants';
|
||||||
|
|
||||||
export type LogDetailProps = {
|
export type LogDetailProps = {
|
||||||
log: ILog | null;
|
log: ILog | null;
|
||||||
selectedTab: VIEWS;
|
selectedTab: VIEWS;
|
||||||
|
onGroupByAttribute?: (
|
||||||
|
fieldKey: string,
|
||||||
|
isJSON?: boolean,
|
||||||
|
dataType?: DataTypes,
|
||||||
|
) => Promise<void>;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
listViewPanelSelectedFields?: IField[] | null;
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import './LogDetails.styles.scss';
|
import './LogDetails.styles.scss';
|
||||||
|
|
||||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||||
|
import Convert from 'ansi-to-html';
|
||||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||||
import { RadioChangeEvent } from 'antd/lib';
|
import { RadioChangeEvent } from 'antd/lib';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
@ -10,8 +11,13 @@ import { LOCALSTORAGE } from 'constants/localStorage';
|
|||||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||||
import JSONView from 'container/LogDetailedView/JsonView';
|
import JSONView from 'container/LogDetailedView/JsonView';
|
||||||
import Overview from 'container/LogDetailedView/Overview';
|
import Overview from 'container/LogDetailedView/Overview';
|
||||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
import {
|
||||||
|
aggregateAttributesResourcesToString,
|
||||||
|
removeEscapeCharacters,
|
||||||
|
unescapeString,
|
||||||
|
} from 'container/LogDetailedView/utils';
|
||||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
|
import dompurify from 'dompurify';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
@ -28,15 +34,19 @@ import { useMemo, useState } from 'react';
|
|||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||||
|
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
import { VIEW_TYPES, VIEWS } from './constants';
|
||||||
import { LogDetailProps } from './LogDetail.interfaces';
|
import { LogDetailProps } from './LogDetail.interfaces';
|
||||||
import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper';
|
import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper';
|
||||||
|
|
||||||
|
const convert = new Convert();
|
||||||
|
|
||||||
function LogDetail({
|
function LogDetail({
|
||||||
log,
|
log,
|
||||||
onClose,
|
onClose,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
@ -89,6 +99,17 @@ function LogDetail({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const htmlBody = useMemo(
|
||||||
|
() => ({
|
||||||
|
__html: convert.toHtml(
|
||||||
|
dompurify.sanitize(unescapeString(log?.body || ''), {
|
||||||
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
[log?.body],
|
||||||
|
);
|
||||||
|
|
||||||
const handleJSONCopy = (): void => {
|
const handleJSONCopy = (): void => {
|
||||||
copyToClipboard(LogJsonData);
|
copyToClipboard(LogJsonData);
|
||||||
notifications.success({
|
notifications.success({
|
||||||
@ -126,8 +147,8 @@ function LogDetail({
|
|||||||
>
|
>
|
||||||
<div className="log-detail-drawer__log">
|
<div className="log-detail-drawer__log">
|
||||||
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
||||||
<Tooltip title={log?.body} placement="left">
|
<Tooltip title={removeEscapeCharacters(log?.body)} placement="left">
|
||||||
<Typography.Text className="log-body">{log?.body}</Typography.Text>
|
<div className="log-body" dangerouslySetInnerHTML={htmlBody} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div className="log-overflow-shadow"> </div>
|
<div className="log-overflow-shadow"> </div>
|
||||||
@ -209,6 +230,7 @@ function LogDetail({
|
|||||||
logData={log}
|
logData={log}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
selectedOptions={options}
|
selectedOptions={options}
|
||||||
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
.addToQueryContainer {
|
.addToQueryContainer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&.small {
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import './AddToQueryHOC.styles.scss';
|
import './AddToQueryHOC.styles.scss';
|
||||||
|
|
||||||
import { Popover } from 'antd';
|
import { Popover } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
|
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
function AddToQueryHOC({
|
function AddToQueryHOC({
|
||||||
fieldKey,
|
fieldKey,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
fontSize,
|
||||||
children,
|
children,
|
||||||
}: AddToQueryHOCProps): JSX.Element {
|
}: AddToQueryHOCProps): JSX.Element {
|
||||||
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
|
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
|
||||||
@ -21,7 +24,7 @@ function AddToQueryHOC({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||||
<div className="addToQueryContainer" onClick={handleQueryAdd}>
|
<div className={cx('addToQueryContainer', fontSize)} onClick={handleQueryAdd}>
|
||||||
<Popover placement="top" content={popOverContent}>
|
<Popover placement="top" content={popOverContent}>
|
||||||
{children}
|
{children}
|
||||||
</Popover>
|
</Popover>
|
||||||
@ -33,6 +36,7 @@ export interface AddToQueryHOCProps {
|
|||||||
fieldKey: string;
|
fieldKey: string;
|
||||||
fieldValue: string;
|
fieldValue: string;
|
||||||
onAddToQuery: (fieldKey: string, fieldValue: string, operator: string) => void;
|
onAddToQuery: (fieldKey: string, fieldValue: string, operator: string) => void;
|
||||||
|
fontSize: FontSize;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { ReactNode, useCallback, useEffect } from 'react';
|
|||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
|
|
||||||
function CopyClipboardHOC({
|
function CopyClipboardHOC({
|
||||||
|
entityKey,
|
||||||
textToCopy,
|
textToCopy,
|
||||||
children,
|
children,
|
||||||
}: CopyClipboardHOCProps): JSX.Element {
|
}: CopyClipboardHOCProps): JSX.Element {
|
||||||
@ -11,11 +12,15 @@ function CopyClipboardHOC({
|
|||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value.value) {
|
if (value.value) {
|
||||||
|
const key = entityKey || '';
|
||||||
|
|
||||||
|
const notificationMessage = `${key} copied to clipboard`;
|
||||||
|
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Copied to clipboard',
|
message: notificationMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [value, notifications]);
|
}, [value, notifications, entityKey]);
|
||||||
|
|
||||||
const onClick = useCallback((): void => {
|
const onClick = useCallback((): void => {
|
||||||
setCopy(textToCopy);
|
setCopy(textToCopy);
|
||||||
@ -34,6 +39,7 @@ function CopyClipboardHOC({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CopyClipboardHOCProps {
|
interface CopyClipboardHOCProps {
|
||||||
|
entityKey: string | undefined;
|
||||||
textToCopy: string;
|
textToCopy: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,21 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 18px; /* 128.571% */
|
line-height: 18px; /* 128.571% */
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.log-value {
|
.log-value {
|
||||||
color: var(--text-vanilla-400, #c0c1c3);
|
color: var(--text-vanilla-400, #c0c1c3);
|
||||||
@ -14,6 +29,21 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 18px; /* 128.571% */
|
line-height: 18px; /* 128.571% */
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.log-line {
|
.log-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -40,6 +70,20 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 18px; /* 128.571% */
|
line-height: 18px; /* 128.571% */
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-log-value {
|
.selected-log-value {
|
||||||
@ -52,12 +96,37 @@
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-log-kv {
|
.selected-log-kv {
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
&.small {
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,11 @@ import './ListLogView.styles.scss';
|
|||||||
import { blue } from '@ant-design/colors';
|
import { blue } from '@ant-design/colors';
|
||||||
import Convert from 'ansi-to-html';
|
import Convert from 'ansi-to-html';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
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 { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
@ -39,6 +42,7 @@ interface LogFieldProps {
|
|||||||
fieldKey: string;
|
fieldKey: string;
|
||||||
fieldValue: string;
|
fieldValue: string;
|
||||||
linesPerRow?: number;
|
linesPerRow?: number;
|
||||||
|
fontSize: FontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogSelectedFieldProps = Omit<LogFieldProps, 'linesPerRow'> &
|
type LogSelectedFieldProps = Omit<LogFieldProps, 'linesPerRow'> &
|
||||||
@ -48,11 +52,12 @@ function LogGeneralField({
|
|||||||
fieldKey,
|
fieldKey,
|
||||||
fieldValue,
|
fieldValue,
|
||||||
linesPerRow = 1,
|
linesPerRow = 1,
|
||||||
|
fontSize,
|
||||||
}: LogFieldProps): JSX.Element {
|
}: LogFieldProps): JSX.Element {
|
||||||
const html = useMemo(
|
const html = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(fieldValue, {
|
dompurify.sanitize(unescapeString(fieldValue), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -62,12 +67,12 @@ function LogGeneralField({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TextContainer>
|
<TextContainer>
|
||||||
<Text ellipsis type="secondary" className="log-field-key">
|
<Text ellipsis type="secondary" className={cx('log-field-key', fontSize)}>
|
||||||
{`${fieldKey} : `}
|
{`${fieldKey} : `}
|
||||||
</Text>
|
</Text>
|
||||||
<LogText
|
<LogText
|
||||||
dangerouslySetInnerHTML={html}
|
dangerouslySetInnerHTML={html}
|
||||||
className="log-value"
|
className={cx('log-value', fontSize)}
|
||||||
linesPerRow={linesPerRow > 1 ? linesPerRow : undefined}
|
linesPerRow={linesPerRow > 1 ? linesPerRow : undefined}
|
||||||
/>
|
/>
|
||||||
</TextContainer>
|
</TextContainer>
|
||||||
@ -78,6 +83,7 @@ function LogSelectedField({
|
|||||||
fieldKey = '',
|
fieldKey = '',
|
||||||
fieldValue = '',
|
fieldValue = '',
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
fontSize,
|
||||||
}: LogSelectedFieldProps): JSX.Element {
|
}: LogSelectedFieldProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="log-selected-fields">
|
<div className="log-selected-fields">
|
||||||
@ -85,16 +91,22 @@ function LogSelectedField({
|
|||||||
fieldKey={fieldKey}
|
fieldKey={fieldKey}
|
||||||
fieldValue={fieldValue}
|
fieldValue={fieldValue}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
<span style={{ color: blue[4] }} className="selected-log-field-key">
|
<span
|
||||||
|
style={{ color: blue[4] }}
|
||||||
|
className={cx('selected-log-field-key', fontSize)}
|
||||||
|
>
|
||||||
{fieldKey}
|
{fieldKey}
|
||||||
</span>
|
</span>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</AddToQueryHOC>
|
</AddToQueryHOC>
|
||||||
<Typography.Text ellipsis className="selected-log-kv">
|
<Typography.Text ellipsis className={cx('selected-log-kv', fontSize)}>
|
||||||
<span className="selected-log-field-key">{': '}</span>
|
<span className={cx('selected-log-field-key', fontSize)}>{': '}</span>
|
||||||
<span className="selected-log-value">{fieldValue || "''"}</span>
|
<span className={cx('selected-log-value', fontSize)}>
|
||||||
|
{fieldValue || "''"}
|
||||||
|
</span>
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -107,6 +119,7 @@ type ListLogViewProps = {
|
|||||||
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
|
onAddToQuery: AddToQueryHOCProps['onAddToQuery'];
|
||||||
activeLog?: ILog | null;
|
activeLog?: ILog | null;
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
|
fontSize: FontSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ListLogView({
|
function ListLogView({
|
||||||
@ -116,6 +129,7 @@ function ListLogView({
|
|||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
activeLog,
|
activeLog,
|
||||||
linesPerRow,
|
linesPerRow,
|
||||||
|
fontSize,
|
||||||
}: ListLogViewProps): JSX.Element {
|
}: ListLogViewProps): JSX.Element {
|
||||||
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
const flattenLogData = useMemo(() => FlatLogData(logData), [logData]);
|
||||||
|
|
||||||
@ -128,6 +142,7 @@ function ListLogView({
|
|||||||
onAddToQuery: handleAddToQuery,
|
onAddToQuery: handleAddToQuery,
|
||||||
onSetActiveLog: handleSetActiveContextLog,
|
onSetActiveLog: handleSetActiveContextLog,
|
||||||
onClearActiveLog: handleClearActiveContextLog,
|
onClearActiveLog: handleClearActiveContextLog,
|
||||||
|
onGroupByAttribute,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
@ -185,6 +200,7 @@ function ListLogView({
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
onClick={handleDetailedView}
|
onClick={handleDetailedView}
|
||||||
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
<div className="log-line">
|
<div className="log-line">
|
||||||
<LogStateIndicator
|
<LogStateIndicator
|
||||||
@ -192,18 +208,28 @@ function ListLogView({
|
|||||||
isActive={
|
isActive={
|
||||||
activeLog?.id === logData.id || activeContextLog?.id === logData.id
|
activeLog?.id === logData.id || activeContextLog?.id === logData.id
|
||||||
}
|
}
|
||||||
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<LogContainer>
|
<LogContainer fontSize={fontSize}>
|
||||||
<LogGeneralField
|
<LogGeneralField
|
||||||
fieldKey="Log"
|
fieldKey="Log"
|
||||||
fieldValue={flattenLogData.body}
|
fieldValue={flattenLogData.body}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
{flattenLogData.stream && (
|
{flattenLogData.stream && (
|
||||||
<LogGeneralField fieldKey="Stream" fieldValue={flattenLogData.stream} />
|
<LogGeneralField
|
||||||
|
fieldKey="Stream"
|
||||||
|
fieldValue={flattenLogData.stream}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<LogGeneralField fieldKey="Timestamp" fieldValue={timestampValue} />
|
<LogGeneralField
|
||||||
|
fieldKey="Timestamp"
|
||||||
|
fieldValue={timestampValue}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>
|
||||||
|
|
||||||
{updatedSelecedFields.map((field) =>
|
{updatedSelecedFields.map((field) =>
|
||||||
isValidLogField(flattenLogData[field.name] as never) ? (
|
isValidLogField(flattenLogData[field.name] as never) ? (
|
||||||
@ -212,6 +238,7 @@ function ListLogView({
|
|||||||
fieldKey={field.name}
|
fieldKey={field.name}
|
||||||
fieldValue={flattenLogData[field.name] as never}
|
fieldValue={flattenLogData[field.name] as never}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
)}
|
)}
|
||||||
@ -232,6 +259,7 @@ function ListLogView({
|
|||||||
onAddToQuery={handleAddToQuery}
|
onAddToQuery={handleAddToQuery}
|
||||||
selectedTab={VIEW_TYPES.CONTEXT}
|
selectedTab={VIEW_TYPES.CONTEXT}
|
||||||
onClose={handlerClearActiveContextLog}
|
onClose={handlerClearActiveContextLog}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,21 +1,46 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Card, Typography } from 'antd';
|
import { Card, Typography } from 'antd';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
interface LogTextProps {
|
interface LogTextProps {
|
||||||
linesPerRow?: number;
|
linesPerRow?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LogContainerProps {
|
||||||
|
fontSize: FontSize;
|
||||||
|
}
|
||||||
|
|
||||||
export const Container = styled(Card)<{
|
export const Container = styled(Card)<{
|
||||||
$isActiveLog: boolean;
|
$isActiveLog: boolean;
|
||||||
$isDarkMode: boolean;
|
$isDarkMode: boolean;
|
||||||
|
fontSize: FontSize;
|
||||||
}>`
|
}>`
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
|
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `margin-bottom:0.1rem;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `margin-bottom: 0.2rem;`
|
||||||
|
: fontSize === FontSize.LARGE
|
||||||
|
? `margin-bottom:0.3rem;`
|
||||||
|
: ``}
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 0.3rem 0.6rem;
|
padding: 0.3rem 0.6rem;
|
||||||
|
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `padding:0.1rem 0.6rem;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `padding: 0.2rem 0.6rem;`
|
||||||
|
: fontSize === FontSize.LARGE
|
||||||
|
? `padding:0.3rem 0.6rem;`
|
||||||
|
: ``}
|
||||||
|
|
||||||
${({ $isActiveLog, $isDarkMode }): string =>
|
${({ $isActiveLog, $isDarkMode }): string =>
|
||||||
$isActiveLog
|
$isActiveLog
|
||||||
? `background-color: ${
|
? `background-color: ${
|
||||||
@ -38,11 +63,17 @@ export const TextContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LogContainer = styled.div`
|
export const LogContainer = styled.div<LogContainerProps>`
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `gap: 2px;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? ` gap:4px;`
|
||||||
|
: `gap:6px;`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LogText = styled.div<LogTextProps>`
|
export const LogText = styled.div<LogTextProps>`
|
||||||
|
@ -9,11 +9,24 @@
|
|||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
min-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
&.INFO {
|
&.INFO {
|
||||||
background-color: var(--bg-slate-400);
|
background-color: var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.WARNING, &.WARN {
|
&.WARNING,
|
||||||
|
&.WARN {
|
||||||
background-color: var(--bg-amber-500);
|
background-color: var(--bg-amber-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
|
||||||
import LogStateIndicator from './LogStateIndicator';
|
import LogStateIndicator from './LogStateIndicator';
|
||||||
|
|
||||||
describe('LogStateIndicator', () => {
|
describe('LogStateIndicator', () => {
|
||||||
it('renders correctly with default props', () => {
|
it('renders correctly with default props', () => {
|
||||||
const { container } = render(<LogStateIndicator type="INFO" />);
|
const { container } = render(
|
||||||
|
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
|
||||||
|
);
|
||||||
const indicator = container.firstChild as HTMLElement;
|
const indicator = container.firstChild as HTMLElement;
|
||||||
expect(indicator.classList.contains('log-state-indicator')).toBe(true);
|
expect(indicator.classList.contains('log-state-indicator')).toBe(true);
|
||||||
expect(indicator.classList.contains('isActive')).toBe(false);
|
expect(indicator.classList.contains('isActive')).toBe(false);
|
||||||
@ -15,28 +18,30 @@ describe('LogStateIndicator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly when isActive is true', () => {
|
it('renders correctly when isActive is true', () => {
|
||||||
const { container } = render(<LogStateIndicator type="INFO" isActive />);
|
const { container } = render(
|
||||||
|
<LogStateIndicator type="INFO" isActive fontSize={FontSize.MEDIUM} />,
|
||||||
|
);
|
||||||
const indicator = container.firstChild as HTMLElement;
|
const indicator = container.firstChild as HTMLElement;
|
||||||
expect(indicator.classList.contains('isActive')).toBe(true);
|
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" />,
|
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
|
||||||
);
|
);
|
||||||
expect(containerInfo.querySelector('.line')?.classList.contains('INFO')).toBe(
|
expect(containerInfo.querySelector('.line')?.classList.contains('INFO')).toBe(
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { container: containerWarning } = render(
|
const { container: containerWarning } = render(
|
||||||
<LogStateIndicator type="WARNING" />,
|
<LogStateIndicator type="WARNING" fontSize={FontSize.MEDIUM} />,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
containerWarning.querySelector('.line')?.classList.contains('WARNING'),
|
containerWarning.querySelector('.line')?.classList.contains('WARNING'),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|
||||||
const { container: containerError } = render(
|
const { container: containerError } = render(
|
||||||
<LogStateIndicator type="ERROR" />,
|
<LogStateIndicator type="ERROR" fontSize={FontSize.MEDIUM} />,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
containerError.querySelector('.line')?.classList.contains('ERROR'),
|
containerError.querySelector('.line')?.classList.contains('ERROR'),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import './LogStateIndicator.styles.scss';
|
import './LogStateIndicator.styles.scss';
|
||||||
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
|
||||||
export const SEVERITY_TEXT_TYPE = {
|
export const SEVERITY_TEXT_TYPE = {
|
||||||
TRACE: 'TRACE',
|
TRACE: 'TRACE',
|
||||||
@ -44,13 +45,15 @@ export const LogType = {
|
|||||||
function LogStateIndicator({
|
function LogStateIndicator({
|
||||||
type,
|
type,
|
||||||
isActive,
|
isActive,
|
||||||
|
fontSize,
|
||||||
}: {
|
}: {
|
||||||
type: string;
|
type: string;
|
||||||
|
fontSize: FontSize;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={cx('log-state-indicator', isActive ? 'isActive' : '')}>
|
<div className={cx('log-state-indicator', isActive ? 'isActive' : '')}>
|
||||||
<div className={cx('line', type)}> </div>
|
<div className={cx('line', type, fontSize)}> </div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import Convert from 'ansi-to-html';
|
|||||||
import { DrawerProps } from 'antd';
|
import { DrawerProps } from 'antd';
|
||||||
import LogDetail from 'components/LogDetail';
|
import LogDetail from 'components/LogDetail';
|
||||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||||
|
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
@ -39,6 +40,7 @@ function RawLogView({
|
|||||||
linesPerRow,
|
linesPerRow,
|
||||||
isTextOverflowEllipsisDisabled,
|
isTextOverflowEllipsisDisabled,
|
||||||
selectedFields = [],
|
selectedFields = [],
|
||||||
|
fontSize,
|
||||||
}: RawLogViewProps): JSX.Element {
|
}: RawLogViewProps): JSX.Element {
|
||||||
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
|
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
|
||||||
data.id,
|
data.id,
|
||||||
@ -54,6 +56,7 @@ function RawLogView({
|
|||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
const [hasActionButtons, setHasActionButtons] = useState<boolean>(false);
|
const [hasActionButtons, setHasActionButtons] = useState<boolean>(false);
|
||||||
@ -143,7 +146,9 @@ function RawLogView({
|
|||||||
const html = useMemo(
|
const html = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(text, { FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS] }),
|
dompurify.sanitize(unescapeString(text), {
|
||||||
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
[text],
|
[text],
|
||||||
@ -160,6 +165,7 @@ function RawLogView({
|
|||||||
$isActiveLog={isActiveLog}
|
$isActiveLog={isActiveLog}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
<LogStateIndicator
|
<LogStateIndicator
|
||||||
type={logType}
|
type={logType}
|
||||||
@ -168,6 +174,7 @@ function RawLogView({
|
|||||||
activeContextLog?.id === data.id ||
|
activeContextLog?.id === data.id ||
|
||||||
isActiveLog
|
isActiveLog
|
||||||
}
|
}
|
||||||
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RawLogContent
|
<RawLogContent
|
||||||
@ -176,6 +183,7 @@ function RawLogView({
|
|||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
$isTextOverflowEllipsisDisabled={isTextOverflowEllipsisDisabled}
|
$isTextOverflowEllipsisDisabled={isTextOverflowEllipsisDisabled}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
|
fontSize={fontSize}
|
||||||
dangerouslySetInnerHTML={html}
|
dangerouslySetInnerHTML={html}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -199,6 +207,7 @@ function RawLogView({
|
|||||||
onClose={handleCloseLogDetail}
|
onClose={handleCloseLogDetail}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</RawLogViewContainer>
|
</RawLogViewContainer>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { blue } from '@ant-design/colors';
|
import { blue } from '@ant-design/colors';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Col, Row, Space } from 'antd';
|
import { Col, Row, Space } from 'antd';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { getActiveLogBackground, getDefaultLogBackground } from 'utils/logs';
|
import { getActiveLogBackground, getDefaultLogBackground } from 'utils/logs';
|
||||||
|
|
||||||
@ -11,6 +13,7 @@ export const RawLogViewContainer = styled(Row)<{
|
|||||||
$isReadOnly?: boolean;
|
$isReadOnly?: boolean;
|
||||||
$isActiveLog?: boolean;
|
$isActiveLog?: boolean;
|
||||||
$isHightlightedLog: boolean;
|
$isHightlightedLog: boolean;
|
||||||
|
fontSize: FontSize;
|
||||||
}>`
|
}>`
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -22,6 +25,13 @@ export const RawLogViewContainer = styled(Row)<{
|
|||||||
|
|
||||||
.log-state-indicator {
|
.log-state-indicator {
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
|
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `margin: 1px 0;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `margin: 1px 0;`
|
||||||
|
: `margin: 2px 0;`}
|
||||||
}
|
}
|
||||||
|
|
||||||
${({ $isActiveLog }): string => getActiveLogBackground($isActiveLog)}
|
${({ $isActiveLog }): string => getActiveLogBackground($isActiveLog)}
|
||||||
@ -50,8 +60,8 @@ export const RawLogContent = styled.div<RawLogContentProps>`
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-family: 'SF Mono', monospace;
|
font-family: 'SF Mono', monospace;
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
font-size: 13px;
|
letter-spacing: -0.07px;
|
||||||
font-weight: 400;
|
padding: 4px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: ${({ $isDarkMode }): string =>
|
color: ${({ $isDarkMode }): string =>
|
||||||
$isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400};
|
$isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400};
|
||||||
@ -66,9 +76,15 @@ export const RawLogContent = styled.div<RawLogContentProps>`
|
|||||||
line-clamp: ${linesPerRow};
|
line-clamp: ${linesPerRow};
|
||||||
-webkit-box-orient: vertical;`};
|
-webkit-box-orient: vertical;`};
|
||||||
|
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
letter-spacing: -0.07px;
|
${({ fontSize }): string =>
|
||||||
padding: 4px;
|
fontSize === FontSize.SMALL
|
||||||
|
? `font-size:11px; line-height:16px; padding:1px;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `font-size:13px; line-height:20px; padding:1px;`
|
||||||
|
: `font-size:14px; line-height:24px; padding:2px;`}
|
||||||
|
|
||||||
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
|
cursor: ${({ $isActiveLog, $isReadOnly }): string =>
|
||||||
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};
|
$isActiveLog || $isReadOnly ? 'initial' : 'pointer'};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
@ -7,11 +8,13 @@ export interface RawLogViewProps {
|
|||||||
isTextOverflowEllipsisDisabled?: boolean;
|
isTextOverflowEllipsisDisabled?: boolean;
|
||||||
data: ILog;
|
data: ILog;
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
|
fontSize: FontSize;
|
||||||
selectedFields?: IField[];
|
selectedFields?: IField[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawLogContentProps {
|
export interface RawLogContentProps {
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
|
fontSize: FontSize;
|
||||||
$isReadOnly?: boolean;
|
$isReadOnly?: boolean;
|
||||||
$isActiveLog?: boolean;
|
$isActiveLog?: boolean;
|
||||||
$isDarkMode?: boolean;
|
$isDarkMode?: boolean;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
interface TableBodyContentProps {
|
interface TableBodyContentProps {
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
|
fontSize: FontSize;
|
||||||
isDarkMode?: boolean;
|
isDarkMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,4 +23,10 @@ export const TableBodyContent = styled.div<TableBodyContentProps>`
|
|||||||
-webkit-line-clamp: ${(props): number => props.linesPerRow};
|
-webkit-line-clamp: ${(props): number => props.linesPerRow};
|
||||||
line-clamp: ${(props): number => props.linesPerRow};
|
line-clamp: ${(props): number => props.linesPerRow};
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `font-size:11px; line-height:16px;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `font-size:13px; line-height:20px;`
|
||||||
|
: `font-size:14px; line-height:24px;`}
|
||||||
`;
|
`;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ColumnsType, ColumnType } from 'antd/es/table';
|
import { ColumnsType, ColumnType } from 'antd/es/table';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ export type LogsTableViewProps = {
|
|||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
fields: IField[];
|
fields: IField[];
|
||||||
linesPerRow: number;
|
linesPerRow: number;
|
||||||
|
fontSize: FontSize;
|
||||||
onClickExpand?: (log: ILog) => void;
|
onClickExpand?: (log: ILog) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,6 +5,21 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 18px; /* 128.571% */
|
line-height: 18px; /* 128.571% */
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-timestamp {
|
.table-timestamp {
|
||||||
@ -25,3 +40,21 @@
|
|||||||
color: var(--bg-slate-400);
|
color: var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paragraph {
|
||||||
|
padding: 0px !important;
|
||||||
|
&.small {
|
||||||
|
font-size: 11px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium {
|
||||||
|
font-size: 13px !important;
|
||||||
|
line-height: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 24px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ import './useTableView.styles.scss';
|
|||||||
import Convert from 'ansi-to-html';
|
import Convert from 'ansi-to-html';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
@ -31,6 +33,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
logs,
|
logs,
|
||||||
fields,
|
fields,
|
||||||
linesPerRow,
|
linesPerRow,
|
||||||
|
fontSize,
|
||||||
appendTo = 'center',
|
appendTo = 'center',
|
||||||
activeContextLog,
|
activeContextLog,
|
||||||
activeLog,
|
activeLog,
|
||||||
@ -57,7 +60,10 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
: getDefaultCellStyle(isDarkMode),
|
: getDefaultCellStyle(isDarkMode),
|
||||||
},
|
},
|
||||||
children: (
|
children: (
|
||||||
<Typography.Paragraph ellipsis={{ rows: linesPerRow }}>
|
<Typography.Paragraph
|
||||||
|
ellipsis={{ rows: linesPerRow }}
|
||||||
|
className={cx('paragraph', fontSize)}
|
||||||
|
>
|
||||||
{field}
|
{field}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
),
|
),
|
||||||
@ -87,8 +93,9 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
isActive={
|
isActive={
|
||||||
activeLog?.id === item.id || activeContextLog?.id === item.id
|
activeLog?.id === item.id || activeContextLog?.id === item.id
|
||||||
}
|
}
|
||||||
|
fontSize={fontSize}
|
||||||
/>
|
/>
|
||||||
<Typography.Paragraph ellipsis className="text">
|
<Typography.Paragraph ellipsis className={cx('text', fontSize)}>
|
||||||
{date}
|
{date}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</div>
|
</div>
|
||||||
@ -109,11 +116,12 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
<TableBodyContent
|
<TableBodyContent
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: convert.toHtml(
|
__html: convert.toHtml(
|
||||||
dompurify.sanitize(field, {
|
dompurify.sanitize(unescapeString(field), {
|
||||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
fontSize={fontSize}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
/>
|
/>
|
||||||
@ -130,6 +138,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
linesPerRow,
|
linesPerRow,
|
||||||
activeLog?.id,
|
activeLog?.id,
|
||||||
activeContextLog?.id,
|
activeContextLog?.id,
|
||||||
|
fontSize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { columns, dataSource: flattenLogData };
|
return { columns, dataSource: flattenLogData };
|
||||||
|
@ -17,17 +17,126 @@
|
|||||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
|
||||||
|
.font-size-dropdown {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.option-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal; /* 142.857% */
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-btn:hover {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-container {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
padding: 4px 0px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border: none !important;
|
||||||
|
.font-value {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.value:hover {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: #52575c;
|
color: var(--bg-slate-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-items {
|
.menu-items {
|
||||||
@ -65,11 +174,11 @@
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: #52575c;
|
color: var(--bg-slate-50);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
line-height: 18px; /* 163.636% */
|
line-height: 18px; /* 163.636% */
|
||||||
letter-spacing: 0.88px;
|
letter-spacing: 0.88px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -149,11 +258,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: #52575c;
|
color: var(--bg-slate-50);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
line-height: 18px; /* 163.636% */
|
line-height: 18px; /* 163.636% */
|
||||||
letter-spacing: 0.88px;
|
letter-spacing: 0.88px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -299,6 +408,38 @@
|
|||||||
|
|
||||||
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
|
||||||
|
|
||||||
|
.font-size-dropdown {
|
||||||
|
.back-btn {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
.option-btn {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-ink-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-container {
|
||||||
|
.title {
|
||||||
|
color: var(--bg-ink-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
.font-value {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.horizontal-line {
|
.horizontal-line {
|
||||||
background: var(--bg-vanilla-300);
|
background: var(--bg-vanilla-300);
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
import './LogsFormatOptionsMenu.styles.scss';
|
import './LogsFormatOptionsMenu.styles.scss';
|
||||||
|
|
||||||
import { Divider, Input, InputNumber, Tooltip } from 'antd';
|
import { Button, Divider, Input, InputNumber, Tooltip, Typography } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
|
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { Check, Minus, Plus, X } from 'lucide-react';
|
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface LogsFormatOptionsMenuProps {
|
interface LogsFormatOptionsMenuProps {
|
||||||
@ -24,10 +24,16 @@ export default function LogsFormatOptionsMenu({
|
|||||||
selectedOptionFormat,
|
selectedOptionFormat,
|
||||||
config,
|
config,
|
||||||
}: LogsFormatOptionsMenuProps): JSX.Element {
|
}: LogsFormatOptionsMenuProps): JSX.Element {
|
||||||
const { maxLines, format, addColumn } = config;
|
const { maxLines, format, addColumn, fontSize } = config;
|
||||||
const [selectedItem, setSelectedItem] = useState(selectedOptionFormat);
|
const [selectedItem, setSelectedItem] = useState(selectedOptionFormat);
|
||||||
const maxLinesNumber = (maxLines?.value as number) || 1;
|
const maxLinesNumber = (maxLines?.value as number) || 1;
|
||||||
const [maxLinesPerRow, setMaxLinesPerRow] = useState<number>(maxLinesNumber);
|
const [maxLinesPerRow, setMaxLinesPerRow] = useState<number>(maxLinesNumber);
|
||||||
|
const [fontSizeValue, setFontSizeValue] = useState<FontSize>(
|
||||||
|
fontSize?.value || FontSize.SMALL,
|
||||||
|
);
|
||||||
|
const [isFontSizeOptionsOpen, setIsFontSizeOptionsOpen] = useState<boolean>(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
const [addNewColumn, setAddNewColumn] = useState(false);
|
const [addNewColumn, setAddNewColumn] = useState(false);
|
||||||
|
|
||||||
@ -88,6 +94,12 @@ export default function LogsFormatOptionsMenu({
|
|||||||
}
|
}
|
||||||
}, [maxLinesPerRow]);
|
}, [maxLinesPerRow]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fontSizeValue && config && config.fontSize?.onChange) {
|
||||||
|
config.fontSize.onChange(fontSizeValue);
|
||||||
|
}
|
||||||
|
}, [fontSizeValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
||||||
@ -96,6 +108,72 @@ export default function LogsFormatOptionsMenu({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{isFontSizeOptionsOpen ? (
|
||||||
|
<div className="font-size-dropdown">
|
||||||
|
<Button
|
||||||
|
onClick={(): void => setIsFontSizeOptionsOpen(false)}
|
||||||
|
className="back-btn"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<ChevronLeft size={14} className="icon" />
|
||||||
|
<Typography.Text className="text">Select font size</Typography.Text>
|
||||||
|
</Button>
|
||||||
|
<div className="horizontal-line" />
|
||||||
|
<div className="content">
|
||||||
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
setFontSizeValue(FontSize.SMALL);
|
||||||
|
}}
|
||||||
|
className="option-btn"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<Typography.Text className="text">{FontSize.SMALL}</Typography.Text>
|
||||||
|
{fontSizeValue === FontSize.SMALL && (
|
||||||
|
<Check size={14} className="icon" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
setFontSizeValue(FontSize.MEDIUM);
|
||||||
|
}}
|
||||||
|
className="option-btn"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<Typography.Text className="text">{FontSize.MEDIUM}</Typography.Text>
|
||||||
|
{fontSizeValue === FontSize.MEDIUM && (
|
||||||
|
<Check size={14} className="icon" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
setFontSizeValue(FontSize.LARGE);
|
||||||
|
}}
|
||||||
|
className="option-btn"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<Typography.Text className="text">{FontSize.LARGE}</Typography.Text>
|
||||||
|
{fontSizeValue === FontSize.LARGE && (
|
||||||
|
<Check size={14} className="icon" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="font-size-container">
|
||||||
|
<div className="title">Font Size</div>
|
||||||
|
<Button
|
||||||
|
className="value"
|
||||||
|
type="text"
|
||||||
|
onClick={(): void => {
|
||||||
|
setIsFontSizeOptionsOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography.Text className="font-value">{fontSizeValue}</Typography.Text>
|
||||||
|
<ChevronRight size={14} className="icon" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="horizontal-line" />
|
||||||
<div className="menu-container">
|
<div className="menu-container">
|
||||||
<div className="title"> {title} </div>
|
<div className="title"> {title} </div>
|
||||||
|
|
||||||
@ -237,6 +315,8 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,5 @@ export const ENVIRONMENT = {
|
|||||||
process?.env?.FRONTEND_API_ENDPOINT ||
|
process?.env?.FRONTEND_API_ENDPOINT ||
|
||||||
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
|
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
|
||||||
'',
|
'',
|
||||||
|
wsURL: process?.env?.WEBSOCKET_API_ENDPOINT || '',
|
||||||
};
|
};
|
||||||
|
@ -32,4 +32,8 @@ export enum QueryParams {
|
|||||||
relativeTime = 'relativeTime',
|
relativeTime = 'relativeTime',
|
||||||
alertType = 'alertType',
|
alertType = 'alertType',
|
||||||
ruleId = 'ruleId',
|
ruleId = 'ruleId',
|
||||||
|
consumerGrp = 'consumerGrp',
|
||||||
|
topic = 'topic',
|
||||||
|
partition = 'partition',
|
||||||
|
selectedTimelineQuery = 'selectedTimelineQuery',
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export const selectValueDivider = '__';
|
|||||||
|
|
||||||
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
'id' | 'isJSON'
|
'id' | 'isJSON' | 'isIndexed'
|
||||||
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
||||||
|
|
||||||
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
||||||
@ -71,6 +71,7 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
|||||||
export enum QueryBuilderKeys {
|
export enum QueryBuilderKeys {
|
||||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||||
|
GET_ATTRIBUTE_SUGGESTIONS = 'GET_ATTRIBUTE_SUGGESTIONS',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapOfOperators = {
|
export const mapOfOperators = {
|
||||||
|
@ -8,4 +8,5 @@ export const REACT_QUERY_KEY = {
|
|||||||
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
|
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
|
||||||
DELETE_DASHBOARD: 'DELETE_DASHBOARD',
|
DELETE_DASHBOARD: 'DELETE_DASHBOARD',
|
||||||
LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
|
LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
|
||||||
|
GET_CONSUMER_LAG_DETAILS: 'GET_CONSUMER_LAG_DETAILS',
|
||||||
};
|
};
|
||||||
|
@ -54,6 +54,8 @@ const ROUTES = {
|
|||||||
WORKSPACE_LOCKED: '/workspace-locked',
|
WORKSPACE_LOCKED: '/workspace-locked',
|
||||||
SHORTCUTS: '/shortcuts',
|
SHORTCUTS: '/shortcuts',
|
||||||
INTEGRATIONS: '/integrations',
|
INTEGRATIONS: '/integrations',
|
||||||
|
MESSAGING_QUEUES: '/messaging-queues',
|
||||||
|
MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default ROUTES;
|
export default ROUTES;
|
||||||
|
@ -9,6 +9,7 @@ export const GlobalShortcuts = {
|
|||||||
NavigateToDashboards: 'd+shift',
|
NavigateToDashboards: 'd+shift',
|
||||||
NavigateToAlerts: 'a+shift',
|
NavigateToAlerts: 'a+shift',
|
||||||
NavigateToExceptions: 'e+shift',
|
NavigateToExceptions: 'e+shift',
|
||||||
|
NavigateToMessagingQueues: 'm+shift',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GlobalShortcutsName = {
|
export const GlobalShortcutsName = {
|
||||||
@ -19,6 +20,7 @@ export const GlobalShortcutsName = {
|
|||||||
NavigateToDashboards: 'shift+d',
|
NavigateToDashboards: 'shift+d',
|
||||||
NavigateToAlerts: 'shift+a',
|
NavigateToAlerts: 'shift+a',
|
||||||
NavigateToExceptions: 'shift+e',
|
NavigateToExceptions: 'shift+e',
|
||||||
|
NavigateToMessagingQueues: 'shift+m',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GlobalShortcutsDescription = {
|
export const GlobalShortcutsDescription = {
|
||||||
@ -29,4 +31,5 @@ export const GlobalShortcutsDescription = {
|
|||||||
NavigateToDashboards: 'Navigate to dashboards page',
|
NavigateToDashboards: 'Navigate to dashboards page',
|
||||||
NavigateToAlerts: 'Navigate to alerts page',
|
NavigateToAlerts: 'Navigate to alerts page',
|
||||||
NavigateToExceptions: 'Navigate to Exceptions page',
|
NavigateToExceptions: 'Navigate to Exceptions page',
|
||||||
|
NavigateToMessagingQueues: 'Navigate to Messaging Queues page',
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ const userOS = getUserOperatingSystem();
|
|||||||
export const LogsExplorerShortcuts = {
|
export const LogsExplorerShortcuts = {
|
||||||
StageAndRunQuery: 'enter+meta',
|
StageAndRunQuery: 'enter+meta',
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: '/+meta',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsName = {
|
export const LogsExplorerShortcutsName = {
|
||||||
@ -11,9 +12,11 @@ export const LogsExplorerShortcutsName = {
|
|||||||
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
||||||
}+enter`,
|
}+enter`,
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsDescription = {
|
export const LogsExplorerShortcutsDescription = {
|
||||||
StageAndRunQuery: 'Stage and Run the current query',
|
StageAndRunQuery: 'Stage and Run the current query',
|
||||||
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
||||||
|
ShowAllFilters: 'Toggle all filters in the filters dropdown',
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,7 @@ import {
|
|||||||
UPDATE_LATEST_VERSION_ERROR,
|
UPDATE_LATEST_VERSION_ERROR,
|
||||||
} from 'types/actions/app';
|
} from 'types/actions/app';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
import { isCloudUser } from 'utils/app';
|
||||||
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
||||||
|
|
||||||
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
||||||
@ -71,7 +72,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const isPremiumChatSupportEnabled =
|
const isPremiumChatSupportEnabled =
|
||||||
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||||
|
|
||||||
|
const isChatSupportEnabled =
|
||||||
|
useFeatureFlags(FeatureKeys.CHAT_SUPPORT)?.active || false;
|
||||||
|
|
||||||
|
const isCloudUserVal = isCloudUser();
|
||||||
|
|
||||||
const showAddCreditCardModal =
|
const showAddCreditCardModal =
|
||||||
|
isChatSupportEnabled &&
|
||||||
|
isCloudUserVal &&
|
||||||
!isPremiumChatSupportEnabled &&
|
!isPremiumChatSupportEnabled &&
|
||||||
!licenseData?.payload?.trialConvertedToSubscription;
|
!licenseData?.payload?.trialConvertedToSubscription;
|
||||||
|
|
||||||
@ -241,6 +249,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const isTracesView = (): boolean =>
|
const isTracesView = (): boolean =>
|
||||||
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
|
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
|
||||||
|
|
||||||
|
const isMessagingQueues = (): boolean =>
|
||||||
|
routeKey === 'MESSAGING_QUEUES' || routeKey === 'MESSAGING_QUEUES_DETAIL';
|
||||||
|
|
||||||
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
|
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
|
||||||
const isDashboardView = (): boolean => {
|
const isDashboardView = (): boolean => {
|
||||||
/**
|
/**
|
||||||
@ -329,7 +340,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isTracesView() ||
|
isTracesView() ||
|
||||||
isDashboardView() ||
|
isDashboardView() ||
|
||||||
isDashboardWidgetView() ||
|
isDashboardWidgetView() ||
|
||||||
isDashboardListView()
|
isDashboardListView() ||
|
||||||
|
isMessagingQueues()
|
||||||
? 0
|
? 0
|
||||||
: '0 1rem',
|
: '0 1rem',
|
||||||
}}
|
}}
|
||||||
|
@ -33,6 +33,7 @@ import useErrorNotification from 'hooks/useErrorNotification';
|
|||||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
ConciergeBell,
|
ConciergeBell,
|
||||||
@ -56,7 +57,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
@ -120,6 +121,21 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
const handleConditionalQueryModification = useCallback((): string => {
|
||||||
|
if (
|
||||||
|
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP
|
||||||
|
) {
|
||||||
|
return JSON.stringify(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify aggregateOperator to count, as noop is not supported in alerts
|
||||||
|
const modifiedQuery = cloneDeep(query);
|
||||||
|
|
||||||
|
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
|
||||||
|
|
||||||
|
return JSON.stringify(modifiedQuery);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
const onCreateAlertsHandler = useCallback(() => {
|
const onCreateAlertsHandler = useCallback(() => {
|
||||||
if (sourcepage === DataSource.TRACES) {
|
if (sourcepage === DataSource.TRACES) {
|
||||||
logEvent('Traces Explorer: Create alert', {
|
logEvent('Traces Explorer: Create alert', {
|
||||||
@ -130,13 +146,16 @@ function ExplorerOptions({
|
|||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifiedQuery = handleConditionalQueryModification();
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||||
JSON.stringify(query),
|
stringifiedQuery,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [history, query]);
|
}, [handleConditionalQueryModification, history]);
|
||||||
|
|
||||||
const onCancel = (value: boolean) => (): void => {
|
const onCancel = (value: boolean) => (): void => {
|
||||||
onModalToggle(value);
|
onModalToggle(value);
|
||||||
@ -482,6 +501,7 @@ function ExplorerOptions({
|
|||||||
shape="circle"
|
shape="circle"
|
||||||
onClick={hideToolbar}
|
onClick={hideToolbar}
|
||||||
icon={<PanelBottomClose size={16} />}
|
icon={<PanelBottomClose size={16} />}
|
||||||
|
data-testid="hide-toolbar"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -511,6 +531,7 @@ function ExplorerOptions({
|
|||||||
icon={<Check size={16} />}
|
icon={<Check size={16} />}
|
||||||
onClick={onSaveHandler}
|
onClick={onSaveHandler}
|
||||||
disabled={isSaveViewLoading}
|
disabled={isSaveViewLoading}
|
||||||
|
data-testid="save-view-btn"
|
||||||
>
|
>
|
||||||
Save this view
|
Save this view
|
||||||
</Button>,
|
</Button>,
|
||||||
|
@ -65,6 +65,7 @@ function ExplorerOptionsHideArea({
|
|||||||
// style={{ alignSelf: 'center', marginRight: 'calc(10% - 20px)' }}
|
// style={{ alignSelf: 'center', marginRight: 'calc(10% - 20px)' }}
|
||||||
className="explorer-show-btn"
|
className="explorer-show-btn"
|
||||||
onClick={handleShowExplorerOption}
|
onClick={handleShowExplorerOption}
|
||||||
|
data-testid="show-explorer-option"
|
||||||
>
|
>
|
||||||
<div className="menu-bar" />
|
<div className="menu-bar" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -47,6 +47,7 @@ function WidgetGraphComponent({
|
|||||||
setRequestData,
|
setRequestData,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
|
customTooltipElement,
|
||||||
}: WidgetGraphComponentProps): JSX.Element {
|
}: WidgetGraphComponentProps): JSX.Element {
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
@ -335,6 +336,7 @@ function WidgetGraphComponent({
|
|||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
|
customTooltipElement={customTooltipElement}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -33,6 +33,7 @@ function GridCardGraph({
|
|||||||
version,
|
version,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
|
customTooltipElement,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
@ -215,6 +216,7 @@ function GridCardGraph({
|
|||||||
setRequestData={setRequestData}
|
setRequestData={setRequestData}
|
||||||
onClickHandler={onClickHandler}
|
onClickHandler={onClickHandler}
|
||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
|
customTooltipElement={customTooltipElement}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,6 +31,7 @@ export interface WidgetGraphComponentProps {
|
|||||||
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
|
customTooltipElement?: HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridCardGraphProps {
|
export interface GridCardGraphProps {
|
||||||
@ -42,6 +43,7 @@ export interface GridCardGraphProps {
|
|||||||
variables?: Dashboard['data']['variables'];
|
variables?: Dashboard['data']['variables'];
|
||||||
version?: string;
|
version?: string;
|
||||||
onDragSelect: (start: number, end: number) => void;
|
onDragSelect: (start: number, end: number) => void;
|
||||||
|
customTooltipElement?: HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||||
|
@ -194,7 +194,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
|||||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||||
history.replace(generatedUrl);
|
history.push(generatedUrl);
|
||||||
|
|
||||||
if (startTimestamp !== endTimestamp) {
|
if (startTimestamp !== endTimestamp) {
|
||||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
@ -590,6 +590,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-dashboard-menu {
|
.new-dashboard-menu {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
.create-dashboard-menu-item {
|
.create-dashboard-menu-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -1067,7 +1069,7 @@
|
|||||||
color: var(--bg-ink-500);
|
color: var(--bg-ink-500);
|
||||||
}
|
}
|
||||||
.subtitle {
|
.subtitle {
|
||||||
color: var(--bg-vanilla-400);
|
color: var(--bg-ink-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-row {
|
.ant-table-row {
|
||||||
@ -1087,6 +1089,10 @@
|
|||||||
|
|
||||||
.dashboard-title {
|
.dashboard-title {
|
||||||
color: var(--bg-slate-300);
|
color: var(--bg-slate-300);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-ink-500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-with-action {
|
.title-with-action {
|
||||||
|
@ -45,6 +45,8 @@ import {
|
|||||||
Ellipsis,
|
Ellipsis,
|
||||||
EllipsisVertical,
|
EllipsisVertical,
|
||||||
Expand,
|
Expand,
|
||||||
|
ExternalLink,
|
||||||
|
Github,
|
||||||
HdmiPort,
|
HdmiPort,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
Link2,
|
Link2,
|
||||||
@ -53,6 +55,8 @@ import {
|
|||||||
RotateCw,
|
RotateCw,
|
||||||
Search,
|
Search,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
// #TODO: lucide will be removing brand icons like Github in future, in that case we can use simple icons
|
||||||
|
// see more: https://github.com/lucide-icons/lucide/issues/94
|
||||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import {
|
import {
|
||||||
@ -600,6 +604,28 @@ function DashboardsList(): JSX.Element {
|
|||||||
),
|
),
|
||||||
key: '1',
|
key: '1',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<a
|
||||||
|
href="https://github.com/SigNoz/dashboards"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
gap="small"
|
||||||
|
>
|
||||||
|
<div className="create-dashboard-menu-item">
|
||||||
|
<Github size={14} /> View templates
|
||||||
|
</div>
|
||||||
|
<ExternalLink size={14} />
|
||||||
|
</Flex>
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
key: '2',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (createNewDashboard) {
|
if (createNewDashboard) {
|
||||||
|
@ -4,7 +4,15 @@ import { red } from '@ant-design/colors';
|
|||||||
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
||||||
import MEditor, { Monaco } from '@monaco-editor/react';
|
import MEditor, { Monaco } from '@monaco-editor/react';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd';
|
import {
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
Upload,
|
||||||
|
UploadProps,
|
||||||
|
} from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
@ -13,7 +21,9 @@ import { MESSAGE } from 'hooks/useFeatureFlag';
|
|||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { MonitorDot, MoveRight, X } from 'lucide-react';
|
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
|
||||||
|
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
|
||||||
|
// See more: https://github.com/lucide-icons/lucide/issues/94
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
@ -174,6 +184,7 @@ function ImportJSON({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="action-btns-container">
|
<div className="action-btns-container">
|
||||||
|
<Flex gap="small">
|
||||||
<Upload
|
<Upload
|
||||||
accept=".json"
|
accept=".json"
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
@ -195,6 +206,21 @@ function ImportJSON({
|
|||||||
{t('upload_json_file')}
|
{t('upload_json_file')}
|
||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
|
<a
|
||||||
|
href="https://github.com/SigNoz/dashboards"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
className="periscope-btn"
|
||||||
|
icon={<Github size={14} />}
|
||||||
|
>
|
||||||
|
{t('view_template')}
|
||||||
|
<ExternalLink size={14} />
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
// disabled={editorValue.length === 0}
|
// disabled={editorValue.length === 0}
|
||||||
|
@ -38,6 +38,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
|||||||
activeLog,
|
activeLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
|||||||
data={log}
|
data={log}
|
||||||
linesPerRow={options.maxLines}
|
linesPerRow={options.maxLines}
|
||||||
selectedFields={selectedFields}
|
selectedFields={selectedFields}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -75,12 +77,14 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
|||||||
linesPerRow={options.maxLines}
|
linesPerRow={options.maxLines}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onSetActiveLog={onSetActiveLog}
|
onSetActiveLog={onSetActiveLog}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
|
options.fontSize,
|
||||||
options.format,
|
options.format,
|
||||||
options.maxLines,
|
options.maxLines,
|
||||||
selectedFields,
|
selectedFields,
|
||||||
@ -123,6 +127,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
|||||||
logs,
|
logs,
|
||||||
fields: selectedFields,
|
fields: selectedFields,
|
||||||
linesPerRow: options.maxLines,
|
linesPerRow: options.maxLines,
|
||||||
|
fontSize: options.fontSize,
|
||||||
appendTo: 'end',
|
appendTo: 'end',
|
||||||
activeLogIndex,
|
activeLogIndex,
|
||||||
}}
|
}}
|
||||||
@ -147,6 +152,7 @@ function LiveLogsList({ logs }: LiveLogsListProps): JSX.Element {
|
|||||||
log={activeLog}
|
log={activeLog}
|
||||||
onClose={onClearActiveLog}
|
onClose={onClearActiveLog}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -3,12 +3,17 @@ import './ContextLogRenderer.styles.scss';
|
|||||||
import { Skeleton } from 'antd';
|
import { Skeleton } from 'antd';
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import ShowButton from 'container/LogsContextList/ShowButton';
|
import ShowButton from 'container/LogsContextList/ShowButton';
|
||||||
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { useContextLogData } from './useContextLogData';
|
import { useContextLogData } from './useContextLogData';
|
||||||
|
|
||||||
@ -22,6 +27,20 @@ function ContextLogRenderer({
|
|||||||
const [afterLogPage, setAfterLogPage] = useState<number>(1);
|
const [afterLogPage, setAfterLogPage] = useState<number>(1);
|
||||||
const [logs, setLogs] = useState<ILog[]>([log]);
|
const [logs, setLogs] = useState<ILog[]>([log]);
|
||||||
|
|
||||||
|
const { initialDataSource, stagedQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const listQuery = useMemo(() => {
|
||||||
|
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
||||||
|
|
||||||
|
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
||||||
|
}, [stagedQuery]);
|
||||||
|
|
||||||
|
const { options } = useOptionsMenu({
|
||||||
|
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||||
|
dataSource: initialDataSource || DataSource.METRICS,
|
||||||
|
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
logs: previousLogs,
|
logs: previousLogs,
|
||||||
isFetching: isPreviousLogsFetching,
|
isFetching: isPreviousLogsFetching,
|
||||||
@ -34,6 +53,7 @@ function ContextLogRenderer({
|
|||||||
order: ORDERBY_FILTERS.ASC,
|
order: ORDERBY_FILTERS.ASC,
|
||||||
page: prevLogPage,
|
page: prevLogPage,
|
||||||
setPage: setPrevLogPage,
|
setPage: setPrevLogPage,
|
||||||
|
fontSize: options.fontSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -48,6 +68,7 @@ function ContextLogRenderer({
|
|||||||
order: ORDERBY_FILTERS.DESC,
|
order: ORDERBY_FILTERS.DESC,
|
||||||
page: afterLogPage,
|
page: afterLogPage,
|
||||||
setPage: setAfterLogPage,
|
setPage: setAfterLogPage,
|
||||||
|
fontSize: options.fontSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -65,6 +86,19 @@ function ContextLogRenderer({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
|
const lengthMultipier = useMemo(() => {
|
||||||
|
switch (options.fontSize) {
|
||||||
|
case FontSize.SMALL:
|
||||||
|
return 24;
|
||||||
|
case FontSize.MEDIUM:
|
||||||
|
return 28;
|
||||||
|
case FontSize.LARGE:
|
||||||
|
return 32;
|
||||||
|
default:
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
}, [options.fontSize]);
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
const getItemContent = useCallback(
|
||||||
(_: number, logTorender: ILog): JSX.Element => (
|
(_: number, logTorender: ILog): JSX.Element => (
|
||||||
<RawLogView
|
<RawLogView
|
||||||
@ -74,9 +108,10 @@ function ContextLogRenderer({
|
|||||||
key={logTorender.id}
|
key={logTorender.id}
|
||||||
data={logTorender}
|
data={logTorender}
|
||||||
linesPerRow={1}
|
linesPerRow={1}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[log.id],
|
[log.id, options.fontSize],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -101,7 +136,7 @@ function ContextLogRenderer({
|
|||||||
initialTopMostItemIndex={0}
|
initialTopMostItemIndex={0}
|
||||||
data={logs}
|
data={logs}
|
||||||
itemContent={getItemContent}
|
itemContent={getItemContent}
|
||||||
style={{ height: `calc(${logs.length} * 32px)` }}
|
style={{ height: `calc(${logs.length} * ${lengthMultipier}px)` }}
|
||||||
/>
|
/>
|
||||||
</OverlayScrollbar>
|
</OverlayScrollbar>
|
||||||
{isAfterLogsFetching && (
|
{isAfterLogsFetching && (
|
||||||
|
@ -4,9 +4,11 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
|||||||
import {
|
import {
|
||||||
getOrderByTimestamp,
|
getOrderByTimestamp,
|
||||||
INITIAL_PAGE_SIZE,
|
INITIAL_PAGE_SIZE,
|
||||||
|
INITIAL_PAGE_SIZE_SMALL_FONT,
|
||||||
LOGS_MORE_PAGE_SIZE,
|
LOGS_MORE_PAGE_SIZE,
|
||||||
} from 'container/LogsContextList/configs';
|
} from 'container/LogsContextList/configs';
|
||||||
import { getRequestData } from 'container/LogsContextList/utils';
|
import { getRequestData } from 'container/LogsContextList/utils';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||||
import {
|
import {
|
||||||
@ -30,6 +32,7 @@ export const useContextLogData = ({
|
|||||||
filters,
|
filters,
|
||||||
page,
|
page,
|
||||||
setPage,
|
setPage,
|
||||||
|
fontSize,
|
||||||
}: {
|
}: {
|
||||||
log: ILog;
|
log: ILog;
|
||||||
query: Query;
|
query: Query;
|
||||||
@ -38,6 +41,7 @@ export const useContextLogData = ({
|
|||||||
filters: TagFilter | null;
|
filters: TagFilter | null;
|
||||||
page: number;
|
page: number;
|
||||||
setPage: Dispatch<SetStateAction<number>>;
|
setPage: Dispatch<SetStateAction<number>>;
|
||||||
|
fontSize?: FontSize;
|
||||||
}): {
|
}): {
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
handleShowNextLines: () => void;
|
handleShowNextLines: () => void;
|
||||||
@ -54,9 +58,14 @@ export const useContextLogData = ({
|
|||||||
const logsMorePageSize = useMemo(() => (page - 1) * LOGS_MORE_PAGE_SIZE, [
|
const logsMorePageSize = useMemo(() => (page - 1) * LOGS_MORE_PAGE_SIZE, [
|
||||||
page,
|
page,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const initialPageSize =
|
||||||
|
fontSize && fontSize === FontSize.SMALL
|
||||||
|
? INITIAL_PAGE_SIZE_SMALL_FONT
|
||||||
|
: INITIAL_PAGE_SIZE;
|
||||||
const pageSize = useMemo(
|
const pageSize = useMemo(
|
||||||
() => (page <= 1 ? INITIAL_PAGE_SIZE : logsMorePageSize + INITIAL_PAGE_SIZE),
|
() => (page <= 1 ? initialPageSize : logsMorePageSize + initialPageSize),
|
||||||
[page, logsMorePageSize],
|
[page, initialPageSize, logsMorePageSize],
|
||||||
);
|
);
|
||||||
const isDisabledFetch = useMemo(() => logs.length < pageSize, [
|
const isDisabledFetch = useMemo(() => logs.length < pageSize, [
|
||||||
logs.length,
|
logs.length,
|
||||||
@ -77,8 +86,16 @@ export const useContextLogData = ({
|
|||||||
log: lastLog,
|
log: lastLog,
|
||||||
orderByTimestamp,
|
orderByTimestamp,
|
||||||
page,
|
page,
|
||||||
|
pageSize: initialPageSize,
|
||||||
}),
|
}),
|
||||||
[currentStagedQueryData, query, lastLog, orderByTimestamp, page],
|
[
|
||||||
|
currentStagedQueryData,
|
||||||
|
query,
|
||||||
|
lastLog,
|
||||||
|
orderByTimestamp,
|
||||||
|
page,
|
||||||
|
initialPageSize,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [requestData, setRequestData] = useState<Query | null>(
|
const [requestData, setRequestData] = useState<Query | null>(
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8;
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import './LogContext.styles.scss';
|
|||||||
|
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
import LogsContextList from 'container/LogsContextList';
|
import LogsContextList from 'container/LogsContextList';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
@ -37,6 +38,7 @@ function LogContext({
|
|||||||
isTextOverflowEllipsisDisabled={false}
|
isTextOverflowEllipsisDisabled={false}
|
||||||
data={log}
|
data={log}
|
||||||
linesPerRow={1}
|
linesPerRow={1}
|
||||||
|
fontSize={FontSize.SMALL}
|
||||||
/>
|
/>
|
||||||
<LogsContextList
|
<LogsContextList
|
||||||
order={ORDERBY_FILTERS.DESC}
|
order={ORDERBY_FILTERS.DESC}
|
||||||
|
@ -18,15 +18,22 @@ import { ChevronDown, ChevronRight, Search } from 'lucide-react';
|
|||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
import TableView from './TableView';
|
import TableView from './TableView';
|
||||||
|
import { removeEscapeCharacters } from './utils';
|
||||||
|
|
||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
selectedOptions: OptionsQuery;
|
selectedOptions: OptionsQuery;
|
||||||
listViewPanelSelectedFields?: IField[] | null;
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
|
onGroupByAttribute?: (
|
||||||
|
fieldKey: string,
|
||||||
|
isJSON?: boolean,
|
||||||
|
dataType?: DataTypes,
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OverviewProps &
|
type Props = OverviewProps &
|
||||||
@ -39,6 +46,7 @@ function Overview({
|
|||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
|
onGroupByAttribute,
|
||||||
listViewPanelSelectedFields,
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
||||||
@ -117,7 +125,7 @@ function Overview({
|
|||||||
children: (
|
children: (
|
||||||
<div className="logs-body-content">
|
<div className="logs-body-content">
|
||||||
<MEditor
|
<MEditor
|
||||||
value={logData.body}
|
value={removeEscapeCharacters(logData.body)}
|
||||||
language="json"
|
language="json"
|
||||||
options={options}
|
options={options}
|
||||||
onChange={(): void => {}}
|
onChange={(): void => {}}
|
||||||
@ -204,6 +212,7 @@ function Overview({
|
|||||||
logData={logData}
|
logData={logData}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
fieldSearchInput={fieldSearchInput}
|
fieldSearchInput={fieldSearchInput}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
selectedOptions={selectedOptions}
|
selectedOptions={selectedOptions}
|
||||||
@ -222,6 +231,7 @@ function Overview({
|
|||||||
Overview.defaultProps = {
|
Overview.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
listViewPanelSelectedFields: null,
|
listViewPanelSelectedFields: null,
|
||||||
|
onGroupByAttribute: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Overview;
|
export default Overview;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,8 +76,10 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: var(--bg-slate-400);
|
background: var(--bg-slate-400);
|
||||||
|
padding: 2px 3px;
|
||||||
height: 24px;
|
gap: 3px;
|
||||||
|
height: 18px;
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,21 @@ import './TableView.styles.scss';
|
|||||||
|
|
||||||
import { LinkOutlined } from '@ant-design/icons';
|
import { LinkOutlined } from '@ant-design/icons';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd';
|
import { Button, Space, Tooltip, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import AddToQueryHOC, {
|
import AddToQueryHOC, {
|
||||||
AddToQueryHOCProps,
|
AddToQueryHOCProps,
|
||||||
} from 'components/Logs/AddToQueryHOC';
|
} from 'components/Logs/AddToQueryHOC';
|
||||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { OptionsQuery } from 'container/OptionsMenu/types';
|
import { FontSize, OptionsQuery } from 'container/OptionsMenu/types';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
import { removeJSONStringifyQuotes } from 'lib/removeJSONStringifyQuotes';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { Pin } from 'lucide-react';
|
||||||
import { ArrowDownToDot, ArrowUpFromDot, Pin } from 'lucide-react';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
@ -29,17 +27,12 @@ import AppActions from 'types/actions';
|
|||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
import FieldRenderer from './FieldRenderer';
|
import FieldRenderer from './FieldRenderer';
|
||||||
import {
|
import { TableViewActions } from './TableView/TableViewActions';
|
||||||
filterKeyForField,
|
import { filterKeyForField, findKeyPath, flattenObject } from './utils';
|
||||||
findKeyPath,
|
|
||||||
flattenObject,
|
|
||||||
jsonToDataNodes,
|
|
||||||
recursiveParseJSON,
|
|
||||||
removeEscapeCharacters,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
// Fields which should be restricted from adding it to query
|
// Fields which should be restricted from adding it to query
|
||||||
const RESTRICTED_FIELDS = ['timestamp'];
|
const RESTRICTED_FIELDS = ['timestamp'];
|
||||||
@ -50,6 +43,11 @@ interface TableViewProps {
|
|||||||
selectedOptions: OptionsQuery;
|
selectedOptions: OptionsQuery;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
listViewPanelSelectedFields?: IField[] | null;
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
|
onGroupByAttribute?: (
|
||||||
|
fieldKey: string,
|
||||||
|
isJSON?: boolean,
|
||||||
|
dataType?: DataTypes,
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = TableViewProps &
|
type Props = TableViewProps &
|
||||||
@ -63,6 +61,7 @@ function TableView({
|
|||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
|
onGroupByAttribute,
|
||||||
listViewPanelSelectedFields,
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
@ -256,6 +255,7 @@ function TableView({
|
|||||||
fieldKey={fieldFilterKey}
|
fieldKey={fieldFilterKey}
|
||||||
fieldValue={flattenLogData[field]}
|
fieldValue={flattenLogData[field]}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
|
fontSize={FontSize.SMALL}
|
||||||
>
|
>
|
||||||
{renderedField}
|
{renderedField}
|
||||||
</AddToQueryHOC>
|
</AddToQueryHOC>
|
||||||
@ -270,75 +270,17 @@ function TableView({
|
|||||||
width: 70,
|
width: 70,
|
||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
className: 'value-field-container attribute-value',
|
className: 'value-field-container attribute-value',
|
||||||
render: (fieldData: Record<string, string>, record): JSX.Element => {
|
render: (fieldData: Record<string, string>, record): JSX.Element => (
|
||||||
const textToCopy = fieldData.value.slice(1, -1);
|
<TableViewActions
|
||||||
|
fieldData={fieldData}
|
||||||
if (record.field === 'body') {
|
record={record}
|
||||||
const parsedBody = recursiveParseJSON(fieldData.value);
|
isListViewPanel={isListViewPanel}
|
||||||
if (!isEmpty(parsedBody)) {
|
isfilterInLoading={isfilterInLoading}
|
||||||
return (
|
isfilterOutLoading={isfilterOutLoading}
|
||||||
<Tree defaultExpandAll showLine treeData={jsonToDataNodes(parsedBody)} />
|
onClickHandler={onClickHandler}
|
||||||
);
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldFilterKey = filterKeyForField(fieldData.field);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="value-field">
|
|
||||||
<CopyClipboardHOC textToCopy={textToCopy}>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: Color.BG_SIENNA_400,
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
tabSize: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{removeEscapeCharacters(fieldData.value)}
|
|
||||||
</span>
|
|
||||||
</CopyClipboardHOC>
|
|
||||||
|
|
||||||
{!isListViewPanel && (
|
|
||||||
<span className="action-btn">
|
|
||||||
<Tooltip title="Filter for value">
|
|
||||||
<Button
|
|
||||||
className="filter-btn periscope-btn"
|
|
||||||
icon={
|
|
||||||
isfilterInLoading ? (
|
|
||||||
<Spin size="small" />
|
|
||||||
) : (
|
|
||||||
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={onClickHandler(
|
|
||||||
OPERATORS.IN,
|
|
||||||
fieldFilterKey,
|
|
||||||
fieldData.value,
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
),
|
||||||
<Tooltip title="Filter out value">
|
|
||||||
<Button
|
|
||||||
className="filter-btn periscope-btn"
|
|
||||||
icon={
|
|
||||||
isfilterOutLoading ? (
|
|
||||||
<Spin size="small" />
|
|
||||||
) : (
|
|
||||||
<ArrowUpFromDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={onClickHandler(
|
|
||||||
OPERATORS.NIN,
|
|
||||||
fieldFilterKey,
|
|
||||||
fieldData.value,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
function sortPinnedAttributes(
|
function sortPinnedAttributes(
|
||||||
@ -379,9 +321,10 @@ function TableView({
|
|||||||
TableView.defaultProps = {
|
TableView.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
listViewPanelSelectedFields: null,
|
listViewPanelSelectedFields: null,
|
||||||
|
onGroupByAttribute: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DataType {
|
export interface DataType {
|
||||||
key: string;
|
key: string;
|
||||||
field: string;
|
field: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
.open-popover {
|
||||||
|
&.value-field {
|
||||||
|
.action-btn {
|
||||||
|
display: flex !important;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 50% !important;
|
||||||
|
right: 16px !important;
|
||||||
|
transform: translateY(-50%) !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-view-actions-content {
|
||||||
|
.ant-popover-inner {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: linear-gradient(
|
||||||
|
139deg,
|
||||||
|
rgba(18, 19, 23, 0.8) 0%,
|
||||||
|
rgba(18, 19, 23, 0.9) 98.68%
|
||||||
|
);
|
||||||
|
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
padding: 0px;
|
||||||
|
.group-by-clause {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0.14px;
|
||||||
|
padding: 12px 18px 12px 14px;
|
||||||
|
|
||||||
|
.ant-btn-icon {
|
||||||
|
margin-inline-end: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-by-clause:hover {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.table-view-actions-content {
|
||||||
|
.ant-popover-inner {
|
||||||
|
border: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-100) !important;
|
||||||
|
|
||||||
|
.group-by-clause {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
import './TableViewActions.styles.scss';
|
||||||
|
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import Convert from 'ansi-to-html';
|
||||||
|
import { Button, Popover, Spin, Tooltip, Tree } from 'antd';
|
||||||
|
import GroupByIcon from 'assets/CustomIcons/GroupByIcon';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||||
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import dompurify from 'dompurify';
|
||||||
|
import { isEmpty } from 'lodash-es';
|
||||||
|
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||||
|
|
||||||
|
import { DataType } from '../TableView';
|
||||||
|
import {
|
||||||
|
filterKeyForField,
|
||||||
|
jsonToDataNodes,
|
||||||
|
recursiveParseJSON,
|
||||||
|
removeEscapeCharacters,
|
||||||
|
unescapeString,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
|
interface ITableViewActionsProps {
|
||||||
|
fieldData: Record<string, string>;
|
||||||
|
record: DataType;
|
||||||
|
isListViewPanel: boolean;
|
||||||
|
isfilterInLoading: boolean;
|
||||||
|
isfilterOutLoading: boolean;
|
||||||
|
onGroupByAttribute?: (
|
||||||
|
fieldKey: string,
|
||||||
|
isJSON?: boolean,
|
||||||
|
dataType?: DataTypes,
|
||||||
|
) => Promise<void>;
|
||||||
|
onClickHandler: (
|
||||||
|
operator: string,
|
||||||
|
fieldKey: string,
|
||||||
|
fieldValue: string,
|
||||||
|
) => () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const convert = new Convert();
|
||||||
|
|
||||||
|
export function TableViewActions(
|
||||||
|
props: ITableViewActionsProps,
|
||||||
|
): React.ReactElement {
|
||||||
|
const {
|
||||||
|
fieldData,
|
||||||
|
record,
|
||||||
|
isListViewPanel,
|
||||||
|
isfilterInLoading,
|
||||||
|
isfilterOutLoading,
|
||||||
|
onClickHandler,
|
||||||
|
onGroupByAttribute,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
// there is no option for where clause in old logs explorer and live logs page
|
||||||
|
const isOldLogsExplorerOrLiveLogsPage = useMemo(
|
||||||
|
() => pathname === ROUTES.OLD_LOGS_EXPLORER || pathname === ROUTES.LIVE_LOGS,
|
||||||
|
[pathname],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
const textToCopy = fieldData.value;
|
||||||
|
|
||||||
|
if (record.field === 'body') {
|
||||||
|
const parsedBody = recursiveParseJSON(fieldData.value);
|
||||||
|
if (!isEmpty(parsedBody)) {
|
||||||
|
return (
|
||||||
|
<Tree defaultExpandAll showLine treeData={jsonToDataNodes(parsedBody)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bodyHtml =
|
||||||
|
record.field === 'body'
|
||||||
|
? {
|
||||||
|
__html: convert.toHtml(
|
||||||
|
dompurify.sanitize(unescapeString(record.value), {
|
||||||
|
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: { __html: '' };
|
||||||
|
|
||||||
|
const fieldFilterKey = filterKeyForField(fieldData.field);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('value-field', isOpen ? 'open-popover' : '')}>
|
||||||
|
{record.field === 'body' ? (
|
||||||
|
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: Color.BG_SIENNA_400,
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
tabSize: 4,
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={bodyHtml}
|
||||||
|
/>
|
||||||
|
</CopyClipboardHOC>
|
||||||
|
) : (
|
||||||
|
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: Color.BG_SIENNA_400,
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
tabSize: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{removeEscapeCharacters(fieldData.value)}
|
||||||
|
</span>
|
||||||
|
</CopyClipboardHOC>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isListViewPanel && (
|
||||||
|
<span className="action-btn">
|
||||||
|
<Tooltip title="Filter for value">
|
||||||
|
<Button
|
||||||
|
className="filter-btn periscope-btn"
|
||||||
|
icon={
|
||||||
|
isfilterInLoading ? (
|
||||||
|
<Spin size="small" />
|
||||||
|
) : (
|
||||||
|
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={onClickHandler(OPERATORS.IN, fieldFilterKey, fieldData.value)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Filter out value">
|
||||||
|
<Button
|
||||||
|
className="filter-btn periscope-btn"
|
||||||
|
icon={
|
||||||
|
isfilterOutLoading ? (
|
||||||
|
<Spin size="small" />
|
||||||
|
) : (
|
||||||
|
<ArrowUpFromDot size={14} style={{ transform: 'rotate(90deg)' }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={onClickHandler(OPERATORS.NIN, fieldFilterKey, fieldData.value)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
{!isOldLogsExplorerOrLiveLogsPage && (
|
||||||
|
<Popover
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
arrow={false}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className="group-by-clause"
|
||||||
|
type="text"
|
||||||
|
icon={<GroupByIcon />}
|
||||||
|
onClick={(): Promise<void> | void =>
|
||||||
|
onGroupByAttribute?.(fieldFilterKey)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Group By Attribute
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
rootClassName="table-view-actions-content"
|
||||||
|
trigger="hover"
|
||||||
|
placement="bottomLeft"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<Ellipsis size={14} />}
|
||||||
|
className="filter-btn periscope-btn"
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableViewActions.defaultProps = {
|
||||||
|
onGroupByAttribute: undefined,
|
||||||
|
};
|
@ -250,19 +250,37 @@ export const getDataTypes = (value: unknown): DataTypes => {
|
|||||||
return determineType(value);
|
return determineType(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// now we do not want to render colors everywhere like in tooltip and monaco editor hence we remove such codes to make
|
||||||
|
// the log line readable
|
||||||
export const removeEscapeCharacters = (str: string): string =>
|
export const removeEscapeCharacters = (str: string): string =>
|
||||||
str.replace(/\\([ntfr'"\\])/g, (_: string, char: string) => {
|
str
|
||||||
const escapeMap: Record<string, string> = {
|
.replace(/\\x1[bB][[0-9;]*m/g, '')
|
||||||
n: '\n',
|
.replace(/\\u001[bB][[0-9;]*m/g, '')
|
||||||
t: '\t',
|
.replace(/\\x[0-9A-Fa-f]{2}/g, '')
|
||||||
f: '\f',
|
.replace(/\\u[0-9A-Fa-f]{4}/g, '')
|
||||||
r: '\r',
|
.replace(/\\[btnfrv0'"\\]/g, '');
|
||||||
"'": "'",
|
|
||||||
'"': '"',
|
// we need to remove the escape from the escaped characters as some recievers like file log escape the unicode escape characters.
|
||||||
'\\': '\\',
|
// example: Log [\u001B[32;1mThis is bright green\u001B[0m] is being sent as [\\u001B[32;1mThis is bright green\\u001B[0m]
|
||||||
};
|
//
|
||||||
return escapeMap[char as keyof typeof escapeMap];
|
// so we need to remove this escapes to render the color properly
|
||||||
});
|
export const unescapeString = (str: string): string =>
|
||||||
|
str
|
||||||
|
.replace(/\\n/g, '\n') // Replaces escaped newlines
|
||||||
|
.replace(/\\r/g, '\r') // Replaces escaped carriage returns
|
||||||
|
.replace(/\\t/g, '\t') // Replaces escaped tabs
|
||||||
|
.replace(/\\b/g, '\b') // Replaces escaped backspaces
|
||||||
|
.replace(/\\f/g, '\f') // Replaces escaped form feeds
|
||||||
|
.replace(/\\v/g, '\v') // Replaces escaped vertical tabs
|
||||||
|
.replace(/\\'/g, "'") // Replaces escaped single quotes
|
||||||
|
.replace(/\\"/g, '"') // Replaces escaped double quotes
|
||||||
|
.replace(/\\\\/g, '\\') // Replaces escaped backslashes
|
||||||
|
.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) =>
|
||||||
|
String.fromCharCode(parseInt(hex, 16)),
|
||||||
|
) // Replaces hexadecimal escape sequences
|
||||||
|
.replace(/\\u([0-9A-Fa-f]{4})/g, (_, hex) =>
|
||||||
|
String.fromCharCode(parseInt(hex, 16)),
|
||||||
|
); // Replaces Unicode escape sequences
|
||||||
|
|
||||||
export function removeExtraSpaces(input: string): string {
|
export function removeExtraSpaces(input: string): string {
|
||||||
return input.replace(/\s+/g, ' ').trim();
|
return input.replace(/\s+/g, ' ').trim();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
|
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
export const INITIAL_PAGE_SIZE = 10;
|
export const INITIAL_PAGE_SIZE = 10;
|
||||||
|
export const INITIAL_PAGE_SIZE_SMALL_FONT = 12;
|
||||||
export const LOGS_MORE_PAGE_SIZE = 10;
|
export const LOGS_MORE_PAGE_SIZE = 10;
|
||||||
|
|
||||||
export const getOrderByTimestamp = (order: string): OrderByPayload => ({
|
export const getOrderByTimestamp = (order: string): OrderByPayload => ({
|
||||||
|
@ -5,6 +5,7 @@ import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
|||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
@ -167,6 +168,7 @@ function LogsContextList({
|
|||||||
key={log.id}
|
key={log.id}
|
||||||
data={log}
|
data={log}
|
||||||
linesPerRow={1}
|
linesPerRow={1}
|
||||||
|
fontSize={FontSize.SMALL}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[],
|
[],
|
||||||
|
@ -2,6 +2,7 @@ import { EditFilled } from '@ant-design/icons';
|
|||||||
import { Modal, Typography } from 'antd';
|
import { Modal, Typography } from 'antd';
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
import RawLogView from 'components/Logs/RawLogView';
|
||||||
import LogsContextList from 'container/LogsContextList';
|
import LogsContextList from 'container/LogsContextList';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
@ -99,6 +100,7 @@ function LogsExplorerContext({
|
|||||||
isTextOverflowEllipsisDisabled
|
isTextOverflowEllipsisDisabled
|
||||||
data={log}
|
data={log}
|
||||||
linesPerRow={1}
|
linesPerRow={1}
|
||||||
|
fontSize={FontSize.SMALL}
|
||||||
/>
|
/>
|
||||||
</LogContainer>
|
</LogContainer>
|
||||||
<LogsContextList
|
<LogsContextList
|
||||||
|
@ -3,6 +3,7 @@ import './TableRow.styles.scss';
|
|||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import LogLinesActionButtons from 'components/Logs/LogLinesActionButtons/LogLinesActionButtons';
|
import LogLinesActionButtons from 'components/Logs/LogLinesActionButtons/LogLinesActionButtons';
|
||||||
import { ColumnTypeRender } from 'components/Logs/TableView/types';
|
import { ColumnTypeRender } from 'components/Logs/TableView/types';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import {
|
import {
|
||||||
@ -24,6 +25,7 @@ interface TableRowProps {
|
|||||||
handleSetActiveContextLog: (log: ILog) => void;
|
handleSetActiveContextLog: (log: ILog) => void;
|
||||||
logs: ILog[];
|
logs: ILog[];
|
||||||
hasActions: boolean;
|
hasActions: boolean;
|
||||||
|
fontSize: FontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TableRow({
|
export default function TableRow({
|
||||||
@ -33,6 +35,7 @@ export default function TableRow({
|
|||||||
handleSetActiveContextLog,
|
handleSetActiveContextLog,
|
||||||
logs,
|
logs,
|
||||||
hasActions,
|
hasActions,
|
||||||
|
fontSize,
|
||||||
}: TableRowProps): JSX.Element {
|
}: TableRowProps): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
@ -78,6 +81,7 @@ export default function TableRow({
|
|||||||
$isDragColumn={false}
|
$isDragColumn={false}
|
||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
key={column.key}
|
key={column.key}
|
||||||
|
fontSize={fontSize}
|
||||||
>
|
>
|
||||||
{cloneElement(children, props)}
|
{cloneElement(children, props)}
|
||||||
</TableCellStyled>
|
</TableCellStyled>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
export const infinityDefaultStyles: CSSProperties = {
|
export const infinityDefaultStyles: CSSProperties = {
|
||||||
@ -5,3 +7,16 @@ export const infinityDefaultStyles: CSSProperties = {
|
|||||||
overflowX: 'scroll',
|
overflowX: 'scroll',
|
||||||
marginTop: '15px',
|
marginTop: '15px',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getInfinityDefaultStyles(fontSize: FontSize): CSSProperties {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
overflowX: 'scroll',
|
||||||
|
marginTop:
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? '10px'
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? '12px'
|
||||||
|
: '15px',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from 'react-virtuoso';
|
} from 'react-virtuoso';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { infinityDefaultStyles } from './config';
|
import { getInfinityDefaultStyles } from './config';
|
||||||
import { LogsCustomTable } from './LogsCustomTable';
|
import { LogsCustomTable } from './LogsCustomTable';
|
||||||
import { TableHeaderCellStyled, TableRowStyled } from './styles';
|
import { TableHeaderCellStyled, TableRowStyled } from './styles';
|
||||||
import TableRow from './TableRow';
|
import TableRow from './TableRow';
|
||||||
@ -59,6 +59,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
const { dataSource, columns } = useTableView({
|
const { dataSource, columns } = useTableView({
|
||||||
@ -95,9 +96,15 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
handleSetActiveContextLog={handleSetActiveContextLog}
|
handleSetActiveContextLog={handleSetActiveContextLog}
|
||||||
logs={tableViewProps.logs}
|
logs={tableViewProps.logs}
|
||||||
hasActions
|
hasActions
|
||||||
|
fontSize={tableViewProps.fontSize}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[handleSetActiveContextLog, tableColumns, tableViewProps.logs],
|
[
|
||||||
|
handleSetActiveContextLog,
|
||||||
|
tableColumns,
|
||||||
|
tableViewProps.fontSize,
|
||||||
|
tableViewProps.logs,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableHeader = useCallback(
|
const tableHeader = useCallback(
|
||||||
@ -112,6 +119,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
$isDragColumn={isDragColumn}
|
$isDragColumn={isDragColumn}
|
||||||
key={column.key}
|
key={column.key}
|
||||||
|
fontSize={tableViewProps?.fontSize}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...(isDragColumn && { className: 'dragHandler' })}
|
{...(isDragColumn && { className: 'dragHandler' })}
|
||||||
>
|
>
|
||||||
@ -121,7 +129,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
),
|
),
|
||||||
[tableColumns, isDarkMode],
|
[tableColumns, isDarkMode, tableViewProps?.fontSize],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickExpand = (index: number): void => {
|
const handleClickExpand = (index: number): void => {
|
||||||
@ -137,7 +145,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
initialTopMostItemIndex={
|
initialTopMostItemIndex={
|
||||||
tableViewProps.activeLogIndex !== -1 ? tableViewProps.activeLogIndex : 0
|
tableViewProps.activeLogIndex !== -1 ? tableViewProps.activeLogIndex : 0
|
||||||
}
|
}
|
||||||
style={infinityDefaultStyles}
|
style={getInfinityDefaultStyles(tableViewProps.fontSize)}
|
||||||
data={dataSource}
|
data={dataSource}
|
||||||
components={{
|
components={{
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
@ -165,6 +173,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
onClose={handleClearActiveContextLog}
|
onClose={handleClearActiveContextLog}
|
||||||
onAddToQuery={handleAddToQuery}
|
onAddToQuery={handleAddToQuery}
|
||||||
selectedTab={VIEW_TYPES.CONTEXT}
|
selectedTab={VIEW_TYPES.CONTEXT}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<LogDetail
|
<LogDetail
|
||||||
@ -173,6 +182,7 @@ const InfinityTable = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
|||||||
onClose={onClearActiveLog}
|
onClose={onClearActiveLog}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { getActiveLogBackground } from 'utils/logs';
|
import { getActiveLogBackground } from 'utils/logs';
|
||||||
|
|
||||||
@ -7,6 +9,7 @@ interface TableHeaderCellStyledProps {
|
|||||||
$isDragColumn: boolean;
|
$isDragColumn: boolean;
|
||||||
$isDarkMode: boolean;
|
$isDarkMode: boolean;
|
||||||
$isTimestamp?: boolean;
|
$isTimestamp?: boolean;
|
||||||
|
fontSize?: FontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableStyled = styled.table`
|
export const TableStyled = styled.table`
|
||||||
@ -15,6 +18,14 @@ export const TableStyled = styled.table`
|
|||||||
|
|
||||||
export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
export const TableCellStyled = styled.td<TableHeaderCellStyledProps>`
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `padding:0.3rem;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `padding:0.4rem;`
|
||||||
|
: fontSize === FontSize.LARGE
|
||||||
|
? `padding:0.5rem;`
|
||||||
|
: ``}
|
||||||
background-color: ${(props): string =>
|
background-color: ${(props): string =>
|
||||||
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
|
props.$isDarkMode ? 'inherit' : themeColors.whiteCream};
|
||||||
|
|
||||||
@ -33,7 +44,7 @@ export const TableRowStyled = styled.tr<{
|
|||||||
? `background-color: ${
|
? `background-color: ${
|
||||||
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
$isDarkMode ? Color.BG_SLATE_500 : Color.BG_VANILLA_300
|
||||||
} !important`
|
} !important`
|
||||||
: ''}
|
: ''};
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -66,9 +77,17 @@ export const TableHeaderCellStyled = styled.th<TableHeaderCellStyledProps>`
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
letter-spacing: -0.07px;
|
letter-spacing: -0.07px;
|
||||||
background: ${(props): string => (props.$isDarkMode ? '#0b0c0d' : '#fdfdfd')};
|
background: ${(props): string => (props.$isDarkMode ? '#0b0c0d' : '#fdfdfd')};
|
||||||
${({ $isTimestamp }): string => ($isTimestamp ? 'padding-left: 24px;' : '')}
|
|
||||||
${({ $isDragColumn }): string => ($isDragColumn ? 'cursor: col-resize;' : '')}
|
${({ $isDragColumn }): string => ($isDragColumn ? 'cursor: col-resize;' : '')}
|
||||||
|
|
||||||
|
${({ fontSize }): string =>
|
||||||
|
fontSize === FontSize.SMALL
|
||||||
|
? `font-size:11px; line-height:16px; padding: 0.1rem;`
|
||||||
|
: fontSize === FontSize.MEDIUM
|
||||||
|
? `font-size:13px; line-height:20px; padding:0.3rem;`
|
||||||
|
: fontSize === FontSize.LARGE
|
||||||
|
? `font-size:14px; line-height:24px; padding: 0.5rem;`
|
||||||
|
: ``};
|
||||||
|
${({ $isTimestamp }): string => ($isTimestamp ? 'padding-left: 24px;' : '')}
|
||||||
color: ${(props): string =>
|
color: ${(props): string =>
|
||||||
props.$isDarkMode ? 'var(--bg-vanilla-100, #fff)' : themeColors.bckgGrey};
|
props.$isDarkMode ? 'var(--bg-vanilla-100, #fff)' : themeColors.bckgGrey};
|
||||||
`;
|
`;
|
||||||
|
@ -14,6 +14,7 @@ import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
|
|||||||
import LogsError from 'container/LogsError/LogsError';
|
import LogsError from 'container/LogsError/LogsError';
|
||||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
||||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
@ -50,6 +51,7 @@ function LogsExplorerList({
|
|||||||
activeLog,
|
activeLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
@ -79,6 +81,7 @@ function LogsExplorerList({
|
|||||||
data={log}
|
data={log}
|
||||||
linesPerRow={options.maxLines}
|
linesPerRow={options.maxLines}
|
||||||
selectedFields={selectedFields}
|
selectedFields={selectedFields}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -91,6 +94,7 @@ function LogsExplorerList({
|
|||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onSetActiveLog={onSetActiveLog}
|
onSetActiveLog={onSetActiveLog}
|
||||||
activeLog={activeLog}
|
activeLog={activeLog}
|
||||||
|
fontSize={options.fontSize}
|
||||||
linesPerRow={options.maxLines}
|
linesPerRow={options.maxLines}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -99,6 +103,7 @@ function LogsExplorerList({
|
|||||||
activeLog,
|
activeLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
|
options.fontSize,
|
||||||
options.format,
|
options.format,
|
||||||
options.maxLines,
|
options.maxLines,
|
||||||
selectedFields,
|
selectedFields,
|
||||||
@ -121,6 +126,7 @@ function LogsExplorerList({
|
|||||||
logs,
|
logs,
|
||||||
fields: selectedFields,
|
fields: selectedFields,
|
||||||
linesPerRow: options.maxLines,
|
linesPerRow: options.maxLines,
|
||||||
|
fontSize: options.fontSize,
|
||||||
appendTo: 'end',
|
appendTo: 'end',
|
||||||
activeLogIndex,
|
activeLogIndex,
|
||||||
}}
|
}}
|
||||||
@ -129,13 +135,27 @@ function LogsExplorerList({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMarginTop(): string {
|
||||||
|
switch (options.fontSize) {
|
||||||
|
case FontSize.SMALL:
|
||||||
|
return '10px';
|
||||||
|
case FontSize.MEDIUM:
|
||||||
|
return '12px';
|
||||||
|
case FontSize.LARGE:
|
||||||
|
return '15px';
|
||||||
|
default:
|
||||||
|
return '15px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{ width: '100%', marginTop: '20px' }}
|
style={{ width: '100%', marginTop: getMarginTop() }}
|
||||||
bodyStyle={CARD_BODY_STYLE}
|
bodyStyle={CARD_BODY_STYLE}
|
||||||
>
|
>
|
||||||
<OverlayScrollbar isVirtuoso>
|
<OverlayScrollbar isVirtuoso>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
|
key={activeLogIndex || 'logs-virtuoso'}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
initialTopMostItemIndex={activeLogIndex !== -1 ? activeLogIndex : 0}
|
initialTopMostItemIndex={activeLogIndex !== -1 ? activeLogIndex : 0}
|
||||||
data={logs}
|
data={logs}
|
||||||
@ -151,6 +171,7 @@ function LogsExplorerList({
|
|||||||
isLoading,
|
isLoading,
|
||||||
options.format,
|
options.format,
|
||||||
options.maxLines,
|
options.maxLines,
|
||||||
|
options.fontSize,
|
||||||
activeLogIndex,
|
activeLogIndex,
|
||||||
logs,
|
logs,
|
||||||
onEndReached,
|
onEndReached,
|
||||||
@ -189,6 +210,7 @@ function LogsExplorerList({
|
|||||||
log={activeLog}
|
log={activeLog}
|
||||||
onClose={onClearActiveLog}
|
onClose={onClearActiveLog}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -80,6 +80,36 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.query-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
.rows {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 150% */
|
||||||
|
letter-spacing: 0.36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
background: #242834;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 150% */
|
||||||
|
letter-spacing: 0.36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-actions-container {
|
.logs-actions-container {
|
||||||
@ -149,6 +179,15 @@
|
|||||||
background: var(--bg-robin-400);
|
background: var(--bg-robin-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.query-stats {
|
||||||
|
.rows {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
.query-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
42
frontend/src/container/LogsExplorerViews/QueryStatus.tsx
Normal file
42
frontend/src/container/LogsExplorerViews/QueryStatus.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import './QueryStatus.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import { CircleCheck } from 'lucide-react';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface IQueryStatusProps {
|
||||||
|
loading: boolean;
|
||||||
|
error: boolean;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueryStatus(
|
||||||
|
props: IQueryStatusProps,
|
||||||
|
): React.ReactElement {
|
||||||
|
const { loading, error, success } = props;
|
||||||
|
|
||||||
|
const content = useMemo((): React.ReactElement => {
|
||||||
|
if (loading) {
|
||||||
|
return <Spin spinning size="small" indicator={<LoadingOutlined spin />} />;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="/Icons/solid-x-circle.svg"
|
||||||
|
alt="header"
|
||||||
|
className="error"
|
||||||
|
style={{ height: '14px', width: '14px' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<CircleCheck className="success" size={14} fill={Color.BG_ROBIN_500} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div />;
|
||||||
|
}, [error, loading, success]);
|
||||||
|
return <div className="query-status">{content}</div>;
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import './LogsExplorerViews.styles.scss';
|
import './LogsExplorerViews.styles.scss';
|
||||||
|
|
||||||
import { Button } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
|
import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||||
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
@ -48,7 +50,15 @@ import {
|
|||||||
} from 'lodash-es';
|
} from 'lodash-es';
|
||||||
import { Sliders } from 'lucide-react';
|
import { Sliders } from 'lucide-react';
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
MutableRefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -69,12 +79,20 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import QueryStatus from './QueryStatus';
|
||||||
|
|
||||||
function LogsExplorerViews({
|
function LogsExplorerViews({
|
||||||
selectedView,
|
selectedView,
|
||||||
showFrequencyChart,
|
showFrequencyChart,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
listQueryKeyRef,
|
||||||
|
chartQueryKeyRef,
|
||||||
}: {
|
}: {
|
||||||
selectedView: SELECTED_VIEWS;
|
selectedView: SELECTED_VIEWS;
|
||||||
showFrequencyChart: boolean;
|
showFrequencyChart: boolean;
|
||||||
|
setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
listQueryKeyRef: MutableRefObject<any>;
|
||||||
|
chartQueryKeyRef: MutableRefObject<any>;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -82,14 +100,14 @@ function LogsExplorerViews({
|
|||||||
// this is to respect the panel type present in the URL rather than defaulting it to list always.
|
// this is to respect the panel type present in the URL rather than defaulting it to list always.
|
||||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
||||||
|
|
||||||
const { activeLogId, timeRange, onTimeRangeChange } = useCopyLogLink();
|
const { activeLogId, onTimeRangeChange } = useCopyLogLink();
|
||||||
|
|
||||||
const { queryData: pageSize } = useUrlQueryData(
|
const { queryData: pageSize } = useUrlQueryData(
|
||||||
QueryParams.pageSize,
|
QueryParams.pageSize,
|
||||||
DEFAULT_PER_PAGE_VALUE,
|
DEFAULT_PER_PAGE_VALUE,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { minTime } = useSelector<AppState, GlobalReducer>(
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -116,6 +134,8 @@ function LogsExplorerViews({
|
|||||||
const [logs, setLogs] = useState<ILog[]>([]);
|
const [logs, setLogs] = useState<ILog[]>([]);
|
||||||
const [requestData, setRequestData] = useState<Query | null>(null);
|
const [requestData, setRequestData] = useState<Query | null>(null);
|
||||||
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
|
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
|
||||||
|
const [queryId, setQueryId] = useState<string>(v4());
|
||||||
|
const [queryStats, setQueryStats] = useState<WsDataEvent>();
|
||||||
|
|
||||||
const handleAxisError = useAxiosError();
|
const handleAxisError = useAxiosError();
|
||||||
|
|
||||||
@ -214,9 +234,18 @@ function LogsExplorerViews({
|
|||||||
{
|
{
|
||||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
chartQueryKeyRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
isSuccess,
|
||||||
|
} = useGetExplorerQueryRange(
|
||||||
requestData,
|
requestData,
|
||||||
panelType,
|
panelType,
|
||||||
DEFAULT_ENTITY_VERSION,
|
DEFAULT_ENTITY_VERSION,
|
||||||
@ -225,13 +254,18 @@ function LogsExplorerViews({
|
|||||||
enabled: !isLimit && !!requestData,
|
enabled: !isLimit && !!requestData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...(timeRange &&
|
...(activeLogId &&
|
||||||
activeLogId &&
|
|
||||||
!logs.length && {
|
!logs.length && {
|
||||||
start: timeRange.start,
|
start: minTime,
|
||||||
end: timeRange.end,
|
end: maxTime,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
undefined,
|
||||||
|
listQueryKeyRef,
|
||||||
|
{
|
||||||
|
...(!isEmpty(queryId) &&
|
||||||
|
selectedPanelType !== PANEL_TYPES.LIST && { 'X-SIGNOZ-QUERY-ID': queryId }),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRequestData = useCallback(
|
const getRequestData = useCallback(
|
||||||
@ -318,6 +352,23 @@ function LogsExplorerViews({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setQueryId(v4());
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isEmpty(queryId) &&
|
||||||
|
(isLoading || isFetching) &&
|
||||||
|
selectedPanelType !== PANEL_TYPES.LIST
|
||||||
|
) {
|
||||||
|
setQueryStats(undefined);
|
||||||
|
setTimeout(() => {
|
||||||
|
getQueryStats({ queryId, setData: setQueryStats });
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, [queryId, isLoading, isFetching, selectedPanelType]);
|
||||||
|
|
||||||
const logEventCalledRef = useRef(false);
|
const logEventCalledRef = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
||||||
@ -469,7 +520,7 @@ function LogsExplorerViews({
|
|||||||
setLogs(newLogs);
|
setLogs(newLogs);
|
||||||
onTimeRangeChange({
|
onTimeRangeChange({
|
||||||
start: currentParams?.start,
|
start: currentParams?.start,
|
||||||
end: timeRange?.end || currentParams?.end,
|
end: currentParams?.end,
|
||||||
pageSize: newLogs.length,
|
pageSize: newLogs.length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -486,8 +537,7 @@ function LogsExplorerViews({
|
|||||||
filters: listQuery?.filters || initialFilters,
|
filters: listQuery?.filters || initialFilters,
|
||||||
page: 1,
|
page: 1,
|
||||||
log: null,
|
log: null,
|
||||||
pageSize:
|
pageSize,
|
||||||
timeRange?.pageSize && activeLogId ? timeRange?.pageSize : pageSize,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
@ -502,7 +552,6 @@ function LogsExplorerViews({
|
|||||||
listQuery,
|
listQuery,
|
||||||
pageSize,
|
pageSize,
|
||||||
minTime,
|
minTime,
|
||||||
timeRange,
|
|
||||||
activeLogId,
|
activeLogId,
|
||||||
onTimeRangeChange,
|
onTimeRangeChange,
|
||||||
panelType,
|
panelType,
|
||||||
@ -569,6 +618,25 @@ function LogsExplorerViews({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isLoading ||
|
||||||
|
isFetching ||
|
||||||
|
isLoadingListChartData ||
|
||||||
|
isFetchingListChartData
|
||||||
|
) {
|
||||||
|
setIsLoadingQueries(true);
|
||||||
|
} else {
|
||||||
|
setIsLoadingQueries(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isFetchingListChartData,
|
||||||
|
isLoadingListChartData,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
]);
|
||||||
|
|
||||||
const flattenLogData = useMemo(
|
const flattenLogData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
@ -665,6 +733,30 @@ function LogsExplorerViews({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
|
||||||
|
selectedPanelType === PANEL_TYPES.TABLE) && (
|
||||||
|
<div className="query-stats">
|
||||||
|
<QueryStatus
|
||||||
|
loading={isLoading || isFetching}
|
||||||
|
error={isError}
|
||||||
|
success={isSuccess}
|
||||||
|
/>
|
||||||
|
{queryStats?.read_rows && (
|
||||||
|
<Typography.Text className="rows">
|
||||||
|
{getYAxisFormattedValue(queryStats.read_rows?.toString(), 'short')}{' '}
|
||||||
|
rows
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
|
{queryStats?.elapsed_ms && (
|
||||||
|
<>
|
||||||
|
<div className="divider" />
|
||||||
|
<Typography.Text className="time">
|
||||||
|
{getYAxisFormattedValue(queryStats?.elapsed_ms?.toString(), 'ms')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -46,6 +46,10 @@ jest.mock(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
jest.mock('api/common/getQueryStats', () => ({
|
||||||
|
getQueryStats: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('constants/panelTypes', () => ({
|
jest.mock('constants/panelTypes', () => ({
|
||||||
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
||||||
}));
|
}));
|
||||||
@ -79,6 +83,9 @@ const renderer = (): RenderResult =>
|
|||||||
<LogsExplorerViews
|
<LogsExplorerViews
|
||||||
selectedView={SELECTED_VIEWS.SEARCH}
|
selectedView={SELECTED_VIEWS.SEARCH}
|
||||||
showFrequencyChart
|
showFrequencyChart
|
||||||
|
setIsLoadingQueries={(): void => {}}
|
||||||
|
listQueryKeyRef={{ current: {} }}
|
||||||
|
chartQueryKeyRef={{ current: {} }}
|
||||||
/>
|
/>
|
||||||
</VirtuosoMockContext.Provider>
|
</VirtuosoMockContext.Provider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
|
@ -108,6 +108,7 @@ function LogsPanelComponent({
|
|||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
const handleRow = useCallback(
|
const handleRow = useCallback(
|
||||||
@ -244,6 +245,7 @@ function LogsPanelComponent({
|
|||||||
onClose={onClearActiveLog}
|
onClose={onClearActiveLog}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
isListViewPanel
|
isListViewPanel
|
||||||
listViewPanelSelectedFields={widget?.selectedLogFields}
|
listViewPanelSelectedFields={widget?.selectedLogFields}
|
||||||
/>
|
/>
|
||||||
|
@ -10,11 +10,14 @@ import LogsTableView from 'components/Logs/TableView';
|
|||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { CARD_BODY_STYLE } from 'constants/card';
|
import { CARD_BODY_STYLE } from 'constants/card';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
// interfaces
|
// interfaces
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
@ -35,6 +38,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
activeLog,
|
activeLog,
|
||||||
onClearActiveLog,
|
onClearActiveLog,
|
||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
|
onGroupByAttribute,
|
||||||
onSetActiveLog,
|
onSetActiveLog,
|
||||||
} = useActiveLog();
|
} = useActiveLog();
|
||||||
|
|
||||||
@ -55,6 +59,14 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
liveTail,
|
liveTail,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const { options } = useOptionsMenu({
|
||||||
|
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||||
|
// this component will alwyays be called on old logs explorer page itself!
|
||||||
|
dataSource: DataSource.LOGS,
|
||||||
|
// and we do not have table / timeseries aggregated views in the old logs explorer!
|
||||||
|
aggregateOperator: StringOperators.NOOP,
|
||||||
|
});
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
const getItemContent = useCallback(
|
||||||
(index: number): JSX.Element => {
|
(index: number): JSX.Element => {
|
||||||
const log = logs[index];
|
const log = logs[index];
|
||||||
@ -66,6 +78,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
data={log}
|
data={log}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
selectedFields={selected}
|
selectedFields={selected}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,10 +91,19 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onSetActiveLog={onSetActiveLog}
|
onSetActiveLog={onSetActiveLog}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[logs, viewMode, selected, onAddToQuery, onSetActiveLog, linesPerRow],
|
[
|
||||||
|
logs,
|
||||||
|
viewMode,
|
||||||
|
selected,
|
||||||
|
linesPerRow,
|
||||||
|
onAddToQuery,
|
||||||
|
onSetActiveLog,
|
||||||
|
options.fontSize,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
@ -92,6 +114,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
logs={logs}
|
logs={logs}
|
||||||
fields={selected}
|
fields={selected}
|
||||||
linesPerRow={linesPerRow}
|
linesPerRow={linesPerRow}
|
||||||
|
fontSize={options.fontSize}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -103,7 +126,15 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
</OverlayScrollbar>
|
</OverlayScrollbar>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}, [getItemContent, linesPerRow, logs, onSetActiveLog, selected, viewMode]);
|
}, [
|
||||||
|
getItemContent,
|
||||||
|
linesPerRow,
|
||||||
|
logs,
|
||||||
|
onSetActiveLog,
|
||||||
|
options.fontSize,
|
||||||
|
selected,
|
||||||
|
viewMode,
|
||||||
|
]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Spinner height={20} tip="Getting Logs" />;
|
return <Spinner height={20} tip="Getting Logs" />;
|
||||||
@ -126,6 +157,7 @@ function LogsTable(props: LogsTableProps): JSX.Element {
|
|||||||
selectedTab={VIEW_TYPES.OVERVIEW}
|
selectedTab={VIEW_TYPES.OVERVIEW}
|
||||||
log={activeLog}
|
log={activeLog}
|
||||||
onClose={onClearActiveLog}
|
onClose={onClearActiveLog}
|
||||||
|
onGroupByAttribute={onGroupByAttribute}
|
||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Col } from 'antd';
|
import { Col } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import Graph from 'container/GridCardLayout/GridCard';
|
import Graph from 'container/GridCardLayout/GridCard';
|
||||||
import {
|
import {
|
||||||
@ -12,8 +13,12 @@ import {
|
|||||||
convertRawQueriesToTraceSelectedTags,
|
convertRawQueriesToTraceSelectedTags,
|
||||||
resourceAttributesToTagFilterItems,
|
resourceAttributesToTagFilterItems,
|
||||||
} from 'hooks/useResourceAttribute/utils';
|
} from 'hooks/useResourceAttribute/utils';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { useParams } from 'react-router-dom';
|
import history from 'lib/history';
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@ -37,6 +42,26 @@ function DBCall(): JSX.Element {
|
|||||||
const servicename = decodeURIComponent(encodedServiceName);
|
const servicename = decodeURIComponent(encodedServiceName);
|
||||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||||
const { queries } = useResourceAttribute();
|
const { queries } = useResourceAttribute();
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||||
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||||
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||||
|
history.push(generatedUrl);
|
||||||
|
|
||||||
|
if (startTimestamp !== endTimestamp) {
|
||||||
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, pathname, urlQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const tagFilterItems: TagFilterItem[] = useMemo(
|
const tagFilterItems: TagFilterItem[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -150,6 +175,7 @@ function DBCall(): JSX.Element {
|
|||||||
'database_call_rps',
|
'database_call_rps',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
@ -185,6 +211,7 @@ function DBCall(): JSX.Element {
|
|||||||
'database_call_avg_duration',
|
'database_call_avg_duration',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Col } from 'antd';
|
import { Col } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import Graph from 'container/GridCardLayout/GridCard';
|
import Graph from 'container/GridCardLayout/GridCard';
|
||||||
import {
|
import {
|
||||||
@ -14,8 +15,12 @@ import {
|
|||||||
convertRawQueriesToTraceSelectedTags,
|
convertRawQueriesToTraceSelectedTags,
|
||||||
resourceAttributesToTagFilterItems,
|
resourceAttributesToTagFilterItems,
|
||||||
} from 'hooks/useResourceAttribute/utils';
|
} from 'hooks/useResourceAttribute/utils';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { useParams } from 'react-router-dom';
|
import history from 'lib/history';
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
@ -40,6 +45,27 @@ function External(): JSX.Element {
|
|||||||
const servicename = decodeURIComponent(encodedServiceName);
|
const servicename = decodeURIComponent(encodedServiceName);
|
||||||
const { queries } = useResourceAttribute();
|
const { queries } = useResourceAttribute();
|
||||||
|
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||||
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||||
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||||
|
history.push(generatedUrl);
|
||||||
|
|
||||||
|
if (startTimestamp !== endTimestamp) {
|
||||||
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, pathname, urlQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const tagFilterItems = useMemo(
|
const tagFilterItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||||
@ -214,6 +240,7 @@ function External(): JSX.Element {
|
|||||||
'external_call_error_percentage',
|
'external_call_error_percentage',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
@ -249,6 +276,7 @@ function External(): JSX.Element {
|
|||||||
'external_call_duration',
|
'external_call_duration',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
@ -285,6 +313,7 @@ function External(): JSX.Element {
|
|||||||
'external_call_rps_by_address',
|
'external_call_rps_by_address',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
@ -320,6 +349,7 @@ function External(): JSX.Element {
|
|||||||
'external_call_duration_by_address',
|
'external_call_duration_by_address',
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
version={ENTITY_VERSION_V4}
|
version={ENTITY_VERSION_V4}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
|
@ -185,7 +185,7 @@ function Application(): JSX.Element {
|
|||||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||||
history.replace(generatedUrl);
|
history.push(generatedUrl);
|
||||||
|
|
||||||
if (startTimestamp !== endTimestamp) {
|
if (startTimestamp !== endTimestamp) {
|
||||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
@ -26,7 +26,9 @@ function MySettings(): JSX.Element {
|
|||||||
label: (
|
label: (
|
||||||
<div className="theme-option">
|
<div className="theme-option">
|
||||||
<Sun size={12} data-testid="light-theme-icon" /> Light{' '}
|
<Sun size={12} data-testid="light-theme-icon" /> Light{' '}
|
||||||
<Tag color="magenta">Beta</Tag>
|
<Tag bordered={false} color="geekblue">
|
||||||
|
Beta
|
||||||
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
value: 'light',
|
value: 'light',
|
||||||
|
@ -247,8 +247,7 @@ export default function Onboarding(): JSX.Element {
|
|||||||
|
|
||||||
const handleNext = (): void => {
|
const handleNext = (): void => {
|
||||||
if (activeStep <= 3) {
|
if (activeStep <= 3) {
|
||||||
handleNextStep();
|
history.push(moduleRouteMap[selectedModule.id as ModulesMap]);
|
||||||
history.replace(moduleRouteMap[selectedModule.id as ModulesMap]);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -258,6 +257,13 @@ export default function Onboarding(): JSX.Element {
|
|||||||
updateSelectedDataSource(null);
|
updateSelectedDataSource(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBackNavigation = (): void => {
|
||||||
|
setCurrent(0);
|
||||||
|
setActiveStep(1);
|
||||||
|
setSelectedModule(useCases.APM);
|
||||||
|
resetProgress();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
||||||
@ -277,9 +283,11 @@ export default function Onboarding(): JSX.Element {
|
|||||||
} else if (pathname === ROUTES.GET_STARTED_AZURE_MONITORING) {
|
} else if (pathname === ROUTES.GET_STARTED_AZURE_MONITORING) {
|
||||||
handleModuleSelect(useCases.AzureMonitoring);
|
handleModuleSelect(useCases.AzureMonitoring);
|
||||||
handleNextStep();
|
handleNextStep();
|
||||||
|
} else {
|
||||||
|
handleBackNavigation();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const [form] = Form.useForm<InviteMemberFormValues>();
|
const [form] = Form.useForm<InviteMemberFormValues>();
|
||||||
const [
|
const [
|
||||||
|
@ -131,6 +131,11 @@ export default function DataSource(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const goToIntegrationsPage = (): void => {
|
const goToIntegrationsPage = (): void => {
|
||||||
|
logEvent('Onboarding V2: Go to integrations', {
|
||||||
|
module: selectedModule?.id,
|
||||||
|
dataSource: selectedDataSource?.name,
|
||||||
|
framework: selectedFramework,
|
||||||
|
});
|
||||||
history.push(ROUTES.INTEGRATIONS);
|
history.push(ROUTES.INTEGRATIONS);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,13 +11,15 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Space, Steps, Typography } from 'antd';
|
import { Button, Space, Steps, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
|
import { onboardingHelpMessage } from 'components/LaunchChatSupport/util';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { stepsMap } from 'container/OnboardingContainer/constants/stepsConfig';
|
import { stepsMap } from 'container/OnboardingContainer/constants/stepsConfig';
|
||||||
import { DataSourceType } from 'container/OnboardingContainer/Steps/DataSource/DataSource';
|
import { DataSourceType } from 'container/OnboardingContainer/Steps/DataSource/DataSource';
|
||||||
import { hasFrameworks } from 'container/OnboardingContainer/utils/dataSourceUtils';
|
import { hasFrameworks } from 'container/OnboardingContainer/utils/dataSourceUtils';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { isEmpty, isNull } from 'lodash-es';
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
import { HelpCircle, UserPlus } from 'lucide-react';
|
import { UserPlus } from 'lucide-react';
|
||||||
import { SetStateAction, useState } from 'react';
|
import { SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
import { useOnboardingContext } from '../../context/OnboardingContext';
|
import { useOnboardingContext } from '../../context/OnboardingContext';
|
||||||
@ -381,31 +383,6 @@ export default function ModuleStepsContainer({
|
|||||||
history.push('/');
|
history.push('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFacingIssuesClick = (): void => {
|
|
||||||
logEvent('Onboarding V2: Facing Issues Sending Data to SigNoz', {
|
|
||||||
dataSource: selectedDataSource?.id,
|
|
||||||
framework: selectedFramework,
|
|
||||||
environment: selectedEnvironment,
|
|
||||||
module: activeStep?.module?.id,
|
|
||||||
step: activeStep?.step?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = `Hi Team,
|
|
||||||
|
|
||||||
I am facing issues sending data to SigNoz. Here are my application details
|
|
||||||
|
|
||||||
Data Source: ${selectedDataSource?.name}
|
|
||||||
Framework:
|
|
||||||
Environment:
|
|
||||||
Module: ${activeStep?.module?.id}
|
|
||||||
|
|
||||||
Thanks
|
|
||||||
`;
|
|
||||||
if (window.Intercom) {
|
|
||||||
window.Intercom('showNewMessage', message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="onboarding-module-steps">
|
<div className="onboarding-module-steps">
|
||||||
<div className="steps-container">
|
<div className="steps-container">
|
||||||
@ -493,19 +470,26 @@ Thanks
|
|||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={handleNext} type="primary" icon={<ArrowRightOutlined />}>
|
<Button onClick={handleNext} type="primary" icon={<ArrowRightOutlined />}>
|
||||||
{current < lastStepIndex ? 'Continue to next step' : 'Done'}
|
{current < lastStepIndex ? 'Continue to next step' : 'Done'}
|
||||||
</Button>
|
</Button>
|
||||||
|
<LaunchChatSupport
|
||||||
<Button
|
attributes={{
|
||||||
className="periscope-btn"
|
dataSource: selectedDataSource?.id,
|
||||||
onClick={handleFacingIssuesClick}
|
framework: selectedFramework,
|
||||||
danger
|
environment: selectedEnvironment,
|
||||||
icon={<HelpCircle size={14} />}
|
module: activeStep?.module?.id,
|
||||||
>
|
step: activeStep?.step?.id,
|
||||||
Facing issues sending data to SigNoz?
|
screen: 'Onboarding',
|
||||||
</Button>
|
}}
|
||||||
|
eventName="Onboarding V2: Facing Issues Sending Data to SigNoz"
|
||||||
|
message={onboardingHelpMessage(
|
||||||
|
selectedDataSource?.name || '',
|
||||||
|
activeStep?.module?.id,
|
||||||
|
)}
|
||||||
|
buttonText="Facing issues sending data to SigNoz?"
|
||||||
|
onHoverText="Click here to get help with sending data to SigNoz"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
import { OptionsQuery } from './types';
|
import { FontSize, OptionsQuery } from './types';
|
||||||
|
|
||||||
export const URL_OPTIONS = 'options';
|
export const URL_OPTIONS = 'options';
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ export const defaultOptionsQuery: OptionsQuery = {
|
|||||||
selectColumns: [],
|
selectColumns: [],
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
format: 'list',
|
format: 'list',
|
||||||
|
fontSize: FontSize.SMALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTraceSelectedColumns = [
|
export const defaultTraceSelectedColumns = [
|
||||||
@ -18,6 +19,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'serviceName--string--tag--true',
|
id: 'serviceName--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@ -26,6 +28,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'name--string--tag--true',
|
id: 'name--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'durationNano',
|
key: 'durationNano',
|
||||||
@ -34,6 +37,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'durationNano--float64--tag--true',
|
id: 'durationNano--float64--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'httpMethod',
|
key: 'httpMethod',
|
||||||
@ -42,6 +46,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'httpMethod--string--tag--true',
|
id: 'httpMethod--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'responseStatusCode',
|
key: 'responseStatusCode',
|
||||||
@ -50,5 +55,6 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'responseStatusCode--string--tag--true',
|
id: 'responseStatusCode--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -2,10 +2,21 @@ import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
|||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export enum FontSize {
|
||||||
|
SMALL = 'small',
|
||||||
|
MEDIUM = 'medium',
|
||||||
|
LARGE = 'large',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FontSizeProps {
|
||||||
|
value: FontSize;
|
||||||
|
onChange: (val: FontSize) => void;
|
||||||
|
}
|
||||||
export interface OptionsQuery {
|
export interface OptionsQuery {
|
||||||
selectColumns: BaseAutocompleteData[];
|
selectColumns: BaseAutocompleteData[];
|
||||||
maxLines: number;
|
maxLines: number;
|
||||||
format: LogViewMode;
|
format: LogViewMode;
|
||||||
|
fontSize: FontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitialOptions
|
export interface InitialOptions
|
||||||
@ -18,6 +29,7 @@ export type OptionsMenuConfig = {
|
|||||||
onChange: (value: LogViewMode) => void;
|
onChange: (value: LogViewMode) => void;
|
||||||
};
|
};
|
||||||
maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>;
|
maxLines?: Pick<InputNumberProps, 'value' | 'onChange'>;
|
||||||
|
fontSize?: FontSizeProps;
|
||||||
addColumn?: Pick<
|
addColumn?: Pick<
|
||||||
SelectProps,
|
SelectProps,
|
||||||
'options' | 'onSelect' | 'onFocus' | 'onSearch' | 'onBlur'
|
'options' | 'onSelect' | 'onFocus' | 'onSearch' | 'onBlur'
|
||||||
|
@ -7,6 +7,10 @@ import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
|
|||||||
import useDebounce from 'hooks/useDebounce';
|
import useDebounce from 'hooks/useDebounce';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||||
|
import {
|
||||||
|
AllTraceFilterKeys,
|
||||||
|
AllTraceFilterKeyValue,
|
||||||
|
} from 'pages/TracesExplorer/Filter/filterUtils';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useQueries } from 'react-query';
|
import { useQueries } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
@ -21,7 +25,12 @@ import {
|
|||||||
defaultTraceSelectedColumns,
|
defaultTraceSelectedColumns,
|
||||||
URL_OPTIONS,
|
URL_OPTIONS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types';
|
import {
|
||||||
|
FontSize,
|
||||||
|
InitialOptions,
|
||||||
|
OptionsMenuConfig,
|
||||||
|
OptionsQuery,
|
||||||
|
} from './types';
|
||||||
import { getOptionsFromKeys } from './utils';
|
import { getOptionsFromKeys } from './utils';
|
||||||
|
|
||||||
interface UseOptionsMenuProps {
|
interface UseOptionsMenuProps {
|
||||||
@ -106,15 +115,40 @@ const useOptionsMenu = ({
|
|||||||
[] as BaseAutocompleteData[],
|
[] as BaseAutocompleteData[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
let initialSelected = initialOptions.selectColumns
|
||||||
(initialOptions.selectColumns
|
|
||||||
?.map((column) => attributesData.find(({ key }) => key === column))
|
?.map((column) => attributesData.find(({ key }) => key === column))
|
||||||
.filter(Boolean) as BaseAutocompleteData[]) || []
|
.filter(Boolean) as BaseAutocompleteData[];
|
||||||
|
|
||||||
|
if (dataSource === DataSource.TRACES) {
|
||||||
|
initialSelected = initialSelected
|
||||||
|
?.map((col) => {
|
||||||
|
if (col && Object.keys(AllTraceFilterKeyValue).includes(col?.key)) {
|
||||||
|
const metaData = defaultTraceSelectedColumns.find(
|
||||||
|
(coln) => coln.key === (col.key as AllTraceFilterKeys),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
key: metaData?.key,
|
||||||
|
dataType: metaData?.dataType,
|
||||||
|
type: metaData?.type,
|
||||||
|
isColumn: metaData?.isColumn,
|
||||||
|
isJSON: metaData?.isJSON,
|
||||||
|
id: metaData?.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return col;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as BaseAutocompleteData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialSelected || [];
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
isFetchedInitialAttributes,
|
isFetchedInitialAttributes,
|
||||||
initialOptions?.selectColumns,
|
initialOptions?.selectColumns,
|
||||||
initialAttributesResult,
|
initialAttributesResult,
|
||||||
|
dataSource,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -248,6 +282,17 @@ const useOptionsMenu = ({
|
|||||||
},
|
},
|
||||||
[handleRedirectWithOptionsData, optionsQueryData],
|
[handleRedirectWithOptionsData, optionsQueryData],
|
||||||
);
|
);
|
||||||
|
const handleFontSizeChange = useCallback(
|
||||||
|
(value: FontSize) => {
|
||||||
|
const optionsData: OptionsQuery = {
|
||||||
|
...optionsQueryData,
|
||||||
|
fontSize: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRedirectWithOptionsData(optionsData);
|
||||||
|
},
|
||||||
|
[handleRedirectWithOptionsData, optionsQueryData],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSearchAttribute = useCallback((value: string) => {
|
const handleSearchAttribute = useCallback((value: string) => {
|
||||||
setSearchText(value);
|
setSearchText(value);
|
||||||
@ -282,18 +327,24 @@ const useOptionsMenu = ({
|
|||||||
value: optionsQueryData.maxLines || defaultOptionsQuery.maxLines,
|
value: optionsQueryData.maxLines || defaultOptionsQuery.maxLines,
|
||||||
onChange: handleMaxLinesChange,
|
onChange: handleMaxLinesChange,
|
||||||
},
|
},
|
||||||
|
fontSize: {
|
||||||
|
value: optionsQueryData?.fontSize || defaultOptionsQuery.fontSize,
|
||||||
|
onChange: handleFontSizeChange,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
optionsFromAttributeKeys,
|
|
||||||
optionsQueryData?.maxLines,
|
|
||||||
optionsQueryData?.format,
|
|
||||||
optionsQueryData?.selectColumns,
|
|
||||||
isSearchedAttributesFetching,
|
isSearchedAttributesFetching,
|
||||||
handleSearchAttribute,
|
optionsQueryData?.selectColumns,
|
||||||
|
optionsQueryData.format,
|
||||||
|
optionsQueryData.maxLines,
|
||||||
|
optionsQueryData?.fontSize,
|
||||||
|
optionsFromAttributeKeys,
|
||||||
handleSelectColumns,
|
handleSelectColumns,
|
||||||
handleRemoveSelectedColumn,
|
handleRemoveSelectedColumn,
|
||||||
|
handleSearchAttribute,
|
||||||
handleFormatChange,
|
handleFormatChange,
|
||||||
handleMaxLinesChange,
|
handleMaxLinesChange,
|
||||||
|
handleFontSizeChange,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ function PanelWrapper({
|
|||||||
onDragSelect,
|
onDragSelect,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
tableProcessedDataRef,
|
tableProcessedDataRef,
|
||||||
|
customTooltipElement,
|
||||||
}: PanelWrapperProps): JSX.Element {
|
}: PanelWrapperProps): JSX.Element {
|
||||||
const Component = PanelTypeVsPanelWrapper[
|
const Component = PanelTypeVsPanelWrapper[
|
||||||
selectedGraph || widget.panelTypes
|
selectedGraph || widget.panelTypes
|
||||||
@ -37,6 +38,7 @@ function PanelWrapper({
|
|||||||
onDragSelect={onDragSelect}
|
onDragSelect={onDragSelect}
|
||||||
selectedGraph={selectedGraph}
|
selectedGraph={selectedGraph}
|
||||||
tableProcessedDataRef={tableProcessedDataRef}
|
tableProcessedDataRef={tableProcessedDataRef}
|
||||||
|
customTooltipElement={customTooltipElement}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ function UplotPanelWrapper({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
selectedGraph,
|
selectedGraph,
|
||||||
|
customTooltipElement,
|
||||||
}: PanelWrapperProps): JSX.Element {
|
}: PanelWrapperProps): JSX.Element {
|
||||||
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
@ -126,6 +127,7 @@ function UplotPanelWrapper({
|
|||||||
stackBarChart: widget?.stackedBarChart,
|
stackBarChart: widget?.stackedBarChart,
|
||||||
hiddenGraph,
|
hiddenGraph,
|
||||||
setHiddenGraph,
|
setHiddenGraph,
|
||||||
|
customTooltipElement,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
widget?.id,
|
widget?.id,
|
||||||
@ -147,6 +149,7 @@ function UplotPanelWrapper({
|
|||||||
selectedGraph,
|
selectedGraph,
|
||||||
currentQuery,
|
currentQuery,
|
||||||
hiddenGraph,
|
hiddenGraph,
|
||||||
|
customTooltipElement,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user