Compare commits
83 Commits
v0.42.2-de
...
v0.43.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec0185da61 | ||
|
|
fc2bdb610f | ||
|
|
a9464de62d | ||
|
|
57bfdedfe1 | ||
|
|
7bdc9c0cb0 | ||
|
|
0d5934d56b | ||
|
|
3a5a61aff9 | ||
|
|
a54b7baa7d | ||
|
|
cd63dd972d | ||
|
|
389058b9b4 | ||
|
|
27e412d1ee | ||
|
|
03dccb0101 | ||
|
|
25b74b48a5 | ||
|
|
6815a96d29 | ||
|
|
e9bb05cc5d | ||
|
|
31c0b94ae6 | ||
|
|
59c242961f | ||
|
|
872ed9e963 | ||
|
|
d6cd155988 | ||
|
|
7f4a61ffb1 | ||
|
|
7737d513a7 | ||
|
|
2bd666efae | ||
|
|
d98265f345 | ||
|
|
b480ff1e48 | ||
|
|
af353b9340 | ||
|
|
96e7505922 | ||
|
|
8f6f2f0018 | ||
|
|
1f25d386df | ||
|
|
2d7a3733da | ||
|
|
ff2a3bc4b0 | ||
|
|
33383a4503 | ||
|
|
f05b94c01e | ||
|
|
fd632f9952 | ||
|
|
fd84d7b492 | ||
|
|
e4808e585a | ||
|
|
5cfeb56f9c | ||
|
|
b947f823d7 | ||
|
|
1520c1c57d | ||
|
|
f8477981d8 | ||
|
|
9b1d596816 | ||
|
|
6a4aa9a956 | ||
|
|
a7b0ef55ad | ||
|
|
87534b6fb6 | ||
|
|
c76cef47ba | ||
|
|
3276dfa03e | ||
|
|
1a14cc305c | ||
|
|
0c7e63d735 | ||
|
|
eb74cb4c5e | ||
|
|
a47d3289d0 | ||
|
|
8ad827130e | ||
|
|
93bdfd3d83 | ||
|
|
22d8889a07 | ||
|
|
7c93944d40 | ||
|
|
ec9dbb6853 | ||
|
|
7a7d814288 | ||
|
|
3babce3ecf | ||
|
|
1610b95b84 | ||
|
|
8c02f8ec31 | ||
|
|
5e0e9da6c4 | ||
|
|
51abe71421 | ||
|
|
00d74bfebb | ||
|
|
39e0ef68ca | ||
|
|
cff20f88cd | ||
|
|
4fbb71484d | ||
|
|
f8e8132b58 | ||
|
|
a1dd170641 | ||
|
|
fe2ddf9d60 | ||
|
|
dfc99a7756 | ||
|
|
c2556facc2 | ||
|
|
2a7ad596a1 | ||
|
|
6c455ab5ce | ||
|
|
7c062163a1 | ||
|
|
d6a256247c | ||
|
|
0e2c699518 | ||
|
|
c04d0e9419 | ||
|
|
d0feff00a7 | ||
|
|
6c2a3d5d43 | ||
|
|
914b035b3f | ||
|
|
71c4fcc382 | ||
|
|
9af1c2320b | ||
|
|
cdabf9060e | ||
|
|
f069ecdb76 | ||
|
|
493aef0241 |
@@ -146,7 +146,7 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.39.0
|
||||
image: signoz/query-service:0.43.0
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
@@ -186,7 +186,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.39.0
|
||||
image: signoz/frontend:0.43.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.88.12
|
||||
image: signoz/signoz-otel-collector:0.88.20
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -237,7 +237,7 @@ services:
|
||||
- query-service
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:0.88.12
|
||||
image: signoz/signoz-schema-migrator:0.88.20
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
- --storage.path=/data
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.12}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.20}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--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`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.88.12
|
||||
image: signoz/signoz-otel-collector:0.88.20
|
||||
command:
|
||||
[
|
||||
"--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`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.39.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.43.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
[
|
||||
@@ -203,7 +203,7 @@ services:
|
||||
<<: *db-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.39.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.43.0}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -215,7 +215,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector-migrator:
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.12}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.20}
|
||||
container_name: otel-migrator
|
||||
command:
|
||||
- "--dsn=tcp://clickhouse:9000"
|
||||
@@ -229,7 +229,7 @@ services:
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.12}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.20}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -152,7 +152,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost)
|
||||
|
||||
// PAT APIs
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/app/metrics"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/parser"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request) {
|
||||
if !ah.CheckFeature(basemodel.CustomMetricsFunction) {
|
||||
zap.L().Info("CustomMetricsFunction feature is not enabled in this plan")
|
||||
ah.APIHandler.QueryRangeMetricsV2(w, r)
|
||||
return
|
||||
}
|
||||
metricsQueryRangeParams, apiErrorObj := parser.ParseMetricQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.L().Error("Error in parsing metric query params", zap.Error(apiErrorObj.Err))
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// prometheus instant query needs same timestamp
|
||||
if metricsQueryRangeParams.CompositeMetricQuery.PanelType == basemodel.QUERY_VALUE &&
|
||||
metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.PROM {
|
||||
metricsQueryRangeParams.Start = metricsQueryRangeParams.End
|
||||
}
|
||||
|
||||
// round up the end to nearest multiple
|
||||
if metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.QUERY_BUILDER {
|
||||
end := (metricsQueryRangeParams.End) / 1000
|
||||
step := metricsQueryRangeParams.Step
|
||||
metricsQueryRangeParams.End = (end / step * step) * 1000
|
||||
}
|
||||
|
||||
type channelResult struct {
|
||||
Series []*basemodel.Series
|
||||
TableName string
|
||||
Err error
|
||||
Name string
|
||||
Query string
|
||||
}
|
||||
|
||||
execClickHouseQueries := func(queries map[string]string) ([]*basemodel.Series, []string, error, map[string]string) {
|
||||
var seriesList []*basemodel.Series
|
||||
var tableName []string
|
||||
ch := make(chan channelResult, len(queries))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for name, query := range queries {
|
||||
wg.Add(1)
|
||||
go func(name, query string) {
|
||||
defer wg.Done()
|
||||
seriesList, tableName, err := ah.opts.DataConnector.GetMetricResultEE(r.Context(), query)
|
||||
for _, series := range seriesList {
|
||||
series.QueryName = name
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query}
|
||||
return
|
||||
}
|
||||
ch <- channelResult{Series: seriesList, TableName: tableName}
|
||||
}(name, query)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
|
||||
var errs []error
|
||||
errQuriesByName := make(map[string]string)
|
||||
// read values from the channel
|
||||
for r := range ch {
|
||||
if r.Err != nil {
|
||||
errs = append(errs, r.Err)
|
||||
errQuriesByName[r.Name] = r.Query
|
||||
continue
|
||||
}
|
||||
seriesList = append(seriesList, r.Series...)
|
||||
tableName = append(tableName, r.TableName)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return nil, nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName
|
||||
}
|
||||
return seriesList, tableName, nil, nil
|
||||
}
|
||||
|
||||
execPromQueries := func(metricsQueryRangeParams *basemodel.QueryRangeParamsV2) ([]*basemodel.Series, error, map[string]string) {
|
||||
var seriesList []*basemodel.Series
|
||||
ch := make(chan channelResult, len(metricsQueryRangeParams.CompositeMetricQuery.PromQueries))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for name, query := range metricsQueryRangeParams.CompositeMetricQuery.PromQueries {
|
||||
if query.Disabled {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(name string, query *basemodel.PromQuery) {
|
||||
var seriesList []*basemodel.Series
|
||||
defer wg.Done()
|
||||
tmpl := template.New("promql-query")
|
||||
tmpl, tmplErr := tmpl.Parse(query.Query)
|
||||
if tmplErr != nil {
|
||||
ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query}
|
||||
return
|
||||
}
|
||||
var queryBuf bytes.Buffer
|
||||
tmplErr = tmpl.Execute(&queryBuf, metricsQueryRangeParams.Variables)
|
||||
if tmplErr != nil {
|
||||
ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query}
|
||||
return
|
||||
}
|
||||
query.Query = queryBuf.String()
|
||||
queryModel := basemodel.QueryRangeParams{
|
||||
Start: time.UnixMilli(metricsQueryRangeParams.Start),
|
||||
End: time.UnixMilli(metricsQueryRangeParams.End),
|
||||
Step: time.Duration(metricsQueryRangeParams.Step * int64(time.Second)),
|
||||
Query: query.Query,
|
||||
}
|
||||
promResult, _, err := ah.opts.DataConnector.GetQueryRangeResult(r.Context(), &queryModel)
|
||||
if err != nil {
|
||||
ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query.Query}
|
||||
return
|
||||
}
|
||||
matrix, _ := promResult.Matrix()
|
||||
for _, v := range matrix {
|
||||
var s basemodel.Series
|
||||
s.QueryName = name
|
||||
s.Labels = v.Metric.Copy().Map()
|
||||
for _, p := range v.Floats {
|
||||
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.F})
|
||||
}
|
||||
seriesList = append(seriesList, &s)
|
||||
}
|
||||
ch <- channelResult{Series: seriesList}
|
||||
}(name, query)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
|
||||
var errs []error
|
||||
errQuriesByName := make(map[string]string)
|
||||
// read values from the channel
|
||||
for r := range ch {
|
||||
if r.Err != nil {
|
||||
errs = append(errs, r.Err)
|
||||
errQuriesByName[r.Name] = r.Query
|
||||
continue
|
||||
}
|
||||
seriesList = append(seriesList, r.Series...)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName
|
||||
}
|
||||
return seriesList, nil, nil
|
||||
}
|
||||
|
||||
var seriesList []*basemodel.Series
|
||||
var tableName []string
|
||||
var err error
|
||||
var errQuriesByName map[string]string
|
||||
switch metricsQueryRangeParams.CompositeMetricQuery.QueryType {
|
||||
case basemodel.QUERY_BUILDER:
|
||||
runQueries := metrics.PrepareBuilderMetricQueries(metricsQueryRangeParams, constants.SIGNOZ_TIMESERIES_TABLENAME)
|
||||
if runQueries.Err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: runQueries.Err}, nil)
|
||||
return
|
||||
}
|
||||
seriesList, tableName, err, errQuriesByName = execClickHouseQueries(runQueries.Queries)
|
||||
|
||||
case basemodel.CLICKHOUSE:
|
||||
queries := make(map[string]string)
|
||||
|
||||
for name, chQuery := range metricsQueryRangeParams.CompositeMetricQuery.ClickHouseQueries {
|
||||
if chQuery.Disabled {
|
||||
continue
|
||||
}
|
||||
tmpl := template.New("clickhouse-query")
|
||||
tmpl, err := tmpl.Parse(chQuery.Query)
|
||||
if err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
var query bytes.Buffer
|
||||
|
||||
// replace go template variables
|
||||
querytemplate.AssignReservedVars(metricsQueryRangeParams)
|
||||
|
||||
err = tmpl.Execute(&query, metricsQueryRangeParams.Variables)
|
||||
if err != nil {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
queries[name] = query.String()
|
||||
}
|
||||
seriesList, tableName, err, errQuriesByName = execClickHouseQueries(queries)
|
||||
case basemodel.PROM:
|
||||
seriesList, err, errQuriesByName = execPromQueries(metricsQueryRangeParams)
|
||||
default:
|
||||
err = fmt.Errorf("invalid query type")
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, errQuriesByName)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
apiErrObj := &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}
|
||||
RespondError(w, apiErrObj, errQuriesByName)
|
||||
return
|
||||
}
|
||||
if metricsQueryRangeParams.CompositeMetricQuery.PanelType == basemodel.QUERY_VALUE &&
|
||||
len(seriesList) > 1 &&
|
||||
(metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.QUERY_BUILDER ||
|
||||
metricsQueryRangeParams.CompositeMetricQuery.QueryType == basemodel.CLICKHOUSE) {
|
||||
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: fmt.Errorf("invalid: query resulted in more than one series for value type")}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
type ResponseFormat struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Result []*basemodel.Series `json:"result"`
|
||||
TableName []string `json:"tableName"`
|
||||
}
|
||||
resp := ResponseFormat{ResultType: "matrix", Result: seriesList, TableName: tableName}
|
||||
ah.Respond(w, resp)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
_ "net/http/pprof" // http profiler
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
@@ -328,7 +329,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
||||
r.Use(loggingMiddleware)
|
||||
|
||||
apiHandler.RegisterRoutes(r, am)
|
||||
apiHandler.RegisterMetricsRoutes(r, am)
|
||||
apiHandler.RegisterLogsRoutes(r, am)
|
||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||
@@ -393,13 +393,14 @@ func (lrw *loggingResponseWriter) Flush() {
|
||||
lrw.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func extractQueryRangeV3Data(path string, r *http.Request) (map[string]interface{}, bool) {
|
||||
pathToExtractBodyFrom := "/api/v3/query_range"
|
||||
func extractQueryRangeData(path string, r *http.Request) (map[string]interface{}, bool) {
|
||||
pathToExtractBodyFromV3 := "/api/v3/query_range"
|
||||
pathToExtractBodyFromV4 := "/api/v4/query_range"
|
||||
|
||||
data := map[string]interface{}{}
|
||||
var postData *v3.QueryRangeParamsV3
|
||||
|
||||
if path == pathToExtractBodyFrom && (r.Method == "POST") {
|
||||
if (r.Method == "POST") && ((path == pathToExtractBodyFromV3) || (path == pathToExtractBodyFromV4)) {
|
||||
if r.Body != nil {
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
@@ -417,6 +418,25 @@ func extractQueryRangeV3Data(path string, r *http.Request) (map[string]interface
|
||||
return nil, false
|
||||
}
|
||||
|
||||
referrer := r.Header.Get("Referer")
|
||||
|
||||
dashboardMatched, err := regexp.MatchString(`/dashboard/[a-zA-Z0-9\-]+/(new|edit)(?:\?.*)?$`, referrer)
|
||||
if err != nil {
|
||||
zap.L().Error("error while matching the referrer", zap.Error(err))
|
||||
}
|
||||
alertMatched, err := regexp.MatchString(`/alerts/(new|edit)(?:\?.*)?$`, referrer)
|
||||
if err != nil {
|
||||
zap.L().Error("error while matching the alert: ", zap.Error(err))
|
||||
}
|
||||
logsExplorerMatched, err := regexp.MatchString(`/logs/logs-explorer(?:\?.*)?$`, referrer)
|
||||
if err != nil {
|
||||
zap.L().Error("error while matching the logs explorer: ", zap.Error(err))
|
||||
}
|
||||
traceExplorerMatched, err := regexp.MatchString(`/traces-explorer(?:\?.*)?$`, referrer)
|
||||
if err != nil {
|
||||
zap.L().Error("error while matching the trace explorer: ", zap.Error(err))
|
||||
}
|
||||
|
||||
signozMetricsUsed := false
|
||||
signozLogsUsed := false
|
||||
signozTracesUsed := false
|
||||
@@ -445,6 +465,20 @@ func extractQueryRangeV3Data(path string, r *http.Request) (map[string]interface
|
||||
data["tracesUsed"] = signozTracesUsed
|
||||
userEmail, err := baseauth.GetEmailFromJwt(r.Context())
|
||||
if err == nil {
|
||||
// switch case to set data["screen"] based on the referrer
|
||||
switch {
|
||||
case dashboardMatched:
|
||||
data["screen"] = "panel"
|
||||
case alertMatched:
|
||||
data["screen"] = "alert"
|
||||
case logsExplorerMatched:
|
||||
data["screen"] = "logs-explorer"
|
||||
case traceExplorerMatched:
|
||||
data["screen"] = "traces-explorer"
|
||||
default:
|
||||
data["screen"] = "unknown"
|
||||
return data, true
|
||||
}
|
||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_API, data, userEmail, true, false)
|
||||
}
|
||||
}
|
||||
@@ -472,7 +506,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
||||
route := mux.CurrentRoute(r)
|
||||
path, _ := route.GetPathTemplate()
|
||||
|
||||
queryRangeV3data, metadataExists := extractQueryRangeV3Data(path, r)
|
||||
queryRangeData, metadataExists := extractQueryRangeData(path, r)
|
||||
getActiveLogs(path, r)
|
||||
|
||||
lrw := NewLoggingResponseWriter(w)
|
||||
@@ -480,7 +514,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
||||
|
||||
data := map[string]interface{}{"path": path, "statusCode": lrw.statusCode}
|
||||
if metadataExists {
|
||||
for key, value := range queryRangeV3data {
|
||||
for key, value := range queryRangeData {
|
||||
data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
3
frontend/.gitignore
vendored
Normal file
3
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
@@ -41,9 +41,12 @@
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "7.102.1",
|
||||
"@sentry/webpack-plugin": "2.14.2",
|
||||
"@sentry/webpack-plugin": "2.16.0",
|
||||
"@signozhq/design-tokens": "0.0.8",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@visx/group": "3.3.0",
|
||||
"@visx/shape": "3.5.0",
|
||||
"@visx/tooltip": "3.3.0",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.11.0",
|
||||
@@ -121,6 +124,7 @@
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-retry-chunk-load-plugin": "3.1.1",
|
||||
"xstate": "^4.31.0"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"button_test_channel": "Test",
|
||||
"button_return": "Back",
|
||||
"field_channel_name": "Name",
|
||||
"field_send_resolved": "Send resolved alerts",
|
||||
"field_channel_type": "Type",
|
||||
"field_webhook_url": "Webhook URL",
|
||||
"field_slack_recipient": "Recipient",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"button_test_channel": "Test",
|
||||
"button_return": "Back",
|
||||
"field_channel_name": "Name",
|
||||
"field_send_resolved": "Send resolved alerts",
|
||||
"field_channel_type": "Type",
|
||||
"field_webhook_url": "Webhook URL",
|
||||
"field_slack_recipient": "Recipient",
|
||||
|
||||
@@ -48,5 +48,5 @@
|
||||
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz",
|
||||
"SHORTCUTS": "SigNoz | Shortcuts",
|
||||
"INTEGRATIONS_INSTALLED": "SigNoz | Integrations"
|
||||
"INTEGRATIONS": "SigNoz | Integrations"
|
||||
}
|
||||
|
||||
@@ -147,7 +147,11 @@ function App(): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnBasicPlan || (isLoggedInState && role && role !== 'ADMIN')) {
|
||||
if (
|
||||
isOnBasicPlan ||
|
||||
(isLoggedInState && role && role !== 'ADMIN') ||
|
||||
!(isCloudUserVal || isEECloudUser())
|
||||
) {
|
||||
const newRoutes = routes.filter((route) => route?.path !== ROUTES.BILLING);
|
||||
setRoutes(newRoutes);
|
||||
}
|
||||
|
||||
@@ -197,11 +197,3 @@ export const InstalledIntegrations = Loadable(
|
||||
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
|
||||
),
|
||||
);
|
||||
|
||||
export const IntegrationsMarketPlace = Loadable(
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "IntegrationsMarketPlace" */ 'pages/IntegrationsModulePage'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
ErrorDetails,
|
||||
IngestionSettings,
|
||||
InstalledIntegrations,
|
||||
IntegrationsMarketPlace,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LiveLogs,
|
||||
@@ -338,18 +337,11 @@ const routes: AppRoutes[] = [
|
||||
key: 'SHORTCUTS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS_INSTALLED,
|
||||
path: ROUTES.INTEGRATIONS,
|
||||
exact: true,
|
||||
component: InstalledIntegrations,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS_INSTALLED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS_MARKETPLACE,
|
||||
exact: true,
|
||||
component: IntegrationsMarketPlace,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS_MARKETPLACE',
|
||||
key: 'INTEGRATIONS',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const create = async (
|
||||
name: props.name,
|
||||
email_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
to: props.to,
|
||||
html: props.html,
|
||||
headers: props.headers,
|
||||
|
||||
@@ -12,7 +12,7 @@ const create = async (
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
|
||||
@@ -12,7 +12,7 @@ const create = async (
|
||||
name: props.name,
|
||||
pagerduty_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
routing_key: props.routing_key,
|
||||
client: props.client,
|
||||
client_url: props.client_url,
|
||||
|
||||
@@ -12,7 +12,7 @@ const create = async (
|
||||
name: props.name,
|
||||
slack_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
api_url: props.api_url,
|
||||
channel: props.channel,
|
||||
title: props.title,
|
||||
|
||||
@@ -30,7 +30,7 @@ const create = async (
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ const editEmail = async (
|
||||
name: props.name,
|
||||
email_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
to: props.to,
|
||||
html: props.html,
|
||||
headers: props.headers,
|
||||
|
||||
@@ -12,7 +12,7 @@ const editMsTeams = async (
|
||||
name: props.name,
|
||||
msteams_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
|
||||
@@ -12,7 +12,7 @@ const editOpsgenie = async (
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
|
||||
@@ -12,7 +12,7 @@ const editPager = async (
|
||||
name: props.name,
|
||||
pagerduty_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
routing_key: props.routing_key,
|
||||
client: props.client,
|
||||
client_url: props.client_url,
|
||||
|
||||
@@ -12,7 +12,7 @@ const editSlack = async (
|
||||
name: props.name,
|
||||
slack_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
api_url: props.api_url,
|
||||
channel: props.channel,
|
||||
title: props.title,
|
||||
|
||||
@@ -29,7 +29,7 @@ const editWebhook = async (
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
send_resolved: props.send_resolved,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
|
||||
28
frontend/src/api/common/logEvent.ts
Normal file
28
frontend/src/api/common/logEvent.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { EventSuccessPayloadProps } from 'types/api/events/types';
|
||||
|
||||
const logEvent = async (
|
||||
eventName: string,
|
||||
attributes: Record<string, unknown>,
|
||||
): Promise<SuccessResponse<EventSuccessPayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/event', {
|
||||
eventName,
|
||||
attributes,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default logEvent;
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'api';
|
||||
import { ApiV4Instance } from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { MetricMetaProps } from 'types/api/metrics/getApDex';
|
||||
|
||||
@@ -6,4 +6,6 @@ export const getMetricMeta = (
|
||||
metricName: string,
|
||||
servicename: string,
|
||||
): Promise<AxiosResponse<MetricMetaProps>> =>
|
||||
axios.get(`/metric_meta?metricName=${metricName}&serviceName=${servicename}`);
|
||||
ApiV4Instance.get(
|
||||
`/metric/metric_metadata?metricName=${metricName}&serviceName=${servicename}`,
|
||||
);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { ApiV2Instance as axios } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricNameProps,
|
||||
MetricNamesPayloadProps,
|
||||
} from 'types/api/metrics/getMetricName';
|
||||
|
||||
export const getMetricName = async (
|
||||
props: MetricNameProps,
|
||||
): Promise<SuccessResponse<MetricNamesPayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/metrics/autocomplete/list?match=${props || ''}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiV2Instance as axios } from 'api';
|
||||
import { ApiV3Instance as axios } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
TagKeyProps,
|
||||
@@ -8,15 +9,19 @@ import {
|
||||
TagValueProps,
|
||||
TagValuesPayloadProps,
|
||||
} from 'types/api/metrics/getResourceAttributes';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
|
||||
export const getResourceAttributesTagKeys = async (
|
||||
props: TagKeyProps,
|
||||
): Promise<SuccessResponse<TagKeysPayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/metrics/autocomplete/tagKey?metricName=${props.metricName}${
|
||||
props.match ? `&match=${props.match}` : ''
|
||||
}`,
|
||||
`/autocomplete/attribute_keys?${createQueryParams({
|
||||
aggregateOperator: MetricAggregateOperator.RATE,
|
||||
searchText: props.match,
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateAttribute: props.metricName,
|
||||
})}`,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -35,7 +40,13 @@ export const getResourceAttributesTagValues = async (
|
||||
): Promise<SuccessResponse<TagValuesPayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/metrics/autocomplete/tagValue?metricName=${props.metricName}&tagKey=${props.tagKey}`,
|
||||
`/autocomplete/attribute_values?${createQueryParams({
|
||||
aggregateOperator: MetricAggregateOperator.RATE,
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateAttribute: props.metricName,
|
||||
attributeKey: props.tagKey,
|
||||
searchText: '',
|
||||
})}`,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ComponentType, lazy, LazyExoticComponent } from 'react';
|
||||
import { lazyRetry } from 'utils/lazyWithRetries';
|
||||
|
||||
function Loadable(importPath: {
|
||||
(): LoadableProps;
|
||||
}): LazyExoticComponent<LazyComponent> {
|
||||
return lazy(() => importPath());
|
||||
return lazy(() => lazyRetry(() => importPath()));
|
||||
}
|
||||
|
||||
type LazyComponent = ComponentType<Record<string, unknown>>;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import './LabelColumn.styles.scss';
|
||||
|
||||
import { Popover, Tag } from 'antd';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { LabelColumnProps } from './TableRenderer.types';
|
||||
import TagWithToolTip from './TagWithToolTip';
|
||||
import { getLabelAndValueContent } from './utils';
|
||||
|
||||
function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
|
||||
const newLabels = labels.length > 3 ? labels.slice(0, 3) : labels;
|
||||
@@ -19,19 +19,17 @@ function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
|
||||
)}
|
||||
{remainingLabels.length > 0 && (
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
content={
|
||||
<div>
|
||||
{labels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<TagWithToolTip
|
||||
key={label}
|
||||
label={label}
|
||||
color={color}
|
||||
value={value}
|
||||
/>
|
||||
<div key={label}>
|
||||
<Tag className="label-column--tag" color={color}>
|
||||
{getLabelAndValueContent(label, value && value[label])}
|
||||
</Tag>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,16 @@ export const getLabelRenderingValue = (
|
||||
return label;
|
||||
};
|
||||
|
||||
export const getLabelAndValueContent = (
|
||||
label: string,
|
||||
value?: string,
|
||||
): string => {
|
||||
if (value) {
|
||||
return `${label}: ${value}`;
|
||||
}
|
||||
return `${label}`;
|
||||
};
|
||||
|
||||
interface GeneratorResizeTableColumnsProp<T> {
|
||||
baseColumnOptions: ColumnsType<T>;
|
||||
dynamicColumnOption: { key: string; columnOption: ColumnType<T> }[];
|
||||
|
||||
@@ -29,6 +29,7 @@ export const getComponentForPanelType = (
|
||||
[PANEL_TYPES.LIST]:
|
||||
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
|
||||
[PANEL_TYPES.BAR]: Uplot,
|
||||
[PANEL_TYPES.PIE]: null,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ export enum PANEL_TYPES {
|
||||
LIST = 'list',
|
||||
TRACE = 'trace',
|
||||
BAR = 'bar',
|
||||
PIE = 'pie',
|
||||
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { QueryFunctionsTypes } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
|
||||
export const queryFunctionOptions: SelectOption<string, string>[] = [
|
||||
export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
{
|
||||
value: QueryFunctionsTypes.CUTOFF_MIN,
|
||||
label: 'Cut Off Min',
|
||||
@@ -65,6 +65,12 @@ export const queryFunctionOptions: SelectOption<string, string>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const logsQueryFunctionOptions: SelectOption<string, string>[] = [
|
||||
{
|
||||
value: QueryFunctionsTypes.TIME_SHIFT,
|
||||
label: 'Time Shift',
|
||||
},
|
||||
];
|
||||
interface QueryFunctionConfigType {
|
||||
[key: string]: {
|
||||
showInput: boolean;
|
||||
|
||||
@@ -51,9 +51,7 @@ const ROUTES = {
|
||||
TRACES_SAVE_VIEWS: '/traces/saved-views',
|
||||
WORKSPACE_LOCKED: '/workspace-locked',
|
||||
SHORTCUTS: '/shortcuts',
|
||||
INTEGRATIONS_BASE: '/integrations',
|
||||
INTEGRATIONS_INSTALLED: '/integrations/installed',
|
||||
INTEGRATIONS_MARKETPLACE: '/integrations/marketplace',
|
||||
INTEGRATIONS: '/integrations',
|
||||
} as const;
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
3
frontend/src/constants/sessionStorage.ts
Normal file
3
frontend/src/constants/sessionStorage.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export enum SESSIONSTORAGE {
|
||||
RETRY_LAZY_REFRESHED = 'retry-lazy-refreshed',
|
||||
}
|
||||
@@ -9,9 +9,10 @@ export const DashboardShortcuts = {
|
||||
|
||||
export const DashboardShortcutsName = {
|
||||
SaveChanges: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+s`,
|
||||
DiscardChanges: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+d`,
|
||||
};
|
||||
|
||||
export const DashboardShortcutsDescription = {
|
||||
SaveChanges: 'Save Changes',
|
||||
DiscardChanges: 'Discard Changes',
|
||||
SaveChanges: 'Save Changes for panel',
|
||||
DiscardChanges: 'Discard Changes for panel',
|
||||
};
|
||||
|
||||
@@ -13,5 +13,5 @@ export const QBShortcutsName = {
|
||||
};
|
||||
|
||||
export const QBShortcutsDescription = {
|
||||
StageAndRunQuery: 'Stage and Run the query',
|
||||
StageAndRunQuery: 'Stage and Run the current query',
|
||||
};
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
width: 100%;
|
||||
|
||||
.app-content {
|
||||
width: 100%;
|
||||
width: calc(100% - 64px);
|
||||
overflow: auto;
|
||||
z-index: 0;
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
@@ -16,6 +17,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.docked {
|
||||
.app-content {
|
||||
width: calc(100% - 240px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isDarkMode {
|
||||
|
||||
@@ -311,7 +311,13 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
|
||||
<Flex
|
||||
className={cx(
|
||||
'app-layout',
|
||||
isDarkMode ? 'darkMode' : 'lightMode',
|
||||
!collapsed && !renderFullScreen ? 'docked' : '',
|
||||
)}
|
||||
>
|
||||
{isToDisplayLayout && !renderFullScreen && (
|
||||
<SideNav
|
||||
licenseData={licenseData}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Spinner from 'components/Spinner';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { lazy, Suspense, useMemo } from 'react';
|
||||
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
||||
import { lazyRetry } from 'utils/lazyWithRetries';
|
||||
|
||||
import ErrorLink from './ErrorLink';
|
||||
import LinkContainer from './Link';
|
||||
@@ -17,8 +18,9 @@ function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
|
||||
|
||||
const items = sortedConfig.map((item) => {
|
||||
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
|
||||
const Component = lazy(
|
||||
() => import(`@ant-design/icons/es/icons/${iconName}.js`),
|
||||
|
||||
const Component = lazy(() =>
|
||||
lazyRetry(() => import(`@ant-design/icons/es/icons/${iconName}.js`)),
|
||||
);
|
||||
return {
|
||||
key: item.text + item.href,
|
||||
|
||||
@@ -53,6 +53,7 @@ function CreateAlertChannels({
|
||||
EmailChannel
|
||||
>
|
||||
>({
|
||||
send_resolved: true,
|
||||
text: `{{ range .Alerts -}}
|
||||
*Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||
|
||||
@@ -119,7 +120,7 @@ function CreateAlertChannels({
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
channel: selectedConfig?.channel || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
}),
|
||||
@@ -158,7 +159,7 @@ function CreateAlertChannels({
|
||||
let request: WebhookChannel = {
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
};
|
||||
|
||||
if (selectedConfig?.username !== '' || selectedConfig?.password !== '') {
|
||||
@@ -226,7 +227,7 @@ function CreateAlertChannels({
|
||||
|
||||
return {
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
routing_key: selectedConfig?.routing_key || '',
|
||||
client: selectedConfig?.client || '',
|
||||
client_url: selectedConfig?.client_url || '',
|
||||
@@ -274,7 +275,7 @@ function CreateAlertChannels({
|
||||
() => ({
|
||||
api_key: selectedConfig?.api_key || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
description: selectedConfig?.description || '',
|
||||
message: selectedConfig?.message || '',
|
||||
priority: selectedConfig?.priority || '',
|
||||
@@ -312,7 +313,7 @@ function CreateAlertChannels({
|
||||
const prepareEmailRequest = useCallback(
|
||||
() => ({
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
to: selectedConfig?.to || '',
|
||||
html: selectedConfig?.html || '',
|
||||
headers: selectedConfig?.headers || {},
|
||||
@@ -350,7 +351,7 @@ function CreateAlertChannels({
|
||||
() => ({
|
||||
webhook_url: selectedConfig?.webhook_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
}),
|
||||
|
||||
@@ -72,7 +72,7 @@ function EditAlertChannels({
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
channel: selectedConfig?.channel || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
id,
|
||||
@@ -115,7 +115,7 @@ function EditAlertChannels({
|
||||
return {
|
||||
api_url: selectedConfig?.api_url || '',
|
||||
name: name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
username,
|
||||
password,
|
||||
id,
|
||||
@@ -284,7 +284,7 @@ function EditAlertChannels({
|
||||
() => ({
|
||||
webhook_url: selectedConfig?.webhook_url || '',
|
||||
name: selectedConfig?.name || '',
|
||||
send_resolved: true,
|
||||
send_resolved: selectedConfig?.send_resolved || false,
|
||||
text: selectedConfig?.text || '',
|
||||
title: selectedConfig?.title || '',
|
||||
id,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.hide-update {
|
||||
left: calc(50% - 41px) !important;
|
||||
left: calc(50% - 72px) !important;
|
||||
}
|
||||
.explorer-update {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: calc(50% - 250px);
|
||||
left: calc(50% - 352px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -37,7 +37,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.explorer-options {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
@@ -78,11 +77,9 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: none;
|
||||
|
||||
border: 1px solid #1d2023;
|
||||
color: #c0c1c3;
|
||||
background-color: #161922;
|
||||
|
||||
box-shadow: none !important;
|
||||
|
||||
|
||||
@@ -373,34 +373,42 @@ function ExplorerOptions({
|
||||
onClick={handleSaveViewModalToggle}
|
||||
className={isEditDeleteSupported ? '' : 'hidden'}
|
||||
disabled={viewsIsLoading || isRefetching}
|
||||
icon={<Disc3 size={16} />}
|
||||
>
|
||||
<Disc3 size={16} /> Save this view
|
||||
Save this view
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
||||
|
||||
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
||||
<Tooltip title="Create Alerts">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onCreateAlertsHandler}
|
||||
icon={<ConciergeBell size={16} />}
|
||||
>
|
||||
Create an Alert
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onAddToDashboard}
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Tooltip title="Hide">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
shape="circle"
|
||||
onClick={onCreateAlertsHandler}
|
||||
>
|
||||
<ConciergeBell size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Add to Dashboard">
|
||||
<Button disabled={disabled} shape="circle" onClick={onAddToDashboard}>
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Hide">
|
||||
<Button disabled={disabled} shape="circle" onClick={hideToolbar}>
|
||||
<PanelBottomClose size={16} />
|
||||
</Button>
|
||||
onClick={hideToolbar}
|
||||
icon={<PanelBottomClose size={16} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -413,6 +421,7 @@ function ExplorerOptions({
|
||||
isQueryUpdated={isQueryUpdated}
|
||||
handleClearSelect={handleClearSelect}
|
||||
onUpdateQueryHandler={onUpdateQueryHandler}
|
||||
isEditDeleteSupported={isEditDeleteSupported}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -16,6 +16,7 @@ interface DroppableAreaProps {
|
||||
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
||||
handleClearSelect: () => void;
|
||||
onUpdateQueryHandler: () => void;
|
||||
isEditDeleteSupported: boolean;
|
||||
}
|
||||
|
||||
function ExplorerOptionsHideArea({
|
||||
@@ -25,6 +26,7 @@ function ExplorerOptionsHideArea({
|
||||
setIsExplorerOptionHidden,
|
||||
handleClearSelect,
|
||||
onUpdateQueryHandler,
|
||||
isEditDeleteSupported,
|
||||
}: DroppableAreaProps): JSX.Element {
|
||||
const handleShowExplorerOption = (): void => {
|
||||
if (setIsExplorerOptionHidden) {
|
||||
@@ -47,14 +49,16 @@ function ExplorerOptionsHideArea({
|
||||
icon={<X size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Update this View">
|
||||
<Button
|
||||
onClick={onUpdateQueryHandler}
|
||||
className="action-btn"
|
||||
style={{ background: Color.BG_ROBIN_500 }}
|
||||
icon={<Disc3 size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isEditDeleteSupported && (
|
||||
<Tooltip title="Update this View">
|
||||
<Button
|
||||
onClick={onUpdateQueryHandler}
|
||||
className="action-btn"
|
||||
style={{ background: Color.BG_ROBIN_500 }}
|
||||
icon={<Disc3 size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, FormInstance, Input, Select, Typography } from 'antd';
|
||||
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import UpgradePrompt from 'components/Upgrade/UpgradePrompt';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@@ -95,6 +95,22 @@ function FormAlertChannels({
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('field_send_resolved')}
|
||||
labelAlign="left"
|
||||
name="send_resolved"
|
||||
>
|
||||
<Switch
|
||||
defaultChecked={initialValue?.send_resolved}
|
||||
onChange={(value): void => {
|
||||
setSelectedConfig((state) => ({
|
||||
...state,
|
||||
send_resolved: value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
||||
<Select.Option value="slack" key="slack">
|
||||
|
||||
@@ -56,8 +56,9 @@ function QuerySection({
|
||||
initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
|
||||
}}
|
||||
showFunctions={
|
||||
alertType === AlertTypes.METRICS_BASED_ALERT &&
|
||||
alertDef.version === ENTITY_VERSION_V4
|
||||
(alertType === AlertTypes.METRICS_BASED_ALERT &&
|
||||
alertDef.version === ENTITY_VERSION_V4) ||
|
||||
alertType === AlertTypes.LOGS_BASED_ALERT
|
||||
}
|
||||
version={alertDef.version || 'v3'}
|
||||
/>
|
||||
|
||||
@@ -26,5 +26,6 @@ export const PANEL_TYPES_VS_FULL_VIEW_TABLE: PanelTypeAndGraphManagerVisibilityP
|
||||
LIST: false,
|
||||
TRACE: false,
|
||||
BAR: true,
|
||||
PIE: false,
|
||||
EMPTY_WIDGET: false,
|
||||
};
|
||||
|
||||
@@ -1,62 +1,60 @@
|
||||
import './WidgetFullView.styles.scss';
|
||||
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import { LoadingOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { Button, Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import {
|
||||
timeItems,
|
||||
timePreferance,
|
||||
} from 'container/NewWidget/RightContainer/timeItems';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||
import { useChartMutable } from 'hooks/useChartMutable';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import history from 'lib/history';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import { getLocalStorageGraphVisibilityState } from '../utils';
|
||||
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
|
||||
import GraphManager from './GraphManager';
|
||||
import { GraphContainer, TimeContainer } from './styles';
|
||||
import { FullViewProps } from './types';
|
||||
|
||||
function FullView({
|
||||
widget,
|
||||
fullViewOptions = true,
|
||||
onClickHandler,
|
||||
name,
|
||||
version,
|
||||
originalName,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
isDependedDataLoaded = false,
|
||||
onToggleModelHandler,
|
||||
parentChartRef,
|
||||
}: FullViewProps): JSX.Element {
|
||||
const { selectedTime: globalSelectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
|
||||
const fullViewRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [chartOptions, setChartOptions] = useState<uPlot.Options>();
|
||||
|
||||
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
||||
|
||||
const getSelectedTime = useCallback(
|
||||
@@ -74,24 +72,70 @@ function FullView({
|
||||
|
||||
const updatedQuery = useStepInterval(widget?.query);
|
||||
|
||||
const response = useGetQueryRange(
|
||||
{
|
||||
selectedTime: selectedTime.enum,
|
||||
graphType:
|
||||
widget.panelTypes === PANEL_TYPES.BAR
|
||||
? PANEL_TYPES.TIME_SERIES
|
||||
: widget.panelTypes,
|
||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||
if (widget.panelTypes !== PANEL_TYPES.LIST) {
|
||||
return {
|
||||
selectedTime: selectedTime.enum,
|
||||
graphType: getGraphType(widget.panelTypes),
|
||||
query: updatedQuery,
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
};
|
||||
}
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
return {
|
||||
query: updatedQuery,
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: widget?.timePreferance || 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
},
|
||||
tableParams: {
|
||||
pagination: {
|
||||
offset: 0,
|
||||
limit: updatedQuery.builder.queryData[0].limit || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setRequestData((prev) => ({
|
||||
...prev,
|
||||
selectedTime: selectedTime.enum,
|
||||
}));
|
||||
}, [selectedTime]);
|
||||
|
||||
const response = useGetQueryRange(
|
||||
requestData,
|
||||
selectedDashboard?.data?.version || version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
|
||||
enabled: !isDependedDataLoaded && widget.panelTypes !== PANEL_TYPES.LIST, // Internally both the list view panel has it's own query range api call, so we don't need to call it again
|
||||
queryKey: [widget?.query, widget?.panelTypes, requestData, version],
|
||||
enabled: !isDependedDataLoaded,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
);
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax('custom', [
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
]);
|
||||
|
||||
urlQuery.set(QueryParams.startTime, minTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, maxTime.toString());
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[dispatch, location.pathname, urlQuery],
|
||||
);
|
||||
|
||||
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<
|
||||
boolean[]
|
||||
>(Array(response.data?.payload.data.result.length).fill(true));
|
||||
@@ -118,60 +162,6 @@ function FullView({
|
||||
response.data.payload.data.result = sortedSeriesData;
|
||||
}
|
||||
|
||||
const chartData = getUPlotChartData(response?.data?.payload, widget.fillSpans);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(response);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, response]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!response.isFetching && fullViewRef.current) {
|
||||
const width = fullViewRef.current?.clientWidth
|
||||
? fullViewRef.current.clientWidth - 45
|
||||
: 700;
|
||||
|
||||
const height = fullViewRef.current?.clientWidth
|
||||
? fullViewRef.current.clientHeight
|
||||
: 300;
|
||||
|
||||
const newChartOptions = getUPlotChartOptions({
|
||||
id: originalName,
|
||||
yAxisUnit: yAxisUnit || '',
|
||||
apiResponse: response.data?.payload,
|
||||
dimensions: {
|
||||
height,
|
||||
width,
|
||||
},
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
graphsVisibilityStates,
|
||||
setGraphsVisibilityStates,
|
||||
thresholds: widget.thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
softMax: widget.softMax === undefined ? null : widget.softMax,
|
||||
softMin: widget.softMin === undefined ? null : widget.softMin,
|
||||
panelType: widget.panelTypes,
|
||||
});
|
||||
|
||||
setChartOptions(newChartOptions);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [response.isFetching, graphsVisibilityStates, fullViewRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
graphsVisibilityStates?.forEach((e, i) => {
|
||||
fullViewChartRef?.current?.toggleGraph(i, e);
|
||||
@@ -180,7 +170,7 @@ function FullView({
|
||||
|
||||
const isListView = widget.panelTypes === PANEL_TYPES.LIST;
|
||||
|
||||
if (response.isFetching) {
|
||||
if (response.isLoading && widget.panelTypes !== PANEL_TYPES.LIST) {
|
||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
@@ -189,6 +179,9 @@ function FullView({
|
||||
<div className="full-view-header-container">
|
||||
{fullViewOptions && (
|
||||
<TimeContainer $panelType={widget.panelTypes}>
|
||||
{response.isFetching && (
|
||||
<Spin spinning indicator={<LoadingOutlined spin />} />
|
||||
)}
|
||||
<TimePreference
|
||||
selectedTime={selectedTime}
|
||||
setSelectedTime={setSelectedTime}
|
||||
@@ -214,47 +207,24 @@ function FullView({
|
||||
})}
|
||||
ref={fullViewRef}
|
||||
>
|
||||
{chartOptions && (
|
||||
<GraphContainer
|
||||
style={{
|
||||
height: isListView ? '100%' : '90%',
|
||||
}}
|
||||
isGraphLegendToggleAvailable={canModifyChart}
|
||||
>
|
||||
<GridPanelSwitch
|
||||
panelType={widget.panelTypes}
|
||||
data={chartData}
|
||||
options={chartOptions}
|
||||
onClickHandler={onClickHandler}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onDragSelect={onDragSelect}
|
||||
panelData={response.data?.payload.data.newResult.data.result || []}
|
||||
query={widget.query}
|
||||
ref={fullViewChartRef}
|
||||
thresholds={widget.thresholds}
|
||||
selectedLogFields={widget.selectedLogFields}
|
||||
dataSource={widget.query.builder.queryData[0].dataSource}
|
||||
selectedTracesFields={widget.selectedTracesFields}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
</GraphContainer>
|
||||
)}
|
||||
<GraphContainer
|
||||
style={{
|
||||
height: isListView ? '100%' : '90%',
|
||||
}}
|
||||
isGraphLegendToggleAvailable={canModifyChart}
|
||||
>
|
||||
<PanelWrapper
|
||||
queryResponse={response}
|
||||
widget={widget}
|
||||
setRequestData={setRequestData}
|
||||
isFullViewMode
|
||||
onToggleModelHandler={onToggleModelHandler}
|
||||
setGraphVisibility={setGraphsVisibilityStates}
|
||||
graphVisibility={graphsVisibilityStates}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</div>
|
||||
|
||||
{canModifyChart && chartOptions && !isDashboardLocked && (
|
||||
<GraphManager
|
||||
data={chartData}
|
||||
name={originalName}
|
||||
options={chartOptions}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onToggleModelHandler={onToggleModelHandler}
|
||||
setGraphsVisibilityStates={setGraphsVisibilityStates}
|
||||
graphsVisibilityStates={graphsVisibilityStates}
|
||||
lineChartRef={fullViewChartRef}
|
||||
parentChartRef={parentChartRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export const NotFoundContainer = styled.div`
|
||||
export const TimeContainer = styled.div<Props>`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
${({ $panelType }): FlattenSimpleInterpolation =>
|
||||
$panelType === PANEL_TYPES.TABLE
|
||||
? css`
|
||||
|
||||
@@ -53,10 +53,8 @@ export interface FullViewProps {
|
||||
version?: string;
|
||||
originalName: string;
|
||||
yAxisUnit?: string;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
isDependedDataLoaded?: boolean;
|
||||
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
|
||||
parentChartRef: GraphManagerProps['lineChartRef'];
|
||||
}
|
||||
|
||||
export interface GraphManagerProps extends UplotProps {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
@@ -33,23 +33,20 @@ import FullView from './FullView';
|
||||
import { Modal } from './styles';
|
||||
import { WidgetGraphComponentProps } from './types';
|
||||
import { getLocalStorageGraphVisibilityState } from './utils';
|
||||
// import { getLocalStorageGraphVisibilityState } from './utils';
|
||||
|
||||
function WidgetGraphComponent({
|
||||
widget,
|
||||
queryResponse,
|
||||
errorMessage,
|
||||
name,
|
||||
version,
|
||||
threshold,
|
||||
headerMenuList,
|
||||
isWarning,
|
||||
data,
|
||||
options,
|
||||
graphVisibiltyState,
|
||||
isFetchingResponse,
|
||||
setRequestData,
|
||||
onClickHandler,
|
||||
onDragSelect,
|
||||
setGraphVisibility,
|
||||
isFetchingResponse,
|
||||
}: WidgetGraphComponentProps): JSX.Element {
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
@@ -61,12 +58,15 @@ function WidgetGraphComponent({
|
||||
const isFullViewOpen = params.get(QueryParams.expandedWidgetId) === widget.id;
|
||||
|
||||
const lineChartRef = useRef<ToggleGraphProps>();
|
||||
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
|
||||
Array(queryResponse.data?.payload?.data.result.length || 0).fill(true),
|
||||
);
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lineChartRef.current) return;
|
||||
|
||||
graphVisibiltyState.forEach((state, index) => {
|
||||
graphVisibility.forEach((state, index) => {
|
||||
lineChartRef.current?.toggleGraph(index, state);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -210,7 +210,7 @@ function WidgetGraphComponent({
|
||||
graphVisibilityStates: localStoredVisibilityState,
|
||||
} = getLocalStorageGraphVisibilityState({
|
||||
apiResponse: queryResponse.data.payload.data.result,
|
||||
name,
|
||||
name: widget.id,
|
||||
});
|
||||
setGraphVisibility(localStoredVisibilityState);
|
||||
}
|
||||
@@ -252,7 +252,7 @@ function WidgetGraphComponent({
|
||||
onBlur={(): void => {
|
||||
setHovered(false);
|
||||
}}
|
||||
id={name}
|
||||
id={widget.id}
|
||||
>
|
||||
<Modal
|
||||
destroyOnClose
|
||||
@@ -278,14 +278,12 @@ function WidgetGraphComponent({
|
||||
className="widget-full-view"
|
||||
>
|
||||
<FullView
|
||||
name={`${name}expanded`}
|
||||
name={`${widget.id}expanded`}
|
||||
version={version}
|
||||
originalName={name}
|
||||
originalName={widget.id}
|
||||
widget={widget}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
onToggleModelHandler={onToggleModelHandler}
|
||||
parentChartRef={lineChartRef}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -305,26 +303,22 @@ function WidgetGraphComponent({
|
||||
isFetchingResponse={isFetchingResponse}
|
||||
/>
|
||||
</div>
|
||||
{queryResponse.isLoading && <Skeleton />}
|
||||
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
|
||||
<Skeleton />
|
||||
)}
|
||||
{(queryResponse.isSuccess || widget.panelTypes === PANEL_TYPES.LIST) && (
|
||||
<div
|
||||
className={cx('widget-graph-container', widget.panelTypes)}
|
||||
ref={graphRef}
|
||||
>
|
||||
<GridPanelSwitch
|
||||
panelType={widget.panelTypes}
|
||||
data={data}
|
||||
name={name}
|
||||
ref={lineChartRef}
|
||||
options={options}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
<PanelWrapper
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
setGraphVisibility={setGraphVisibility}
|
||||
graphVisibility={graphVisibility}
|
||||
onClickHandler={onClickHandler}
|
||||
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
|
||||
query={widget.query}
|
||||
thresholds={widget.thresholds}
|
||||
selectedLogFields={widget.selectedLogFields}
|
||||
dataSource={widget.query.builder?.queryData[0]?.dataSource}
|
||||
selectedTracesFields={widget.selectedTracesFields}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,80 +4,47 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import _noop from 'lodash-es/noop';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import EmptyWidget from '../EmptyWidget';
|
||||
import { MenuItemKeys } from '../WidgetHeader/contants';
|
||||
import { GridCardGraphProps } from './types';
|
||||
import { getLocalStorageGraphVisibilityState } from './utils';
|
||||
import WidgetGraphComponent from './WidgetGraphComponent';
|
||||
|
||||
function GridCardGraph({
|
||||
widget,
|
||||
name,
|
||||
onClickHandler = _noop,
|
||||
headerMenuList = [MenuItemKeys.View],
|
||||
isQueryEnabled,
|
||||
threshold,
|
||||
variables,
|
||||
fillSpans = false,
|
||||
version,
|
||||
onClickHandler,
|
||||
onDragSelect,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
const {
|
||||
toScrollWidgetId,
|
||||
setToScrollWidgetId,
|
||||
variablesToGetUpdated,
|
||||
} = useDashboard();
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax('custom', [
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
]);
|
||||
|
||||
urlQuery.set(QueryParams.startTime, minTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, maxTime.toString());
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[dispatch, location.pathname, urlQuery],
|
||||
);
|
||||
|
||||
const handleBackNavigation = (): void => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const startTime = searchParams.get(QueryParams.startTime);
|
||||
@@ -131,15 +98,39 @@ function GridCardGraph({
|
||||
isVisible &&
|
||||
!isEmptyWidget &&
|
||||
isQueryEnabled &&
|
||||
widget.panelTypes !== PANEL_TYPES.LIST;
|
||||
isEmpty(variablesToGetUpdated);
|
||||
|
||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||
if (widget.panelTypes !== PANEL_TYPES.LIST) {
|
||||
return {
|
||||
selectedTime: widget?.timePreferance,
|
||||
graphType: getGraphType(widget.panelTypes),
|
||||
query: updatedQuery,
|
||||
globalSelectedInterval,
|
||||
variables: getDashboardVariables(variables),
|
||||
};
|
||||
}
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
return {
|
||||
query: updatedQuery,
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
||||
globalSelectedInterval,
|
||||
tableParams: {
|
||||
pagination: {
|
||||
offset: 0,
|
||||
limit: updatedQuery.builder.queryData[0].limit || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const queryResponse = useGetQueryRange(
|
||||
{
|
||||
selectedTime: widget?.timePreferance,
|
||||
graphType: getGraphType(widget.panelTypes),
|
||||
query: updatedQuery,
|
||||
globalSelectedInterval,
|
||||
...requestData,
|
||||
variables: getDashboardVariables(variables),
|
||||
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
||||
globalSelectedInterval,
|
||||
},
|
||||
version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
@@ -151,6 +142,7 @@ function GridCardGraph({
|
||||
widget?.query,
|
||||
widget?.panelTypes,
|
||||
widget.timePreferance,
|
||||
requestData,
|
||||
],
|
||||
retry(failureCount, error): boolean {
|
||||
if (
|
||||
@@ -173,15 +165,6 @@ function GridCardGraph({
|
||||
|
||||
const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET;
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(queryResponse);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
|
||||
|
||||
if (queryResponse.data && widget.panelTypes === PANEL_TYPES.BAR) {
|
||||
const sortedSeriesData = getSortedSeriesData(
|
||||
queryResponse.data?.payload.data.result,
|
||||
@@ -189,89 +172,30 @@ function GridCardGraph({
|
||||
queryResponse.data.payload.data.result = sortedSeriesData;
|
||||
}
|
||||
|
||||
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const menuList =
|
||||
widget.panelTypes === PANEL_TYPES.TABLE ||
|
||||
widget.panelTypes === PANEL_TYPES.LIST
|
||||
widget.panelTypes === PANEL_TYPES.LIST ||
|
||||
widget.panelTypes === PANEL_TYPES.PIE
|
||||
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
|
||||
: headerMenuList;
|
||||
|
||||
const [graphVisibility, setGraphVisibility] = useState<boolean[]>(
|
||||
Array(queryResponse.data?.payload?.data.result.length || 0).fill(true),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
graphVisibilityStates: localStoredVisibilityState,
|
||||
} = getLocalStorageGraphVisibilityState({
|
||||
apiResponse: queryResponse.data?.payload.data.result || [],
|
||||
name,
|
||||
});
|
||||
setGraphVisibility(localStoredVisibilityState);
|
||||
}, [name, queryResponse.data?.payload.data.result]);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
id: widget?.id,
|
||||
apiResponse: queryResponse.data?.payload,
|
||||
dimensions: containerDimensions,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
yAxisUnit: widget?.yAxisUnit,
|
||||
onClickHandler,
|
||||
thresholds: widget.thresholds,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
softMax: widget.softMax === undefined ? null : widget.softMax,
|
||||
softMin: widget.softMin === undefined ? null : widget.softMin,
|
||||
graphsVisibilityStates: graphVisibility,
|
||||
setGraphsVisibilityStates: setGraphVisibility,
|
||||
panelType: widget.panelTypes,
|
||||
}),
|
||||
[
|
||||
widget?.id,
|
||||
widget?.yAxisUnit,
|
||||
widget.thresholds,
|
||||
widget.softMax,
|
||||
widget.softMin,
|
||||
queryResponse.data?.payload,
|
||||
containerDimensions,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
onClickHandler,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
graphVisibility,
|
||||
setGraphVisibility,
|
||||
widget.panelTypes,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
|
||||
{isEmptyLayout ? (
|
||||
<EmptyWidget />
|
||||
) : (
|
||||
<WidgetGraphComponent
|
||||
data={chartData}
|
||||
options={options}
|
||||
widget={widget}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
isWarning={false}
|
||||
name={name}
|
||||
version={version}
|
||||
onDragSelect={onDragSelect}
|
||||
threshold={threshold}
|
||||
headerMenuList={menuList}
|
||||
onClickHandler={onClickHandler}
|
||||
graphVisibiltyState={graphVisibility}
|
||||
setGraphVisibility={setGraphVisibility}
|
||||
isFetchingResponse={queryResponse.isFetching}
|
||||
setRequestData={setRequestData}
|
||||
onClickHandler={onClickHandler}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { UplotProps } from 'components/Uplot/Uplot';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
@@ -16,35 +16,32 @@ export interface GraphVisibilityLegendEntryProps {
|
||||
legendEntry: LegendEntryProps[];
|
||||
}
|
||||
|
||||
export interface WidgetGraphComponentProps extends UplotProps {
|
||||
export interface WidgetGraphComponentProps {
|
||||
widget: Widgets;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
errorMessage: string | undefined;
|
||||
name: string;
|
||||
version?: string;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
threshold?: ReactNode;
|
||||
headerMenuList: MenuItemKeys[];
|
||||
isWarning: boolean;
|
||||
graphVisibiltyState: boolean[];
|
||||
setGraphVisibility: Dispatch<SetStateAction<boolean[]>>;
|
||||
isFetchingResponse: boolean;
|
||||
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
}
|
||||
|
||||
export interface GridCardGraphProps {
|
||||
widget: Widgets;
|
||||
name: string;
|
||||
onDragSelect?: (start: number, end: number) => void;
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
threshold?: ReactNode;
|
||||
headerMenuList?: WidgetGraphComponentProps['headerMenuList'];
|
||||
onClickHandler?: OnClickPluginOpts['onClick'];
|
||||
isQueryEnabled: boolean;
|
||||
variables?: Dashboard['data']['variables'];
|
||||
fillSpans?: boolean;
|
||||
version?: string;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
}
|
||||
|
||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
.react-grid-layout {
|
||||
border: none !important;
|
||||
margin-top: 0;
|
||||
|
||||
.widget-graph-container {
|
||||
&.graph {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +29,7 @@
|
||||
.ant-modal-header {
|
||||
background-color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
|
||||
@@ -3,20 +3,25 @@ import './GridCardLayout.styles.scss';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { FullscreenIcon } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
@@ -45,6 +50,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
} = useDashboard();
|
||||
const { data } = selectedDashboard || {};
|
||||
const handle = useFullScreenHandle();
|
||||
const { pathname } = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { widgets, variables } = data || {};
|
||||
|
||||
@@ -61,6 +68,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
const updateDashboardMutation = useUpdateDashboard();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
||||
|
||||
@@ -126,6 +134,23 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
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.replace(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
dashboardLayout &&
|
||||
@@ -200,11 +225,10 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
||||
>
|
||||
<GridCard
|
||||
widget={currentWidget || ({ id, query: {} } as Widgets)}
|
||||
name={currentWidget?.id || ''}
|
||||
headerMenuList={widgetActions}
|
||||
variables={variables}
|
||||
fillSpans={currentWidget?.fillSpans}
|
||||
version={selectedDashboard?.data?.version}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</Card>
|
||||
</CardContainer>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { getComponentForPanelType } from 'constants/panelTypes';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
|
||||
import { FC, forwardRef, memo, useMemo } from 'react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { GridPanelSwitchProps, PropsTypePropsMap } from './types';
|
||||
|
||||
@@ -21,10 +19,7 @@ const GridPanelSwitch = forwardRef<
|
||||
query,
|
||||
options,
|
||||
thresholds,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
dataSource,
|
||||
selectedTime,
|
||||
},
|
||||
ref,
|
||||
): JSX.Element | null => {
|
||||
@@ -46,20 +41,8 @@ const GridPanelSwitch = forwardRef<
|
||||
query,
|
||||
thresholds,
|
||||
},
|
||||
[PANEL_TYPES.LIST]:
|
||||
dataSource === DataSource.LOGS
|
||||
? {
|
||||
selectedLogsFields: selectedLogFields || [],
|
||||
query,
|
||||
version: DEFAULT_ENTITY_VERSION, // As we don't support for Metrics, defaulting to v3
|
||||
selectedTime,
|
||||
}
|
||||
: {
|
||||
selectedTracesFields: selectedTracesFields || [],
|
||||
query,
|
||||
version: DEFAULT_ENTITY_VERSION, // As we don't support for Metrics, defaulting to v3
|
||||
selectedTime,
|
||||
},
|
||||
[PANEL_TYPES.LIST]: null,
|
||||
[PANEL_TYPES.PIE]: null,
|
||||
[PANEL_TYPES.TRACE]: null,
|
||||
[PANEL_TYPES.BAR]: {
|
||||
data,
|
||||
@@ -70,19 +53,7 @@ const GridPanelSwitch = forwardRef<
|
||||
};
|
||||
|
||||
return result;
|
||||
}, [
|
||||
data,
|
||||
options,
|
||||
ref,
|
||||
yAxisUnit,
|
||||
thresholds,
|
||||
panelData,
|
||||
query,
|
||||
dataSource,
|
||||
selectedLogFields,
|
||||
selectedTime,
|
||||
selectedTracesFields,
|
||||
]);
|
||||
}, [data, options, ref, yAxisUnit, thresholds, panelData, query]);
|
||||
|
||||
const Component = getComponentForPanelType(panelType, dataSource) as FC<
|
||||
PropsTypePropsMap[typeof panelType]
|
||||
|
||||
@@ -2,9 +2,7 @@ import { StaticLineProps, ToggleGraphProps } from 'components/Graph/types';
|
||||
import { UplotProps } from 'components/Uplot/Uplot';
|
||||
import { GridTableComponentProps } from 'container/GridTableComponent/types';
|
||||
import { GridValueComponentProps } from 'container/GridValueComponent/types';
|
||||
import { LogsPanelComponentProps } from 'container/LogsPanelTable/LogsPanelComponent';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { TracesTableComponentProps } from 'container/TracesTableComponent/TracesTableComponent';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { ForwardedRef } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
@@ -40,7 +38,8 @@ export type PropsTypePropsMap = {
|
||||
[PANEL_TYPES.VALUE]: GridValueComponentProps;
|
||||
[PANEL_TYPES.TABLE]: GridTableComponentProps;
|
||||
[PANEL_TYPES.TRACE]: null;
|
||||
[PANEL_TYPES.LIST]: LogsPanelComponentProps | TracesTableComponentProps;
|
||||
[PANEL_TYPES.PIE]: null;
|
||||
[PANEL_TYPES.LIST]: null;
|
||||
[PANEL_TYPES.BAR]: UplotProps & {
|
||||
ref: ForwardedRef<ToggleGraphProps | undefined>;
|
||||
};
|
||||
|
||||
@@ -608,6 +608,7 @@ function LogsExplorerViews({
|
||||
className="periscope-btn"
|
||||
onClick={handleToggleShowFormatOptions}
|
||||
icon={<Sliders size={14} />}
|
||||
data-testid="periscope-btn"
|
||||
/>
|
||||
|
||||
{showFormatMenuItems && (
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||
import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import i18n from 'ReactI18';
|
||||
import store from 'store';
|
||||
|
||||
import LogsExplorerViews from '..';
|
||||
import { logsQueryRangeSuccessNewFormatResponse } from './mock';
|
||||
|
||||
const logExplorerRoute = '/logs/logs-explorer';
|
||||
|
||||
const queryRangeURL = 'http://localhost/api/v3/query_range';
|
||||
|
||||
const lodsQueryServerRequest = (): void =>
|
||||
server.use(
|
||||
rest.post(queryRangeURL, (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(logsQueryRangeSuccessResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
// mocking the graph components in this test as this should be handled separately
|
||||
jest.mock(
|
||||
'container/TimeSeriesView/TimeSeriesView',
|
||||
() =>
|
||||
// eslint-disable-next-line func-names, @typescript-eslint/explicit-function-return-type, react/display-name
|
||||
function () {
|
||||
return <div>Time Series Chart</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'container/LogsExplorerChart',
|
||||
() =>
|
||||
// eslint-disable-next-line func-names, @typescript-eslint/explicit-function-return-type, react/display-name
|
||||
function () {
|
||||
return <div>Histogram Chart</div>;
|
||||
},
|
||||
);
|
||||
|
||||
jest.mock('constants/panelTypes', () => ({
|
||||
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
||||
}));
|
||||
|
||||
jest.mock('d3-interpolate', () => ({
|
||||
interpolate: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetExplorerQueryRange', () => ({
|
||||
__esModule: true,
|
||||
useGetExplorerQueryRange: jest.fn(),
|
||||
}));
|
||||
|
||||
// Set up the specific behavior for useGetExplorerQueryRange in individual test cases
|
||||
beforeEach(() => {
|
||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
||||
});
|
||||
});
|
||||
|
||||
const renderer = (): RenderResult =>
|
||||
render(
|
||||
<MemoryRouter initialEntries={[logExplorerRoute]}>
|
||||
<Provider store={store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<MockQueryClientProvider>
|
||||
<QueryBuilderProvider>
|
||||
<VirtuosoMockContext.Provider
|
||||
value={{ viewportHeight: 300, itemHeight: 100 }}
|
||||
>
|
||||
<LogsExplorerViews selectedView={SELECTED_VIEWS.SEARCH} showHistogram />
|
||||
</VirtuosoMockContext.Provider>
|
||||
</QueryBuilderProvider>
|
||||
</MockQueryClientProvider>
|
||||
</I18nextProvider>
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
describe('LogsExplorerViews -', () => {
|
||||
it('render correctly with props - list and table', async () => {
|
||||
lodsQueryServerRequest();
|
||||
const { queryByText, queryByTestId } = renderer();
|
||||
|
||||
expect(queryByTestId('periscope-btn')).toBeInTheDocument();
|
||||
await userEvent.click(queryByTestId('periscope-btn') as HTMLElement);
|
||||
|
||||
expect(document.querySelector('.menu-container')).toBeInTheDocument();
|
||||
|
||||
const menuItems = document.querySelectorAll('.menu-items .item');
|
||||
expect(menuItems.length).toBe(3);
|
||||
|
||||
// switch to table view
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
||||
|
||||
expect(
|
||||
queryByText(
|
||||
'{"container_id":"container_id","container_name":"container_name","driver":"driver","eta":"2m0s","location":"frontend","log_level":"INFO","message":"Dispatch successful","service":"frontend","span_id":"span_id","trace_id":"span_id"}',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('check isLoading state', async () => {
|
||||
lodsQueryServerRequest();
|
||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
||||
isLoading: true,
|
||||
isFetching: false,
|
||||
});
|
||||
const { queryByText, queryByTestId } = renderer();
|
||||
|
||||
// switch to table view
|
||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
||||
expect(
|
||||
queryByText(
|
||||
'Just a bit of patience, just a little bit’s enough ⎯ we’re getting your logs!',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('check error state', async () => {
|
||||
lodsQueryServerRequest();
|
||||
(useGetExplorerQueryRange as jest.Mock).mockReturnValue({
|
||||
data: { payload: logsQueryRangeSuccessNewFormatResponse },
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
isError: true,
|
||||
});
|
||||
const { queryByText, queryByTestId } = renderer();
|
||||
|
||||
expect(
|
||||
queryByText('Something went wrong. Please try again or contact support.'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// switch to table view
|
||||
await userEvent.click(queryByTestId('table-view') as HTMLElement);
|
||||
|
||||
expect(
|
||||
queryByText('Something went wrong. Please try again or contact support.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
51
frontend/src/container/LogsExplorerViews/tests/mock.ts
Normal file
51
frontend/src/container/LogsExplorerViews/tests/mock.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export const logsQueryRangeSuccessNewFormatResponse = {
|
||||
data: {
|
||||
result: [],
|
||||
resultType: '',
|
||||
newResult: {
|
||||
status: 'success',
|
||||
data: {
|
||||
resultType: '',
|
||||
result: [
|
||||
{
|
||||
queryName: 'A',
|
||||
series: null,
|
||||
list: [
|
||||
{
|
||||
timestamp: '2024-02-15T21:20:22Z',
|
||||
data: {
|
||||
attributes_bool: {},
|
||||
attributes_float64: {},
|
||||
attributes_int64: {},
|
||||
attributes_string: {
|
||||
container_id: 'container_id',
|
||||
container_name: 'container_name',
|
||||
driver: 'driver',
|
||||
eta: '2m0s',
|
||||
location: 'frontend',
|
||||
log_level: 'INFO',
|
||||
message: 'Dispatch successful',
|
||||
service: 'frontend',
|
||||
span_id: 'span_id',
|
||||
trace_id: 'span_id',
|
||||
},
|
||||
body:
|
||||
'2024-02-15T21:20:22.035Z\tINFO\tfrontend\tDispatch successful\t{"service": "frontend", "trace_id": "span_id", "span_id": "span_id", "driver": "driver", "eta": "2m0s"}',
|
||||
id: 'id',
|
||||
resources_string: {
|
||||
'container.name': 'container_name',
|
||||
},
|
||||
severity_number: 0,
|
||||
severity_text: '',
|
||||
span_id: '',
|
||||
trace_flags: 0,
|
||||
trace_id: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -4,82 +4,53 @@ import { Table } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import Controls from 'container/Controls';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
|
||||
import { tableStyles } from 'container/TracesExplorer/ListView/styles';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { useLogsData } from 'hooks/useLogsData';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
Dispatch,
|
||||
HTMLAttributes,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { getLogPanelColumnsList } from './utils';
|
||||
import { getLogPanelColumnsList, getNextOrPreviousItems } from './utils';
|
||||
|
||||
function LogsPanelComponent({
|
||||
selectedLogsFields,
|
||||
query,
|
||||
selectedTime,
|
||||
widget,
|
||||
setRequestData,
|
||||
queryResponse,
|
||||
}: LogsPanelComponentProps): JSX.Element {
|
||||
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [pagination, setPagination] = useState<Pagination>({
|
||||
offset: 0,
|
||||
limit: query.builder.queryData[0].limit || 0,
|
||||
});
|
||||
|
||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||
const updatedQuery = { ...query };
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
return {
|
||||
query: updatedQuery,
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
tableParams: {
|
||||
pagination,
|
||||
},
|
||||
};
|
||||
limit: widget.query.builder.queryData[0].limit || 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setRequestData({
|
||||
...requestData,
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
setRequestData((prev) => ({
|
||||
...prev,
|
||||
tableParams: {
|
||||
pagination,
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pagination]);
|
||||
}));
|
||||
}, [pagination, setRequestData]);
|
||||
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const handleChangePageSize = (value: number): void => {
|
||||
setPagination({
|
||||
@@ -88,53 +59,35 @@ function LogsPanelComponent({
|
||||
offset: value,
|
||||
});
|
||||
setPageSize(value);
|
||||
const newQueryData = { ...requestData.query };
|
||||
newQueryData.builder.queryData[0].pageSize = value;
|
||||
const newRequestData = {
|
||||
...requestData,
|
||||
query: newQueryData,
|
||||
tableParams: {
|
||||
pagination,
|
||||
},
|
||||
};
|
||||
setRequestData(newRequestData);
|
||||
setRequestData((prev) => {
|
||||
const newQueryData = { ...prev.query };
|
||||
newQueryData.builder.queryData[0].pageSize = value;
|
||||
return {
|
||||
...prev,
|
||||
query: newQueryData,
|
||||
tableParams: {
|
||||
pagination: {
|
||||
limit: 0,
|
||||
offset: value,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const { data, isFetching, isError } = useGetQueryRange(
|
||||
{
|
||||
...requestData,
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
selectedTime: selectedTime?.enum || 'GLOBAL_TIME',
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
},
|
||||
DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
requestData,
|
||||
pagination,
|
||||
selectedDashboard?.data.variables,
|
||||
],
|
||||
enabled: !!requestData.query && !!selectedLogsFields?.length,
|
||||
},
|
||||
);
|
||||
|
||||
const columns = getLogPanelColumnsList(selectedLogsFields);
|
||||
const columns = getLogPanelColumnsList(widget.selectedLogFields);
|
||||
|
||||
const dataLength =
|
||||
data?.payload?.data?.newResult?.data?.result[0]?.list?.length;
|
||||
queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length;
|
||||
const totalCount = useMemo(() => dataLength || 0, [dataLength]);
|
||||
|
||||
const [firstLog, setFirstLog] = useState<ILog>();
|
||||
const [lastLog, setLastLog] = useState<ILog>();
|
||||
|
||||
const { logs } = useLogsData({
|
||||
result: data?.payload.data.newResult.data.result,
|
||||
result: queryResponse.data?.payload.data.newResult.data.result,
|
||||
panelType: PANEL_TYPES.LIST,
|
||||
stagedQuery: query,
|
||||
stagedQuery: widget.query,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -167,92 +120,86 @@ function LogsPanelComponent({
|
||||
);
|
||||
|
||||
const isOrderByTimeStamp =
|
||||
query.builder.queryData[0].orderBy.length > 0 &&
|
||||
query.builder.queryData[0].orderBy[0].columnName === 'timestamp';
|
||||
widget.query.builder.queryData[0].orderBy.length > 0 &&
|
||||
widget.query.builder.queryData[0].orderBy[0].columnName === 'timestamp';
|
||||
|
||||
const handlePreviousPagination = (): void => {
|
||||
if (isOrderByTimeStamp) {
|
||||
setRequestData({
|
||||
...requestData,
|
||||
setRequestData((prev) => ({
|
||||
...prev,
|
||||
query: {
|
||||
...requestData.query,
|
||||
...prev.query,
|
||||
builder: {
|
||||
...requestData.query.builder,
|
||||
...prev.query.builder,
|
||||
queryData: [
|
||||
{
|
||||
...requestData.query.builder.queryData[0],
|
||||
...prev.query.builder.queryData[0],
|
||||
filters: {
|
||||
...requestData.query.builder.queryData[0].filters,
|
||||
...prev.query.builder.queryData[0].filters,
|
||||
items: [
|
||||
{
|
||||
id: uuid(),
|
||||
key: {
|
||||
key: 'id',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['>'],
|
||||
value: firstLog?.id || '',
|
||||
},
|
||||
...getNextOrPreviousItems(
|
||||
prev.query.builder.queryData[0].filters.items,
|
||||
'PREV',
|
||||
firstLog,
|
||||
),
|
||||
],
|
||||
},
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}));
|
||||
}
|
||||
if (!isOrderByTimeStamp) {
|
||||
setPagination({
|
||||
...pagination,
|
||||
limit: 0,
|
||||
offset: pagination.offset - pageSize,
|
||||
});
|
||||
}
|
||||
setPagination({
|
||||
...pagination,
|
||||
limit: 0,
|
||||
offset: pagination.offset - pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handleNextPagination = (): void => {
|
||||
if (isOrderByTimeStamp) {
|
||||
setRequestData({
|
||||
...requestData,
|
||||
setRequestData((prev) => ({
|
||||
...prev,
|
||||
query: {
|
||||
...requestData.query,
|
||||
...prev.query,
|
||||
builder: {
|
||||
...requestData.query.builder,
|
||||
...prev.query.builder,
|
||||
queryData: [
|
||||
{
|
||||
...requestData.query.builder.queryData[0],
|
||||
...prev.query.builder.queryData[0],
|
||||
filters: {
|
||||
...requestData.query.builder.queryData[0].filters,
|
||||
...prev.query.builder.queryData[0].filters,
|
||||
items: [
|
||||
{
|
||||
id: uuid(),
|
||||
key: {
|
||||
key: 'id',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['<'],
|
||||
value: lastLog?.id || '',
|
||||
},
|
||||
...getNextOrPreviousItems(
|
||||
prev.query.builder.queryData[0].filters.items,
|
||||
'NEXT',
|
||||
lastLog,
|
||||
),
|
||||
],
|
||||
},
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}));
|
||||
}
|
||||
if (!isOrderByTimeStamp) {
|
||||
setPagination({
|
||||
...pagination,
|
||||
limit: 0,
|
||||
offset: pagination.offset + pageSize,
|
||||
});
|
||||
}
|
||||
setPagination({
|
||||
...pagination,
|
||||
limit: 0,
|
||||
offset: pagination.offset + pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
if (isError) {
|
||||
if (queryResponse.isError) {
|
||||
return <div>{SOMETHING_WENT_WRONG}</div>;
|
||||
}
|
||||
|
||||
@@ -265,19 +212,19 @@ function LogsPanelComponent({
|
||||
tableLayout="fixed"
|
||||
scroll={{ x: `calc(50vw - 10px)` }}
|
||||
sticky
|
||||
loading={isFetching}
|
||||
loading={queryResponse.isFetching}
|
||||
style={tableStyles}
|
||||
dataSource={flattenLogData}
|
||||
columns={columns}
|
||||
onRow={handleRow}
|
||||
/>
|
||||
</div>
|
||||
{!query.builder.queryData[0].limit && (
|
||||
{!widget.query.builder.queryData[0].limit && (
|
||||
<div className="controller">
|
||||
<Controls
|
||||
totalCount={totalCount}
|
||||
perPageOptions={PER_PAGE_OPTIONS}
|
||||
isLoading={isFetching}
|
||||
isLoading={queryResponse.isFetching}
|
||||
offset={pagination.offset}
|
||||
countPerPage={pageSize}
|
||||
handleNavigatePrevious={handlePreviousPagination}
|
||||
@@ -301,13 +248,12 @@ function LogsPanelComponent({
|
||||
}
|
||||
|
||||
export type LogsPanelComponentProps = {
|
||||
selectedLogsFields: Widgets['selectedLogFields'];
|
||||
query: Query;
|
||||
selectedTime?: timePreferance;
|
||||
};
|
||||
|
||||
LogsPanelComponent.defaultProps = {
|
||||
selectedTime: undefined,
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
widget: Widgets;
|
||||
};
|
||||
|
||||
export default LogsPanelComponent;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { Typography } from 'antd/lib';
|
||||
import { OPERATORS } from 'constants/queryBuilder';
|
||||
// import Typography from 'antd/es/typography/Typography';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { ReactNode } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export const getLogPanelColumnsList = (
|
||||
selectedLogFields: Widgets['selectedLogFields'],
|
||||
@@ -36,3 +41,49 @@ export const getLogPanelColumnsList = (
|
||||
|
||||
return [...initialColumns, ...columns];
|
||||
};
|
||||
|
||||
export const getNextOrPreviousItems = (
|
||||
items: TagFilterItem[],
|
||||
direction: 'NEXT' | 'PREV',
|
||||
log?: ILog,
|
||||
): TagFilterItem[] => {
|
||||
const nextItem = {
|
||||
id: uuid(),
|
||||
key: {
|
||||
key: 'id',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['<'],
|
||||
value: log?.id || '',
|
||||
};
|
||||
const prevItem = {
|
||||
id: uuid(),
|
||||
key: {
|
||||
key: 'id',
|
||||
type: '',
|
||||
dataType: DataTypes.String,
|
||||
isColumn: true,
|
||||
},
|
||||
op: OPERATORS['>'],
|
||||
value: log?.id || '',
|
||||
};
|
||||
let index = items.findIndex((item) => item.op === OPERATORS['<']);
|
||||
if (index === -1) {
|
||||
index = items.findIndex((item) => item.op === OPERATORS['>']);
|
||||
}
|
||||
if (index === -1) {
|
||||
if (direction === 'NEXT') {
|
||||
return [...items, nextItem];
|
||||
}
|
||||
return [...items, prevItem];
|
||||
}
|
||||
const newItems = [...items];
|
||||
if (direction === 'NEXT') {
|
||||
newItems[index] = nextItem;
|
||||
} else {
|
||||
newItems[index] = prevItem;
|
||||
}
|
||||
return newItems;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export const getWidgetQueryBuilder = ({
|
||||
title = '',
|
||||
panelTypes,
|
||||
yAxisUnit = '',
|
||||
fillSpans = false,
|
||||
id,
|
||||
}: GetWidgetQueryBuilderProps): Widgets => ({
|
||||
description: '',
|
||||
@@ -24,4 +25,5 @@ export const getWidgetQueryBuilder = ({
|
||||
softMin: null,
|
||||
selectedLogFields: [],
|
||||
selectedTracesFields: [],
|
||||
fillSpans,
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ function DBCall(): JSX.Element {
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'reqps',
|
||||
id: SERVICE_CHART_ID.dbCallsRPS,
|
||||
fillSpans: false,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -89,7 +90,8 @@ function DBCall(): JSX.Element {
|
||||
title: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ms',
|
||||
id: SERVICE_CHART_ID.dbCallsAvgDuration,
|
||||
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -112,8 +114,6 @@ function DBCall(): JSX.Element {
|
||||
<Card data-testid="database_call_rps">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans={false}
|
||||
name="database_call_rps"
|
||||
widget={databaseCallsRPSWidget}
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
@@ -147,8 +147,6 @@ function DBCall(): JSX.Element {
|
||||
<Card data-testid="database_call_avg_duration">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans
|
||||
name="database_call_avg_duration"
|
||||
widget={databaseCallsAverageDurationWidget}
|
||||
headerMenuList={MENU_ITEMS}
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useParams } from 'react-router-dom';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { GraphTitle, legend, MENU_ITEMS, SERVICE_CHART_ID } from '../constant';
|
||||
import { GraphTitle, legend, MENU_ITEMS } from '../constant';
|
||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||
import { Card, GraphContainer, Row } from '../styles';
|
||||
import { Button } from './styles';
|
||||
@@ -60,7 +60,7 @@ function External(): JSX.Element {
|
||||
title: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: '%',
|
||||
id: SERVICE_CHART_ID.externalCallErrorPercentage,
|
||||
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -86,7 +86,8 @@ function External(): JSX.Element {
|
||||
title: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ms',
|
||||
id: SERVICE_CHART_ID.externalCallDuration,
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -108,7 +109,8 @@ function External(): JSX.Element {
|
||||
title: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'reqps',
|
||||
id: SERVICE_CHART_ID.externalCallRPSByAddress,
|
||||
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -130,7 +132,8 @@ function External(): JSX.Element {
|
||||
title: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ms',
|
||||
id: SERVICE_CHART_ID.externalCallDurationByAddress,
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
@@ -155,9 +158,7 @@ function External(): JSX.Element {
|
||||
<Card data-testid="external_call_error_percentage">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans={false}
|
||||
headerMenuList={MENU_ITEMS}
|
||||
name="external_call_error_percentage"
|
||||
widget={externalCallErrorWidget}
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
@@ -192,8 +193,6 @@ function External(): JSX.Element {
|
||||
<Card data-testid="external_call_duration">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans
|
||||
name="external_call_duration"
|
||||
headerMenuList={MENU_ITEMS}
|
||||
widget={externalCallDurationWidget}
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||
@@ -230,8 +229,6 @@ function External(): JSX.Element {
|
||||
<Card data-testid="external_call_rps_by_address">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans
|
||||
name="external_call_rps_by_address"
|
||||
widget={externalCallRPSWidget}
|
||||
headerMenuList={MENU_ITEMS}
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): Promise<void> =>
|
||||
@@ -267,10 +264,8 @@ function External(): JSX.Element {
|
||||
<Card data-testid="external_call_duration_by_address">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
name="external_call_duration_by_address"
|
||||
widget={externalCallDurationAddressWidget}
|
||||
headerMenuList={MENU_ITEMS}
|
||||
fillSpans
|
||||
onClickHandler={(xValue, yValue, mouseX, mouseY): void => {
|
||||
onGraphClickHandler(setSelectedTimeStamp)(
|
||||
xValue,
|
||||
|
||||
@@ -19,13 +19,10 @@ import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { GraphTitle, SERVICE_CHART_ID } from '../constant';
|
||||
@@ -49,9 +46,6 @@ import {
|
||||
} from './util';
|
||||
|
||||
function Application(): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
@@ -59,10 +53,6 @@ function Application(): JSX.Element {
|
||||
const { queries } = useResourceAttribute();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const selectedTags = useMemo(
|
||||
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||
[queries],
|
||||
);
|
||||
const isSpanMetricEnabled = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS)
|
||||
?.active;
|
||||
|
||||
@@ -94,7 +84,7 @@ function Application(): JSX.Element {
|
||||
isLoading: topLevelOperationsIsLoading,
|
||||
isError: topLevelOperationsIsError,
|
||||
} = useQuery<ServiceDataProps>({
|
||||
queryKey: [servicename, minTime, maxTime, selectedTags],
|
||||
queryKey: [servicename],
|
||||
queryFn: getTopLevelOperations,
|
||||
});
|
||||
|
||||
@@ -116,49 +106,41 @@ function Application(): JSX.Element {
|
||||
[servicename, topLevelOperations],
|
||||
);
|
||||
|
||||
const operationPerSecWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: operationPerSec({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.RATE_PER_OPS,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
const operationPerSecWidget = getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: operationPerSec({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.RATE_PER_OPS,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
});
|
||||
|
||||
const errorPercentageWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: errorPercentage({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.ERROR_PERCENTAGE,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: '%',
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
const errorPercentageWidget = getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: errorPercentage({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.ERROR_PERCENTAGE,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: '%',
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
});
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number) => {
|
||||
|
||||
@@ -89,8 +89,6 @@ function ApDexMetrics({
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name="apdex"
|
||||
fillSpans={false}
|
||||
widget={apDexMetricsWidget}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('ApDex')}
|
||||
|
||||
@@ -50,7 +50,6 @@ function ApDexTraces({
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name="apdex"
|
||||
widget={apDexTracesWidget}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('ApDex')}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Skeleton } from 'antd';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -46,28 +47,24 @@ function ServiceOverview({
|
||||
[isSpanMetricEnable, queries],
|
||||
);
|
||||
|
||||
const latencyWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: latency({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.LATENCY,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
const latencyWidget = getWidgetQueryBuilder({
|
||||
query: {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
builder: latency({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
}),
|
||||
[servicename, isSpanMetricEnable, topLevelOperationsRoute, tagFilterItems],
|
||||
);
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
title: GraphTitle.LATENCY,
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
});
|
||||
|
||||
const isQueryEnabled =
|
||||
!topLevelOperationsIsLoading && topLevelOperationsRoute.length > 0;
|
||||
@@ -88,15 +85,23 @@ function ServiceOverview({
|
||||
</Button>
|
||||
<Card data-testid="service_latency">
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
name="service_latency"
|
||||
onDragSelect={onDragSelect}
|
||||
widget={latencyWidget}
|
||||
onClickHandler={handleGraphClick('Service')}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
fillSpans={false}
|
||||
version={ENTITY_VERSION_V4}
|
||||
/>
|
||||
{topLevelOperationsIsLoading && (
|
||||
<Skeleton
|
||||
style={{
|
||||
height: '100%',
|
||||
padding: '16px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!topLevelOperationsIsLoading && (
|
||||
<Graph
|
||||
onDragSelect={onDragSelect}
|
||||
widget={latencyWidget}
|
||||
onClickHandler={handleGraphClick('Service')}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
version={ENTITY_VERSION_V4}
|
||||
/>
|
||||
)}
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import { Skeleton, Typography } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
@@ -27,15 +27,23 @@ function TopLevelOperation({
|
||||
</Typography>
|
||||
) : (
|
||||
<GraphContainer>
|
||||
<Graph
|
||||
fillSpans={false}
|
||||
name={name}
|
||||
widget={widget}
|
||||
onClickHandler={handleGraphClick(opName)}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={!topLevelOperationsIsLoading}
|
||||
version={ENTITY_VERSION_V4}
|
||||
/>
|
||||
{topLevelOperationsIsLoading && (
|
||||
<Skeleton
|
||||
style={{
|
||||
height: '100%',
|
||||
padding: '16px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!topLevelOperationsIsLoading && (
|
||||
<Graph
|
||||
widget={widget}
|
||||
onClickHandler={handleGraphClick(opName)}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={!topLevelOperationsIsLoading}
|
||||
version={ENTITY_VERSION_V4}
|
||||
/>
|
||||
)}
|
||||
</GraphContainer>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
|
||||
import { DownloadOptions } from 'container/Download/Download.types';
|
||||
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
|
||||
|
||||
@@ -20,7 +22,7 @@ export enum FORMULA {
|
||||
ERROR_PERCENTAGE = 'A*100/B',
|
||||
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
||||
APDEX_TRACES = '((B + C)/2)/A',
|
||||
APDEX_DELTA_SPAN_METRICS = '(B + C/2)/A',
|
||||
APDEX_DELTA_SPAN_METRICS = '((B + C)/2)/A',
|
||||
APDEX_CUMULATIVE_SPAN_METRICS = '((B + C)/2)/A',
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Card = styled(CardComponent)`
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
height: calc(100% - 40px);
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
@@ -40,7 +40,7 @@ export const ColErrorContainer = styled(ColComponent)`
|
||||
|
||||
export const GraphContainer = styled.div`
|
||||
min-height: calc(40vh - 40px);
|
||||
height: calc(100% - 40px);
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const GraphTitle = styled(Typography)`
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface GetWidgetQueryBuilderProps {
|
||||
panelTypes: Widgets['panelTypes'];
|
||||
yAxisUnit?: Widgets['yAxisUnit'];
|
||||
id?: Widgets['id'];
|
||||
fillSpans?: Widgets['fillSpans'];
|
||||
}
|
||||
|
||||
export interface NavigateToTraceProps {
|
||||
|
||||
@@ -33,6 +33,8 @@ export const getNearestHighestBucketValue = (
|
||||
value: number,
|
||||
buckets: number[],
|
||||
): string => {
|
||||
// sort the buckets
|
||||
buckets.sort((a, b) => a - b);
|
||||
const nearestBucket = buckets.find((bucket) => bucket >= value);
|
||||
return nearestBucket?.toString() || '+Inf';
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ export const PANEL_TYPES_INITIAL_QUERY = {
|
||||
[PANEL_TYPES.LIST]: initialQueriesMap.logs,
|
||||
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
||||
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { BarChart3, LineChart, List, SigmaSquare, Table } from 'lucide-react';
|
||||
import {
|
||||
BarChart3,
|
||||
LineChart,
|
||||
List,
|
||||
PieChart,
|
||||
SigmaSquare,
|
||||
Table,
|
||||
} from 'lucide-react';
|
||||
|
||||
const Items: ItemsProps[] = [
|
||||
{
|
||||
@@ -28,9 +35,14 @@ const Items: ItemsProps[] = [
|
||||
icon: <BarChart3 size={32} color={Color.BG_ROBIN_400} />,
|
||||
display: 'Bar',
|
||||
},
|
||||
{
|
||||
name: PANEL_TYPES.PIE,
|
||||
icon: <PieChart size={32} color={Color.BG_ROBIN_400} />,
|
||||
display: 'Pie',
|
||||
},
|
||||
];
|
||||
|
||||
interface ItemsProps {
|
||||
export interface ItemsProps {
|
||||
name: PANEL_TYPES;
|
||||
icon: JSX.Element;
|
||||
display: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Row } from 'antd';
|
||||
import { isNull } from 'lodash-es';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { convertVariablesToDbFormat } from './util';
|
||||
import VariableItem from './VariableItem';
|
||||
|
||||
function DashboardVariableSelection(): JSX.Element | null {
|
||||
@@ -11,15 +11,14 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
selectedDashboard,
|
||||
setSelectedDashboard,
|
||||
updateLocalStorageDashboardVariables,
|
||||
variablesToGetUpdated,
|
||||
setVariablesToGetUpdated,
|
||||
} = useDashboard();
|
||||
|
||||
const { data } = selectedDashboard || {};
|
||||
|
||||
const { variables } = data || {};
|
||||
|
||||
const [update, setUpdate] = useState<boolean>(false);
|
||||
const [lastUpdatedVar, setLastUpdatedVar] = useState<string>('');
|
||||
|
||||
const [variablesTableData, setVariablesTableData] = useState<any>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -45,8 +44,27 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
}, [variables]);
|
||||
|
||||
const onVarChanged = (name: string): void => {
|
||||
setLastUpdatedVar(name);
|
||||
setUpdate(!update);
|
||||
/**
|
||||
* this function takes care of adding the dependent variables to current update queue and removing
|
||||
* the updated variable name from the queue
|
||||
*/
|
||||
const dependentVariables = variablesTableData
|
||||
?.map((variable: any) => {
|
||||
if (variable.type === 'QUERY') {
|
||||
const re = new RegExp(`\\{\\{\\s*?\\.${name}\\s*?\\}\\}`); // regex for `{{.var}}`
|
||||
const queryValue = variable.queryValue || '';
|
||||
const dependVarReMatch = queryValue.match(re);
|
||||
if (dependVarReMatch !== null && dependVarReMatch.length > 0) {
|
||||
return variable.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((val: string | null) => !isNull(val));
|
||||
setVariablesToGetUpdated((prev) => [
|
||||
...prev.filter((v) => v !== name),
|
||||
...dependentVariables,
|
||||
]);
|
||||
};
|
||||
|
||||
const onValueUpdate = (
|
||||
@@ -56,37 +74,31 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
allSelected: boolean,
|
||||
): void => {
|
||||
if (id) {
|
||||
const newVariablesArr = variablesTableData.map(
|
||||
(variable: IDashboardVariable) => {
|
||||
const variableCopy = { ...variable };
|
||||
|
||||
if (variableCopy.id === id) {
|
||||
variableCopy.selectedValue = value;
|
||||
variableCopy.allSelected = allSelected;
|
||||
}
|
||||
|
||||
return variableCopy;
|
||||
},
|
||||
);
|
||||
updateLocalStorageDashboardVariables(name, value, allSelected);
|
||||
|
||||
const variables = convertVariablesToDbFormat(newVariablesArr);
|
||||
|
||||
if (selectedDashboard) {
|
||||
setSelectedDashboard({
|
||||
...selectedDashboard,
|
||||
data: {
|
||||
...selectedDashboard?.data,
|
||||
variables: {
|
||||
...variables,
|
||||
},
|
||||
},
|
||||
setSelectedDashboard((prev) => {
|
||||
if (prev) {
|
||||
return {
|
||||
...prev,
|
||||
data: {
|
||||
...prev?.data,
|
||||
variables: {
|
||||
...prev?.data.variables,
|
||||
[id]: {
|
||||
...prev.data.variables[id],
|
||||
selectedValue: value,
|
||||
allSelected,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
onVarChanged(name);
|
||||
|
||||
setUpdate(!update);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -107,13 +119,12 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
<VariableItem
|
||||
key={`${variable.name}${variable.id}}${variable.order}`}
|
||||
existingVariables={variables}
|
||||
lastUpdatedVar={lastUpdatedVar}
|
||||
variableData={{
|
||||
name: variable.name,
|
||||
...variable,
|
||||
change: update,
|
||||
}}
|
||||
onValueUpdate={onValueUpdate}
|
||||
variablesToGetUpdated={variablesToGetUpdated}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -68,7 +68,7 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -82,7 +82,7 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -110,7 +110,7 @@ describe('VariableItem', () => {
|
||||
variableData={mockCustomVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -131,7 +131,7 @@ describe('VariableItem', () => {
|
||||
variableData={customVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -146,7 +146,7 @@ describe('VariableItem', () => {
|
||||
variableData={mockCustomVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
lastUpdatedVar=""
|
||||
variablesToGetUpdated={[]}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ interface VariableItemProps {
|
||||
arg1: IDashboardVariable['selectedValue'],
|
||||
allSelected: boolean,
|
||||
) => void;
|
||||
lastUpdatedVar: string;
|
||||
variablesToGetUpdated: string[];
|
||||
}
|
||||
|
||||
const getSelectValue = (
|
||||
@@ -49,7 +49,7 @@ function VariableItem({
|
||||
variableData,
|
||||
existingVariables,
|
||||
onValueUpdate,
|
||||
lastUpdatedVar,
|
||||
variablesToGetUpdated,
|
||||
}: VariableItemProps): JSX.Element {
|
||||
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
||||
[],
|
||||
@@ -108,16 +108,10 @@ function VariableItem({
|
||||
|
||||
if (!areArraysEqual(newOptionsData, oldOptionsData)) {
|
||||
/* eslint-disable no-useless-escape */
|
||||
const re = new RegExp(`\\{\\{\\s*?\\.${lastUpdatedVar}\\s*?\\}\\}`); // regex for `{{.var}}`
|
||||
// If the variable is dependent on the last updated variable
|
||||
// and contains the last updated variable in its query (of the form `{{.var}}`)
|
||||
// then we need to update the value of the variable
|
||||
const queryValue = variableData.queryValue || '';
|
||||
const dependVarReMatch = queryValue.match(re);
|
||||
if (
|
||||
variableData.type === 'QUERY' &&
|
||||
dependVarReMatch !== null &&
|
||||
dependVarReMatch.length > 0
|
||||
variableData.name &&
|
||||
variablesToGetUpdated.includes(variableData.name)
|
||||
) {
|
||||
let value = variableData.selectedValue;
|
||||
let allSelected = false;
|
||||
|
||||
@@ -2,14 +2,11 @@ import './QuerySection.styles.scss';
|
||||
|
||||
import { Button, Tabs, Tooltip, Typography } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
||||
import { WidgetGraphProps } from 'container/NewWidget/types';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||
@@ -22,9 +19,12 @@ import {
|
||||
getSelectedWidgetIndex,
|
||||
} from 'providers/Dashboard/util';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
@@ -35,7 +35,7 @@ import PromQLQueryContainer from './QueryBuilder/promQL';
|
||||
|
||||
function QuerySection({
|
||||
selectedGraph,
|
||||
selectedTime,
|
||||
queryResponse,
|
||||
}: QueryProps): JSX.Element {
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const urlQuery = useUrlQuery();
|
||||
@@ -51,14 +51,6 @@ function QuerySection({
|
||||
|
||||
const { selectedDashboard, setSelectedDashboard } = useDashboard();
|
||||
|
||||
const getWidgetQueryRange = useGetWidgetQueryRange(
|
||||
{
|
||||
graphType: selectedGraph,
|
||||
selectedTime: selectedTime.enum,
|
||||
},
|
||||
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
);
|
||||
|
||||
const { widgets } = selectedDashboard?.data || {};
|
||||
|
||||
const getWidget = useCallback(() => {
|
||||
@@ -233,7 +225,7 @@ function QuerySection({
|
||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<TextToolTip text="This will temporarily save the current query and graph state. This will persist across tab change" />
|
||||
<Button
|
||||
loading={getWidgetQueryRange.isFetching}
|
||||
loading={queryResponse.isFetching}
|
||||
type="primary"
|
||||
onClick={handleRunQuery}
|
||||
className="stage-run-query"
|
||||
@@ -251,7 +243,10 @@ function QuerySection({
|
||||
|
||||
interface QueryProps {
|
||||
selectedGraph: PANEL_TYPES;
|
||||
selectedTime: WidgetGraphProps['selectedTime'];
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
}
|
||||
|
||||
export default QuerySection;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Card, Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { WidgetGraphProps } from 'container/NewWidget/types';
|
||||
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
import { WidgetGraphContainerProps } from 'container/NewWidget/types';
|
||||
// import useUrlQuery from 'hooks/useUrlQuery';
|
||||
// import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
|
||||
|
||||
import { NotFoundContainer } from './styles';
|
||||
@@ -14,58 +11,36 @@ import WidgetGraph from './WidgetGraphs';
|
||||
|
||||
function WidgetGraphContainer({
|
||||
selectedGraph,
|
||||
yAxisUnit,
|
||||
selectedTime,
|
||||
thresholds,
|
||||
fillSpans = false,
|
||||
softMax,
|
||||
softMin,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const { widgets = [] } = selectedDashboard?.data || {};
|
||||
|
||||
const params = useUrlQuery();
|
||||
|
||||
const widgetId = params.get('widgetId');
|
||||
|
||||
const selectedWidget = widgets.find((e) => e.id === widgetId);
|
||||
|
||||
const getWidgetQueryRange = useGetWidgetQueryRange(
|
||||
{
|
||||
graphType: getGraphType(selectedGraph),
|
||||
selectedTime: selectedTime.enum,
|
||||
},
|
||||
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
);
|
||||
|
||||
if (getWidgetQueryRange.data && selectedGraph === PANEL_TYPES.BAR) {
|
||||
queryResponse,
|
||||
setRequestData,
|
||||
selectedWidget,
|
||||
}: WidgetGraphContainerProps): JSX.Element {
|
||||
if (queryResponse.data && selectedGraph === PANEL_TYPES.BAR) {
|
||||
const sortedSeriesData = getSortedSeriesData(
|
||||
getWidgetQueryRange.data?.payload.data.result,
|
||||
queryResponse.data?.payload.data.result,
|
||||
);
|
||||
getWidgetQueryRange.data.payload.data.result = sortedSeriesData;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
queryResponse.data.payload.data.result = sortedSeriesData;
|
||||
}
|
||||
|
||||
if (selectedWidget === undefined) {
|
||||
return <Card>Invalid widget</Card>;
|
||||
}
|
||||
|
||||
if (getWidgetQueryRange.error) {
|
||||
if (queryResponse?.error) {
|
||||
return (
|
||||
<NotFoundContainer>
|
||||
<Typography>{getWidgetQueryRange.error.message}</Typography>
|
||||
<Typography>{queryResponse.error.message}</Typography>
|
||||
</NotFoundContainer>
|
||||
);
|
||||
}
|
||||
if (getWidgetQueryRange.isLoading) {
|
||||
if (queryResponse.isLoading && selectedGraph !== PANEL_TYPES.LIST) {
|
||||
return <Spinner size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedGraph !== PANEL_TYPES.LIST &&
|
||||
getWidgetQueryRange.data?.payload.data.result.length === 0
|
||||
queryResponse.data?.payload.data.result.length === 0
|
||||
) {
|
||||
return (
|
||||
<NotFoundContainer>
|
||||
@@ -75,7 +50,7 @@ function WidgetGraphContainer({
|
||||
}
|
||||
if (
|
||||
selectedGraph === PANEL_TYPES.LIST &&
|
||||
getWidgetQueryRange.data?.payload.data.newResult.data.result.length === 0
|
||||
queryResponse.data?.payload.data.newResult.data.result.length === 0
|
||||
) {
|
||||
return (
|
||||
<NotFoundContainer>
|
||||
@@ -86,16 +61,9 @@ function WidgetGraphContainer({
|
||||
|
||||
return (
|
||||
<WidgetGraph
|
||||
yAxisUnit={yAxisUnit || ''}
|
||||
getWidgetQueryRange={getWidgetQueryRange}
|
||||
selectedWidget={selectedWidget}
|
||||
thresholds={thresholds}
|
||||
fillSpans={fillSpans}
|
||||
softMax={softMax}
|
||||
softMin={softMin}
|
||||
selectedLogFields={selectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
selectedTime={selectedTime}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
selectedGraph={selectedGraph}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,98 +1,37 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GridPanelSwitch from 'container/GridPanelSwitch';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
function WidgetGraph({
|
||||
getWidgetQueryRange,
|
||||
selectedWidget,
|
||||
yAxisUnit,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
softMax,
|
||||
softMin,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
selectedTime,
|
||||
queryResponse,
|
||||
setRequestData,
|
||||
selectedGraph,
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
const { stagedQuery, currentQuery } = useQueryBuilder();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(getWidgetQueryRange);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [getWidgetQueryRange, maxTime, minTime, globalSelectedInterval]);
|
||||
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
const chartData = getUPlotChartData(
|
||||
getWidgetQueryRange?.data?.payload,
|
||||
fillSpans,
|
||||
);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const params = useUrlQuery();
|
||||
|
||||
const widgetId = params.get('widgetId');
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax('custom', [
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
]);
|
||||
|
||||
params.set(QueryParams.startTime, minTime.toString());
|
||||
params.set(QueryParams.endTime, maxTime.toString());
|
||||
const generatedUrl = `${location.pathname}?${params.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[dispatch, location.pathname, params],
|
||||
);
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
|
||||
const handleBackNavigation = (): void => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
@@ -114,6 +53,28 @@ function WidgetGraph({
|
||||
}
|
||||
};
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax('custom', [
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
]);
|
||||
|
||||
urlQuery.set(QueryParams.startTime, minTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, maxTime.toString());
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[dispatch, location.pathname, urlQuery],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('popstate', handleBackNavigation);
|
||||
|
||||
@@ -123,78 +84,26 @@ function WidgetGraph({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
getUPlotChartOptions({
|
||||
id: widgetId || 'legend_widget',
|
||||
yAxisUnit,
|
||||
apiResponse: getWidgetQueryRange?.data?.payload,
|
||||
dimensions: containerDimensions,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
softMax,
|
||||
softMin,
|
||||
panelType: selectedGraph,
|
||||
currentQuery,
|
||||
}),
|
||||
[
|
||||
widgetId,
|
||||
yAxisUnit,
|
||||
getWidgetQueryRange?.data?.payload,
|
||||
containerDimensions,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
softMax,
|
||||
softMin,
|
||||
selectedGraph,
|
||||
currentQuery,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={graphRef} style={{ height: '100%' }}>
|
||||
<GridPanelSwitch
|
||||
data={chartData}
|
||||
options={options}
|
||||
panelType={selectedWidget.panelTypes}
|
||||
name={widgetId || 'legend_widget'}
|
||||
yAxisUnit={yAxisUnit}
|
||||
panelData={
|
||||
getWidgetQueryRange.data?.payload.data.newResult.data.result || []
|
||||
}
|
||||
query={stagedQuery || selectedWidget.query}
|
||||
thresholds={thresholds}
|
||||
selectedLogFields={selectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
dataSource={currentQuery.builder.queryData[0].dataSource}
|
||||
selectedTime={selectedTime}
|
||||
<PanelWrapper
|
||||
widget={selectedWidget}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
onDragSelect={onDragSelect}
|
||||
selectedGraph={selectedGraph}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface WidgetGraphProps {
|
||||
thresholds: ThresholdProps[];
|
||||
yAxisUnit: string;
|
||||
selectedWidget: Widgets;
|
||||
fillSpans: boolean;
|
||||
getWidgetQueryRange: UseQueryResult<
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
softMax: number | null;
|
||||
softMin: number | null;
|
||||
selectedLogFields: Widgets['selectedLogFields'];
|
||||
selectedTracesFields: Widgets['selectedTracesFields'];
|
||||
selectedTime: timePreferance;
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,20 @@
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { Card } from 'container/GridCardLayout/styles';
|
||||
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo } from 'react';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
|
||||
import { WidgetGraphProps } from '../../types';
|
||||
import { WidgetGraphContainerProps } from '../../types';
|
||||
import PlotTag from './PlotTag';
|
||||
import { AlertIconContainer, Container } from './styles';
|
||||
import WidgetGraphComponent from './WidgetGraphContainer';
|
||||
|
||||
function WidgetGraph({
|
||||
selectedGraph,
|
||||
yAxisUnit,
|
||||
selectedTime,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
softMax,
|
||||
softMin,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
queryResponse,
|
||||
setRequestData,
|
||||
selectedWidget,
|
||||
}: WidgetGraphContainerProps): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const { widgets = [] } = selectedDashboard?.data || {};
|
||||
|
||||
const params = useUrlQuery();
|
||||
|
||||
const widgetId = params.get('widgetId');
|
||||
|
||||
const selectedWidget = widgets.find((e) => e.id === widgetId);
|
||||
|
||||
const getWidgetQueryRange = useGetWidgetQueryRange(
|
||||
{
|
||||
graphType: getGraphType(selectedGraph),
|
||||
selectedTime: selectedTime.enum,
|
||||
},
|
||||
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
);
|
||||
|
||||
if (selectedWidget === undefined) {
|
||||
return <Card $panelType={selectedGraph}>Invalid widget</Card>;
|
||||
@@ -50,22 +23,17 @@ function WidgetGraph({
|
||||
return (
|
||||
<Container $panelType={selectedGraph}>
|
||||
<PlotTag queryType={currentQuery.queryType} panelType={selectedGraph} />
|
||||
{getWidgetQueryRange.error && (
|
||||
<AlertIconContainer color="red" title={getWidgetQueryRange.error.message}>
|
||||
{queryResponse.error && (
|
||||
<AlertIconContainer color="red" title={queryResponse.error.message}>
|
||||
<InfoCircleOutlined />
|
||||
</AlertIconContainer>
|
||||
)}
|
||||
|
||||
<WidgetGraphComponent
|
||||
thresholds={thresholds}
|
||||
selectedTime={selectedTime}
|
||||
selectedGraph={selectedGraph}
|
||||
yAxisUnit={yAxisUnit}
|
||||
fillSpans={fillSpans}
|
||||
softMax={softMax}
|
||||
softMin={softMin}
|
||||
selectedLogFields={selectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
selectedWidget={selectedWidget}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { memo } from 'react';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
|
||||
import { WidgetGraphProps } from '../types';
|
||||
import ExplorerColumnsRenderer from './ExplorerColumnsRenderer';
|
||||
@@ -9,32 +20,84 @@ import WidgetGraph from './WidgetGraph';
|
||||
|
||||
function LeftContainer({
|
||||
selectedGraph,
|
||||
yAxisUnit,
|
||||
selectedTime,
|
||||
thresholds,
|
||||
fillSpans,
|
||||
softMax,
|
||||
softMin,
|
||||
selectedLogFields,
|
||||
setSelectedLogFields,
|
||||
selectedTracesFields,
|
||||
setSelectedTracesFields,
|
||||
selectedWidget,
|
||||
selectedTime,
|
||||
}: WidgetGraphProps): JSX.Element {
|
||||
const { stagedQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const { selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||
if (selectedWidget && selectedGraph !== PANEL_TYPES.LIST) {
|
||||
return {
|
||||
selectedTime: selectedWidget?.timePreferance,
|
||||
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
|
||||
query: stagedQuery || initialQueriesMap.metrics,
|
||||
globalSelectedInterval,
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
};
|
||||
}
|
||||
const updatedQuery = { ...(stagedQuery || initialQueriesMap.metrics) };
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
redirectWithQueryBuilderData(updatedQuery);
|
||||
return {
|
||||
query: updatedQuery,
|
||||
graphType: PANEL_TYPES.LIST,
|
||||
selectedTime: selectedTime.enum || 'GLOBAL_TIME',
|
||||
globalSelectedInterval,
|
||||
tableParams: {
|
||||
pagination: {
|
||||
offset: 0,
|
||||
limit: updatedQuery.builder.queryData[0].limit || 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (stagedQuery) {
|
||||
setRequestData((prev) => ({
|
||||
...prev,
|
||||
selectedTime: selectedTime.enum || prev.selectedTime,
|
||||
graphType: getGraphType(selectedGraph || selectedWidget.panelTypes),
|
||||
query: stagedQuery,
|
||||
}));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [stagedQuery, selectedTime]);
|
||||
|
||||
const queryResponse = useGetQueryRange(
|
||||
requestData,
|
||||
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
enabled: !!stagedQuery,
|
||||
retry: false,
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
globalSelectedInterval,
|
||||
requestData,
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetGraph
|
||||
thresholds={thresholds}
|
||||
selectedTime={selectedTime}
|
||||
selectedGraph={selectedGraph}
|
||||
yAxisUnit={yAxisUnit}
|
||||
fillSpans={fillSpans}
|
||||
softMax={softMax}
|
||||
softMin={softMin}
|
||||
selectedLogFields={selectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
queryResponse={queryResponse}
|
||||
setRequestData={setRequestData}
|
||||
selectedWidget={selectedWidget}
|
||||
/>
|
||||
<QueryContainer>
|
||||
<QuerySection selectedTime={selectedTime} selectedGraph={selectedGraph} />
|
||||
<QuerySection selectedGraph={selectedGraph} queryResponse={queryResponse} />
|
||||
{selectedGraph === PANEL_TYPES.LIST && (
|
||||
<ExplorerColumnsRenderer
|
||||
selectedLogFields={selectedLogFields}
|
||||
|
||||
@@ -26,6 +26,7 @@ export const panelTypeVsThreshold: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: true,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
@@ -36,6 +37,7 @@ export const panelTypeVsSoftMinMax: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
@@ -45,6 +47,7 @@ export const panelTypeVsDragAndDrop: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.TIME_SERIES]: false,
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: true,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
@@ -56,6 +59,7 @@ export const panelTypeVsFillSpan: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
@@ -66,6 +70,7 @@ export const panelTypeVsYAxisUnit: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: true,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
@@ -76,6 +81,7 @@ export const panelTypeVsCreateAlert: { [key in PANEL_TYPES]: boolean } = {
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
@@ -88,6 +94,7 @@ export const panelTypeVsPanelTimePreferences: {
|
||||
[PANEL_TYPES.VALUE]: true,
|
||||
[PANEL_TYPES.TABLE]: true,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: true,
|
||||
[PANEL_TYPES.BAR]: true,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
|
||||
@@ -12,10 +12,20 @@ import {
|
||||
import InputComponent from 'components/Input';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||
import GraphTypes, {
|
||||
ItemsProps,
|
||||
} from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
panelTypeVsCreateAlert,
|
||||
@@ -75,6 +85,24 @@ function RightContainer({
|
||||
const allowPanelTimePreference =
|
||||
panelTypeVsPanelTimePreferences[selectedGraph];
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes);
|
||||
|
||||
useEffect(() => {
|
||||
const queryContainsMetricsDataSource = currentQuery.builder.queryData.some(
|
||||
(query) => query.dataSource === DataSource.METRICS,
|
||||
);
|
||||
|
||||
if (queryContainsMetricsDataSource) {
|
||||
setGraphTypes((prev) =>
|
||||
prev.filter((graph) => graph.name !== PANEL_TYPES.LIST),
|
||||
);
|
||||
} else {
|
||||
setGraphTypes(GraphTypes);
|
||||
}
|
||||
}, [currentQuery]);
|
||||
|
||||
const softMinHandler = useCallback(
|
||||
(value: number | null) => {
|
||||
setSoftMin(value);
|
||||
@@ -95,10 +123,9 @@ function RightContainer({
|
||||
<Select
|
||||
onChange={setGraphHandler}
|
||||
value={selectedGraph}
|
||||
disabled
|
||||
style={{ width: '100%', marginBottom: 24 }}
|
||||
>
|
||||
{GraphTypes.map((item) => (
|
||||
{graphTypes.map((item) => (
|
||||
<Option key={item.name} value={item.name}>
|
||||
{item.display}
|
||||
</Option>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LockFilled, WarningOutlined } from '@ant-design/icons';
|
||||
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
@@ -44,7 +45,7 @@ import {
|
||||
RightContainerWrapper,
|
||||
} from './styles';
|
||||
import { NewWidgetProps } from './types';
|
||||
import { getIsQueryModified } from './utils';
|
||||
import { getIsQueryModified, handleQueryChange } from './utils';
|
||||
|
||||
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
const {
|
||||
@@ -57,7 +58,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
const { currentQuery, stagedQuery } = useQueryBuilder();
|
||||
const {
|
||||
currentQuery,
|
||||
stagedQuery,
|
||||
redirectWithQueryBuilderData,
|
||||
supersetQuery,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const isQueryModified = useMemo(
|
||||
() => getIsQueryModified(currentQuery, stagedQuery),
|
||||
@@ -70,8 +76,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
const { widgets = [] } = selectedDashboard?.data || {};
|
||||
|
||||
const { search } = useLocation();
|
||||
|
||||
const query = useUrlQuery();
|
||||
|
||||
const { dashboardId } = useParams<DashboardWidgetPageParams>();
|
||||
@@ -81,7 +85,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
return widgets?.find((e) => e.id === widgetId);
|
||||
}, [query, widgets]);
|
||||
|
||||
const selectedWidget = getWidget();
|
||||
const [selectedWidget, setSelectedWidget] = useState(getWidget());
|
||||
|
||||
const [title, setTitle] = useState<string>(
|
||||
selectedWidget?.title?.toString() || '',
|
||||
@@ -129,6 +133,44 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
: selectedWidget?.softMax || 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedWidget((prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
query: currentQuery,
|
||||
title,
|
||||
description,
|
||||
isStacked: stacked,
|
||||
opacity,
|
||||
nullZeroValues: selectedNullZeroValue,
|
||||
yAxisUnit,
|
||||
thresholds,
|
||||
softMin,
|
||||
softMax,
|
||||
fillSpans: isFillSpans,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
currentQuery,
|
||||
description,
|
||||
isFillSpans,
|
||||
opacity,
|
||||
selectedLogFields,
|
||||
selectedNullZeroValue,
|
||||
selectedTracesFields,
|
||||
softMax,
|
||||
softMin,
|
||||
stacked,
|
||||
thresholds,
|
||||
title,
|
||||
yAxisUnit,
|
||||
]);
|
||||
|
||||
const closeModal = (): void => {
|
||||
setSaveModal(false);
|
||||
setDiscardModal(false);
|
||||
@@ -194,21 +236,21 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
...preWidgets,
|
||||
{
|
||||
...(selectedWidget || ({} as Widgets)),
|
||||
description,
|
||||
description: selectedWidget?.description || '',
|
||||
timePreferance: selectedTime.enum,
|
||||
isStacked: stacked,
|
||||
opacity,
|
||||
nullZeroValues: selectedNullZeroValue,
|
||||
title,
|
||||
yAxisUnit,
|
||||
isStacked: selectedWidget?.isStacked || false,
|
||||
opacity: selectedWidget?.opacity || '1',
|
||||
nullZeroValues: selectedWidget?.nullZeroValues || 'zero',
|
||||
title: selectedWidget?.title,
|
||||
yAxisUnit: selectedWidget?.yAxisUnit,
|
||||
panelTypes: graphType,
|
||||
query: currentQuery,
|
||||
thresholds,
|
||||
softMin,
|
||||
softMax,
|
||||
fillSpans: isFillSpans,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
thresholds: selectedWidget?.thresholds,
|
||||
softMin: selectedWidget?.softMin || 0,
|
||||
softMax: selectedWidget?.softMax || 0,
|
||||
fillSpans: selectedWidget?.fillSpans,
|
||||
selectedLogFields: selectedWidget?.selectedLogFields || [],
|
||||
selectedTracesFields: selectedWidget?.selectedTracesFields || [],
|
||||
},
|
||||
...afterWidgets,
|
||||
],
|
||||
@@ -234,21 +276,9 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
selectedDashboard,
|
||||
preWidgets,
|
||||
selectedWidget,
|
||||
description,
|
||||
selectedTime.enum,
|
||||
stacked,
|
||||
opacity,
|
||||
selectedNullZeroValue,
|
||||
title,
|
||||
yAxisUnit,
|
||||
graphType,
|
||||
currentQuery,
|
||||
thresholds,
|
||||
softMin,
|
||||
softMax,
|
||||
isFillSpans,
|
||||
selectedLogFields,
|
||||
selectedTracesFields,
|
||||
afterWidgets,
|
||||
updateDashboardMutation,
|
||||
setSelectedDashboard,
|
||||
@@ -271,9 +301,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
}, [dashboardId]);
|
||||
|
||||
const setGraphHandler = (type: PANEL_TYPES): void => {
|
||||
const params = new URLSearchParams(search);
|
||||
params.set('graphType', type);
|
||||
const updatedQuery = handleQueryChange(type as any, supersetQuery);
|
||||
setGraphType(type);
|
||||
redirectWithQueryBuilderData(
|
||||
updatedQuery,
|
||||
{ [QueryParams.graphType]: type },
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
const onSaveDashboard = useCallback((): void => {
|
||||
@@ -358,19 +393,17 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
<PanelContainer>
|
||||
<LeftContainerWrapper flex={5}>
|
||||
<LeftContainer
|
||||
selectedTime={selectedTime}
|
||||
selectedGraph={graphType}
|
||||
yAxisUnit={yAxisUnit}
|
||||
thresholds={thresholds}
|
||||
fillSpans={isFillSpans}
|
||||
softMax={softMax}
|
||||
softMin={softMin}
|
||||
selectedLogFields={selectedLogFields}
|
||||
setSelectedLogFields={setSelectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
setSelectedTracesFields={setSelectedTracesFields}
|
||||
/>
|
||||
{selectedWidget && (
|
||||
<LeftContainer
|
||||
selectedGraph={graphType}
|
||||
selectedLogFields={selectedLogFields}
|
||||
setSelectedLogFields={setSelectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
setSelectedTracesFields={setSelectedTracesFields}
|
||||
selectedWidget={selectedWidget}
|
||||
selectedTime={selectedTime}
|
||||
/>
|
||||
)}
|
||||
</LeftContainerWrapper>
|
||||
|
||||
<RightContainerWrapper flex={1}>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { ThresholdProps } from './RightContainer/Threshold/types';
|
||||
import { timePreferance } from './RightContainer/timeItems';
|
||||
|
||||
export interface NewWidgetProps {
|
||||
@@ -11,15 +14,24 @@ export interface NewWidgetProps {
|
||||
fillSpans: Widgets['fillSpans'];
|
||||
}
|
||||
|
||||
export interface WidgetGraphProps extends NewWidgetProps {
|
||||
selectedTime: timePreferance;
|
||||
thresholds: ThresholdProps[];
|
||||
softMin: number | null;
|
||||
softMax: number | null;
|
||||
export interface WidgetGraphProps {
|
||||
selectedLogFields: Widgets['selectedLogFields'];
|
||||
setSelectedLogFields?: Dispatch<SetStateAction<Widgets['selectedLogFields']>>;
|
||||
selectedTracesFields: Widgets['selectedTracesFields'];
|
||||
setSelectedTracesFields?: Dispatch<
|
||||
SetStateAction<Widgets['selectedTracesFields']>
|
||||
>;
|
||||
selectedWidget: Widgets;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
selectedTime: timePreferance;
|
||||
}
|
||||
|
||||
export type WidgetGraphContainerProps = {
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
setRequestData: Dispatch<SetStateAction<GetQueryResultsProps>>;
|
||||
selectedGraph: PANEL_TYPES;
|
||||
selectedWidget: Widgets;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import { isEqual, set, unset } from 'lodash-es';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const getIsQueryModified = (
|
||||
currentQuery: Query,
|
||||
@@ -13,3 +18,287 @@ export const getIsQueryModified = (
|
||||
const omitIdFromCurrentQuery = omitIdFromQuery(currentQuery);
|
||||
return !isEqual(omitIdFromStageQuery, omitIdFromCurrentQuery);
|
||||
};
|
||||
|
||||
export type PartialPanelTypes = {
|
||||
[PANEL_TYPES.BAR]: 'bar';
|
||||
[PANEL_TYPES.LIST]: 'list';
|
||||
[PANEL_TYPES.TABLE]: 'table';
|
||||
[PANEL_TYPES.TIME_SERIES]: 'graph';
|
||||
[PANEL_TYPES.VALUE]: 'value';
|
||||
[PANEL_TYPES.PIE]: 'pie';
|
||||
};
|
||||
|
||||
export const panelTypeDataSourceFormValuesMap: Record<
|
||||
keyof PartialPanelTypes,
|
||||
Record<DataSource, any>
|
||||
> = {
|
||||
[PANEL_TYPES.BAR]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'spaceAggregation',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.TIME_SERIES]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'spaceAggregation',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.TABLE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'spaceAggregation',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.PIE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
'functions',
|
||||
'spaceAggregation',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.LIST]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: ['filters', 'limit', 'orderBy'],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: ['filters', 'limit', 'orderBy'],
|
||||
},
|
||||
},
|
||||
},
|
||||
[PANEL_TYPES.VALUE]: {
|
||||
[DataSource.LOGS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'reduceTo',
|
||||
'having',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.METRICS]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'having',
|
||||
'reduceTo',
|
||||
'functions',
|
||||
'spaceAggregation',
|
||||
],
|
||||
},
|
||||
},
|
||||
[DataSource.TRACES]: {
|
||||
builder: {
|
||||
queryData: [
|
||||
'filters',
|
||||
'aggregateOperator',
|
||||
'aggregateAttribute',
|
||||
'groupBy',
|
||||
'limit',
|
||||
'having',
|
||||
'orderBy',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function handleQueryChange(
|
||||
newPanelType: keyof PartialPanelTypes,
|
||||
supersetQuery: Query,
|
||||
): Query {
|
||||
return {
|
||||
...supersetQuery,
|
||||
builder: {
|
||||
...supersetQuery.builder,
|
||||
queryData: supersetQuery.builder.queryData.map((query, index) => {
|
||||
const { dataSource } = query;
|
||||
const tempQuery = { ...initialQueryBuilderFormValuesMap[dataSource] };
|
||||
|
||||
const fieldsToSelect =
|
||||
panelTypeDataSourceFormValuesMap[newPanelType][dataSource].builder
|
||||
.queryData;
|
||||
|
||||
fieldsToSelect.forEach((field: keyof IBuilderQuery) => {
|
||||
set(tempQuery, field, supersetQuery.builder.queryData[index][field]);
|
||||
});
|
||||
|
||||
if (newPanelType === PANEL_TYPES.LIST) {
|
||||
set(tempQuery, 'aggregateOperator', 'noop');
|
||||
set(tempQuery, 'offset', 0);
|
||||
set(tempQuery, 'pageSize', 10);
|
||||
} else if (tempQuery.aggregateOperator === 'noop') {
|
||||
set(tempQuery, 'aggregateOperator', 'count');
|
||||
unset(tempQuery, 'offset');
|
||||
unset(tempQuery, 'pageSize');
|
||||
}
|
||||
|
||||
return tempQuery;
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
### Step 1: Install OpenTelemetry Dependencies
|
||||
Dependencies related to OpenTelemetry exporter and SDK have to be installed first.
|
||||
|
||||
Run the below commands after navigating to the application source folder:
|
||||
```bash
|
||||
dotnet add package OpenTelemetry
|
||||
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
|
||||
dotnet add package OpenTelemetry.Extensions.Hosting
|
||||
dotnet add package OpenTelemetry.Instrumentation.Runtime
|
||||
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
|
||||
dotnet add package OpenTelemetry.AutoInstrumentation
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Step 2: Adding OpenTelemetry as a service and configuring exporter options
|
||||
|
||||
In your `Program.cs` file, add OpenTelemetry as a service. Here, we are configuring these variables:
|
||||
|
||||
`serviceName` - It is the name of your service.
|
||||
|
||||
`otlpOptions.Endpoint` - It is the endpoint for your OTel Collector agent.
|
||||
|
||||
|
||||
|
||||
Here’s a sample `Program.cs` file with the configured variables:
|
||||
|
||||
```bash
|
||||
using System.Diagnostics;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Configure OpenTelemetry with tracing and auto-start.
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.ConfigureResource(resource =>
|
||||
resource.AddService(serviceName: "{{MYAPP}}"))
|
||||
.WithTracing(tracing => tracing
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddOtlpExporter(otlpOptions =>
|
||||
{
|
||||
//sigNoz Cloud Endpoint
|
||||
otlpOptions.Endpoint = new Uri("https://ingest.{{REGION}}.signoz.cloud:443");
|
||||
|
||||
otlpOptions.Protocol = OtlpExportProtocol.Grpc;
|
||||
|
||||
//SigNoz Cloud account Ingestion key
|
||||
string headerKey = "signoz-access-token";
|
||||
string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
|
||||
|
||||
string formattedHeader = $"{headerKey}={headerValue}";
|
||||
otlpOptions.Headers = formattedHeader;
|
||||
}));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
//The index route ("/") is set up to write out the OpenTelemetry trace information on the response:
|
||||
app.MapGet("/", () => $"Hello World! OpenTelemetry Trace: {Activity.Current?.Id}");
|
||||
|
||||
app.Run();
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
The OpenTelemetry.Exporter.Options get or set the target to which the exporter is going to send traces. Here, we’re configuring it to send traces to the OTel Collector agent. The target must be a valid Uri with the scheme (http or https) and host and may contain a port and a path.
|
||||
|
||||
This is done by configuring an OpenTelemetry [TracerProvider](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/trace/customizing-the-sdk#readme) using extension methods and setting it to auto-start when the host is started.
|
||||
|
||||
|
||||
### Step 3: Dockerize your application
|
||||
|
||||
Since the environment variables like SIGNOZ_INGESTION_KEY, Ingestion Endpoint and Service name are set in the `program.cs` file, you don't need to add any additional steps in your Dockerfile.
|
||||
|
||||
An **example** of a Dockerfile could look like this:
|
||||
|
||||
```bash
|
||||
|
||||
# Use the Microsoft official .NET SDK image to build the application
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the CSPROJ file and restore any dependencies (via NUGET)
|
||||
COPY *.csproj ./
|
||||
RUN dotnet restore
|
||||
|
||||
# Copy the rest of the project files and build the application
|
||||
COPY . ./
|
||||
RUN dotnet publish -c Release -o out
|
||||
|
||||
# Generate the runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/out .
|
||||
|
||||
# Expose port 5145 for the application
|
||||
EXPOSE 5145
|
||||
|
||||
# Set the ASPNETCORE_URLS environment variable to listen on port 5145
|
||||
ENV ASPNETCORE_URLS=http://+:5145
|
||||
|
||||
ENTRYPOINT ["dotnet", "YOUR-APPLICATION.dll"]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
Once you update your Dockerfile, you can build and run it using the commands below.
|
||||
|
||||
|
||||
|
||||
### Step 1: Build your dockerfile
|
||||
|
||||
Build your docker image
|
||||
|
||||
```bash
|
||||
docker build -t <your-image-name> .
|
||||
```
|
||||
|
||||
- `<your-image-name>` is the name of your Docker Image
|
||||
|
||||
|
||||
|
||||
### Step 2: Run your docker image
|
||||
|
||||
```bash
|
||||
docker run <your-image-name>
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
## Setup OpenTelemetry Binary as an agent
|
||||
|
||||
|
||||
|
||||
As a first step, you should install the OTel collector Binary according to the instructions provided on [this link](https://signoz.io/docs/tutorial/opentelemetry-binary-usage-in-virtual-machine/).
|
||||
|
||||
|
||||
|
||||
Once you are done setting up the OTel collector binary, you can follow the next steps.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
After setting up the Otel collector agent, follow the steps below to instrument your .NET Application
|
||||
|
||||
|
||||
|
||||
|
||||
### Step 1: Install OpenTelemetry Dependencies
|
||||
Install the following dependencies in your application.
|
||||
|
||||
```bash
|
||||
dotnet add package OpenTelemetry
|
||||
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
|
||||
dotnet add package OpenTelemetry.Extensions.Hosting
|
||||
dotnet add package OpenTelemetry.Instrumentation.Runtime
|
||||
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
|
||||
dotnet add package OpenTelemetry.AutoInstrumentation
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Step 2: Adding OpenTelemetry as a service and configuring exporter options
|
||||
|
||||
In your `Program.cs` file, add OpenTelemetry as a service. Here, we are configuring these variables:
|
||||
|
||||
`serviceName` - It is the name of your service.
|
||||
|
||||
`otlpOptions.Endpoint` - It is the endpoint for your OTel Collector agent.
|
||||
|
||||
|
||||
|
||||
Here’s a sample `Program.cs` file with the configured variables:
|
||||
|
||||
```bash
|
||||
using System.Diagnostics;
|
||||
using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Configure OpenTelemetry with tracing and auto-start.
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.ConfigureResource(resource =>
|
||||
resource.AddService(serviceName: "{{MYAPP}}"))
|
||||
.WithTracing(tracing => tracing
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddOtlpExporter(otlpOptions =>
|
||||
{
|
||||
otlpOptions.Endpoint = new Uri("http://localhost:4317");
|
||||
|
||||
otlpOptions.Protocol = OtlpExportProtocol.Grpc;
|
||||
}));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
//The index route ("/") is set up to write out the OpenTelemetry trace information on the response:
|
||||
app.MapGet("/", () => $"Hello World! OpenTelemetry Trace: {Activity.Current?.Id}");
|
||||
|
||||
app.Run();
|
||||
```
|
||||
|
||||
|
||||
The OpenTelemetry.Exporter.Options get or set the target to which the exporter is going to send traces. Here, we’re configuring it to send traces to the OTel Collector agent. The target must be a valid Uri with the scheme (http or https) and host and may contain a port and a path.
|
||||
|
||||
This is done by configuring an OpenTelemetry [TracerProvider](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/trace/customizing-the-sdk#readme) using extension methods and setting it to auto-start when the host is started.
|
||||
|
||||
|
||||
|
||||
|
||||
### Step 3: Dockerize your application
|
||||
|
||||
Since the crucial environment variables like SIGNOZ_INGESTION_KEY, Ingestion Endpoint and Service name are set in the `program.cs` file, you don't need to add any additional steps in your Dockerfile.
|
||||
|
||||
An **example** of a Dockerfile could look like this:
|
||||
|
||||
```bash
|
||||
|
||||
# Use the Microsoft official .NET SDK image to build the application
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the CSPROJ file and restore any dependencies (via NUGET)
|
||||
COPY *.csproj ./
|
||||
RUN dotnet restore
|
||||
|
||||
# Copy the rest of the project files and build the application
|
||||
COPY . ./
|
||||
RUN dotnet publish -c Release -o out
|
||||
|
||||
# Generate the runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||
WORKDIR /app
|
||||
COPY --from=build-env /app/out .
|
||||
|
||||
# Expose port 5145 for the application
|
||||
EXPOSE 5145
|
||||
|
||||
# Set the ASPNETCORE_URLS environment variable to listen on port 5145
|
||||
ENV ASPNETCORE_URLS=http://+:5145
|
||||
|
||||
ENTRYPOINT ["dotnet", "YOUR-APPLICATION.dll"]
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
Once you update your Dockerfile, you can build and run it using the commands below.
|
||||
|
||||
|
||||
|
||||
### Step 1: Build your dockerfile
|
||||
|
||||
Build your docker image
|
||||
|
||||
```bash
|
||||
docker build -t <your-image-name> .
|
||||
```
|
||||
|
||||
- `<your-image-name>` is the name of your Docker Image
|
||||
|
||||
|
||||
|
||||
### Step 2: Run your docker image
|
||||
|
||||
```bash
|
||||
docker run <your-image-name>
|
||||
```
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
### Step 1: Download otel-collector tar.gz
|
||||
```bash
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.79.0/otelcol-contrib_0.79.0_linux_amd64.tar.gz
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v{{OTEL_VERSION}}/otelcol-contrib_{{OTEL_VERSION}}_linux_amd64.tar.gz
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Step 2: Extract otel-collector tar.gz to the `otelcol-contrib` folder
|
||||
```bash
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_0.79.0_linux_amd64.tar.gz -C otelcol-contrib
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_{{OTEL_VERSION}}_linux_amd64.tar.gz -C otelcol-contrib
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
### Step 1: Download otel-collector tar.gz
|
||||
```bash
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.79.0/otelcol-contrib_0.79.0_linux_arm64.tar.gz
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v{{OTEL_VERSION}}/otelcol-contrib_{{OTEL_VERSION}}_linux_arm64.tar.gz
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Step 2: Extract otel-collector tar.gz to the `otelcol-contrib` folder
|
||||
```bash
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_0.79.0_linux_arm64.tar.gz -C otelcol-contrib
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_{{OTEL_VERSION}}_linux_arm64.tar.gz -C otelcol-contrib
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
### Step 1: Download otel-collector tar.gz
|
||||
```bash
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.79.0/otelcol-contrib_0.79.0_darwin_amd64.tar.gz
|
||||
wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v{{OTEL_VERSION}}/otelcol-contrib_{{OTEL_VERSION}}_darwin_amd64.tar.gz
|
||||
```
|
||||
|
||||
|
||||
### Step 2: Extract otel-collector tar.gz to the `otelcol-contrib` folder
|
||||
```bash
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_0.79.0_darwin_amd64.tar.gz -C otelcol-contrib
|
||||
mkdir otelcol-contrib && tar xvzf otelcol-contrib_{{OTEL_VERSION}}_darwin_amd64.tar.gz -C otelcol-contrib
|
||||
```
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user