Compare commits
42 Commits
v0.52.0-cl
...
v0.52.0-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0401c27dbc | ||
|
|
57c45f22d6 | ||
|
|
29f1883edd | ||
|
|
5d903b5487 | ||
|
|
1b9683d699 | ||
|
|
65280cf4e1 | ||
|
|
1308f0f15f | ||
|
|
6c634b99d0 | ||
|
|
9856335840 | ||
|
|
e85b405396 | ||
|
|
e2e965bc7f | ||
|
|
7811fdd17a | ||
|
|
0dca1237b9 | ||
|
|
f3d73f6d44 | ||
|
|
187927403a | ||
|
|
0157b47424 | ||
|
|
156905afc7 | ||
|
|
a4878f6430 | ||
|
|
4489df6f39 | ||
|
|
06c075466b | ||
|
|
62be3e7c13 | ||
|
|
bb84960442 | ||
|
|
52199361d5 | ||
|
|
f031845300 | ||
|
|
6f73bb6eca | ||
|
|
fe398bcc49 | ||
|
|
6781c29082 | ||
|
|
eb146491f2 | ||
|
|
ae325ec1ca | ||
|
|
fd6f0574f5 | ||
|
|
b819a90c80 | ||
|
|
a6848f6abd | ||
|
|
abe65975c9 | ||
|
|
5cedd57aa2 | ||
|
|
80a7b9d16d | ||
|
|
9f7b2542ec | ||
|
|
4a4c9f26a2 | ||
|
|
c957c0f757 | ||
|
|
3ff0aa4b4b | ||
|
|
063c9adba6 | ||
|
|
5c3ce146fa | ||
|
|
481bb6e8b8 |
@@ -1,17 +1,48 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
featureSet, err := ah.FF().GetFeatureFlags()
|
featureSet, err := ah.FF().GetFeatureFlags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ah.HandleError(w, err, http.StatusInternalServerError)
|
ah.HandleError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constants.FetchFeatures == "true" {
|
||||||
|
zap.L().Debug("fetching license")
|
||||||
|
license, err := ah.LM().GetRepo().GetActiveLicense(ctx)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to fetch license", zap.Error(err))
|
||||||
|
} else if license == nil {
|
||||||
|
zap.L().Debug("no active license found")
|
||||||
|
} else {
|
||||||
|
licenseKey := license.Key
|
||||||
|
|
||||||
|
zap.L().Debug("fetching zeus features")
|
||||||
|
zeusFeatures, err := fetchZeusFeatures(constants.ZeusFeaturesURL, licenseKey)
|
||||||
|
if err == nil {
|
||||||
|
zap.L().Debug("fetched zeus features", zap.Any("features", zeusFeatures))
|
||||||
|
// merge featureSet and zeusFeatures in featureSet with higher priority to zeusFeatures
|
||||||
|
featureSet = MergeFeatureSets(zeusFeatures, featureSet)
|
||||||
|
} else {
|
||||||
|
zap.L().Error("failed to fetch zeus features", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ah.opts.PreferSpanMetrics {
|
if ah.opts.PreferSpanMetrics {
|
||||||
for idx := range featureSet {
|
for idx := range featureSet {
|
||||||
feature := &featureSet[idx]
|
feature := &featureSet[idx]
|
||||||
@@ -20,5 +51,96 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ah.Respond(w, featureSet)
|
ah.Respond(w, featureSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
||||||
|
// and returns the FeatureSet.
|
||||||
|
func fetchZeusFeatures(url, licenseKey string) (basemodel.FeatureSet, error) {
|
||||||
|
// Check if the URL is empty
|
||||||
|
if url == "" {
|
||||||
|
return nil, fmt.Errorf("url is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the licenseKey is empty
|
||||||
|
if licenseKey == "" {
|
||||||
|
return nil, fmt.Errorf("licenseKey is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating an HTTP client with a timeout for better control
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
// Creating a new GET request
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the custom header
|
||||||
|
req.Header.Set("X-Signoz-Cloud-Api-Key", licenseKey)
|
||||||
|
|
||||||
|
// Making the GET request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to make GET request: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check for non-OK status code
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%w: %d %s", errors.New("received non-OK HTTP status code"), resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading and decoding the response body
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeusResponse ZeusFeaturesResponse
|
||||||
|
if err := json.Unmarshal(body, &zeusResponse); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", errors.New("failed to decode response body"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if zeusResponse.Status != "success" {
|
||||||
|
return nil, fmt.Errorf("%w: %s", errors.New("failed to fetch zeus features"), zeusResponse.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return zeusResponse.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZeusFeaturesResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data basemodel.FeatureSet `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
||||||
|
func MergeFeatureSets(zeusFeatures, internalFeatures basemodel.FeatureSet) basemodel.FeatureSet {
|
||||||
|
// Create a map to store the merged features
|
||||||
|
featureMap := make(map[string]basemodel.Feature)
|
||||||
|
|
||||||
|
// Add all features from the otherFeatures set to the map
|
||||||
|
for _, feature := range internalFeatures {
|
||||||
|
featureMap[feature.Name] = feature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all features from the zeusFeatures set to the map
|
||||||
|
// If a feature already exists (i.e., same name), the zeusFeature will overwrite it
|
||||||
|
for _, feature := range zeusFeatures {
|
||||||
|
featureMap[feature.Name] = feature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the map back to a FeatureSet slice
|
||||||
|
var mergedFeatures basemodel.FeatureSet
|
||||||
|
for _, feature := range featureMap {
|
||||||
|
mergedFeatures = append(mergedFeatures, feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedFeatures
|
||||||
|
}
|
||||||
|
|||||||
88
ee/query-service/app/api/featureFlags_test.go
Normal file
88
ee/query-service/app/api/featureFlags_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeFeatureSets(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
zeusFeatures basemodel.FeatureSet
|
||||||
|
internalFeatures basemodel.FeatureSet
|
||||||
|
expected basemodel.FeatureSet
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty zeusFeatures and internalFeatures",
|
||||||
|
zeusFeatures: basemodel.FeatureSet{},
|
||||||
|
internalFeatures: basemodel.FeatureSet{},
|
||||||
|
expected: basemodel.FeatureSet{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-empty zeusFeatures and empty internalFeatures",
|
||||||
|
zeusFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
},
|
||||||
|
internalFeatures: basemodel.FeatureSet{},
|
||||||
|
expected: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty zeusFeatures and non-empty internalFeatures",
|
||||||
|
zeusFeatures: basemodel.FeatureSet{},
|
||||||
|
internalFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
},
|
||||||
|
expected: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
||||||
|
zeusFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature3", Active: false},
|
||||||
|
},
|
||||||
|
internalFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature2", Active: true},
|
||||||
|
{Name: "Feature4", Active: false},
|
||||||
|
},
|
||||||
|
expected: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: true},
|
||||||
|
{Name: "Feature3", Active: false},
|
||||||
|
{Name: "Feature4", Active: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
||||||
|
zeusFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
},
|
||||||
|
internalFeatures: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: false},
|
||||||
|
{Name: "Feature3", Active: true},
|
||||||
|
},
|
||||||
|
expected: basemodel.FeatureSet{
|
||||||
|
{Name: "Feature1", Active: true},
|
||||||
|
{Name: "Feature2", Active: false},
|
||||||
|
{Name: "Feature3", Active: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
actual := MergeFeatureSets(test.zeusFeatures, test.internalFeatures)
|
||||||
|
assert.ElementsMatch(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -28,6 +29,7 @@ import (
|
|||||||
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
|
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
|
||||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
|
||||||
@@ -179,6 +181,13 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err = migrate.ClickHouseMigrate(reader.GetConn(), serverOptions.Cluster)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("error while running clickhouse migrations", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// initiate opamp
|
// initiate opamp
|
||||||
_, err = opAmpModel.InitDB(localDB)
|
_, err = opAmpModel.InitDB(localDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,7 +318,7 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
|
|||||||
// ip here for alert manager
|
// ip here for alert manager
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH"},
|
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH"},
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "SIGNOZ-API-KEY"},
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "SIGNOZ-API-KEY", "X-SIGNOZ-QUERY-ID", "Sec-WebSocket-Protocol"},
|
||||||
})
|
})
|
||||||
|
|
||||||
handler := c.Handler(r)
|
handler := c.Handler(r)
|
||||||
@@ -354,7 +363,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"},
|
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"},
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "cache-control"},
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "cache-control", "X-SIGNOZ-QUERY-ID", "Sec-WebSocket-Protocol"},
|
||||||
})
|
})
|
||||||
|
|
||||||
handler := c.Handler(r)
|
handler := c.Handler(r)
|
||||||
@@ -410,6 +419,15 @@ func (lrw *loggingResponseWriter) Flush() {
|
|||||||
lrw.ResponseWriter.(http.Flusher).Flush()
|
lrw.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support websockets
|
||||||
|
func (lrw *loggingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
h, ok := lrw.ResponseWriter.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.New("hijack not supported")
|
||||||
|
}
|
||||||
|
return h.Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
func extractQueryRangeData(path string, r *http.Request) (map[string]interface{}, bool) {
|
func extractQueryRangeData(path string, r *http.Request) (map[string]interface{}, bool) {
|
||||||
pathToExtractBodyFromV3 := "/api/v3/query_range"
|
pathToExtractBodyFromV3 := "/api/v3/query_range"
|
||||||
pathToExtractBodyFromV4 := "/api/v4/query_range"
|
pathToExtractBodyFromV4 := "/api/v4/query_range"
|
||||||
@@ -728,6 +746,7 @@ func makeRulesManager(
|
|||||||
DisableRules: disableRules,
|
DisableRules: disableRules,
|
||||||
FeatureFlags: fm,
|
FeatureFlags: fm,
|
||||||
Reader: ch,
|
Reader: ch,
|
||||||
|
EvalDelay: baseconst.GetEvalDelay(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// create Manager
|
// create Manager
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
|||||||
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
||||||
var SpanRenderLimitStr = GetOrDefaultEnv("SPAN_RENDER_LIMIT", "2500")
|
var SpanRenderLimitStr = GetOrDefaultEnv("SPAN_RENDER_LIMIT", "2500")
|
||||||
var MaxSpansInTraceStr = GetOrDefaultEnv("MAX_SPANS_IN_TRACE", "250000")
|
var MaxSpansInTraceStr = GetOrDefaultEnv("MAX_SPANS_IN_TRACE", "250000")
|
||||||
|
var FetchFeatures = GetOrDefaultEnv("FETCH_FEATURES", "false")
|
||||||
|
var ZeusFeaturesURL = GetOrDefaultEnv("ZEUS_FEATURES_URL", "ZeusFeaturesURL")
|
||||||
|
|
||||||
func GetOrDefaultEnv(key string, fallback string) string {
|
func GetOrDefaultEnv(key string, fallback string) string {
|
||||||
v := os.Getenv(key)
|
v := os.Getenv(key)
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, a
|
|||||||
for _, l := range licenses {
|
for _, l := range licenses {
|
||||||
l.ParsePlan()
|
l.ParsePlan()
|
||||||
|
|
||||||
if l.Key == lm.activeLicense.Key {
|
if lm.activeLicense != nil && l.Key == lm.activeLicense.Key {
|
||||||
l.IsCurrent = true
|
l.IsCurrent = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const DisableUpsell = "DISABLE_UPSELL"
|
|||||||
const Onboarding = "ONBOARDING"
|
const Onboarding = "ONBOARDING"
|
||||||
const ChatSupport = "CHAT_SUPPORT"
|
const ChatSupport = "CHAT_SUPPORT"
|
||||||
const Gateway = "GATEWAY"
|
const Gateway = "GATEWAY"
|
||||||
|
const PremiumSupport = "PREMIUM_SUPPORT"
|
||||||
|
|
||||||
var BasicPlan = basemodel.FeatureSet{
|
var BasicPlan = basemodel.FeatureSet{
|
||||||
basemodel.Feature{
|
basemodel.Feature{
|
||||||
@@ -119,6 +120,13 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: false,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ProPlan = basemodel.FeatureSet{
|
var ProPlan = basemodel.FeatureSet{
|
||||||
@@ -220,6 +228,13 @@ var ProPlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: true,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnterprisePlan = basemodel.FeatureSet{
|
var EnterprisePlan = basemodel.FeatureSet{
|
||||||
@@ -335,4 +350,11 @@ var EnterprisePlan = basemodel.FeatureSet{
|
|||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
|
basemodel.Feature{
|
||||||
|
Name: PremiumSupport,
|
||||||
|
Active: true,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend/public/Icons/solid-x-circle.svg
Normal file
1
frontend/public/Icons/solid-x-circle.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#prefix__clip0_4062_7291)" stroke-width="1.167" stroke-linecap="round" stroke-linejoin="round"><path d="M7 12.833A5.833 5.833 0 107 1.167a5.833 5.833 0 000 11.666z" fill="#E5484D" stroke="#E5484D"/><path d="M8.75 5.25l-3.5 3.5M5.25 5.25l3.5 3.5" stroke="#121317"/></g><defs><clipPath id="prefix__clip0_4062_7291"><path fill="#fff" d="M0 0h14v14H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 467 B |
@@ -66,6 +66,14 @@ function App(): JSX.Element {
|
|||||||
allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active ||
|
allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active ||
|
||||||
false;
|
false;
|
||||||
|
|
||||||
|
const isPremiumSupportEnabled =
|
||||||
|
allFlags.find((flag) => flag.name === FeatureKeys.PREMIUM_SUPPORT)?.active ||
|
||||||
|
false;
|
||||||
|
|
||||||
|
const showAddCreditCardModal =
|
||||||
|
!isPremiumSupportEnabled &&
|
||||||
|
!licenseData?.payload?.trialConvertedToSubscription;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -82,7 +90,7 @@ function App(): JSX.Element {
|
|||||||
setRoutes(newRoutes);
|
setRoutes(newRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedInState && isChatSupportEnabled) {
|
if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.Intercom('boot', {
|
window.Intercom('boot', {
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
|||||||
// making the error status code as standard Error Status Code
|
// making the error status code as standard Error Status Code
|
||||||
const statusCode = response.status as ErrorStatusCode;
|
const statusCode = response.status as ErrorStatusCode;
|
||||||
|
|
||||||
if (statusCode >= 400 && statusCode < 500) {
|
const { data } = response as AxiosResponse;
|
||||||
const { data } = response as AxiosResponse;
|
|
||||||
|
|
||||||
|
if (statusCode >= 400 && statusCode < 500) {
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
return {
|
return {
|
||||||
statusCode,
|
statusCode,
|
||||||
@@ -34,12 +34,11 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
|||||||
body: JSON.stringify((response.data as any).data),
|
body: JSON.stringify((response.data as any).data),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode,
|
statusCode,
|
||||||
payload: null,
|
payload: null,
|
||||||
error: 'Something went wrong',
|
error: 'Something went wrong',
|
||||||
message: null,
|
message: data?.error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (request) {
|
if (request) {
|
||||||
|
|||||||
32
frontend/src/api/common/getQueryStats.ts
Normal file
32
frontend/src/api/common/getQueryStats.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||||
|
import { ENVIRONMENT } from 'constants/env';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
|
||||||
|
export interface WsDataEvent {
|
||||||
|
read_rows: number;
|
||||||
|
read_bytes: number;
|
||||||
|
elapsed_ms: number;
|
||||||
|
}
|
||||||
|
interface GetQueryStatsProps {
|
||||||
|
queryId: string;
|
||||||
|
setData: React.Dispatch<React.SetStateAction<WsDataEvent | undefined>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getQueryStats(props: GetQueryStatsProps): void {
|
||||||
|
const { queryId, setData } = props;
|
||||||
|
|
||||||
|
const token = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || '';
|
||||||
|
const socket = new WebSocket(
|
||||||
|
`${ENVIRONMENT.wsURL}api/v3/query_progress?q=${queryId}`,
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(event?.data);
|
||||||
|
setData(parsedData);
|
||||||
|
} catch {
|
||||||
|
setData(event?.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -12,10 +12,13 @@ export const getMetricsQueryRange = async (
|
|||||||
props: QueryRangePayload,
|
props: QueryRangePayload,
|
||||||
version: string,
|
version: string,
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
|
headers?: Record<string, string>,
|
||||||
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
if (version && version === ENTITY_VERSION_V4) {
|
if (version && version === ENTITY_VERSION_V4) {
|
||||||
const response = await ApiV4Instance.post('/query_range', props, { signal });
|
const response = await ApiV4Instance.post('/query_range', props, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
@@ -26,7 +29,10 @@ export const getMetricsQueryRange = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await ApiV3Instance.post('/query_range', props, { signal });
|
const response = await ApiV3Instance.post('/query_range', props, {
|
||||||
|
signal,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { ApiV3Instance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
||||||
|
import { encode } from 'js-base64';
|
||||||
|
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IGetAttributeSuggestionsPayload,
|
||||||
|
IGetAttributeSuggestionsSuccessResponse,
|
||||||
|
} from 'types/api/queryBuilder/getAttributeSuggestions';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const getAttributeSuggestions = async ({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
filters,
|
||||||
|
}: IGetAttributeSuggestionsPayload): Promise<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
let base64EncodedFiltersString;
|
||||||
|
try {
|
||||||
|
// the replace function is to remove the padding at the end of base64 encoded string which is auto added to make it a multiple of 4
|
||||||
|
// why ? because the current working of qs doesn't work well with padding
|
||||||
|
base64EncodedFiltersString = encode(JSON.stringify(filters)).replace(
|
||||||
|
/=+$/,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// default base64 encoded string for empty filters object
|
||||||
|
base64EncodedFiltersString = 'eyJpdGVtcyI6W10sIm9wIjoiQU5EIn0';
|
||||||
|
}
|
||||||
|
const response: AxiosResponse<{
|
||||||
|
data: IGetAttributeSuggestionsSuccessResponse;
|
||||||
|
}> = await ApiV3Instance.get(
|
||||||
|
`/filter_suggestions?${createQueryParams({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
existingFilter: base64EncodedFiltersString,
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload: BaseAutocompleteData[] =
|
||||||
|
response.data.data.attributes?.map(({ id: _, ...item }) => ({
|
||||||
|
...item,
|
||||||
|
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.statusText,
|
||||||
|
payload: {
|
||||||
|
attributes: payload,
|
||||||
|
example_queries: response.data.data.example_queries,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return ErrorResponseHandler(e as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { Button, Modal, Typography } from 'antd';
|
||||||
|
import updateCreditCardApi from 'api/billing/checkout';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import useLicense from 'hooks/useLicense';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { CreditCard, X } from 'lucide-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import { License } from 'types/api/licenses/def';
|
||||||
|
|
||||||
|
export default function ChatSupportGateway(): JSX.Element {
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const [activeLicense, setActiveLicense] = useState<License | null>(null);
|
||||||
|
|
||||||
|
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: licenseData, isFetching } = useLicense();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const activeValidLicense =
|
||||||
|
licenseData?.payload?.licenses?.find(
|
||||||
|
(license) => license.isCurrent === true,
|
||||||
|
) || null;
|
||||||
|
|
||||||
|
setActiveLicense(activeValidLicense);
|
||||||
|
}, [licenseData, isFetching]);
|
||||||
|
|
||||||
|
const handleBillingOnSuccess = (
|
||||||
|
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
||||||
|
): void => {
|
||||||
|
if (data?.payload?.redirectURL) {
|
||||||
|
const newTab = document.createElement('a');
|
||||||
|
newTab.href = data.payload.redirectURL;
|
||||||
|
newTab.target = '_blank';
|
||||||
|
newTab.rel = 'noopener noreferrer';
|
||||||
|
newTab.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBillingOnError = (): void => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutate: updateCreditCard, isLoading: isLoadingBilling } = useMutation(
|
||||||
|
updateCreditCardApi,
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
handleBillingOnSuccess(data);
|
||||||
|
},
|
||||||
|
onError: handleBillingOnError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddCreditCard = (): void => {
|
||||||
|
logEvent('Add Credit card modal: Clicked', {
|
||||||
|
source: `intercom icon`,
|
||||||
|
page: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCreditCard({
|
||||||
|
licenseKey: activeLicense?.key || '',
|
||||||
|
successURL: window.location.href,
|
||||||
|
cancelURL: window.location.href,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="chat-support-gateway">
|
||||||
|
<Button
|
||||||
|
className="chat-support-gateway-btn"
|
||||||
|
onClick={(): void => {
|
||||||
|
logEvent('Disabled Chat Support: Clicked', {
|
||||||
|
source: `intercom icon`,
|
||||||
|
page: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsAddCreditCardModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 28 32"
|
||||||
|
className="chat-support-gateway-btn-icon"
|
||||||
|
>
|
||||||
|
<path d="M28 32s-4.714-1.855-8.527-3.34H3.437C1.54 28.66 0 27.026 0 25.013V3.644C0 1.633 1.54 0 3.437 0h21.125c1.898 0 3.437 1.632 3.437 3.645v18.404H28V32zm-4.139-11.982a.88.88 0 00-1.292-.105c-.03.026-3.015 2.681-8.57 2.681-5.486 0-8.517-2.636-8.571-2.684a.88.88 0 00-1.29.107 1.01 1.01 0 00-.219.708.992.992 0 00.318.664c.142.128 3.537 3.15 9.762 3.15 6.226 0 9.621-3.022 9.763-3.15a.992.992 0 00.317-.664 1.01 1.01 0 00-.218-.707z" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add Credit Card Modal */}
|
||||||
|
<Modal
|
||||||
|
className="add-credit-card-modal"
|
||||||
|
title={<span className="title">Add Credit Card for Chat Support</span>}
|
||||||
|
open={isAddCreditCardModalOpen}
|
||||||
|
closable
|
||||||
|
onCancel={(): void => setIsAddCreditCardModalOpen(false)}
|
||||||
|
destroyOnClose
|
||||||
|
footer={[
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={(): void => setIsAddCreditCardModalOpen(false)}
|
||||||
|
className="cancel-btn"
|
||||||
|
icon={<X size={16} />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
icon={<CreditCard size={16} />}
|
||||||
|
size="middle"
|
||||||
|
loading={isLoadingBilling}
|
||||||
|
disabled={isLoadingBilling}
|
||||||
|
onClick={handleAddCreditCard}
|
||||||
|
className="add-credit-card-btn"
|
||||||
|
>
|
||||||
|
Add Credit Card
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Typography.Text className="add-credit-card-text">
|
||||||
|
You're currently on <span className="highlight-text">Trial plan</span>
|
||||||
|
. Add a credit card to access SigNoz chat support to your workspace.
|
||||||
|
</Typography.Text>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
184
frontend/src/components/LaunchChatSupport/LaunchChatSupport.tsx
Normal file
184
frontend/src/components/LaunchChatSupport/LaunchChatSupport.tsx
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import './LaunchChatSupport.styles.scss';
|
||||||
|
|
||||||
|
import { Button, Modal, Tooltip, Typography } from 'antd';
|
||||||
|
import updateCreditCardApi from 'api/billing/checkout';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
|
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||||
|
import useLicense from 'hooks/useLicense';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { defaultTo } from 'lodash-es';
|
||||||
|
import { CreditCard, HelpCircle, X } from 'lucide-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useMutation } from 'react-query';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import { License } from 'types/api/licenses/def';
|
||||||
|
import { isCloudUser } from 'utils/app';
|
||||||
|
|
||||||
|
export interface LaunchChatSupportProps {
|
||||||
|
eventName: string;
|
||||||
|
attributes: Record<string, unknown>;
|
||||||
|
message?: string;
|
||||||
|
buttonText?: string;
|
||||||
|
className?: string;
|
||||||
|
onHoverText?: string;
|
||||||
|
intercomMessageDisabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
function LaunchChatSupport({
|
||||||
|
attributes,
|
||||||
|
eventName,
|
||||||
|
message = '',
|
||||||
|
buttonText = '',
|
||||||
|
className = '',
|
||||||
|
onHoverText = '',
|
||||||
|
intercomMessageDisabled = false,
|
||||||
|
}: LaunchChatSupportProps): JSX.Element | null {
|
||||||
|
const isChatSupportEnabled = useFeatureFlags(FeatureKeys.CHAT_SUPPORT)?.active;
|
||||||
|
const isCloudUserVal = isCloudUser();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
const { data: licenseData, isFetching } = useLicense();
|
||||||
|
const [activeLicense, setActiveLicense] = useState<License | null>(null);
|
||||||
|
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] = useState(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isPremiumChatSupportEnabled =
|
||||||
|
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||||
|
|
||||||
|
const showAddCreditCardModal =
|
||||||
|
!isPremiumChatSupportEnabled &&
|
||||||
|
!licenseData?.payload?.trialConvertedToSubscription;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const activeValidLicense =
|
||||||
|
licenseData?.payload?.licenses?.find(
|
||||||
|
(license) => license.isCurrent === true,
|
||||||
|
) || null;
|
||||||
|
|
||||||
|
setActiveLicense(activeValidLicense);
|
||||||
|
}, [licenseData, isFetching]);
|
||||||
|
|
||||||
|
const handleFacingIssuesClick = (): void => {
|
||||||
|
if (showAddCreditCardModal) {
|
||||||
|
setIsAddCreditCardModalOpen(true);
|
||||||
|
} else {
|
||||||
|
logEvent(eventName, attributes);
|
||||||
|
if (window.Intercom && !intercomMessageDisabled) {
|
||||||
|
window.Intercom('showNewMessage', defaultTo(message, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBillingOnSuccess = (
|
||||||
|
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
||||||
|
): void => {
|
||||||
|
if (data?.payload?.redirectURL) {
|
||||||
|
const newTab = document.createElement('a');
|
||||||
|
newTab.href = data.payload.redirectURL;
|
||||||
|
newTab.target = '_blank';
|
||||||
|
newTab.rel = 'noopener noreferrer';
|
||||||
|
newTab.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBillingOnError = (): void => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutate: updateCreditCard, isLoading: isLoadingBilling } = useMutation(
|
||||||
|
updateCreditCardApi,
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
handleBillingOnSuccess(data);
|
||||||
|
},
|
||||||
|
onError: handleBillingOnError,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAddCreditCard = (): void => {
|
||||||
|
logEvent('Add Credit card modal: Clicked', {
|
||||||
|
source: `facing issues button`,
|
||||||
|
page: '',
|
||||||
|
...attributes,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCreditCard({
|
||||||
|
licenseKey: activeLicense?.key || '',
|
||||||
|
successURL: window.location.href,
|
||||||
|
cancelURL: window.location.href,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return isCloudUserVal && isChatSupportEnabled ? ( // Note: we would need to move this condition to license based in future
|
||||||
|
<div className="facing-issue-button">
|
||||||
|
<Tooltip
|
||||||
|
title={onHoverText}
|
||||||
|
autoAdjustOverflow
|
||||||
|
style={{ padding: 8 }}
|
||||||
|
overlayClassName="tooltip-overlay"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={cx('periscope-btn', 'facing-issue-button', className)}
|
||||||
|
onClick={handleFacingIssuesClick}
|
||||||
|
icon={<HelpCircle size={14} />}
|
||||||
|
>
|
||||||
|
{buttonText || 'Facing issues?'}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Add Credit Card Modal */}
|
||||||
|
<Modal
|
||||||
|
className="add-credit-card-modal"
|
||||||
|
title={<span className="title">Add Credit Card for Chat Support</span>}
|
||||||
|
open={isAddCreditCardModalOpen}
|
||||||
|
closable
|
||||||
|
onCancel={(): void => setIsAddCreditCardModalOpen(false)}
|
||||||
|
destroyOnClose
|
||||||
|
footer={[
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
onClick={(): void => setIsAddCreditCardModalOpen(false)}
|
||||||
|
className="cancel-btn"
|
||||||
|
icon={<X size={16} />}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="submit"
|
||||||
|
type="primary"
|
||||||
|
icon={<CreditCard size={16} />}
|
||||||
|
size="middle"
|
||||||
|
loading={isLoadingBilling}
|
||||||
|
disabled={isLoadingBilling}
|
||||||
|
onClick={handleAddCreditCard}
|
||||||
|
className="add-credit-card-btn"
|
||||||
|
>
|
||||||
|
Add Credit Card
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Typography.Text className="add-credit-card-text">
|
||||||
|
You're currently on <span className="highlight-text">Trial plan</span>
|
||||||
|
. Add a credit card to access SigNoz chat support to your workspace.
|
||||||
|
</Typography.Text>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchChatSupport.defaultProps = {
|
||||||
|
message: '',
|
||||||
|
buttonText: '',
|
||||||
|
className: '',
|
||||||
|
onHoverText: '',
|
||||||
|
intercomMessageDisabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LaunchChatSupport;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DrawerProps } from 'antd';
|
import { DrawerProps } from 'antd';
|
||||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||||
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { VIEWS } from './constants';
|
import { VIEWS } from './constants';
|
||||||
@@ -9,6 +10,7 @@ export type LogDetailProps = {
|
|||||||
log: ILog | null;
|
log: ILog | null;
|
||||||
selectedTab: VIEWS;
|
selectedTab: VIEWS;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
|
||||||
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
|
||||||
Pick<DrawerProps, 'onClose'>;
|
Pick<DrawerProps, 'onClose'>;
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
|||||||
import { RadioChangeEvent } from 'antd/lib';
|
import { RadioChangeEvent } from 'antd/lib';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||||
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||||
import JSONView from 'container/LogDetailedView/JsonView';
|
import JSONView from 'container/LogDetailedView/JsonView';
|
||||||
import Overview from 'container/LogDetailedView/Overview';
|
import Overview from 'container/LogDetailedView/Overview';
|
||||||
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
|
||||||
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||||
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import {
|
import {
|
||||||
@@ -21,9 +24,10 @@ import {
|
|||||||
TextSelect,
|
TextSelect,
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
import { VIEW_TYPES, VIEWS } from './constants';
|
||||||
import { LogDetailProps } from './LogDetail.interfaces';
|
import { LogDetailProps } from './LogDetail.interfaces';
|
||||||
@@ -36,6 +40,7 @@ function LogDetail({
|
|||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: LogDetailProps): JSX.Element {
|
}: LogDetailProps): JSX.Element {
|
||||||
const [, copyToClipboard] = useCopyToClipboard();
|
const [, copyToClipboard] = useCopyToClipboard();
|
||||||
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
||||||
@@ -45,6 +50,19 @@ function LogDetail({
|
|||||||
const [contextQuery, setContextQuery] = useState<Query | undefined>();
|
const [contextQuery, setContextQuery] = useState<Query | undefined>();
|
||||||
const [filters, setFilters] = useState<TagFilter | null>(null);
|
const [filters, setFilters] = useState<TagFilter | null>(null);
|
||||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||||
|
const { initialDataSource, stagedQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const listQuery = useMemo(() => {
|
||||||
|
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
||||||
|
|
||||||
|
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
||||||
|
}, [stagedQuery]);
|
||||||
|
|
||||||
|
const { options } = useOptionsMenu({
|
||||||
|
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||||
|
dataSource: initialDataSource || DataSource.LOGS,
|
||||||
|
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
|
||||||
|
});
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
@@ -192,6 +210,8 @@ function LogDetail({
|
|||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
|
selectedOptions={options}
|
||||||
|
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
|
|||||||
import { ColumnGroupType, ColumnType } from 'antd/es/table';
|
import { ColumnGroupType, ColumnType } from 'antd/es/table';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
import { SlidersHorizontal } from 'lucide-react';
|
import { SlidersHorizontal } from 'lucide-react';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
@@ -96,7 +96,7 @@ function DynamicColumnTable({
|
|||||||
return (
|
return (
|
||||||
<div className="DynamicColumnTable">
|
<div className="DynamicColumnTable">
|
||||||
<Flex justify="flex-end" align="center" gap={8}>
|
<Flex justify="flex-end" align="center" gap={8}>
|
||||||
{facingIssueBtn && <FacingIssueBtn {...facingIssueBtn} />}
|
{facingIssueBtn && <LaunchChatSupport {...facingIssueBtn} />}
|
||||||
{dynamicColumns && (
|
{dynamicColumns && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { TableProps } from 'antd';
|
import { TableProps } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
|
import { ColumnGroupType, ColumnType } from 'antd/lib/table';
|
||||||
import { FacingIssueBtnProps } from 'components/facingIssueBtn/FacingIssueBtn';
|
import { LaunchChatSupportProps } from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
|
|
||||||
import { TableDataSource } from './contants';
|
import { TableDataSource } from './contants';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export interface DynamicColumnTableProps extends TableProps<any> {
|
|||||||
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
tablesource: typeof TableDataSource[keyof typeof TableDataSource];
|
||||||
dynamicColumns: TableProps<any>['columns'];
|
dynamicColumns: TableProps<any>['columns'];
|
||||||
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
onDragColumn?: (fromIndex: number, toIndex: number) => void;
|
||||||
facingIssueBtn?: FacingIssueBtnProps;
|
facingIssueBtn?: LaunchChatSupportProps;
|
||||||
shouldSendAlertsLogEvent?: boolean;
|
shouldSendAlertsLogEvent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import './FacingIssueBtn.style.scss';
|
|
||||||
|
|
||||||
import { Button, Tooltip } from 'antd';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import { FeatureKeys } from 'constants/features';
|
|
||||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
|
||||||
import { defaultTo } from 'lodash-es';
|
|
||||||
import { HelpCircle } from 'lucide-react';
|
|
||||||
import { isCloudUser } from 'utils/app';
|
|
||||||
|
|
||||||
export interface FacingIssueBtnProps {
|
|
||||||
eventName: string;
|
|
||||||
attributes: Record<string, unknown>;
|
|
||||||
message?: string;
|
|
||||||
buttonText?: string;
|
|
||||||
className?: string;
|
|
||||||
onHoverText?: string;
|
|
||||||
intercomMessageDisabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FacingIssueBtn({
|
|
||||||
attributes,
|
|
||||||
eventName,
|
|
||||||
message = '',
|
|
||||||
buttonText = '',
|
|
||||||
className = '',
|
|
||||||
onHoverText = '',
|
|
||||||
intercomMessageDisabled = false,
|
|
||||||
}: FacingIssueBtnProps): JSX.Element | null {
|
|
||||||
const handleFacingIssuesClick = (): void => {
|
|
||||||
logEvent(eventName, attributes);
|
|
||||||
|
|
||||||
if (window.Intercom && !intercomMessageDisabled) {
|
|
||||||
window.Intercom('showNewMessage', defaultTo(message, ''));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isChatSupportEnabled = useFeatureFlags(FeatureKeys.CHAT_SUPPORT)?.active;
|
|
||||||
const isCloudUserVal = isCloudUser();
|
|
||||||
|
|
||||||
return isCloudUserVal && isChatSupportEnabled ? ( // Note: we would need to move this condition to license based in future
|
|
||||||
<div className="facing-issue-button">
|
|
||||||
<Tooltip
|
|
||||||
title={onHoverText}
|
|
||||||
autoAdjustOverflow
|
|
||||||
style={{ padding: 8 }}
|
|
||||||
overlayClassName="tooltip-overlay"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
className={cx('periscope-btn', 'facing-issue-button', className)}
|
|
||||||
onClick={handleFacingIssuesClick}
|
|
||||||
icon={<HelpCircle size={14} />}
|
|
||||||
>
|
|
||||||
{buttonText || 'Facing issues?'}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
FacingIssueBtn.defaultProps = {
|
|
||||||
message: '',
|
|
||||||
buttonText: '',
|
|
||||||
className: '',
|
|
||||||
onHoverText: '',
|
|
||||||
intercomMessageDisabled: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FacingIssueBtn;
|
|
||||||
@@ -3,4 +3,5 @@ export const ENVIRONMENT = {
|
|||||||
process?.env?.FRONTEND_API_ENDPOINT ||
|
process?.env?.FRONTEND_API_ENDPOINT ||
|
||||||
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
|
process?.env?.GITPOD_WORKSPACE_URL?.replace('://', '://8080-') ||
|
||||||
'',
|
'',
|
||||||
|
wsURL: process?.env?.WEBSOCKET_API_ENDPOINT || '',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ export enum FeatureKeys {
|
|||||||
ONBOARDING = 'ONBOARDING',
|
ONBOARDING = 'ONBOARDING',
|
||||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||||
GATEWAY = 'GATEWAY',
|
GATEWAY = 'GATEWAY',
|
||||||
|
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const selectValueDivider = '__';
|
|||||||
|
|
||||||
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
'id' | 'isJSON'
|
'id' | 'isJSON' | 'isIndexed'
|
||||||
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
||||||
|
|
||||||
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
||||||
@@ -71,6 +71,7 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
|||||||
export enum QueryBuilderKeys {
|
export enum QueryBuilderKeys {
|
||||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||||
|
GET_ATTRIBUTE_SUGGESTIONS = 'GET_ATTRIBUTE_SUGGESTIONS',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapOfOperators = {
|
export const mapOfOperators = {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
|
|||||||
value: QueryFunctionsTypes.ABSOLUTE,
|
value: QueryFunctionsTypes.ABSOLUTE,
|
||||||
label: 'Absolute',
|
label: 'Absolute',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: QueryFunctionsTypes.RUNNING_DIFF,
|
||||||
|
label: 'Running Diff',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: QueryFunctionsTypes.LOG_2,
|
value: QueryFunctionsTypes.LOG_2,
|
||||||
label: 'Log2',
|
label: 'Log2',
|
||||||
@@ -103,6 +107,9 @@ export const queryFunctionsTypesConfig: QueryFunctionConfigType = {
|
|||||||
absolute: {
|
absolute: {
|
||||||
showInput: false,
|
showInput: false,
|
||||||
},
|
},
|
||||||
|
runningDiff: {
|
||||||
|
showInput: false,
|
||||||
|
},
|
||||||
log2: {
|
log2: {
|
||||||
showInput: false,
|
showInput: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const userOS = getUserOperatingSystem();
|
|||||||
export const LogsExplorerShortcuts = {
|
export const LogsExplorerShortcuts = {
|
||||||
StageAndRunQuery: 'enter+meta',
|
StageAndRunQuery: 'enter+meta',
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: '/+meta',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsName = {
|
export const LogsExplorerShortcutsName = {
|
||||||
@@ -11,9 +12,11 @@ export const LogsExplorerShortcutsName = {
|
|||||||
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
||||||
}+enter`,
|
}+enter`,
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsDescription = {
|
export const LogsExplorerShortcutsDescription = {
|
||||||
StageAndRunQuery: 'Stage and Run the current query',
|
StageAndRunQuery: 'Stage and Run the current query',
|
||||||
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
||||||
|
ShowAllFilters: 'Toggle all filters in the filters dropdown',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import AlertChannels from 'container/AllAlertChannels';
|
||||||
|
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
|
||||||
|
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
jest.mock('hooks/useFetch', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
payload: allAlertChannels,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const successNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
success: successNotification,
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Alert Channels Settings List page', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<AlertChannels />);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
describe('Should display the Alert Channels page properly', () => {
|
||||||
|
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
|
||||||
|
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if "New Alert Channel" Button is visble ', () => {
|
||||||
|
expect(screen.getByText('button_new_channel')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
|
||||||
|
const helpIcon = screen.getByLabelText('question-circle');
|
||||||
|
|
||||||
|
fireEvent.mouseOver(helpIcon);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltip = screen.getByText('tooltip_notification_channels');
|
||||||
|
expect(tooltip).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Should check if the channels table is properly displayed', () => {
|
||||||
|
it('Should check if the table columns are properly displayed', () => {
|
||||||
|
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('column_channel_action')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the data in the table is displayed properly', () => {
|
||||||
|
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('column_channel_edit')[0]).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('Delete')[0]).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if clicking on Delete displays Success Toast "Channel Deleted Successfully"', async () => {
|
||||||
|
const deleteButton = screen.getAllByRole('button', { name: 'Delete' })[0];
|
||||||
|
expect(deleteButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(deleteButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(successNotification).toBeCalledWith({
|
||||||
|
message: 'Success',
|
||||||
|
description: 'channel_delete_success',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import AlertChannels from 'container/AllAlertChannels';
|
||||||
|
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
|
||||||
|
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
jest.mock('hooks/useFetch', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
payload: allAlertChannels,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const successNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
success: successNotification,
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/useComponentPermission', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => [false]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Alert Channels Settings List page (Normal User)', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<AlertChannels />);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
describe('Should display the Alert Channels page properly', () => {
|
||||||
|
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
|
||||||
|
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if "New Alert Channel" Button is visble and disabled', () => {
|
||||||
|
const newAlertButton = screen.getByRole('button', {
|
||||||
|
name: 'plus button_new_channel',
|
||||||
|
});
|
||||||
|
expect(newAlertButton).toBeInTheDocument();
|
||||||
|
expect(newAlertButton).toBeDisabled();
|
||||||
|
});
|
||||||
|
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
|
||||||
|
const helpIcon = screen.getByLabelText('question-circle');
|
||||||
|
|
||||||
|
fireEvent.mouseOver(helpIcon);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const tooltip = screen.getByText('tooltip_notification_channels');
|
||||||
|
expect(tooltip).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Should check if the channels table is properly displayed', () => {
|
||||||
|
it('Should check if the table columns are properly displayed', () => {
|
||||||
|
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('column_channel_action')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the data in the table is displayed properly', () => {
|
||||||
|
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('column_channel_edit')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Delete')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
|
|
||||||
|
import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||||
|
import { ChannelType } from 'container/CreateAlertChannels/config';
|
||||||
|
import {
|
||||||
|
opsGenieDescriptionDefaultValue,
|
||||||
|
opsGenieMessageDefaultValue,
|
||||||
|
opsGeniePriorityDefaultValue,
|
||||||
|
pagerDutyAdditionalDetailsDefaultValue,
|
||||||
|
pagerDutyDescriptionDefaultVaule,
|
||||||
|
pagerDutySeverityTextDefaultValue,
|
||||||
|
slackDescriptionDefaultValue,
|
||||||
|
slackTitleDefaultValue,
|
||||||
|
} from 'mocks-server/__mockdata__/alerts';
|
||||||
|
import { server } from 'mocks-server/server';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
import { testLabelInputAndHelpValue } from './testUtils';
|
||||||
|
|
||||||
|
const successNotification = jest.fn();
|
||||||
|
const errorNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
success: successNotification,
|
||||||
|
error: errorNotification,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/useFeatureFlag', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
active: true,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Create Alert Channel', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Slack} />);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
it('Should check if the title is "New Notification Channels"', () => {
|
||||||
|
expect(screen.getByText('page_title_create')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_name',
|
||||||
|
testId: 'channel-name-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_send_resolved',
|
||||||
|
testId: 'field-send-resolved-checkbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if channel type label and dropdown are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_type',
|
||||||
|
testId: 'channel-type-select',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Default Channel type (Slack) fields
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
|
||||||
|
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_recipient',
|
||||||
|
testId: 'slack-channel-textbox',
|
||||||
|
helpText: 'slack_channel_help',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_title',
|
||||||
|
testId: 'title-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Title contains template', () => {
|
||||||
|
const titleTextArea = screen.getByTestId('title-textarea');
|
||||||
|
|
||||||
|
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Description label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_description',
|
||||||
|
testId: 'description-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Description contains template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId('description-textarea');
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
|
||||||
|
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_return')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if saving the form without filling the name displays "Something went wrong"', async () => {
|
||||||
|
const saveButton = screen.getByRole('button', {
|
||||||
|
name: 'button_save_channel',
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(errorNotification).toHaveBeenCalledWith({
|
||||||
|
description: 'Something went wrong',
|
||||||
|
message: 'Error',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
|
||||||
|
server.use(
|
||||||
|
rest.post('http://localhost/api/v1/testChannel', (req, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(200),
|
||||||
|
ctx.json({
|
||||||
|
status: 'success',
|
||||||
|
data: 'test alert sent',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const testButton = screen.getByRole('button', {
|
||||||
|
name: 'button_test_channel',
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(testButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(successNotification).toHaveBeenCalledWith({
|
||||||
|
message: 'Success',
|
||||||
|
description: 'channel_test_done',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Should check if clicking on Test button shows "Something went wrong" error message if testing fails', async () => {
|
||||||
|
const testButton = screen.getByRole('button', {
|
||||||
|
name: 'button_test_channel',
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(testButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(errorNotification).toHaveBeenCalledWith({
|
||||||
|
message: 'Error',
|
||||||
|
description: 'channel_test_failed',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
|
||||||
|
describe('Webhook', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
|
||||||
|
expect(screen.getByText('Webhook')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_username',
|
||||||
|
testId: 'webhook-username-textbox',
|
||||||
|
helpText: 'help_webhook_username',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Password label and textbox, and help text are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'Password (optional)',
|
||||||
|
testId: 'webhook-password-textbox',
|
||||||
|
helpText: 'help_webhook_password',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('PagerDuty', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
|
||||||
|
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_routing_key',
|
||||||
|
testId: 'pager-routing-key-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_description',
|
||||||
|
testId: 'pager-description-textarea',
|
||||||
|
helpText: 'help_pager_description',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if the description contains default template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId(
|
||||||
|
'pager-description-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(
|
||||||
|
pagerDutyDescriptionDefaultVaule,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_severity',
|
||||||
|
testId: 'pager-severity-textbox',
|
||||||
|
helpText: 'help_pager_severity',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Severity contains the default template', () => {
|
||||||
|
const severityTextbox = screen.getByTestId('pager-severity-textbox');
|
||||||
|
|
||||||
|
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_details',
|
||||||
|
testId: 'pager-additional-details-textarea',
|
||||||
|
helpText: 'help_pager_details',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Additional Information contains the default template', () => {
|
||||||
|
const detailsTextArea = screen.getByTestId(
|
||||||
|
'pager-additional-details-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_group',
|
||||||
|
testId: 'pager-group-textarea',
|
||||||
|
helpText: 'help_pager_group',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_class',
|
||||||
|
testId: 'pager-class-textarea',
|
||||||
|
helpText: 'help_pager_class',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_client',
|
||||||
|
testId: 'pager-client-textarea',
|
||||||
|
helpText: 'help_pager_client',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
|
||||||
|
const clientTextArea = screen.getByTestId('pager-client-textarea');
|
||||||
|
|
||||||
|
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
|
||||||
|
});
|
||||||
|
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_client_url',
|
||||||
|
testId: 'pager-client-url-textarea',
|
||||||
|
helpText: 'help_pager_client_url',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
|
||||||
|
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
|
||||||
|
|
||||||
|
expect(clientUrlTextArea).toHaveValue(
|
||||||
|
'https://enter-signoz-host-n-port-here/alerts',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Opsgenie', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
|
||||||
|
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if API key label, required, and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_api_key',
|
||||||
|
testId: 'opsgenie-api-key-textbox',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_message',
|
||||||
|
testId: 'opsgenie-message-textarea',
|
||||||
|
helpText: 'help_opsgenie_message',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message contains the default template ', () => {
|
||||||
|
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
|
||||||
|
|
||||||
|
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_description',
|
||||||
|
testId: 'opsgenie-description-textarea',
|
||||||
|
helpText: 'help_opsgenie_description',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId(
|
||||||
|
'opsgenie-description-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(
|
||||||
|
opsGenieDescriptionDefaultValue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_priority',
|
||||||
|
testId: 'opsgenie-priority-textarea',
|
||||||
|
helpText: 'help_opsgenie_priority',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message contains the default template', () => {
|
||||||
|
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
|
||||||
|
|
||||||
|
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Opsgenie', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Email} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Email"', () => {
|
||||||
|
expect(screen.getByText('Email')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_email_to',
|
||||||
|
testId: 'email-to-textbox',
|
||||||
|
helpText: 'help_email_to',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Microsoft Teams', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "msteams"', () => {
|
||||||
|
expect(screen.getByText('msteams')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_title',
|
||||||
|
testId: 'title-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title contains template', () => {
|
||||||
|
const titleTextArea = screen.getByTestId('title-textarea');
|
||||||
|
|
||||||
|
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Description label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_description',
|
||||||
|
testId: 'description-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description contains template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId('description-textarea');
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
/* eslint-disable sonarjs/no-duplicate-string */
|
||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
|
|
||||||
|
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||||
|
import CreateAlertChannels from 'container/CreateAlertChannels';
|
||||||
|
import { ChannelType } from 'container/CreateAlertChannels/config';
|
||||||
|
import {
|
||||||
|
opsGenieDescriptionDefaultValue,
|
||||||
|
opsGenieMessageDefaultValue,
|
||||||
|
opsGeniePriorityDefaultValue,
|
||||||
|
pagerDutyAdditionalDetailsDefaultValue,
|
||||||
|
pagerDutyDescriptionDefaultVaule,
|
||||||
|
pagerDutySeverityTextDefaultValue,
|
||||||
|
slackDescriptionDefaultValue,
|
||||||
|
slackTitleDefaultValue,
|
||||||
|
} from 'mocks-server/__mockdata__/alerts';
|
||||||
|
import { render, screen } from 'tests/test-utils';
|
||||||
|
|
||||||
|
import { testLabelInputAndHelpValue } from './testUtils';
|
||||||
|
|
||||||
|
describe('Create Alert Channel (Normal User)', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Slack} />);
|
||||||
|
});
|
||||||
|
it('Should check if the title is "New Notification Channels"', () => {
|
||||||
|
expect(screen.getByText('page_title_create')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_name',
|
||||||
|
testId: 'channel-name-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_send_resolved',
|
||||||
|
testId: 'field-send-resolved-checkbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if channel type label and dropdown are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_type',
|
||||||
|
testId: 'channel-type-select',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Default Channel type (Slack) fields
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
|
||||||
|
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_recipient',
|
||||||
|
testId: 'slack-channel-textbox',
|
||||||
|
helpText: 'slack_channel_help',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_title',
|
||||||
|
testId: 'title-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Title contains template', () => {
|
||||||
|
const titleTextArea = screen.getByTestId('title-textarea');
|
||||||
|
|
||||||
|
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Description label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_description',
|
||||||
|
testId: 'description-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Description contains template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId('description-textarea');
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
|
||||||
|
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_return')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
|
||||||
|
describe('Webhook', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
|
||||||
|
expect(screen.getByText('Webhook')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_username',
|
||||||
|
testId: 'webhook-username-textbox',
|
||||||
|
helpText: 'help_webhook_username',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Password label and textbox, and help text are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'Password (optional)',
|
||||||
|
testId: 'webhook-password-textbox',
|
||||||
|
helpText: 'help_webhook_password',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('PagerDuty', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
|
||||||
|
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_routing_key',
|
||||||
|
testId: 'pager-routing-key-textbox',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_description',
|
||||||
|
testId: 'pager-description-textarea',
|
||||||
|
helpText: 'help_pager_description',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if the description contains default template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId(
|
||||||
|
'pager-description-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(
|
||||||
|
pagerDutyDescriptionDefaultVaule,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_severity',
|
||||||
|
testId: 'pager-severity-textbox',
|
||||||
|
helpText: 'help_pager_severity',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Severity contains the default template', () => {
|
||||||
|
const severityTextbox = screen.getByTestId('pager-severity-textbox');
|
||||||
|
|
||||||
|
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_details',
|
||||||
|
testId: 'pager-additional-details-textarea',
|
||||||
|
helpText: 'help_pager_details',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Additional Information contains the default template', () => {
|
||||||
|
const detailsTextArea = screen.getByTestId(
|
||||||
|
'pager-additional-details-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
|
||||||
|
});
|
||||||
|
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_group',
|
||||||
|
testId: 'pager-group-textarea',
|
||||||
|
helpText: 'help_pager_group',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_class',
|
||||||
|
testId: 'pager-class-textarea',
|
||||||
|
helpText: 'help_pager_class',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_client',
|
||||||
|
testId: 'pager-client-textarea',
|
||||||
|
helpText: 'help_pager_client',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
|
||||||
|
const clientTextArea = screen.getByTestId('pager-client-textarea');
|
||||||
|
|
||||||
|
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
|
||||||
|
});
|
||||||
|
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_pager_client_url',
|
||||||
|
testId: 'pager-client-url-textarea',
|
||||||
|
helpText: 'help_pager_client_url',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
|
||||||
|
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
|
||||||
|
|
||||||
|
expect(clientUrlTextArea).toHaveValue(
|
||||||
|
'https://enter-signoz-host-n-port-here/alerts',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Opsgenie', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
|
||||||
|
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if API key label, required, and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_api_key',
|
||||||
|
testId: 'opsgenie-api-key-textbox',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_message',
|
||||||
|
testId: 'opsgenie-message-textarea',
|
||||||
|
helpText: 'help_opsgenie_message',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message contains the default template ', () => {
|
||||||
|
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
|
||||||
|
|
||||||
|
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_description',
|
||||||
|
testId: 'opsgenie-description-textarea',
|
||||||
|
helpText: 'help_opsgenie_description',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId(
|
||||||
|
'opsgenie-description-textarea',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(
|
||||||
|
opsGenieDescriptionDefaultValue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_opsgenie_priority',
|
||||||
|
testId: 'opsgenie-priority-textarea',
|
||||||
|
helpText: 'help_opsgenie_priority',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Message contains the default template', () => {
|
||||||
|
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
|
||||||
|
|
||||||
|
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Opsgenie', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.Email} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Email"', () => {
|
||||||
|
expect(screen.getByText('Email')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_email_to',
|
||||||
|
testId: 'email-to-textbox',
|
||||||
|
helpText: 'help_email_to',
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Microsoft Teams', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Microsoft Teams (Supported in Paid Plans Only)"', () => {
|
||||||
|
expect(
|
||||||
|
screen.getByText('Microsoft Teams (Supported in Paid Plans Only)'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the upgrade plan message is shown', () => {
|
||||||
|
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(/This feature is available for paid plans only./),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
const link = screen.getByRole('link', { name: 'Click here' });
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).toHaveAttribute('href', SIGNOZ_UPGRADE_PLAN_URL);
|
||||||
|
expect(screen.getByText(/to Upgrade/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'button_save_channel' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'button_test_channel' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'button_return' }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should check if save and test buttons are disabled', () => {
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'button_save_channel' }),
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'button_test_channel' }),
|
||||||
|
).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import EditAlertChannels from 'container/EditAlertChannels';
|
||||||
|
import {
|
||||||
|
editAlertChannelInitialValue,
|
||||||
|
editSlackDescriptionDefaultValue,
|
||||||
|
slackTitleDefaultValue,
|
||||||
|
} from 'mocks-server/__mockdata__/alerts';
|
||||||
|
import { render, screen } from 'tests/test-utils';
|
||||||
|
|
||||||
|
import { testLabelInputAndHelpValue } from './testUtils';
|
||||||
|
|
||||||
|
const successNotification = jest.fn();
|
||||||
|
const errorNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
success: successNotification,
|
||||||
|
error: errorNotification,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/useFeatureFlag', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: jest.fn().mockImplementation(() => ({
|
||||||
|
active: true,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Should check if the edit alert channel is properly displayed ', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
it('Should check if the title is "Edit Notification Channels"', () => {
|
||||||
|
expect(screen.getByText('page_title_edit')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_name',
|
||||||
|
testId: 'channel-name-textbox',
|
||||||
|
value: 'Dummy-Channel',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should check if Send resolved alerts label and checkbox are displayed properly and the checkbox is checked ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_send_resolved',
|
||||||
|
testId: 'field-send-resolved-checkbox',
|
||||||
|
});
|
||||||
|
expect(screen.getByTestId('field-send-resolved-checkbox')).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if channel type label and dropdown are displayed properly', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_channel_type',
|
||||||
|
testId: 'channel-type-select',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
|
||||||
|
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_webhook_url',
|
||||||
|
testId: 'webhook-url-textbox',
|
||||||
|
value:
|
||||||
|
'https://discord.com/api/webhooks/dummy_webhook_id/dummy_webhook_token/slack',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_recipient',
|
||||||
|
testId: 'slack-channel-textbox',
|
||||||
|
helpText: 'slack_channel_help',
|
||||||
|
value: '#dummy_channel',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_title',
|
||||||
|
testId: 'title-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Title contains template', () => {
|
||||||
|
const titleTextArea = screen.getByTestId('title-textarea');
|
||||||
|
|
||||||
|
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description label and text area are displayed properly ', () => {
|
||||||
|
testLabelInputAndHelpValue({
|
||||||
|
labelText: 'field_slack_description',
|
||||||
|
testId: 'description-textarea',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if Description contains template', () => {
|
||||||
|
const descriptionTextArea = screen.getByTestId('description-textarea');
|
||||||
|
|
||||||
|
expect(descriptionTextArea).toHaveTextContent(
|
||||||
|
editSlackDescriptionDefaultValue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
|
||||||
|
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button_return')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { screen } from 'tests/test-utils';
|
||||||
|
|
||||||
|
export const testLabelInputAndHelpValue = ({
|
||||||
|
labelText,
|
||||||
|
testId,
|
||||||
|
helpText,
|
||||||
|
required = false,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
labelText: string;
|
||||||
|
testId: string;
|
||||||
|
helpText?: string;
|
||||||
|
required?: boolean;
|
||||||
|
value?: string;
|
||||||
|
}): void => {
|
||||||
|
const label = screen.getByText(labelText);
|
||||||
|
expect(label).toBeInTheDocument();
|
||||||
|
|
||||||
|
const input = screen.getByTestId(testId);
|
||||||
|
expect(input).toBeInTheDocument();
|
||||||
|
|
||||||
|
if (helpText !== undefined) {
|
||||||
|
expect(screen.getByText(helpText)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
if (required) {
|
||||||
|
expect(input).toBeRequired();
|
||||||
|
}
|
||||||
|
if (value) {
|
||||||
|
expect(input).toHaveValue(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -24,6 +24,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-support-gateway {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.chat-support-gateway-btn {
|
||||||
|
max-width: 48px;
|
||||||
|
width: 48px;
|
||||||
|
max-height: 48px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: #f25733;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white !important;
|
||||||
|
border-color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-support-gateway-btn-icon {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-credit-card-btn,
|
||||||
|
.cancel-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-text {
|
||||||
|
border-radius: 2px;
|
||||||
|
background: rgba(78, 116, 248, 0.1);
|
||||||
|
padding-right: 4px;
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
color: var(--bg-robin-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-credit-card-modal {
|
||||||
|
.ant-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: none;
|
||||||
|
background: var(--bg-slate-500, #161922);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-credit-card-btn {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.isDarkMode {
|
.isDarkMode {
|
||||||
.app-layout {
|
.app-layout {
|
||||||
.app-content {
|
.app-content {
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
|||||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||||
import getUserVersion from 'api/user/getVersion';
|
import getUserVersion from 'api/user/getVersion';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import SideNav from 'container/SideNav';
|
import SideNav from 'container/SideNav';
|
||||||
import TopNav from 'container/TopNav';
|
import TopNav from 'container/TopNav';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import useFeatureFlags from 'hooks/useFeatureFlag';
|
||||||
import useLicense from 'hooks/useLicense';
|
import useLicense from 'hooks/useLicense';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@@ -49,6 +52,7 @@ import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
|||||||
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
||||||
import { getRouteKey } from './utils';
|
import { getRouteKey } from './utils';
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||||
const { isLoggedIn, user, role } = useSelector<AppState, AppReducer>(
|
const { isLoggedIn, user, role } = useSelector<AppState, AppReducer>(
|
||||||
(state) => state.app,
|
(state) => state.app,
|
||||||
@@ -58,10 +62,19 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
|
|
||||||
const { data: licenseData, isFetching } = useLicense();
|
const { data: licenseData, isFetching } = useLicense();
|
||||||
|
|
||||||
|
const isPremiumChatSupportEnabled =
|
||||||
|
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||||
|
|
||||||
|
const showAddCreditCardModal =
|
||||||
|
!isPremiumChatSupportEnabled &&
|
||||||
|
!licenseData?.payload?.trialConvertedToSubscription;
|
||||||
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { t } = useTranslation(['titles']);
|
const { t } = useTranslation(['titles']);
|
||||||
|
|
||||||
@@ -95,8 +108,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const latestCurrentCounter = useRef(0);
|
const latestCurrentCounter = useRef(0);
|
||||||
const latestVersionCounter = useRef(0);
|
const latestVersionCounter = useRef(0);
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const onCollapse = useCallback(() => {
|
const onCollapse = useCallback(() => {
|
||||||
setCollapsed((collapsed) => !collapsed);
|
setCollapsed((collapsed) => !collapsed);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -331,6 +342,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
</Sentry.ErrorBoundary>
|
</Sentry.ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{showAddCreditCardModal && <ChatSupportGateway />}
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import useErrorNotification from 'hooks/useErrorNotification';
|
|||||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
ConciergeBell,
|
ConciergeBell,
|
||||||
@@ -56,7 +57,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
import { USER_ROLES } from 'types/roles';
|
||||||
|
|
||||||
@@ -120,6 +121,21 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
const handleConditionalQueryModification = useCallback((): string => {
|
||||||
|
if (
|
||||||
|
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP
|
||||||
|
) {
|
||||||
|
return JSON.stringify(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify aggregateOperator to count, as noop is not supported in alerts
|
||||||
|
const modifiedQuery = cloneDeep(query);
|
||||||
|
|
||||||
|
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
|
||||||
|
|
||||||
|
return JSON.stringify(modifiedQuery);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
const onCreateAlertsHandler = useCallback(() => {
|
const onCreateAlertsHandler = useCallback(() => {
|
||||||
if (sourcepage === DataSource.TRACES) {
|
if (sourcepage === DataSource.TRACES) {
|
||||||
logEvent('Traces Explorer: Create alert', {
|
logEvent('Traces Explorer: Create alert', {
|
||||||
@@ -130,13 +146,16 @@ function ExplorerOptions({
|
|||||||
panelType,
|
panelType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifiedQuery = handleConditionalQueryModification();
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||||
JSON.stringify(query),
|
stringifiedQuery,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [history, query]);
|
}, [handleConditionalQueryModification, history]);
|
||||||
|
|
||||||
const onCancel = (value: boolean) => (): void => {
|
const onCancel = (value: boolean) => (): void => {
|
||||||
onModalToggle(value);
|
onModalToggle(value);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ function EmailForm({ setSelectedConfig }: EmailFormProps): JSX.Element {
|
|||||||
<Input
|
<Input
|
||||||
onChange={handleInputChange('to')}
|
onChange={handleInputChange('to')}
|
||||||
placeholder={t('placeholder_email_to')}
|
placeholder={t('placeholder_email_to')}
|
||||||
|
data-testid="email-to-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
|
|||||||
webhook_url: event.target.value,
|
webhook_url: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="webhook-url-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
|
|||||||
title: event.target.value,
|
title: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="title-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
|
|||||||
text: event.target.value,
|
text: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="description-textarea"
|
||||||
placeholder={t('placeholder_slack_description')}
|
placeholder={t('placeholder_slack_description')}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
|
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
|
||||||
<Input onChange={handleInputChange('api_key')} />
|
<Input
|
||||||
|
onChange={handleInputChange('api_key')}
|
||||||
|
data-testid="opsgenie-api-key-textbox"
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -33,6 +36,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
|
|||||||
rows={4}
|
rows={4}
|
||||||
onChange={handleInputChange('message')}
|
onChange={handleInputChange('message')}
|
||||||
placeholder={t('placeholder_opsgenie_message')}
|
placeholder={t('placeholder_opsgenie_message')}
|
||||||
|
data-testid="opsgenie-message-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -46,6 +50,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
|
|||||||
rows={4}
|
rows={4}
|
||||||
onChange={handleInputChange('description')}
|
onChange={handleInputChange('description')}
|
||||||
placeholder={t('placeholder_opsgenie_description')}
|
placeholder={t('placeholder_opsgenie_description')}
|
||||||
|
data-testid="opsgenie-description-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -59,6 +64,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
|
|||||||
rows={4}
|
rows={4}
|
||||||
onChange={handleInputChange('priority')}
|
onChange={handleInputChange('priority')}
|
||||||
placeholder={t('placeholder_opsgenie_priority')}
|
placeholder={t('placeholder_opsgenie_priority')}
|
||||||
|
data-testid="opsgenie-priority-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
routing_key: event.target.value,
|
routing_key: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="pager-routing-key-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
placeholder={t('placeholder_pager_description')}
|
placeholder={t('placeholder_pager_description')}
|
||||||
|
data-testid="pager-description-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -51,6 +53,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
severity: event.target.value,
|
severity: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-severity-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -67,6 +70,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
details: event.target.value,
|
details: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-additional-details-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
group: event.target.value,
|
group: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-group-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -112,6 +117,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
class: event.target.value,
|
class: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-class-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -126,6 +132,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
client: event.target.value,
|
client: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-client-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -141,6 +148,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
|
|||||||
client_url: event.target.value,
|
client_url: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="pager-client-url-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
api_url: event.target.value,
|
api_url: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="webhook-url-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -34,11 +35,13 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
channel: event.target.value,
|
channel: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
data-testid="slack-channel-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="title" label={t('field_slack_title')}>
|
<Form.Item name="title" label={t('field_slack_title')}>
|
||||||
<TextArea
|
<TextArea
|
||||||
|
data-testid="title-textarea"
|
||||||
rows={4}
|
rows={4}
|
||||||
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
|
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
|
||||||
onChange={(event): void =>
|
onChange={(event): void =>
|
||||||
@@ -59,6 +62,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
placeholder={t('placeholder_slack_description')}
|
placeholder={t('placeholder_slack_description')}
|
||||||
|
data-testid="description-textarea"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
api_url: event.target.value,
|
api_url: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="webhook-url-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -31,6 +32,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
username: event.target.value,
|
username: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="webhook-username-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -46,6 +48,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
|||||||
password: event.target.value,
|
password: event.target.value,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
|
data-testid="webhook-password-textbox"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ function FormAlertChannels({
|
|||||||
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
|
||||||
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
|
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
|
||||||
<Input
|
<Input
|
||||||
|
data-testid="channel-name-textbox"
|
||||||
disabled={editing}
|
disabled={editing}
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setSelectedConfig((state) => ({
|
setSelectedConfig((state) => ({
|
||||||
@@ -102,6 +103,7 @@ function FormAlertChannels({
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={initialValue?.send_resolved}
|
defaultChecked={initialValue?.send_resolved}
|
||||||
|
data-testid="field-send-resolved-checkbox"
|
||||||
onChange={(value): void => {
|
onChange={(value): void => {
|
||||||
setSelectedConfig((state) => ({
|
setSelectedConfig((state) => ({
|
||||||
...state,
|
...state,
|
||||||
@@ -112,24 +114,37 @@ function FormAlertChannels({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
|
||||||
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
|
<Select
|
||||||
<Select.Option value="slack" key="slack">
|
disabled={editing}
|
||||||
|
onChange={onTypeChangeHandler}
|
||||||
|
value={type}
|
||||||
|
data-testid="channel-type-select"
|
||||||
|
>
|
||||||
|
<Select.Option value="slack" key="slack" data-testid="select-option">
|
||||||
Slack
|
Slack
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
<Select.Option value="webhook" key="webhook">
|
<Select.Option value="webhook" key="webhook" data-testid="select-option">
|
||||||
Webhook
|
Webhook
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
<Select.Option value="pagerduty" key="pagerduty">
|
<Select.Option
|
||||||
|
value="pagerduty"
|
||||||
|
key="pagerduty"
|
||||||
|
data-testid="select-option"
|
||||||
|
>
|
||||||
Pagerduty
|
Pagerduty
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
<Select.Option value="opsgenie" key="opsgenie">
|
<Select.Option
|
||||||
|
value="opsgenie"
|
||||||
|
key="opsgenie"
|
||||||
|
data-testid="select-option"
|
||||||
|
>
|
||||||
Opsgenie
|
Opsgenie
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
<Select.Option value="email" key="email">
|
<Select.Option value="email" key="email" data-testid="select-option">
|
||||||
Email
|
Email
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
{!isOssFeature?.active && (
|
{!isOssFeature?.active && (
|
||||||
<Select.Option value="msteams" key="msteams">
|
<Select.Option value="msteams" key="msteams" data-testid="select-option">
|
||||||
<div>
|
<div>
|
||||||
Microsoft Teams {!isUserOnEEPlan && '(Supported in Paid Plans Only)'}{' '}
|
Microsoft Teams {!isUserOnEEPlan && '(Supported in Paid Plans Only)'}{' '}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import testAlertApi from 'api/alerts/testAlert';
|
import testAlertApi from 'api/alerts/testAlert';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
import { alertHelpMessage } from 'components/facingIssueBtn/util';
|
import { alertHelpMessage } from 'components/LaunchChatSupport/util';
|
||||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
@@ -712,7 +712,7 @@ function FormAlertRules({
|
|||||||
>
|
>
|
||||||
Check an example alert
|
Check an example alert
|
||||||
</Button>
|
</Button>
|
||||||
<FacingIssueBtn
|
<LaunchChatSupport
|
||||||
attributes={{
|
attributes={{
|
||||||
alert: alertDef?.alert,
|
alert: alertDef?.alert,
|
||||||
alertType: alertDef?.alertType,
|
alertType: alertDef?.alertType,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { ColumnsType } from 'antd/es/table/interface';
|
|||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import DropDown from 'components/DropDown/DropDown';
|
import DropDown from 'components/DropDown/DropDown';
|
||||||
import { listAlertMessage } from 'components/facingIssueBtn/util';
|
import { listAlertMessage } from 'components/LaunchChatSupport/util';
|
||||||
import {
|
import {
|
||||||
DynamicColumnsKey,
|
DynamicColumnsKey,
|
||||||
TableDataSource,
|
TableDataSource,
|
||||||
|
|||||||
@@ -43,10 +43,15 @@
|
|||||||
background: var(--bg-ink-400);
|
background: var(--bg-ink-400);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
.title-with-action {
|
.title-with-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
|
|
||||||
@@ -55,6 +60,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
width: 60%;
|
||||||
|
|
||||||
|
.dashboard-icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
min-height: 6px;
|
min-height: 6px;
|
||||||
@@ -62,6 +75,20 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 20px;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
color: var(--bg-vanilla-100);
|
color: var(--bg-vanilla-100);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
@@ -82,6 +109,45 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tags-with-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 40%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.dashboard-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(173, 127, 88, 0.2);
|
||||||
|
background: rgba(173, 127, 88, 0.1);
|
||||||
|
color: var(--bg-sienna-400);
|
||||||
|
text-align: center;
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
margin-inline-end: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-action-icon {
|
||||||
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-details {
|
.dashboard-details {
|
||||||
@@ -521,35 +587,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-with-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.dashboard-tags {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
display: flex;
|
|
||||||
padding: 4px 8px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 20px;
|
|
||||||
border: 1px solid rgba(173, 127, 88, 0.2);
|
|
||||||
background: rgba(173, 127, 88, 0.1);
|
|
||||||
color: var(--bg-sienna-400);
|
|
||||||
text-align: center;
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-dashboard-menu {
|
.new-dashboard-menu {
|
||||||
@@ -677,13 +714,13 @@
|
|||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 14px;
|
padding: 8px;
|
||||||
height: unset;
|
height: unset;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
color: var(--bg-vanilla-400);
|
color: var(--bg-vanilla-400);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
@@ -702,12 +739,12 @@
|
|||||||
|
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 14px;
|
padding: 12px 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
color: var(--bg-cherry-400) !important;
|
color: var(--bg-cherry-400) !important;
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
@@ -1048,6 +1085,10 @@
|
|||||||
border: 1px solid var(--bg-vanilla-200);
|
border: 1px solid var(--bg-vanilla-200);
|
||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
color: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
|
||||||
.title-with-action {
|
.title-with-action {
|
||||||
.dashboard-title {
|
.dashboard-title {
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
@@ -1313,3 +1354,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-toolip {
|
||||||
|
.ant-tooltip-content {
|
||||||
|
.ant-tooltip-inner {
|
||||||
|
height: 400px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import logEvent from 'api/common/logEvent';
|
|||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
import { dashboardListMessage } from 'components/facingIssueBtn/util';
|
import { dashboardListMessage } from 'components/LaunchChatSupport/util';
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
|
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
|
||||||
@@ -66,7 +66,7 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath, Link } from 'react-router-dom';
|
||||||
import { useCopyToClipboard } from 'react-use';
|
import { useCopyToClipboard } from 'react-use';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||||
@@ -449,77 +449,94 @@ function DashboardsList(): JSX.Element {
|
|||||||
<div className="dashboard-list-item" onClick={onClickHandler}>
|
<div className="dashboard-list-item" onClick={onClickHandler}>
|
||||||
<div className="title-with-action">
|
<div className="title-with-action">
|
||||||
<div className="dashboard-title">
|
<div className="dashboard-title">
|
||||||
<img
|
<Tooltip
|
||||||
src={dashboard?.image || Base64Icons[0]}
|
title={dashboard?.name?.length > 50 ? dashboard?.name : ''}
|
||||||
style={{ height: '14px', width: '14px' }}
|
placement="left"
|
||||||
alt="dashboard-image"
|
overlayClassName="title-toolip"
|
||||||
/>
|
>
|
||||||
<Typography.Text data-testid={`dashboard-title-${index}`}>
|
<Typography.Text data-testid={`dashboard-title-${index}`}>
|
||||||
{dashboard.name}
|
<Link to={getLink()} className="title">
|
||||||
</Typography.Text>
|
<img
|
||||||
|
src={dashboard?.image || Base64Icons[0]}
|
||||||
|
style={{ height: '14px', width: '14px' }}
|
||||||
|
alt="dashboard-image"
|
||||||
|
className="dashboard-icon"
|
||||||
|
/>
|
||||||
|
{dashboard.name}
|
||||||
|
</Link>
|
||||||
|
</Typography.Text>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tags-with-actions">
|
<div className="tags-with-actions">
|
||||||
{dashboard?.tags && dashboard.tags.length > 0 && (
|
{dashboard?.tags && dashboard.tags.length > 0 && (
|
||||||
<div className="dashboard-tags">
|
<div className="dashboard-tags">
|
||||||
{dashboard.tags.map((tag) => (
|
{dashboard.tags.slice(0, 3).map((tag) => (
|
||||||
<Tag className="tag" key={tag}>
|
<Tag className="tag" key={tag}>
|
||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{dashboard.tags.length > 3 && (
|
||||||
|
<Tag className="tag" key={dashboard.tags[3]}>
|
||||||
|
+ <span> {dashboard.tags.length - 3} </span>
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{action && (
|
|
||||||
<Popover
|
|
||||||
trigger="click"
|
|
||||||
content={
|
|
||||||
<div className="dashboard-action-content">
|
|
||||||
<section className="section-1">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
className="action-btn"
|
|
||||||
icon={<Expand size={14} />}
|
|
||||||
onClick={onClickHandler}
|
|
||||||
>
|
|
||||||
View
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
className="action-btn"
|
|
||||||
icon={<Link2 size={14} />}
|
|
||||||
onClick={(e): void => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
setCopy(`${window.location.origin}${getLink()}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy Link
|
|
||||||
</Button>
|
|
||||||
</section>
|
|
||||||
<section className="section-2">
|
|
||||||
<DeleteButton
|
|
||||||
name={dashboard.name}
|
|
||||||
id={dashboard.id}
|
|
||||||
isLocked={dashboard.isLocked}
|
|
||||||
createdBy={dashboard.createdBy}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
placement="bottomRight"
|
|
||||||
arrow={false}
|
|
||||||
rootClassName="dashboard-actions"
|
|
||||||
>
|
|
||||||
<EllipsisVertical
|
|
||||||
size={14}
|
|
||||||
onClick={(e): void => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{action && (
|
||||||
|
<Popover
|
||||||
|
trigger="click"
|
||||||
|
content={
|
||||||
|
<div className="dashboard-action-content">
|
||||||
|
<section className="section-1">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
className="action-btn"
|
||||||
|
icon={<Expand size={12} />}
|
||||||
|
onClick={onClickHandler}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
className="action-btn"
|
||||||
|
icon={<Link2 size={12} />}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
setCopy(`${window.location.origin}${getLink()}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy Link
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section className="section-2">
|
||||||
|
<DeleteButton
|
||||||
|
name={dashboard.name}
|
||||||
|
id={dashboard.id}
|
||||||
|
isLocked={dashboard.isLocked}
|
||||||
|
createdBy={dashboard.createdBy}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
placement="bottomRight"
|
||||||
|
arrow={false}
|
||||||
|
rootClassName="dashboard-actions"
|
||||||
|
>
|
||||||
|
<EllipsisVertical
|
||||||
|
className="dashboard-action-icon"
|
||||||
|
size={14}
|
||||||
|
onClick={(e): void => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="dashboard-details">
|
<div className="dashboard-details">
|
||||||
<div className="dashboard-created-at">
|
<div className="dashboard-created-at">
|
||||||
@@ -647,7 +664,7 @@ function DashboardsList(): JSX.Element {
|
|||||||
<Typography.Text className="subtitle">
|
<Typography.Text className="subtitle">
|
||||||
Create and manage dashboards for your workspace.
|
Create and manage dashboards for your workspace.
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<FacingIssueBtn
|
<LaunchChatSupport
|
||||||
attributes={{
|
attributes={{
|
||||||
screen: 'Dashboard list page',
|
screen: 'Dashboard list page',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||||
|
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { ChevronDown, ChevronRight, Search } from 'lucide-react';
|
import { ChevronDown, ChevronRight, Search } from 'lucide-react';
|
||||||
import { ReactNode, useState } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
@@ -23,6 +25,8 @@ import TableView from './TableView';
|
|||||||
interface OverviewProps {
|
interface OverviewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
selectedOptions: OptionsQuery;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OverviewProps &
|
type Props = OverviewProps &
|
||||||
@@ -34,6 +38,8 @@ function Overview({
|
|||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
selectedOptions,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
const [isWrapWord, setIsWrapWord] = useState<boolean>(true);
|
||||||
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
const [isSearchVisible, setIsSearchVisible] = useState<boolean>(false);
|
||||||
@@ -200,6 +206,8 @@ function Overview({
|
|||||||
fieldSearchInput={fieldSearchInput}
|
fieldSearchInput={fieldSearchInput}
|
||||||
onClickActionItem={onClickActionItem}
|
onClickActionItem={onClickActionItem}
|
||||||
isListViewPanel={isListViewPanel}
|
isListViewPanel={isListViewPanel}
|
||||||
|
selectedOptions={selectedOptions}
|
||||||
|
listViewPanelSelectedFields={listViewPanelSelectedFields}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -213,6 +221,7 @@ function Overview({
|
|||||||
|
|
||||||
Overview.defaultProps = {
|
Overview.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
|
listViewPanelSelectedFields: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Overview;
|
export default Overview;
|
||||||
|
|||||||
@@ -6,17 +6,15 @@ import { LinkOutlined } from '@ant-design/icons';
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd';
|
import { Button, Space, Spin, Tooltip, Tree, Typography } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
|
||||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import AddToQueryHOC, {
|
import AddToQueryHOC, {
|
||||||
AddToQueryHOCProps,
|
AddToQueryHOCProps,
|
||||||
} from 'components/Logs/AddToQueryHOC';
|
} from 'components/Logs/AddToQueryHOC';
|
||||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||||
@@ -29,12 +27,14 @@ import { generatePath } from 'react-router-dom';
|
|||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||||
|
import { IField } from 'types/api/logs/fields';
|
||||||
import { ILog } from 'types/api/logs/log';
|
import { ILog } from 'types/api/logs/log';
|
||||||
|
|
||||||
import { ActionItemProps } from './ActionItem';
|
import { ActionItemProps } from './ActionItem';
|
||||||
import FieldRenderer from './FieldRenderer';
|
import FieldRenderer from './FieldRenderer';
|
||||||
import {
|
import {
|
||||||
filterKeyForField,
|
filterKeyForField,
|
||||||
|
findKeyPath,
|
||||||
flattenObject,
|
flattenObject,
|
||||||
jsonToDataNodes,
|
jsonToDataNodes,
|
||||||
recursiveParseJSON,
|
recursiveParseJSON,
|
||||||
@@ -47,7 +47,9 @@ const RESTRICTED_FIELDS = ['timestamp'];
|
|||||||
interface TableViewProps {
|
interface TableViewProps {
|
||||||
logData: ILog;
|
logData: ILog;
|
||||||
fieldSearchInput: string;
|
fieldSearchInput: string;
|
||||||
|
selectedOptions: OptionsQuery;
|
||||||
isListViewPanel?: boolean;
|
isListViewPanel?: boolean;
|
||||||
|
listViewPanelSelectedFields?: IField[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = TableViewProps &
|
type Props = TableViewProps &
|
||||||
@@ -60,6 +62,8 @@ function TableView({
|
|||||||
onAddToQuery,
|
onAddToQuery,
|
||||||
onClickActionItem,
|
onClickActionItem,
|
||||||
isListViewPanel = false,
|
isListViewPanel = false,
|
||||||
|
selectedOptions,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
}: Props): JSX.Element | null {
|
}: Props): JSX.Element | null {
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
const [isfilterInLoading, setIsFilterInLoading] = useState<boolean>(false);
|
||||||
@@ -71,21 +75,31 @@ function TableView({
|
|||||||
>({});
|
>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pinnedAttributesFromLocalStorage = getLocalStorageApi(
|
const pinnedAttributes: Record<string, boolean> = {};
|
||||||
LOCALSTORAGE.PINNED_ATTRIBUTES,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pinnedAttributesFromLocalStorage) {
|
if (isListViewPanel) {
|
||||||
try {
|
listViewPanelSelectedFields?.forEach((val) => {
|
||||||
const parsedPinnedAttributes = JSON.parse(pinnedAttributesFromLocalStorage);
|
const path = findKeyPath(logData, val.name, '');
|
||||||
setPinnedAttributes(parsedPinnedAttributes);
|
if (path) {
|
||||||
} catch (e) {
|
pinnedAttributes[path] = true;
|
||||||
console.error('Error parsing pinned attributes from local storgage');
|
}
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
setPinnedAttributes({});
|
selectedOptions.selectColumns.forEach((val) => {
|
||||||
|
const path = findKeyPath(logData, val.key, '');
|
||||||
|
if (path) {
|
||||||
|
pinnedAttributes[path] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
|
||||||
|
setPinnedAttributes(pinnedAttributes);
|
||||||
|
}, [
|
||||||
|
logData,
|
||||||
|
selectedOptions.selectColumns,
|
||||||
|
listViewPanelSelectedFields,
|
||||||
|
isListViewPanel,
|
||||||
|
]);
|
||||||
|
|
||||||
const flattenLogData: Record<string, string> | null = useMemo(
|
const flattenLogData: Record<string, string> | null = useMemo(
|
||||||
() => (logData ? flattenObject(logData) : null),
|
() => (logData ? flattenObject(logData) : null),
|
||||||
@@ -103,19 +117,6 @@ function TableView({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePinAttribute = (record: DataType): void => {
|
|
||||||
if (record) {
|
|
||||||
const newPinnedAttributes = { ...pinnedAttributes };
|
|
||||||
newPinnedAttributes[record.key] = !newPinnedAttributes[record.key];
|
|
||||||
setPinnedAttributes(newPinnedAttributes);
|
|
||||||
|
|
||||||
setLocalStorageApi(
|
|
||||||
LOCALSTORAGE.PINNED_ATTRIBUTES,
|
|
||||||
JSON.stringify(newPinnedAttributes),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickHandler = (
|
const onClickHandler = (
|
||||||
operator: string,
|
operator: string,
|
||||||
fieldKey: string,
|
fieldKey: string,
|
||||||
@@ -201,11 +202,8 @@ function TableView({
|
|||||||
'pin-attribute-icon',
|
'pin-attribute-icon',
|
||||||
pinnedAttributes[record?.key] ? 'pinned' : '',
|
pinnedAttributes[record?.key] ? 'pinned' : '',
|
||||||
)}
|
)}
|
||||||
onClick={(): void => {
|
|
||||||
togglePinAttribute(record);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Pin size={14} color={pinColor} />
|
{pinnedAttributes[record?.key] && <Pin size={14} color={pinColor} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -380,6 +378,7 @@ function TableView({
|
|||||||
|
|
||||||
TableView.defaultProps = {
|
TableView.defaultProps = {
|
||||||
isListViewPanel: false,
|
isListViewPanel: false,
|
||||||
|
listViewPanelSelectedFields: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DataType {
|
interface DataType {
|
||||||
|
|||||||
@@ -267,3 +267,27 @@ export const removeEscapeCharacters = (str: string): string =>
|
|||||||
export function removeExtraSpaces(input: string): string {
|
export function removeExtraSpaces(input: string): string {
|
||||||
return input.replace(/\s+/g, ' ').trim();
|
return input.replace(/\s+/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findKeyPath(
|
||||||
|
obj: AnyObject,
|
||||||
|
targetKey: string,
|
||||||
|
currentPath = '',
|
||||||
|
): string | null {
|
||||||
|
let finalPath = null;
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const value = obj[key];
|
||||||
|
const newPath = currentPath ? `${currentPath}.${key}` : key;
|
||||||
|
|
||||||
|
if (key === targetKey) {
|
||||||
|
finalPath = newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const result = findKeyPath(value, targetKey, newPath);
|
||||||
|
if (result) {
|
||||||
|
finalPath = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return finalPath;
|
||||||
|
}
|
||||||
|
|||||||
112
frontend/src/container/Login/__tests__/Login.test.tsx
Normal file
112
frontend/src/container/Login/__tests__/Login.test.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import Login from 'container/Login';
|
||||||
|
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
const errorNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
error: errorNotification,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Login Flow', () => {
|
||||||
|
test('Login form is rendered correctly', async () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="" />);
|
||||||
|
|
||||||
|
const headingElement = screen.getByRole('heading', {
|
||||||
|
name: 'login_page_title',
|
||||||
|
});
|
||||||
|
expect(headingElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
const textboxElement = screen.getByRole('textbox');
|
||||||
|
expect(textboxElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
const buttonElement = screen.getByRole('button', {
|
||||||
|
name: 'button_initiate_login',
|
||||||
|
});
|
||||||
|
expect(buttonElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
const noAccountPromptElement = screen.getByText('prompt_no_account');
|
||||||
|
expect(noAccountPromptElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`Display "invalid_email" if email is not provided`, async () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="" />);
|
||||||
|
|
||||||
|
const buttonElement = screen.getByText('button_initiate_login');
|
||||||
|
fireEvent.click(buttonElement);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(errorNotification).toHaveBeenCalledWith({
|
||||||
|
message: 'invalid_email',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Display invalid_config if invalid email is provided and next clicked', async () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="" />);
|
||||||
|
|
||||||
|
const textboxElement = screen.getByRole('textbox');
|
||||||
|
fireEvent.change(textboxElement, {
|
||||||
|
target: { value: 'failEmail@signoz.io' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const buttonElement = screen.getByRole('button', {
|
||||||
|
name: 'button_initiate_login',
|
||||||
|
});
|
||||||
|
fireEvent.click(buttonElement);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(errorNotification).toHaveBeenCalledWith({
|
||||||
|
message: 'invalid_config',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('providing shaheer@signoz.io as email and pressing next, should make the login_with_sso button visible', async () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="" />);
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(screen.getByTestId('email'), {
|
||||||
|
target: { value: 'shaheer@signoz.io' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('initiate_login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('login_with_sso')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Display email, password, forgot password if password=Y', () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="Y" />);
|
||||||
|
|
||||||
|
const emailTextBox = screen.getByTestId('email');
|
||||||
|
expect(emailTextBox).toBeInTheDocument();
|
||||||
|
|
||||||
|
const passwordTextBox = screen.getByTestId('password');
|
||||||
|
expect(passwordTextBox).toBeInTheDocument();
|
||||||
|
|
||||||
|
const forgotPasswordLink = screen.getByText('forgot_password');
|
||||||
|
expect(forgotPasswordLink).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Display tooltip with "prompt_forgot_password" if forgot password is clicked while password=Y', async () => {
|
||||||
|
render(<Login ssoerror="" jwt="" refreshjwt="" userId="" withPassword="Y" />);
|
||||||
|
const forgotPasswordLink = screen.getByText('forgot_password');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.mouseOver(forgotPasswordLink);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const forgotPasswordTooltip = screen.getByRole('tooltip', {
|
||||||
|
name: 'prompt_forgot_password',
|
||||||
|
});
|
||||||
|
expect(forgotPasswordLink).toBeInTheDocument();
|
||||||
|
expect(forgotPasswordTooltip).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -220,6 +220,7 @@ function Login({
|
|||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
id="loginEmail"
|
id="loginEmail"
|
||||||
|
data-testid="email"
|
||||||
required
|
required
|
||||||
placeholder={t('placeholder_email')}
|
placeholder={t('placeholder_email')}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -231,7 +232,12 @@ function Login({
|
|||||||
<ParentContainer>
|
<ParentContainer>
|
||||||
<Label htmlFor="Password">{t('label_password')}</Label>
|
<Label htmlFor="Password">{t('label_password')}</Label>
|
||||||
<FormContainer.Item name="password">
|
<FormContainer.Item name="password">
|
||||||
<Input.Password required id="currentPassword" disabled={isLoading} />
|
<Input.Password
|
||||||
|
required
|
||||||
|
id="currentPassword"
|
||||||
|
data-testid="password"
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
</FormContainer.Item>
|
</FormContainer.Item>
|
||||||
<Tooltip title={t('prompt_forgot_password')}>
|
<Tooltip title={t('prompt_forgot_password')}>
|
||||||
<Typography.Link>{t('forgot_password')}</Typography.Link>
|
<Typography.Link>{t('forgot_password')}</Typography.Link>
|
||||||
@@ -250,6 +256,7 @@ function Login({
|
|||||||
loading={precheckInProcess}
|
loading={precheckInProcess}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onNextHandler}
|
onClick={onNextHandler}
|
||||||
|
data-testid="initiate_login"
|
||||||
>
|
>
|
||||||
{t('button_initiate_login')}
|
{t('button_initiate_login')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -80,6 +80,36 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.query-stats {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
.rows {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 150% */
|
||||||
|
letter-spacing: 0.36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
background: #242834;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: 'Geist Mono';
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 150% */
|
||||||
|
letter-spacing: 0.36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-actions-container {
|
.logs-actions-container {
|
||||||
@@ -149,6 +179,15 @@
|
|||||||
background: var(--bg-robin-400);
|
background: var(--bg-robin-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.query-stats {
|
||||||
|
.rows {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.query-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
42
frontend/src/container/LogsExplorerViews/QueryStatus.tsx
Normal file
42
frontend/src/container/LogsExplorerViews/QueryStatus.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import './QueryStatus.styles.scss';
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import { CircleCheck } from 'lucide-react';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
interface IQueryStatusProps {
|
||||||
|
loading: boolean;
|
||||||
|
error: boolean;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueryStatus(
|
||||||
|
props: IQueryStatusProps,
|
||||||
|
): React.ReactElement {
|
||||||
|
const { loading, error, success } = props;
|
||||||
|
|
||||||
|
const content = useMemo((): React.ReactElement => {
|
||||||
|
if (loading) {
|
||||||
|
return <Spin spinning size="small" indicator={<LoadingOutlined spin />} />;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="/Icons/solid-x-circle.svg"
|
||||||
|
alt="header"
|
||||||
|
className="error"
|
||||||
|
style={{ height: '14px', width: '14px' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<CircleCheck className="success" size={14} fill={Color.BG_ROBIN_500} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div />;
|
||||||
|
}, [error, loading, success]);
|
||||||
|
return <div className="query-status">{content}</div>;
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import './LogsExplorerViews.styles.scss';
|
import './LogsExplorerViews.styles.scss';
|
||||||
|
|
||||||
import { Button } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
|
import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||||
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
@@ -48,7 +50,15 @@ import {
|
|||||||
} from 'lodash-es';
|
} from 'lodash-es';
|
||||||
import { Sliders } from 'lucide-react';
|
import { Sliders } from 'lucide-react';
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
MutableRefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@@ -69,12 +79,20 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import QueryStatus from './QueryStatus';
|
||||||
|
|
||||||
function LogsExplorerViews({
|
function LogsExplorerViews({
|
||||||
selectedView,
|
selectedView,
|
||||||
showFrequencyChart,
|
showFrequencyChart,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
listQueryKeyRef,
|
||||||
|
chartQueryKeyRef,
|
||||||
}: {
|
}: {
|
||||||
selectedView: SELECTED_VIEWS;
|
selectedView: SELECTED_VIEWS;
|
||||||
showFrequencyChart: boolean;
|
showFrequencyChart: boolean;
|
||||||
|
setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
listQueryKeyRef: MutableRefObject<any>;
|
||||||
|
chartQueryKeyRef: MutableRefObject<any>;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -116,6 +134,8 @@ function LogsExplorerViews({
|
|||||||
const [logs, setLogs] = useState<ILog[]>([]);
|
const [logs, setLogs] = useState<ILog[]>([]);
|
||||||
const [requestData, setRequestData] = useState<Query | null>(null);
|
const [requestData, setRequestData] = useState<Query | null>(null);
|
||||||
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
|
const [showFormatMenuItems, setShowFormatMenuItems] = useState(false);
|
||||||
|
const [queryId, setQueryId] = useState<string>(v4());
|
||||||
|
const [queryStats, setQueryStats] = useState<WsDataEvent>();
|
||||||
|
|
||||||
const handleAxisError = useAxiosError();
|
const handleAxisError = useAxiosError();
|
||||||
|
|
||||||
@@ -214,9 +234,18 @@ function LogsExplorerViews({
|
|||||||
{
|
{
|
||||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
chartQueryKeyRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
isSuccess,
|
||||||
|
} = useGetExplorerQueryRange(
|
||||||
requestData,
|
requestData,
|
||||||
panelType,
|
panelType,
|
||||||
DEFAULT_ENTITY_VERSION,
|
DEFAULT_ENTITY_VERSION,
|
||||||
@@ -232,6 +261,11 @@ function LogsExplorerViews({
|
|||||||
end: timeRange.end,
|
end: timeRange.end,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
undefined,
|
||||||
|
listQueryKeyRef,
|
||||||
|
{
|
||||||
|
...(!isEmpty(queryId) && { 'X-SIGNOZ-QUERY-ID': queryId }),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRequestData = useCallback(
|
const getRequestData = useCallback(
|
||||||
@@ -318,6 +352,23 @@ function LogsExplorerViews({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setQueryId(v4());
|
||||||
|
}, [isError, isSuccess]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isEmpty(queryId) &&
|
||||||
|
(isLoading || isFetching) &&
|
||||||
|
selectedPanelType !== PANEL_TYPES.LIST
|
||||||
|
) {
|
||||||
|
setQueryStats(undefined);
|
||||||
|
setTimeout(() => {
|
||||||
|
getQueryStats({ queryId, setData: setQueryStats });
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}, [queryId, isLoading, isFetching, selectedPanelType]);
|
||||||
|
|
||||||
const logEventCalledRef = useRef(false);
|
const logEventCalledRef = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
||||||
@@ -569,6 +620,25 @@ function LogsExplorerViews({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isLoading ||
|
||||||
|
isFetching ||
|
||||||
|
isLoadingListChartData ||
|
||||||
|
isFetchingListChartData
|
||||||
|
) {
|
||||||
|
setIsLoadingQueries(true);
|
||||||
|
} else {
|
||||||
|
setIsLoadingQueries(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isFetchingListChartData,
|
||||||
|
isLoadingListChartData,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
]);
|
||||||
|
|
||||||
const flattenLogData = useMemo(
|
const flattenLogData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
@@ -665,6 +735,30 @@ function LogsExplorerViews({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
|
||||||
|
selectedPanelType === PANEL_TYPES.TABLE) && (
|
||||||
|
<div className="query-stats">
|
||||||
|
<QueryStatus
|
||||||
|
loading={isLoading || isFetching}
|
||||||
|
error={isError}
|
||||||
|
success={isSuccess}
|
||||||
|
/>
|
||||||
|
{queryStats?.read_rows && (
|
||||||
|
<Typography.Text className="rows">
|
||||||
|
{getYAxisFormattedValue(queryStats.read_rows?.toString(), 'short')}{' '}
|
||||||
|
rows
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
|
{queryStats?.elapsed_ms && (
|
||||||
|
<>
|
||||||
|
<div className="divider" />
|
||||||
|
<Typography.Text className="time">
|
||||||
|
{getYAxisFormattedValue(queryStats?.elapsed_ms?.toString(), 'ms')}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ jest.mock(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
jest.mock('api/common/getQueryStats', () => jest.fn());
|
||||||
|
|
||||||
jest.mock('constants/panelTypes', () => ({
|
jest.mock('constants/panelTypes', () => ({
|
||||||
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
AVAILABLE_EXPORT_PANEL_TYPES: ['graph', 'table'],
|
||||||
}));
|
}));
|
||||||
@@ -79,6 +81,9 @@ const renderer = (): RenderResult =>
|
|||||||
<LogsExplorerViews
|
<LogsExplorerViews
|
||||||
selectedView={SELECTED_VIEWS.SEARCH}
|
selectedView={SELECTED_VIEWS.SEARCH}
|
||||||
showFrequencyChart
|
showFrequencyChart
|
||||||
|
setIsLoadingQueries={(): void => {}}
|
||||||
|
listQueryKeyRef={{ current: {} }}
|
||||||
|
chartQueryKeyRef={{ current: {} }}
|
||||||
/>
|
/>
|
||||||
</VirtuosoMockContext.Provider>
|
</VirtuosoMockContext.Provider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ function LogsPanelComponent({
|
|||||||
onAddToQuery={onAddToQuery}
|
onAddToQuery={onAddToQuery}
|
||||||
onClickActionItem={onAddToQuery}
|
onClickActionItem={onAddToQuery}
|
||||||
isListViewPanel
|
isListViewPanel
|
||||||
|
listViewPanelSelectedFields={widget?.selectedLogFields}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -90,18 +90,23 @@ function PasswordContainer(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Space direction="vertical" size="small">
|
<Space direction="vertical" size="small">
|
||||||
<Typography.Title level={4} style={{ marginTop: 0 }}>
|
<Typography.Title
|
||||||
|
level={4}
|
||||||
|
style={{ marginTop: 0 }}
|
||||||
|
data-testid="change-password-header"
|
||||||
|
>
|
||||||
{t('change_password', {
|
{t('change_password', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Space direction="vertical">
|
<Space direction="vertical">
|
||||||
<Typography>
|
<Typography data-testid="current-password-label">
|
||||||
{t('current_password', {
|
{t('current_password', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Password
|
<Password
|
||||||
|
data-testid="current-password-textbox"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
placeholder={defaultPlaceHolder}
|
placeholder={defaultPlaceHolder}
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
@@ -111,12 +116,13 @@ function PasswordContainer(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
<Space direction="vertical">
|
<Space direction="vertical">
|
||||||
<Typography>
|
<Typography data-testid="new-password-label">
|
||||||
{t('new_password', {
|
{t('new_password', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Password
|
<Password
|
||||||
|
data-testid="new-password-textbox"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
placeholder={defaultPlaceHolder}
|
placeholder={defaultPlaceHolder}
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
@@ -129,6 +135,7 @@ function PasswordContainer(): JSX.Element {
|
|||||||
<Space>
|
<Space>
|
||||||
{isPasswordPolicyError && (
|
{isPasswordPolicyError && (
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
|
data-testid="validation-message"
|
||||||
style={{
|
style={{
|
||||||
color: '#D89614',
|
color: '#D89614',
|
||||||
marginTop: '0.50rem',
|
marginTop: '0.50rem',
|
||||||
@@ -143,8 +150,13 @@ function PasswordContainer(): JSX.Element {
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={onChangePasswordClickHandler}
|
onClick={onChangePasswordClickHandler}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
data-testid="update-password-button"
|
||||||
>
|
>
|
||||||
<Save size={12} style={{ marginRight: '8px' }} />{' '}
|
<Save
|
||||||
|
size={12}
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
data-testid="update-password-icon"
|
||||||
|
/>{' '}
|
||||||
{t('change_password', {
|
{t('change_password', {
|
||||||
ns: 'settings',
|
ns: 'settings',
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -86,8 +86,11 @@ function UserInfo(): JSX.Element {
|
|||||||
|
|
||||||
<Flex gap={16}>
|
<Flex gap={16}>
|
||||||
<Space>
|
<Space>
|
||||||
<Typography className="userInfo-label">Name</Typography>
|
<Typography className="userInfo-label" data-testid="name-label">
|
||||||
|
Name
|
||||||
|
</Typography>
|
||||||
<NameInput
|
<NameInput
|
||||||
|
data-testid="name-textbox"
|
||||||
placeholder="Your Name"
|
placeholder="Your Name"
|
||||||
onChange={(event): void => {
|
onChange={(event): void => {
|
||||||
setChangedName(event.target.value);
|
setChangedName(event.target.value);
|
||||||
@@ -102,6 +105,7 @@ function UserInfo(): JSX.Element {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={onClickUpdateHandler}
|
onClick={onClickUpdateHandler}
|
||||||
|
data-testid="update-name-button"
|
||||||
type="primary"
|
type="primary"
|
||||||
>
|
>
|
||||||
<PencilIcon size={12} /> Update
|
<PencilIcon size={12} /> Update
|
||||||
@@ -109,13 +113,29 @@ function UserInfo(): JSX.Element {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
<Typography className="userInfo-label"> Email </Typography>
|
<Typography className="userInfo-label" data-testid="email-label">
|
||||||
<Input className="userInfo-value" value={user.email} disabled />
|
{' '}
|
||||||
|
Email{' '}
|
||||||
|
</Typography>
|
||||||
|
<Input
|
||||||
|
className="userInfo-value"
|
||||||
|
data-testid="email-textbox"
|
||||||
|
value={user.email}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
<Typography className="userInfo-label"> Role </Typography>
|
<Typography className="userInfo-label" data-testid="role-label">
|
||||||
<Input className="userInfo-value" value={role || ''} disabled />
|
{' '}
|
||||||
|
Role{' '}
|
||||||
|
</Typography>
|
||||||
|
<Input
|
||||||
|
className="userInfo-value"
|
||||||
|
value={role || ''}
|
||||||
|
disabled
|
||||||
|
data-testid="role-textbox"
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
219
frontend/src/container/MySettings/__tests__/MySettings.test.tsx
Normal file
219
frontend/src/container/MySettings/__tests__/MySettings.test.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import MySettingsContainer from 'container/MySettings';
|
||||||
|
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||||
|
|
||||||
|
const toggleThemeFunction = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('hooks/useDarkMode', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useIsDarkMode: jest.fn(() => ({
|
||||||
|
toggleTheme: toggleThemeFunction,
|
||||||
|
})),
|
||||||
|
default: jest.fn(() => ({
|
||||||
|
toggleTheme: toggleThemeFunction,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const errorNotification = jest.fn();
|
||||||
|
const successNotification = jest.fn();
|
||||||
|
jest.mock('hooks/useNotifications', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useNotifications: jest.fn(() => ({
|
||||||
|
notifications: {
|
||||||
|
error: errorNotification,
|
||||||
|
success: successNotification,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
enum ThemeOptions {
|
||||||
|
Dark = 'Dark',
|
||||||
|
Light = 'Light Beta',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MySettings Flows', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
render(<MySettingsContainer />);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Dark/Light Theme Switch', () => {
|
||||||
|
it('Should display Dark and Light theme buttons properly', async () => {
|
||||||
|
expect(screen.getByText('Dark')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const darkThemeIcon = screen.getByTestId('dark-theme-icon');
|
||||||
|
expect(darkThemeIcon).toBeInTheDocument();
|
||||||
|
expect(darkThemeIcon.tagName).toBe('svg');
|
||||||
|
|
||||||
|
expect(screen.getByText('Light')).toBeInTheDocument();
|
||||||
|
const lightThemeIcon = screen.getByTestId('light-theme-icon');
|
||||||
|
expect(lightThemeIcon).toBeInTheDocument();
|
||||||
|
expect(lightThemeIcon.tagName).toBe('svg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should activate Dark and Light buttons on click', async () => {
|
||||||
|
const initialSelectedOption = screen.getByRole('radio', {
|
||||||
|
name: ThemeOptions.Dark,
|
||||||
|
});
|
||||||
|
expect(initialSelectedOption).toBeChecked();
|
||||||
|
|
||||||
|
const newThemeOption = screen.getByRole('radio', {
|
||||||
|
name: ThemeOptions.Light,
|
||||||
|
});
|
||||||
|
fireEvent.click(newThemeOption);
|
||||||
|
|
||||||
|
expect(newThemeOption).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should switch the them on clicking Light theme', async () => {
|
||||||
|
const lightThemeOption = screen.getByRole('radio', {
|
||||||
|
name: /light/i,
|
||||||
|
});
|
||||||
|
fireEvent.click(lightThemeOption);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(toggleThemeFunction).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('User Details Form', () => {
|
||||||
|
it('Should properly display the User Details Form', () => {
|
||||||
|
const userDetailsHeader = screen.getByRole('heading', {
|
||||||
|
name: /user details/i,
|
||||||
|
});
|
||||||
|
const nameLabel = screen.getByTestId('name-label');
|
||||||
|
const nameTextbox = screen.getByTestId('name-textbox');
|
||||||
|
const updateNameButton = screen.getByTestId('update-name-button');
|
||||||
|
const emailLabel = screen.getByTestId('email-label');
|
||||||
|
const emailTextbox = screen.getByTestId('email-textbox');
|
||||||
|
const roleLabel = screen.getByTestId('role-label');
|
||||||
|
const roleTextbox = screen.getByTestId('role-textbox');
|
||||||
|
|
||||||
|
expect(userDetailsHeader).toBeInTheDocument();
|
||||||
|
expect(nameLabel).toBeInTheDocument();
|
||||||
|
expect(nameTextbox).toBeInTheDocument();
|
||||||
|
expect(updateNameButton).toBeInTheDocument();
|
||||||
|
expect(emailLabel).toBeInTheDocument();
|
||||||
|
expect(emailTextbox).toBeInTheDocument();
|
||||||
|
expect(roleLabel).toBeInTheDocument();
|
||||||
|
expect(roleTextbox).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should update the name on clicking Update button', async () => {
|
||||||
|
const nameTextbox = screen.getByTestId('name-textbox');
|
||||||
|
const updateNameButton = screen.getByTestId('update-name-button');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(nameTextbox, { target: { value: 'New Name' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(updateNameButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(successNotification).toHaveBeenCalledWith({
|
||||||
|
message: 'success',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Reset password', () => {
|
||||||
|
let currentPasswordTextbox: Node | Window;
|
||||||
|
let newPasswordTextbox: Node | Window;
|
||||||
|
let submitButtonElement: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
currentPasswordTextbox = screen.getByTestId('current-password-textbox');
|
||||||
|
newPasswordTextbox = screen.getByTestId('new-password-textbox');
|
||||||
|
submitButtonElement = screen.getByTestId('update-password-button');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should properly display the Password Reset Form', () => {
|
||||||
|
const passwordResetHeader = screen.getByTestId('change-password-header');
|
||||||
|
expect(passwordResetHeader).toBeInTheDocument();
|
||||||
|
|
||||||
|
const currentPasswordLabel = screen.getByTestId('current-password-label');
|
||||||
|
expect(currentPasswordLabel).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(currentPasswordTextbox).toBeInTheDocument();
|
||||||
|
|
||||||
|
const newPasswordLabel = screen.getByTestId('new-password-label');
|
||||||
|
expect(newPasswordLabel).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(newPasswordTextbox).toBeInTheDocument();
|
||||||
|
expect(submitButtonElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
const savePasswordIcon = screen.getByTestId('update-password-icon');
|
||||||
|
expect(savePasswordIcon).toBeInTheDocument();
|
||||||
|
expect(savePasswordIcon.tagName).toBe('svg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should display validation error if password is less than 8 characters', async () => {
|
||||||
|
const currentPasswordTextbox = screen.getByTestId(
|
||||||
|
'current-password-textbox',
|
||||||
|
);
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(currentPasswordTextbox, { target: { value: '123' } });
|
||||||
|
});
|
||||||
|
const validationMessage = await screen.findByTestId('validation-message');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(validationMessage).toHaveTextContent(
|
||||||
|
'Password must a have minimum of 8 characters',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should display 'inavlid credentials' error if different current and new passwords are provided", async () => {
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(currentPasswordTextbox, {
|
||||||
|
target: { value: '123456879' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.change(newPasswordTextbox, { target: { value: '123456789' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(submitButtonElement);
|
||||||
|
|
||||||
|
await waitFor(() => expect(errorNotification).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should check if the "Change Password" button is disabled in case current / new password is less than 8 characters', () => {
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(currentPasswordTextbox, {
|
||||||
|
target: { value: '123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(newPasswordTextbox, { target: { value: '123' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(submitButtonElement).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should check if 'Change Password' button is enabled when password is at least 8 characters ", async () => {
|
||||||
|
expect(submitButtonElement).toBeDisabled();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(currentPasswordTextbox, {
|
||||||
|
target: { value: '123456789' },
|
||||||
|
});
|
||||||
|
fireEvent.change(newPasswordTextbox, { target: { value: '1234567890' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(submitButtonElement).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Should check if 'Change Password' button is disabled when current and new passwords are the same ", async () => {
|
||||||
|
expect(submitButtonElement).toBeDisabled();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(currentPasswordTextbox, {
|
||||||
|
target: { value: '123456789' },
|
||||||
|
});
|
||||||
|
fireEvent.change(newPasswordTextbox, { target: { value: '123456789' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(submitButtonElement).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import './MySettings.styles.scss';
|
import './MySettings.styles.scss';
|
||||||
|
|
||||||
import { Button, Radio, RadioChangeEvent, Space, Typography } from 'antd';
|
import { Button, Radio, RadioChangeEvent, Space, Tag, Typography } from 'antd';
|
||||||
import { Logout } from 'api/utils';
|
import { Logout } from 'api/utils';
|
||||||
import useThemeMode, { useIsDarkMode } from 'hooks/useDarkMode';
|
import useThemeMode, { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { LogOut, Moon, Sun } from 'lucide-react';
|
import { LogOut, Moon, Sun } from 'lucide-react';
|
||||||
@@ -17,7 +17,7 @@ function MySettings(): JSX.Element {
|
|||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<div className="theme-option">
|
<div className="theme-option">
|
||||||
<Moon size={12} /> Dark{' '}
|
<Moon data-testid="dark-theme-icon" size={12} /> Dark{' '}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
value: 'dark',
|
value: 'dark',
|
||||||
@@ -25,7 +25,8 @@ function MySettings(): JSX.Element {
|
|||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<div className="theme-option">
|
<div className="theme-option">
|
||||||
<Sun size={12} /> Light{' '}
|
<Sun size={12} data-testid="light-theme-icon" /> Light{' '}
|
||||||
|
<Tag color="magenta">Beta</Tag>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
value: 'light',
|
value: 'light',
|
||||||
@@ -63,6 +64,7 @@ function MySettings(): JSX.Element {
|
|||||||
value={theme}
|
value={theme}
|
||||||
optionType="button"
|
optionType="button"
|
||||||
buttonStyle="solid"
|
buttonStyle="solid"
|
||||||
|
data-testid="theme-selector"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -74,7 +76,12 @@ function MySettings(): JSX.Element {
|
|||||||
<Password />
|
<Password />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="flexBtn" onClick={(): void => Logout()} type="primary">
|
<Button
|
||||||
|
className="flexBtn"
|
||||||
|
onClick={(): void => Logout()}
|
||||||
|
type="primary"
|
||||||
|
data-testid="logout-button"
|
||||||
|
>
|
||||||
<LogOut size={12} /> Logout
|
<LogOut size={12} /> Logout
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -59,13 +59,16 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.dashboard-breadcrumbs {
|
.dashboard-breadcrumbs {
|
||||||
|
width: 100%;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
.dashboard-btn {
|
.dashboard-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -99,6 +102,14 @@
|
|||||||
line-height: 20px; /* 142.857% */
|
line-height: 20px; /* 142.857% */
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
||||||
|
max-width: calc(100% - 120px);
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-btn-icon {
|
.ant-btn-icon {
|
||||||
margin-inline-end: 4px;
|
margin-inline-end: 4px;
|
||||||
}
|
}
|
||||||
@@ -110,14 +121,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashbord-details {
|
.dashboard-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 16px 0px 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.left-section {
|
.left-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px 0px 0px 16px;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: 45%;
|
||||||
|
|
||||||
.dashboard-title {
|
.dashboard-title {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -128,13 +145,23 @@
|
|||||||
line-height: 24px; /* 150% */
|
line-height: 24px; /* 150% */
|
||||||
letter-spacing: -0.08px;
|
letter-spacing: -0.08px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
min-width: fit-content;
|
||||||
|
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-section {
|
.right-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 55%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 16px 0px 0px;
|
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
|
||||||
.icons {
|
.icons {
|
||||||
@@ -199,6 +226,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 16px 16px 0px 16px;
|
padding: 16px 16px 0px 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
@@ -219,7 +248,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dashboard-description-section {
|
.dashboard-description-section {
|
||||||
max-width: 957px;
|
|
||||||
color: var(--bg-vanilla-400);
|
color: var(--bg-vanilla-400);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -578,7 +606,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashbord-details {
|
.dashboard-details {
|
||||||
.left-section {
|
.left-section {
|
||||||
.dashboard-title {
|
.dashboard-title {
|
||||||
color: var(--bg-ink-300);
|
color: var(--bg-ink-300);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
import { dashboardHelpMessage } from 'components/facingIssueBtn/util';
|
import { dashboardHelpMessage } from 'components/LaunchChatSupport/util';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
@@ -294,36 +294,35 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
|||||||
>
|
>
|
||||||
Dashboard /
|
Dashboard /
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button type="text" className="id-btn dashboard-name-btn">
|
||||||
type="text"
|
<img
|
||||||
className="id-btn"
|
src={image}
|
||||||
icon={
|
alt="dashboard-icon"
|
||||||
// eslint-disable-next-line jsx-a11y/img-redundant-alt
|
style={{ height: '14px', width: '14px' }}
|
||||||
<img
|
/>
|
||||||
src={image}
|
|
||||||
alt="dashboard-image"
|
|
||||||
style={{ height: '14px', width: '14px' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{title}
|
{title}
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<section className="dashbord-details">
|
<section className="dashboard-details">
|
||||||
<div className="left-section">
|
<div className="left-section">
|
||||||
<img
|
<Tooltip title={title.length > 30 ? title : ''}>
|
||||||
src={image}
|
<Typography.Text
|
||||||
alt="dashboard-img"
|
className="dashboard-title"
|
||||||
style={{ width: '16px', height: '16px' }}
|
data-testid="dashboard-title"
|
||||||
/>
|
>
|
||||||
<Typography.Text className="dashboard-title" data-testid="dashboard-title">
|
<img
|
||||||
{title}
|
src={image}
|
||||||
</Typography.Text>
|
alt="dashboard-img"
|
||||||
|
style={{ width: '16px', height: '16px' }}
|
||||||
|
/>{' '}
|
||||||
|
{title}
|
||||||
|
</Typography.Text>
|
||||||
|
</Tooltip>
|
||||||
{isDashboardLocked && <LockKeyhole size={14} />}
|
{isDashboardLocked && <LockKeyhole size={14} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="right-section">
|
<div className="right-section">
|
||||||
<FacingIssueBtn
|
<LaunchChatSupport
|
||||||
attributes={{
|
attributes={{
|
||||||
uuid: selectedDashboard?.uuid,
|
uuid: selectedDashboard?.uuid,
|
||||||
title: updatedTitle,
|
title: updatedTitle,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Color } from '@signozhq/design-tokens';
|
|||||||
import { Button, Tabs, Tooltip, Typography } from 'antd';
|
import { Button, Tabs, Tooltip, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import PromQLIcon from 'assets/Dashboard/PromQl';
|
import PromQLIcon from 'assets/Dashboard/PromQl';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
|
||||||
@@ -237,7 +237,7 @@ function QuerySection({
|
|||||||
onChange={handleQueryCategoryChange}
|
onChange={handleQueryCategoryChange}
|
||||||
tabBarExtraContent={
|
tabBarExtraContent={
|
||||||
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||||
<FacingIssueBtn
|
<LaunchChatSupport
|
||||||
attributes={{
|
attributes={{
|
||||||
uuid: selectedDashboard?.uuid,
|
uuid: selectedDashboard?.uuid,
|
||||||
title: selectedDashboard?.data.title,
|
title: selectedDashboard?.data.title,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
listViewInitialTraceQuery,
|
listViewInitialTraceQuery,
|
||||||
PANEL_TYPES_INITIAL_QUERY,
|
PANEL_TYPES_INITIAL_QUERY,
|
||||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
} from 'container/NewDashboard/ComponentsSlider/constants';
|
||||||
import { isEqual, set, unset } from 'lodash-es';
|
import { cloneDeep, isEqual, set, unset } from 'lodash-es';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
@@ -43,54 +43,59 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'functions',
|
||||||
'queryName',
|
'stepInterval',
|
||||||
'expression',
|
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
|
||||||
'queryName',
|
|
||||||
'expression',
|
|
||||||
'disabled',
|
|
||||||
'legend',
|
'legend',
|
||||||
|
'queryName',
|
||||||
|
'disabled',
|
||||||
|
'functions',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'queryName',
|
'functions',
|
||||||
'expression',
|
'stepInterval',
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -99,54 +104,59 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'functions',
|
||||||
'queryName',
|
'stepInterval',
|
||||||
'expression',
|
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
|
||||||
'queryName',
|
|
||||||
'expression',
|
|
||||||
'disabled',
|
|
||||||
'legend',
|
'legend',
|
||||||
|
'queryName',
|
||||||
|
'disabled',
|
||||||
|
'functions',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'queryName',
|
'functions',
|
||||||
'expression',
|
'stepInterval',
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -155,48 +165,59 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
|
||||||
'disabled',
|
|
||||||
'legend',
|
'legend',
|
||||||
|
'queryName',
|
||||||
|
'disabled',
|
||||||
|
'functions',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
'disabled',
|
'disabled',
|
||||||
|
'queryName',
|
||||||
'legend',
|
'legend',
|
||||||
|
'expression',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -205,18 +226,18 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
|
'disabled',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
|
||||||
'reduceTo',
|
|
||||||
'legend',
|
'legend',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -224,37 +245,40 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
|
'reduceTo',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
'legend',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
'disabled',
|
||||||
'reduceTo',
|
'functions',
|
||||||
'legend',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
|
'disabled',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
|
||||||
'reduceTo',
|
|
||||||
'legend',
|
'legend',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -264,17 +288,18 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
|
'disabled',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
|
||||||
'legend',
|
'legend',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -282,35 +307,40 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
|
'reduceTo',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
'legend',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
'disabled',
|
||||||
'legend',
|
'functions',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'groupBy',
|
'groupBy',
|
||||||
'limit',
|
'limit',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
|
'disabled',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
|
||||||
'legend',
|
'legend',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -319,7 +349,7 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[PANEL_TYPES.LIST]: {
|
[PANEL_TYPES.LIST]: {
|
||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: ['filters', 'limit', 'orderBy'],
|
queryData: ['filters', 'limit', 'orderBy', 'functions'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
@@ -329,7 +359,7 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: ['filters', 'limit', 'orderBy'],
|
queryData: ['filters', 'limit', 'orderBy', 'functions'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -337,12 +367,13 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.LOGS]: {
|
[DataSource.LOGS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'filters',
|
||||||
'reduceTo',
|
'reduceTo',
|
||||||
'having',
|
'having',
|
||||||
'functions',
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
'disabled',
|
||||||
@@ -353,30 +384,32 @@ export const panelTypeDataSourceFormValuesMap: Record<
|
|||||||
[DataSource.METRICS]: {
|
[DataSource.METRICS]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
|
'aggregateOperator',
|
||||||
|
'timeAggregation',
|
||||||
|
'filters',
|
||||||
|
'spaceAggregation',
|
||||||
'having',
|
'having',
|
||||||
'reduceTo',
|
'reduceTo',
|
||||||
'functions',
|
'stepInterval',
|
||||||
'spaceAggregation',
|
'legend',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
'disabled',
|
||||||
'legend',
|
'functions',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[DataSource.TRACES]: {
|
[DataSource.TRACES]: {
|
||||||
builder: {
|
builder: {
|
||||||
queryData: [
|
queryData: [
|
||||||
'filters',
|
|
||||||
'aggregateOperator',
|
|
||||||
'aggregateAttribute',
|
'aggregateAttribute',
|
||||||
'groupBy',
|
'aggregateOperator',
|
||||||
'limit',
|
'filters',
|
||||||
|
'reduceTo',
|
||||||
'having',
|
'having',
|
||||||
'orderBy',
|
'functions',
|
||||||
|
'stepInterval',
|
||||||
'queryName',
|
'queryName',
|
||||||
'expression',
|
'expression',
|
||||||
'disabled',
|
'disabled',
|
||||||
@@ -396,12 +429,8 @@ export function handleQueryChange(
|
|||||||
builder: {
|
builder: {
|
||||||
...supersetQuery.builder,
|
...supersetQuery.builder,
|
||||||
queryData: supersetQuery.builder.queryData.map((query, index) => {
|
queryData: supersetQuery.builder.queryData.map((query, index) => {
|
||||||
const { dataSource, expression, queryName } = query;
|
const { dataSource } = query;
|
||||||
const tempQuery = {
|
const tempQuery = cloneDeep(initialQueryBuilderFormValuesMap[dataSource]);
|
||||||
...initialQueryBuilderFormValuesMap[dataSource],
|
|
||||||
expression,
|
|
||||||
queryName,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fieldsToSelect =
|
const fieldsToSelect =
|
||||||
panelTypeDataSourceFormValuesMap[newPanelType][dataSource].builder
|
panelTypeDataSourceFormValuesMap[newPanelType][dataSource].builder
|
||||||
@@ -416,6 +445,8 @@ export function handleQueryChange(
|
|||||||
set(tempQuery, 'offset', 0);
|
set(tempQuery, 'offset', 0);
|
||||||
set(tempQuery, 'pageSize', 10);
|
set(tempQuery, 'pageSize', 10);
|
||||||
} else if (tempQuery.aggregateOperator === 'noop') {
|
} else if (tempQuery.aggregateOperator === 'noop') {
|
||||||
|
// this condition takes care of the part where we start with the list panel type and then shift to other panels
|
||||||
|
// because in other cases we never set list operator and other fields in superset query rather just update in the current / staged query
|
||||||
set(tempQuery, 'aggregateOperator', 'count');
|
set(tempQuery, 'aggregateOperator', 'count');
|
||||||
unset(tempQuery, 'offset');
|
unset(tempQuery, 'offset');
|
||||||
unset(tempQuery, 'pageSize');
|
unset(tempQuery, 'pageSize');
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,47 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: aws
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{region}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: <SIGNOZ_INGESTION_KEY>
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
|
resourceDetection:
|
||||||
|
detectors:
|
||||||
|
- eks
|
||||||
|
- system
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -5,17 +5,39 @@ Add the SigNoz Helm Chart repository
|
|||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
|
||||||
--set otelInsecure=false \
|
```yaml
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
global:
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
cloud: others
|
||||||
|
clusterName: <CLUSTER_NAME>
|
||||||
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,24 +1,43 @@
|
|||||||
## Install otel-collector in your Kubernetes infra
|
### Install otel-collector in your Kubernetes infra
|
||||||
|
|
||||||
|
|
||||||
Add the SigNoz Helm Chart repository
|
Add the SigNoz Helm Chart repository
|
||||||
```bash
|
```bash
|
||||||
helm repo add signoz https://charts.signoz.io
|
helm repo add signoz https://charts.signoz.io
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If the chart is already present, update the chart to the latest using:
|
If the chart is already present, update the chart to the latest using:
|
||||||
```bash
|
```bash
|
||||||
helm repo update
|
helm repo update
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Install the Kubernetes Infrastructure chart provided by SigNoz
|
For generic Kubernetes clusters, you can create *override-values.yaml* with the following configuration:
|
||||||
```bash
|
|
||||||
helm install my-release signoz/k8s-infra \
|
```yaml
|
||||||
--set otelCollectorEndpoint=ingest.{{REGION}}.signoz.cloud:443 \
|
global:
|
||||||
--set otelInsecure=false \
|
cloud: others
|
||||||
--set signozApiKey={{SIGNOZ_INGESTION_KEY}} \
|
clusterName: <CLUSTER_NAME>
|
||||||
--set global.clusterName=<CLUSTER_NAME>
|
deploymentEnvironment: <DEPLOYMENT_ENVIRONMENT>
|
||||||
|
otelCollectorEndpoint: ingest.{{REGION}}.signoz.cloud:443
|
||||||
|
otelInsecure: false
|
||||||
|
signozApiKey: {{SIGNOZ_INGESTION_KEY}}
|
||||||
|
presets:
|
||||||
|
otlpExporter:
|
||||||
|
enabled: true
|
||||||
|
loggingExporter:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
- Replace `<CLUSTER_NAME>` with the name of the Kubernetes cluster or a unique identifier of the cluster.
|
||||||
|
- Replace `<DEPLOYMENT_ENVIRONMENT>` with the deployment environment of your application. Example: **"staging"**, **"production"**, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To install the k8s-infra chart with the above configuration, run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install my-release signoz/k8s-infra -f override-values.yaml
|
||||||
|
```
|
||||||
|
|||||||
@@ -41,3 +41,37 @@ div[class*='-setup-instructions-container'] {
|
|||||||
.service-name-container {
|
.service-name-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.intgeration-page-container {
|
||||||
|
background-color: var(--bg-ink-400);
|
||||||
|
border-color: var(--bg-slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intgeration-page-container-text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigate-integrations-page-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
background-color: var(--bg-slate-300);
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataSourceName {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightMode {
|
||||||
|
.dataSourceName {
|
||||||
|
color: var(--bg-slate-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { LoadingOutlined } from '@ant-design/icons';
|
|||||||
import { Button, Card, Form, Input, Select, Space, Typography } from 'antd';
|
import { Button, Card, Form, Input, Select, Space, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
|
||||||
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
|
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
|
||||||
import {
|
import {
|
||||||
@@ -14,9 +15,10 @@ import {
|
|||||||
hasFrameworks,
|
hasFrameworks,
|
||||||
} from 'container/OnboardingContainer/utils/dataSourceUtils';
|
} from 'container/OnboardingContainer/utils/dataSourceUtils';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { Check } from 'lucide-react';
|
import { Blocks, Check } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
|
|
||||||
export interface DataSourceType {
|
export interface DataSourceType {
|
||||||
@@ -29,6 +31,7 @@ export interface DataSourceType {
|
|||||||
export default function DataSource(): JSX.Element {
|
export default function DataSource(): JSX.Element {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
serviceName,
|
serviceName,
|
||||||
@@ -127,6 +130,10 @@ export default function DataSource(): JSX.Element {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goToIntegrationsPage = (): void => {
|
||||||
|
history.push(ROUTES.INTEGRATIONS);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-container">
|
<div className="module-container">
|
||||||
<Typography.Text className="data-source-title">
|
<Typography.Text className="data-source-title">
|
||||||
@@ -156,7 +163,7 @@ export default function DataSource(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Typography.Text className="serviceName">
|
<Typography.Text className="dataSourceName">
|
||||||
{dataSource.name}
|
{dataSource.name}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,6 +221,20 @@ export default function DataSource(): JSX.Element {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="request-entity-container intgeration-page-container">
|
||||||
|
<Typography.Text className="intgeration-page-container-text">
|
||||||
|
Not able to find datasources you are looking for, check our Integrations
|
||||||
|
page which allows more sources of sending data
|
||||||
|
</Typography.Text>
|
||||||
|
<Button
|
||||||
|
onClick={goToIntegrationsPage}
|
||||||
|
icon={<Blocks size={14} />}
|
||||||
|
className="navigate-integrations-page-btn"
|
||||||
|
>
|
||||||
|
Go to integrations
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="request-entity-container">
|
<div className="request-entity-container">
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
Cannot find what you’re looking for? Request a data source
|
Cannot find what you’re looking for? Request a data source
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'serviceName--string--tag--true',
|
id: 'serviceName--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@@ -26,6 +27,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'name--string--tag--true',
|
id: 'name--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'durationNano',
|
key: 'durationNano',
|
||||||
@@ -34,6 +36,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'durationNano--float64--tag--true',
|
id: 'durationNano--float64--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'httpMethod',
|
key: 'httpMethod',
|
||||||
@@ -42,6 +45,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'httpMethod--string--tag--true',
|
id: 'httpMethod--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'responseStatusCode',
|
key: 'responseStatusCode',
|
||||||
@@ -50,5 +54,6 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'responseStatusCode--string--tag--true',
|
id: 'responseStatusCode--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -544,19 +544,21 @@ export const Query = memo(function Query({
|
|||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{isVersionV4 && isMetricsDataSource && panelType === PANEL_TYPES.TABLE && (
|
{isVersionV4 &&
|
||||||
<Col flex="1 1 12.5rem">
|
isMetricsDataSource &&
|
||||||
<Row>
|
(panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.PIE) && (
|
||||||
<Col span={6}>
|
<Col flex="1 1 12.5rem">
|
||||||
<FilterLabel label="Reduce to" />
|
<Row>
|
||||||
</Col>
|
<Col span={6}>
|
||||||
|
<FilterLabel label="Reduce to" />
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Col span={18}>
|
<Col span={18}>
|
||||||
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
|
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,18 +3,27 @@ import './ToolbarActions.styles.scss';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import { Play } from 'lucide-react';
|
import { Play, X } from 'lucide-react';
|
||||||
import { useEffect } from 'react';
|
import { MutableRefObject, useEffect } from 'react';
|
||||||
|
import { useQueryClient } from 'react-query';
|
||||||
|
|
||||||
interface RightToolbarActionsProps {
|
interface RightToolbarActionsProps {
|
||||||
onStageRunQuery: () => void;
|
onStageRunQuery: () => void;
|
||||||
|
isLoadingQueries?: boolean;
|
||||||
|
listQueryKeyRef?: MutableRefObject<any>;
|
||||||
|
chartQueryKeyRef?: MutableRefObject<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RightToolbarActions({
|
export default function RightToolbarActions({
|
||||||
onStageRunQuery,
|
onStageRunQuery,
|
||||||
|
isLoadingQueries,
|
||||||
|
listQueryKeyRef,
|
||||||
|
chartQueryKeyRef,
|
||||||
}: RightToolbarActionsProps): JSX.Element {
|
}: RightToolbarActionsProps): JSX.Element {
|
||||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerShortcut(LogsExplorerShortcuts.StageAndRunQuery, onStageRunQuery);
|
registerShortcut(LogsExplorerShortcuts.StageAndRunQuery, onStageRunQuery);
|
||||||
|
|
||||||
@@ -25,14 +34,41 @@ export default function RightToolbarActions({
|
|||||||
}, [onStageRunQuery]);
|
}, [onStageRunQuery]);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
{isLoadingQueries ? (
|
||||||
type="primary"
|
<div className="loading-container">
|
||||||
className="right-toolbar"
|
<Button className="loading-btn" loading={isLoadingQueries} />
|
||||||
onClick={onStageRunQuery}
|
<Button
|
||||||
icon={<Play size={14} />}
|
icon={<X size={14} />}
|
||||||
>
|
className="cancel-run"
|
||||||
Stage & Run Query
|
onClick={(): void => {
|
||||||
</Button>
|
if (listQueryKeyRef?.current) {
|
||||||
|
queryClient.cancelQueries(listQueryKeyRef.current);
|
||||||
|
}
|
||||||
|
if (chartQueryKeyRef?.current) {
|
||||||
|
queryClient.cancelQueries(chartQueryKeyRef.current);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel Run
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
className="right-toolbar"
|
||||||
|
disabled={isLoadingQueries}
|
||||||
|
onClick={onStageRunQuery}
|
||||||
|
icon={<Play size={14} />}
|
||||||
|
>
|
||||||
|
Stage & Run Query
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RightToolbarActions.defaultProps = {
|
||||||
|
isLoadingQueries: false,
|
||||||
|
listQueryKeyRef: null,
|
||||||
|
chartQueryKeyRef: null,
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
.left-toolbar-query-actions {
|
.left-toolbar-query-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
border: 1px solid var(--bg-slate-400);
|
||||||
background: var(--bg-ink-300, #16181d);
|
background: var(--bg-ink-300);
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.prom-ql-icon {
|
.prom-ql-icon {
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
&.active-tab {
|
&.active-tab {
|
||||||
background-color: #1d212d;
|
background-color: var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.action-btn + .action-btn {
|
.action-btn + .action-btn {
|
||||||
border-left: 1px solid var(--bg-slate-400, #1d212d);
|
border-left: 1px solid var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +51,50 @@
|
|||||||
background-color: var(--bg-robin-600);
|
background-color: var(--bg-robin-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-btn {
|
||||||
|
display: flex;
|
||||||
|
width: 32px;
|
||||||
|
height: 33px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-slate-300);
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run {
|
||||||
|
display: flex;
|
||||||
|
height: 33px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-cherry-500);
|
||||||
|
border-color: none;
|
||||||
|
}
|
||||||
|
.cancel-run:hover {
|
||||||
|
background-color: #ff7875 !important;
|
||||||
|
color: var(--bg-vanilla-100) !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.left-toolbar {
|
.left-toolbar {
|
||||||
.left-toolbar-query-actions {
|
.left-toolbar-query-actions {
|
||||||
@@ -68,4 +112,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.loading-container {
|
||||||
|
.loading-btn {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run:hover {
|
||||||
|
background-color: #ff7875;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||||
|
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||||
|
|
||||||
import LeftToolbarActions from '../LeftToolbarActions';
|
import LeftToolbarActions from '../LeftToolbarActions';
|
||||||
import RightToolbarActions from '../RightToolbarActions';
|
import RightToolbarActions from '../RightToolbarActions';
|
||||||
@@ -94,7 +95,9 @@ describe('ToolbarActions', () => {
|
|||||||
it('RightToolbarActions - render correctly with props', async () => {
|
it('RightToolbarActions - render correctly with props', async () => {
|
||||||
const onStageRunQuery = jest.fn();
|
const onStageRunQuery = jest.fn();
|
||||||
const { queryByText } = render(
|
const { queryByText } = render(
|
||||||
<RightToolbarActions onStageRunQuery={onStageRunQuery} />,
|
<MockQueryClientProvider>
|
||||||
|
<RightToolbarActions onStageRunQuery={onStageRunQuery} />,
|
||||||
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stageNRunBtn = queryByText('Stage & Run Query');
|
const stageNRunBtn = queryByText('Stage & Run Query');
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
import './QueryBuilderSearch.styles.scss';
|
||||||
|
|
||||||
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
function ExampleQueriesRendererForLogs({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
handleAddTag,
|
||||||
|
}: ExampleQueriesRendererForLogsProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="example-query-container"
|
||||||
|
onClick={(): void => {
|
||||||
|
handleAddTag(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="example-query">{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExampleQueriesRendererForLogsProps {
|
||||||
|
label: string;
|
||||||
|
value: TagFilter;
|
||||||
|
handleAddTag: (value: TagFilter) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExampleQueriesRendererForLogs;
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import './QueryBuilderSearch.styles.scss';
|
||||||
|
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Tooltip, Typography } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { Zap } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { getOptionType } from './utils';
|
||||||
|
|
||||||
|
function OptionRendererForLogs({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
dataType,
|
||||||
|
isIndexed,
|
||||||
|
setDynamicPlaceholder,
|
||||||
|
}: OptionRendererProps): JSX.Element {
|
||||||
|
const [truncated, setTruncated] = useState<boolean>(false);
|
||||||
|
const optionType = getOptionType(label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="option"
|
||||||
|
onMouseEnter={(): void => setDynamicPlaceholder(value)}
|
||||||
|
onFocus={(): void => setDynamicPlaceholder(value)}
|
||||||
|
>
|
||||||
|
{optionType ? (
|
||||||
|
<Tooltip title={truncated ? `${value}` : ''} placement="topLeft">
|
||||||
|
<div className="logs-options-select">
|
||||||
|
<section className="left-section">
|
||||||
|
{isIndexed ? (
|
||||||
|
<Zap size={12} fill={Color.BG_AMBER_500} />
|
||||||
|
) : (
|
||||||
|
<div className="dot" />
|
||||||
|
)}
|
||||||
|
<Typography.Text
|
||||||
|
className="text value"
|
||||||
|
ellipsis={{ onEllipsis: (ellipsis): void => setTruncated(ellipsis) }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Typography.Text>
|
||||||
|
</section>
|
||||||
|
<section className="right-section">
|
||||||
|
<div className="text tags data-type-tag">{dataType}</div>
|
||||||
|
<div className={cx('text tags option-type-tag', optionType)}>
|
||||||
|
<div className="dot" />
|
||||||
|
{optionType}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={truncated ? `${label}` : ''} placement="topLeft">
|
||||||
|
<div className="without-option-type">
|
||||||
|
<div className="dot" />
|
||||||
|
<Typography.Text
|
||||||
|
className="text"
|
||||||
|
ellipsis={{ onEllipsis: (ellipsis): void => setTruncated(ellipsis) }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OptionRendererProps {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
dataType: string;
|
||||||
|
isIndexed: boolean;
|
||||||
|
setDynamicPlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OptionRendererForLogs;
|
||||||
@@ -11,6 +11,290 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs-popup {
|
||||||
|
&.hide-scroll {
|
||||||
|
.rc-virtual-list-holder {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-explorer-popup {
|
||||||
|
padding: 0px;
|
||||||
|
.ant-select-item-group {
|
||||||
|
padding: 12px 14px 8px 14px;
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
.keyboard-shortcut-slash {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2.286px;
|
||||||
|
border-top: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-right: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-bottom: 2.286px solid var(--bg-ink-200);
|
||||||
|
border-left: 1.143px solid var(--bg-ink-200);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-queries {
|
||||||
|
cursor: default;
|
||||||
|
.heading {
|
||||||
|
padding: 12px 14px 8px 14px;
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0px 12px 12px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.example-query {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-ink-200);
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-query:hover {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-grouped {
|
||||||
|
padding-inline-start: 0px;
|
||||||
|
padding: 7px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0px 0px 4px 4px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
padding: 11px 16px;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2.286px;
|
||||||
|
border-top: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-right: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-bottom: 2.286px solid var(--Ink-200, #23262e);
|
||||||
|
border-left: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
background: var(--Ink-400, #121317);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigate {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 12px;
|
||||||
|
gap: 4px;
|
||||||
|
border-right: 1px solid #1d212d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-query {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 12px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.without-option-type {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
.dot {
|
||||||
|
height: 5px;
|
||||||
|
width: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-options-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: 5px;
|
||||||
|
width: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.data-type-tag {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-type-tag {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0px 6px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
border-radius: 50px;
|
||||||
|
background: rgba(189, 153, 121, 0.1);
|
||||||
|
color: var(--bg-sienna-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-sienna-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
border-radius: 50px;
|
||||||
|
background: rgba(245, 108, 135, 0.1);
|
||||||
|
color: var(--bg-sakura-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-sakura-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-active {
|
||||||
|
.logs-options-select {
|
||||||
|
.left-section {
|
||||||
|
.value {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.query-builder-search {
|
.query-builder-search {
|
||||||
.ant-select-dropdown {
|
.ant-select-dropdown {
|
||||||
@@ -21,4 +305,108 @@
|
|||||||
background-color: var(--bg-vanilla-200) !important;
|
background-color: var(--bg-vanilla-200) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.logs-explorer-popup {
|
||||||
|
.ant-select-item-group {
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props {
|
||||||
|
.content {
|
||||||
|
.left-section {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-slate-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-section {
|
||||||
|
.keyboard-shortcut-slash {
|
||||||
|
border-top: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-right: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-bottom: 2.286px solid var(--bg-ink-200);
|
||||||
|
border-left: 1.143px solid var(--bg-ink-200);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props:hover {
|
||||||
|
background: var(--bg-vanilla-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-queries {
|
||||||
|
.heading {
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-container {
|
||||||
|
.example-query-container {
|
||||||
|
.example-query {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-query:hover {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts {
|
||||||
|
border: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
border-top: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-right: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-bottom: 2.286px solid var(--Ink-200, #23262e);
|
||||||
|
border-left: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigate {
|
||||||
|
border-right: 1px solid #1d212d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-options-select {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
.data-type-tag {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background: rgba(189, 153, 121, 0.1);
|
||||||
|
color: var(--bg-sienna-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
background: rgba(245, 108, 135, 0.1);
|
||||||
|
color: var(--bg-sakura-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-active {
|
||||||
|
.logs-options-select {
|
||||||
|
.left-section {
|
||||||
|
.value {
|
||||||
|
color: var(--bg-ink-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user