Compare commits
28 Commits
v0.70.1
...
fix/handle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd765f8646 | ||
|
|
5bb3563713 | ||
|
|
6d8d2e6b11 | ||
|
|
a8e8f31b00 | ||
|
|
c3164912e6 | ||
|
|
b215c6a0ce | ||
|
|
94c2398a08 | ||
|
|
acd9b97ee3 | ||
|
|
c5219ac157 | ||
|
|
2b32ce190f | ||
|
|
c7c7b25651 | ||
|
|
f548afe284 | ||
|
|
586f5255f0 | ||
|
|
62064f136d | ||
|
|
66adc7fbf9 | ||
|
|
f6b2d5a519 | ||
|
|
035999250e | ||
|
|
aecbb71ce4 | ||
|
|
01b6e22bbd | ||
|
|
dc15ee8176 | ||
|
|
e414215786 | ||
|
|
5fe04078e5 | ||
|
|
cf95b15ba1 | ||
|
|
3b550c485d | ||
|
|
784dccf298 | ||
|
|
aa26dc77af | ||
|
|
e33a0fdd47 | ||
|
|
c8032f771e |
@@ -181,7 +181,7 @@ services:
|
||||
- query-service
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:0.70.1
|
||||
image: signoz/query-service:0.71.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -214,7 +214,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:0.70.1
|
||||
image: signoz/frontend:0.71.0
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
@@ -224,7 +224,7 @@ services:
|
||||
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:0.111.25
|
||||
image: signoz/signoz-otel-collector:0.111.26
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
- query-service
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:0.70.1
|
||||
image: signoz/query-service:0.71.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
- --use-logs-new-schema=true
|
||||
@@ -150,7 +150,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:0.70.1
|
||||
image: signoz/frontend:0.71.0
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
@@ -160,7 +160,7 @@ services:
|
||||
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:0.111.25
|
||||
image: signoz/signoz-otel-collector:0.111.26
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
|
||||
@@ -188,7 +188,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -222,7 +222,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
@@ -234,7 +234,7 @@ services:
|
||||
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.25}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
|
||||
@@ -121,7 +121,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
@@ -168,7 +168,7 @@ services:
|
||||
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.25}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
|
||||
@@ -121,7 +121,7 @@ services:
|
||||
condition: service_healthy
|
||||
query-service:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-query-service
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -155,7 +155,7 @@ services:
|
||||
retries: 3
|
||||
frontend:
|
||||
!!merge <<: *common
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.70.1}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.71.0}
|
||||
container_name: signoz-frontend
|
||||
depends_on:
|
||||
- alertmanager
|
||||
@@ -166,7 +166,7 @@ services:
|
||||
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.25}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.26}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
|
||||
@@ -10,8 +10,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -20,6 +25,7 @@ type CloudIntegrationConnectionParamsResponse struct {
|
||||
IngestionUrl string `json:"ingestion_url,omitempty"`
|
||||
IngestionKey string `json:"ingestion_key,omitempty"`
|
||||
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
|
||||
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
|
||||
}
|
||||
|
||||
func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -31,44 +37,64 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
||||
return
|
||||
}
|
||||
|
||||
license, err := ah.LM().GetRepo().GetActiveLicense(r.Context())
|
||||
currentUser, err := auth.GetUserFromRequest(r)
|
||||
if err != nil {
|
||||
RespondError(w, basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't look for active license: %w", err,
|
||||
RespondError(w, basemodel.UnauthorizedError(fmt.Errorf(
|
||||
"couldn't deduce current user: %w", err,
|
||||
)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if license == nil {
|
||||
RespondError(w, basemodel.ForbiddenError(fmt.Errorf(
|
||||
"no active license found",
|
||||
)), nil)
|
||||
return
|
||||
}
|
||||
|
||||
ingestionUrl, signozApiUrl, err := getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
|
||||
if err != nil {
|
||||
RespondError(w, basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't deduce ingestion url and signoz api url: %w", err,
|
||||
)), nil)
|
||||
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider)
|
||||
if apiErr != nil {
|
||||
RespondError(w, basemodel.WrapApiError(
|
||||
apiErr, "couldn't provision PAT for cloud integration:",
|
||||
), nil)
|
||||
return
|
||||
}
|
||||
|
||||
result := CloudIntegrationConnectionParamsResponse{
|
||||
IngestionUrl: ingestionUrl,
|
||||
SigNozAPIUrl: signozApiUrl,
|
||||
SigNozAPIKey: apiKey,
|
||||
}
|
||||
|
||||
license, apiErr := ah.LM().GetRepo().GetActiveLicense(r.Context())
|
||||
if apiErr != nil {
|
||||
RespondError(w, basemodel.WrapApiError(
|
||||
apiErr, "couldn't look for active license",
|
||||
), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if license == nil {
|
||||
// Return the API Key (PAT) even if the rest of the params can not be deduced.
|
||||
// Params not returned from here will be requested from the user via form inputs.
|
||||
// This enables gracefully degraded but working experience even for non-cloud deployments.
|
||||
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
|
||||
ah.Respond(w, result)
|
||||
return
|
||||
}
|
||||
|
||||
ingestionUrl, signozApiUrl, apiErr := getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
|
||||
if apiErr != nil {
|
||||
RespondError(w, basemodel.WrapApiError(
|
||||
apiErr, "couldn't deduce ingestion url and signoz api url",
|
||||
), nil)
|
||||
return
|
||||
}
|
||||
|
||||
result.IngestionUrl = ingestionUrl
|
||||
result.SigNozAPIUrl = signozApiUrl
|
||||
|
||||
gatewayUrl := ah.opts.GatewayUrl
|
||||
if len(gatewayUrl) > 0 {
|
||||
|
||||
ingestionKey, err := getOrCreateCloudProviderIngestionKey(
|
||||
ingestionKey, apiErr := getOrCreateCloudProviderIngestionKey(
|
||||
r.Context(), gatewayUrl, license.Key, cloudProvider,
|
||||
)
|
||||
if err != nil {
|
||||
RespondError(w, basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't get or create ingestion key: %w", err,
|
||||
)), nil)
|
||||
if apiErr != nil {
|
||||
RespondError(w, basemodel.WrapApiError(
|
||||
apiErr, "couldn't get or create ingestion key",
|
||||
), nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,6 +107,100 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
||||
ah.Respond(w, result)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
|
||||
string, *basemodel.ApiError,
|
||||
) {
|
||||
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
|
||||
|
||||
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
|
||||
if apiErr != nil {
|
||||
return "", apiErr
|
||||
}
|
||||
|
||||
allPats, err := ah.AppDao().ListPATs(ctx)
|
||||
if err != nil {
|
||||
return "", basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't list PATs: %w", err.Error(),
|
||||
))
|
||||
}
|
||||
for _, p := range allPats {
|
||||
if p.UserID == integrationUser.Id && p.Name == integrationPATName {
|
||||
return p.Token, nil
|
||||
}
|
||||
}
|
||||
|
||||
zap.L().Info(
|
||||
"no PAT found for cloud integration, creating a new one",
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
)
|
||||
|
||||
newPAT := model.PAT{
|
||||
Token: generatePATToken(),
|
||||
UserID: integrationUser.Id,
|
||||
Name: integrationPATName,
|
||||
Role: baseconstants.ViewerGroup,
|
||||
ExpiresAt: 0,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
}
|
||||
integrationPAT, err := ah.AppDao().CreatePAT(ctx, newPAT)
|
||||
if err != nil {
|
||||
return "", basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't create cloud integration PAT: %w", err.Error(),
|
||||
))
|
||||
}
|
||||
return integrationPAT.Token, nil
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
||||
ctx context.Context, orgId string, cloudProvider string,
|
||||
) (*basemodel.User, *basemodel.ApiError) {
|
||||
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)
|
||||
|
||||
integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
|
||||
if apiErr != nil {
|
||||
return nil, basemodel.WrapApiError(apiErr, "couldn't look for integration user")
|
||||
}
|
||||
|
||||
if integrationUserResult != nil {
|
||||
return &integrationUserResult.User, nil
|
||||
}
|
||||
|
||||
zap.L().Info(
|
||||
"cloud integration user not found. Attempting to create the user",
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
)
|
||||
|
||||
newUser := &basemodel.User{
|
||||
Id: cloudIntegrationUserId,
|
||||
Name: fmt.Sprintf("%s integration", cloudProvider),
|
||||
Email: fmt.Sprintf("%s@signoz.io", cloudIntegrationUserId),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
OrgId: orgId,
|
||||
}
|
||||
|
||||
viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
|
||||
if apiErr != nil {
|
||||
return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user")
|
||||
}
|
||||
newUser.GroupId = viewerGroup.Id
|
||||
|
||||
passwordHash, err := auth.PasswordHash(uuid.NewString())
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf(
|
||||
"couldn't hash random password for cloud integration user: %w", err,
|
||||
))
|
||||
}
|
||||
newUser.Password = passwordHash
|
||||
|
||||
integrationUser, apiErr := ah.AppDao().CreateUser(ctx, newUser, false)
|
||||
if apiErr != nil {
|
||||
return nil, basemodel.WrapApiError(apiErr, "couldn't create cloud integration user")
|
||||
}
|
||||
|
||||
return integrationUser, nil
|
||||
}
|
||||
|
||||
func getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
|
||||
string, string, *basemodel.ApiError,
|
||||
) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof" // http profiler
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
@@ -149,25 +148,20 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
var reader interfaces.DataConnector
|
||||
storage := os.Getenv("STORAGE")
|
||||
if storage == "clickhouse" {
|
||||
zap.L().Info("Using ClickHouse as datastore ...")
|
||||
qb := db.NewDataConnector(
|
||||
serverOptions.SigNoz.SQLStore.SQLxDB(),
|
||||
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
|
||||
serverOptions.PromConfigPath,
|
||||
lm,
|
||||
serverOptions.Cluster,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
fluxIntervalForTraceDetail,
|
||||
serverOptions.SigNoz.Cache,
|
||||
)
|
||||
go qb.Start(readerReady)
|
||||
reader = qb
|
||||
} else {
|
||||
return nil, fmt.Errorf("storage type: %s is not supported in query service", storage)
|
||||
}
|
||||
qb := db.NewDataConnector(
|
||||
serverOptions.SigNoz.SQLStore.SQLxDB(),
|
||||
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
|
||||
serverOptions.PromConfigPath,
|
||||
lm,
|
||||
serverOptions.Cluster,
|
||||
serverOptions.UseLogsNewSchema,
|
||||
serverOptions.UseTraceNewSchema,
|
||||
fluxIntervalForTraceDetail,
|
||||
serverOptions.SigNoz.Cache,
|
||||
)
|
||||
go qb.Start(readerReady)
|
||||
reader = qb
|
||||
|
||||
skipConfig := &basemodel.SkipConfig{}
|
||||
if serverOptions.SkipTopLvlOpsPath != "" {
|
||||
// read skip config
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
||||
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type modelDao struct {
|
||||
@@ -29,41 +28,6 @@ func (m *modelDao) checkFeature(key string) error {
|
||||
return m.flags.CheckFeature(key)
|
||||
}
|
||||
|
||||
func columnExists(db *sqlx.DB, tableName, columnName string) bool {
|
||||
query := fmt.Sprintf("PRAGMA table_info(%s);", tableName)
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to query table info", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var (
|
||||
cid int
|
||||
name string
|
||||
ctype string
|
||||
notnull int
|
||||
dflt_value *string
|
||||
pk int
|
||||
)
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&cid, &name, &ctype, ¬null, &dflt_value, &pk)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to scan table info", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
if name == columnName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to scan table info", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InitDB creates and extends base model DB repository
|
||||
func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
|
||||
dao, err := basedsql.InitDB(inputDB)
|
||||
@@ -73,69 +37,6 @@ func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
|
||||
// set package variable so dependent base methods (e.g. AuthCache) will work
|
||||
basedao.SetDB(dao)
|
||||
m := &modelDao{ModelDaoSqlite: dao}
|
||||
|
||||
table_schema := `
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS org_domains(
|
||||
id TEXT PRIMARY KEY,
|
||||
org_id TEXT NOT NULL,
|
||||
name VARCHAR(50) NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER,
|
||||
data TEXT NOT NULL,
|
||||
FOREIGN KEY(org_id) REFERENCES organizations(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS personal_access_tokens (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
role TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
last_used INTEGER NOT NULL,
|
||||
revoked BOOLEAN NOT NULL,
|
||||
updated_by_user_id TEXT NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
);
|
||||
`
|
||||
|
||||
_, err = m.DB().Exec(table_schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
|
||||
}
|
||||
|
||||
if !columnExists(m.DB(), "personal_access_tokens", "role") {
|
||||
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN role TEXT NOT NULL DEFAULT 'ADMIN';")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in adding column: %v", err.Error())
|
||||
}
|
||||
}
|
||||
if !columnExists(m.DB(), "personal_access_tokens", "updated_at") {
|
||||
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in adding column: %v", err.Error())
|
||||
}
|
||||
}
|
||||
if !columnExists(m.DB(), "personal_access_tokens", "last_used") {
|
||||
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in adding column: %v", err.Error())
|
||||
}
|
||||
}
|
||||
if !columnExists(m.DB(), "personal_access_tokens", "revoked") {
|
||||
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN revoked BOOLEAN NOT NULL DEFAULT FALSE;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in adding column: %v", err.Error())
|
||||
}
|
||||
}
|
||||
if !columnExists(m.DB(), "personal_access_tokens", "updated_by_user_id") {
|
||||
_, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_by_user_id TEXT NOT NULL DEFAULT '';")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in adding column: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/license/sqlite"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
@@ -28,10 +27,6 @@ func NewLicenseRepo(db *sqlx.DB) Repo {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repo) InitDB(inputDB *sqlx.DB) error {
|
||||
return sqlite.InitDB(inputDB)
|
||||
}
|
||||
|
||||
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
||||
licensesData := []model.LicenseDB{}
|
||||
licenseV3Data := []*model.LicenseV3{}
|
||||
|
||||
@@ -2,7 +2,6 @@ package license
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -50,11 +49,6 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)
|
||||
}
|
||||
|
||||
repo := NewLicenseRepo(db)
|
||||
err := repo.InitDB(db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
|
||||
}
|
||||
|
||||
m := &Manager{
|
||||
repo: &repo,
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func InitDB(db *sqlx.DB) error {
|
||||
var err error
|
||||
if db == nil {
|
||||
return fmt.Errorf("invalid db connection")
|
||||
}
|
||||
|
||||
table_schema := `CREATE TABLE IF NOT EXISTS licenses(
|
||||
key TEXT PRIMARY KEY,
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
planDetails TEXT,
|
||||
activationId TEXT,
|
||||
validationMessage TEXT,
|
||||
lastValidated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sites(
|
||||
uuid TEXT PRIMARY KEY,
|
||||
alias VARCHAR(180) DEFAULT 'PROD',
|
||||
url VARCHAR(300),
|
||||
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`
|
||||
|
||||
_, err = db.Exec(table_schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating licenses table: %s", err.Error())
|
||||
}
|
||||
|
||||
table_schema = `CREATE TABLE IF NOT EXISTS feature_status (
|
||||
name TEXT PRIMARY KEY,
|
||||
active bool,
|
||||
usage INTEGER DEFAULT 0,
|
||||
usage_limit INTEGER DEFAULT 0,
|
||||
route TEXT
|
||||
);`
|
||||
|
||||
_, err = db.Exec(table_schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating feature_status table: %s", err.Error())
|
||||
}
|
||||
|
||||
table_schema = `CREATE TABLE IF NOT EXISTS licenses_v3 (
|
||||
id TEXT PRIMARY KEY,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
data TEXT
|
||||
);`
|
||||
|
||||
_, err = db.Exec(table_schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creating licenses_v3 table: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"go.signoz.io/signoz/pkg/config/fileprovider"
|
||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/migrate"
|
||||
"go.signoz.io/signoz/pkg/query-service/version"
|
||||
"go.signoz.io/signoz/pkg/signoz"
|
||||
"google.golang.org/grpc"
|
||||
@@ -183,12 +182,6 @@ func main() {
|
||||
zap.L().Info("JWT secret key set successfully.")
|
||||
}
|
||||
|
||||
if err := migrate.Migrate(signoz.SQLStore.SQLxDB()); err != nil {
|
||||
zap.L().Error("Failed to migrate", zap.Error(err))
|
||||
} else {
|
||||
zap.L().Info("Migration successful")
|
||||
}
|
||||
|
||||
server, err := app.NewServer(serverOptions)
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create server", zap.Error(err))
|
||||
|
||||
@@ -139,8 +139,8 @@ func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if license status is inactive then default it to basic
|
||||
if status == LicenseStatusInactive {
|
||||
// if license status is invalid then default it to basic
|
||||
if status == LicenseStatusInvalid {
|
||||
planName = PlanNameBasic
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
LicenseStatusInactive = "INACTIVE"
|
||||
LicenseStatusInvalid = "INVALID"
|
||||
)
|
||||
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
@@ -157,6 +157,13 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AWSIntegration,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var ProPlan = basemodel.FeatureSet{
|
||||
@@ -279,6 +286,13 @@ var ProPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AWSIntegration,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var EnterprisePlan = basemodel.FeatureSet{
|
||||
@@ -415,4 +429,11 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AWSIntegration,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -57,5 +57,8 @@
|
||||
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
||||
"MESSAGING_QUEUES": "SigNoz | Messaging Queues",
|
||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
|
||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring"
|
||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
||||
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
||||
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer"
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const currentRoute = mapRoutes.get('current');
|
||||
const shouldSuspendWorkspace =
|
||||
activeLicenseV3.status === LicenseStatus.SUSPENDED &&
|
||||
activeLicenseV3.state === LicenseState.PAYMENT_FAILED;
|
||||
activeLicenseV3.state === LicenseState.DEFAULTED;
|
||||
|
||||
if (shouldSuspendWorkspace && currentRoute) {
|
||||
navigateToWorkSpaceSuspended(currentRoute);
|
||||
|
||||
@@ -264,3 +264,8 @@ export const CeleryOverview = Loadable(
|
||||
/* webpackChunkName: "CeleryOverview" */ 'pages/Celery/CeleryOverview/CeleryOverview'
|
||||
),
|
||||
);
|
||||
|
||||
export const MetricsExplorer = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "MetricsExplorer" */ 'pages/MetricsExplorer'),
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
LogsExplorer,
|
||||
LogsIndexToFields,
|
||||
LogsSaveViews,
|
||||
MetricsExplorer,
|
||||
MySettings,
|
||||
NewDashboardPage,
|
||||
OldLogsExplorer,
|
||||
@@ -435,6 +436,27 @@ const routes: AppRoutes[] = [
|
||||
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_VIEWS,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_VIEWS',
|
||||
isPrivate: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const SUPPORT_ROUTE: AppRoutes = {
|
||||
|
||||
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
const removeAwsIntegrationAccount = async (
|
||||
accountId: string,
|
||||
): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`/cloud-integrations/aws/accounts/${accountId}/disconnect`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default removeAwsIntegrationAccount;
|
||||
@@ -9,19 +9,22 @@ import {
|
||||
import {
|
||||
AccountConfigPayload,
|
||||
AccountConfigResponse,
|
||||
ConnectionParams,
|
||||
ConnectionUrlResponse,
|
||||
} from 'types/api/integrations/aws';
|
||||
|
||||
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
|
||||
const response = await axios.get('/cloud-integrations/aws/accounts');
|
||||
|
||||
return response.data.data;
|
||||
return response.data.data.accounts;
|
||||
};
|
||||
|
||||
export const getAwsServices = async (
|
||||
accountId?: string,
|
||||
cloudAccountId?: string,
|
||||
): Promise<Service[]> => {
|
||||
const params = accountId ? { account_id: accountId } : undefined;
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
const response = await axios.get('/cloud-integrations/aws/services', {
|
||||
params,
|
||||
});
|
||||
@@ -31,9 +34,11 @@ export const getAwsServices = async (
|
||||
|
||||
export const getServiceDetails = async (
|
||||
serviceId: string,
|
||||
accountId?: string,
|
||||
cloudAccountId?: string,
|
||||
): Promise<ServiceData> => {
|
||||
const params = accountId ? { account_id: accountId } : undefined;
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/aws/services/${serviceId}`,
|
||||
{ params },
|
||||
@@ -74,3 +79,10 @@ export const updateServiceConfig = async (
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getConnectionParams = async (): Promise<ConnectionParams> => {
|
||||
const response = await axios.get(
|
||||
'/cloud-integrations/aws/accounts/generate-connection-params',
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import './CeleryOverviewConfigOptions.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Row, Select, Spin, Tooltip } from 'antd';
|
||||
import { Row, Select, Spin } from 'antd';
|
||||
import {
|
||||
getValuesFromQueryParams,
|
||||
setQueryParamsFromOptions,
|
||||
@@ -10,10 +9,7 @@ import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOpt
|
||||
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Check, Share2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
interface SelectOptionConfig {
|
||||
placeholder: string;
|
||||
@@ -66,10 +62,6 @@ function FilterSelect({
|
||||
}
|
||||
|
||||
function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
const [isURLCopied, setIsURLCopied] = useState(false);
|
||||
|
||||
const [, handleCopyToClipboard] = useCopyToClipboard();
|
||||
|
||||
const selectConfigs: SelectOptionConfig[] = [
|
||||
{
|
||||
placeholder: 'Service Name',
|
||||
@@ -98,14 +90,6 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const handleShareURL = (): void => {
|
||||
handleCopyToClipboard(window.location.href);
|
||||
setIsURLCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsURLCopied(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="celery-overview-filters">
|
||||
<Row className="celery-filters">
|
||||
@@ -118,19 +102,6 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
<Tooltip title="Share this" arrow={false}>
|
||||
<Button
|
||||
className="periscope-btn copy-url-btn"
|
||||
onClick={handleShareURL}
|
||||
icon={
|
||||
isURLCopied ? (
|
||||
<Check size={14} color={Color.BG_FOREST_500} />
|
||||
) : (
|
||||
<Share2 size={14} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
font-weight: 600;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&::before {
|
||||
background-color: transparent;
|
||||
|
||||
@@ -218,35 +218,44 @@ function getColumns(data: RowData[]): TableColumnsType<RowData> {
|
||||
showTitle: false,
|
||||
},
|
||||
width: 200,
|
||||
sorter: (a: RowData, b: RowData): number =>
|
||||
String(a.error_percentage).localeCompare(String(b.error_percentage)),
|
||||
sorter: (a: RowData, b: RowData): number => {
|
||||
const aValue = Number(a.error_percentage);
|
||||
const bValue = Number(b.error_percentage);
|
||||
return aValue - bValue;
|
||||
},
|
||||
render: ProgressRender,
|
||||
},
|
||||
{
|
||||
title: 'LATENCY (P95)',
|
||||
title: 'LATENCY (P95) in ms',
|
||||
dataIndex: 'p95_latency',
|
||||
key: 'p95_latency',
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
width: 100,
|
||||
sorter: (a: RowData, b: RowData): number =>
|
||||
String(a.p95_latency).localeCompare(String(b.p95_latency)),
|
||||
sorter: (a: RowData, b: RowData): number => {
|
||||
const aValue = Number(a.p95_latency);
|
||||
const bValue = Number(b.p95_latency);
|
||||
return aValue - bValue;
|
||||
},
|
||||
render: (value: number | string): string => {
|
||||
if (!isNumber(value)) return value.toString();
|
||||
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'THROUGHPUT',
|
||||
title: 'THROUGHPUT (ops/s)',
|
||||
dataIndex: 'throughput',
|
||||
key: 'throughput',
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
width: 100,
|
||||
sorter: (a: RowData, b: RowData): number =>
|
||||
String(a.throughput).localeCompare(String(b.throughput)),
|
||||
sorter: (a: RowData, b: RowData): number => {
|
||||
const aValue = Number(a.throughput);
|
||||
const bValue = Number(b.throughput);
|
||||
return aValue - bValue;
|
||||
},
|
||||
render: (value: number | string): string => {
|
||||
if (!isNumber(value)) return value.toString();
|
||||
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);
|
||||
|
||||
@@ -2,24 +2,16 @@ import './CeleryTaskDetail.style.scss';
|
||||
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Divider, Drawer, Typography } from 'antd';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { useState } from 'react';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
||||
import { createFiltersFromData } from '../CeleryUtils';
|
||||
import { useNavigateToTraces } from '../useNavigateToTraces';
|
||||
|
||||
export type CeleryTaskData = {
|
||||
@@ -39,40 +31,6 @@ export type CeleryTaskDetailProps = {
|
||||
drawerOpen: boolean;
|
||||
};
|
||||
|
||||
const createFiltersFromData = (
|
||||
data: Record<string, any>,
|
||||
): Array<{
|
||||
id: string;
|
||||
key: {
|
||||
key: string;
|
||||
dataType: DataTypes;
|
||||
type: string;
|
||||
isColumn: boolean;
|
||||
isJSON: boolean;
|
||||
id: string;
|
||||
};
|
||||
op: string;
|
||||
value: string;
|
||||
}> => {
|
||||
const excludeKeys = ['A', 'A_without_unit'];
|
||||
|
||||
return Object.entries(data)
|
||||
.filter(([key]) => !excludeKeys.includes(key))
|
||||
.map(([key, value]) => ({
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--string--tag--false`,
|
||||
},
|
||||
op: '=',
|
||||
value: value.toString(),
|
||||
}));
|
||||
};
|
||||
|
||||
export default function CeleryTaskDetail({
|
||||
widgetData,
|
||||
taskData,
|
||||
@@ -85,7 +43,7 @@ export default function CeleryTaskDetail({
|
||||
!!taskData.entity && !!taskData.timeRange[0] && drawerOpen;
|
||||
|
||||
const formatTimestamp = (timestamp: number): string =>
|
||||
dayjs(timestamp * 1000).format('MM-DD-YYYY hh:mm A');
|
||||
dayjs(timestamp).format('DD-MM-YYYY hh:mm A');
|
||||
|
||||
const [totalTask, setTotalTask] = useState(0);
|
||||
|
||||
@@ -93,52 +51,9 @@ export default function CeleryTaskDetail({
|
||||
setTotalTask((graphData?.result?.[0] as any)?.table?.rows.length);
|
||||
};
|
||||
|
||||
// set time range
|
||||
const { minTime, maxTime, selectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const startTime = taskData.timeRange[0];
|
||||
const endTime = taskData.timeRange[1];
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
urlQuery.set(QueryParams.startTime, startTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTime.toString());
|
||||
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
|
||||
if (startTime !== endTime) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTime, endTime]));
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
|
||||
if (selectedTime !== 'custom') {
|
||||
dispatch(UpdateTimeInterval(selectedTime));
|
||||
urlQuery.set(QueryParams.relativeTime, selectedTime);
|
||||
} else {
|
||||
dispatch(UpdateTimeInterval('custom', [minTime / 1e6, maxTime / 1e6]));
|
||||
urlQuery.set(QueryParams.startTime, Math.floor(minTime / 1e6).toString());
|
||||
urlQuery.set(QueryParams.endTime, Math.floor(maxTime / 1e6).toString());
|
||||
}
|
||||
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const navigateToTrace = useNavigateToTraces();
|
||||
|
||||
return (
|
||||
@@ -149,10 +64,8 @@ export default function CeleryTaskDetail({
|
||||
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
|
||||
<div>
|
||||
<Typography.Text className="subtitle">
|
||||
{`${formatTimestamp(taskData.timeRange[0])} ${
|
||||
taskData.timeRange[1]
|
||||
? `- ${formatTimestamp(taskData.timeRange[1])}`
|
||||
: ''
|
||||
{`${formatTimestamp(startTime)} ${
|
||||
endTime ? `- ${formatTimestamp(endTime)}` : ''
|
||||
}`}
|
||||
</Typography.Text>
|
||||
<Divider type="vertical" />
|
||||
@@ -185,8 +98,10 @@ export default function CeleryTaskDetail({
|
||||
...rowData,
|
||||
[taskData.entity]: taskData.value,
|
||||
});
|
||||
navigateToTrace(filters);
|
||||
navigateToTrace(filters, startTime, endTime);
|
||||
}}
|
||||
start={startTime}
|
||||
end={endTime}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,10 @@ import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
|
||||
import {
|
||||
applyCeleryFilterOnWidgetData,
|
||||
getFiltersFromQueryParams,
|
||||
} from '../CeleryUtils';
|
||||
import { useGetGraphCustomSeries } from '../useGetGraphCustomSeries';
|
||||
import {
|
||||
celeryAllStateWidgetData,
|
||||
@@ -72,26 +76,60 @@ function CeleryTaskBar({
|
||||
|
||||
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
|
||||
|
||||
const selectedFilters = useMemo(
|
||||
() =>
|
||||
getFiltersFromQueryParams(
|
||||
QueryParams.taskName,
|
||||
urlQuery,
|
||||
'celery.task_name',
|
||||
),
|
||||
[urlQuery],
|
||||
);
|
||||
|
||||
const celeryAllStateData = useMemo(
|
||||
() => celeryAllStateWidgetData(minTime, maxTime),
|
||||
[minTime, maxTime],
|
||||
);
|
||||
|
||||
const celeryAllStateFilteredData = useMemo(
|
||||
() =>
|
||||
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryAllStateData),
|
||||
[selectedFilters, celeryAllStateData],
|
||||
);
|
||||
|
||||
const celeryFailedStateData = useMemo(
|
||||
() => celeryFailedStateWidgetData(minTime, maxTime),
|
||||
[minTime, maxTime],
|
||||
);
|
||||
|
||||
const celeryFailedStateFilteredData = useMemo(
|
||||
() =>
|
||||
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryFailedStateData),
|
||||
[selectedFilters, celeryFailedStateData],
|
||||
);
|
||||
|
||||
const celeryRetryStateData = useMemo(
|
||||
() => celeryRetryStateWidgetData(minTime, maxTime),
|
||||
[minTime, maxTime],
|
||||
);
|
||||
|
||||
const celeryRetryStateFilteredData = useMemo(
|
||||
() =>
|
||||
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryRetryStateData),
|
||||
[selectedFilters, celeryRetryStateData],
|
||||
);
|
||||
|
||||
const celerySuccessStateData = useMemo(
|
||||
() => celerySuccessStateWidgetData(minTime, maxTime),
|
||||
[minTime, maxTime],
|
||||
);
|
||||
|
||||
const celerySuccessStateFilteredData = useMemo(
|
||||
() =>
|
||||
applyCeleryFilterOnWidgetData(selectedFilters || [], celerySuccessStateData),
|
||||
[selectedFilters, celerySuccessStateData],
|
||||
);
|
||||
|
||||
const onGraphClick = (
|
||||
widgetData: Widgets,
|
||||
xValue: number,
|
||||
@@ -141,7 +179,7 @@ function CeleryTaskBar({
|
||||
<div className="celery-task-graph-grid-content">
|
||||
{barState === CeleryTaskState.All && (
|
||||
<GridCard
|
||||
widget={celeryAllStateData}
|
||||
widget={celeryAllStateFilteredData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={queryEnabled}
|
||||
@@ -153,7 +191,7 @@ function CeleryTaskBar({
|
||||
)}
|
||||
{barState === CeleryTaskState.Failed && (
|
||||
<GridCard
|
||||
widget={celeryFailedStateData}
|
||||
widget={celeryFailedStateFilteredData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={queryEnabled}
|
||||
@@ -165,7 +203,7 @@ function CeleryTaskBar({
|
||||
)}
|
||||
{barState === CeleryTaskState.Retry && (
|
||||
<GridCard
|
||||
widget={celeryRetryStateData}
|
||||
widget={celeryRetryStateFilteredData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={queryEnabled}
|
||||
@@ -177,7 +215,7 @@ function CeleryTaskBar({
|
||||
)}
|
||||
{barState === CeleryTaskState.Successful && (
|
||||
<GridCard
|
||||
widget={celerySuccessStateData}
|
||||
widget={celerySuccessStateFilteredData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={queryEnabled}
|
||||
|
||||
@@ -116,9 +116,8 @@
|
||||
gap: 10px;
|
||||
|
||||
.metric-page-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
|
||||
@@ -144,6 +143,11 @@
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
border: 1px dashed var(--bg-slate-50);
|
||||
border-radius: 4px;
|
||||
padding: 6px 24px 6px 12px;
|
||||
width: max-content;
|
||||
|
||||
.configure-option-Info-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
@@ -241,11 +245,13 @@
|
||||
|
||||
.lightMode {
|
||||
.celery-task-graph-grid-container {
|
||||
.celery-task-graph-grid {
|
||||
.celery-task-graph-worker-count {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: unset;
|
||||
}
|
||||
.celery-task-graph-worker-count {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: unset;
|
||||
}
|
||||
|
||||
.row-panel .row-panel-section .section-title {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,4 +280,8 @@
|
||||
background-color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.configure-option-Info {
|
||||
border: 1px dashed var(--bg-robin-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ function CeleryTaskGraph({
|
||||
openTracesButton,
|
||||
onOpenTraceBtnClick,
|
||||
applyCeleryTaskFilter,
|
||||
customErrorMessage,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
widgetData: Widgets;
|
||||
onClick?: (task: CaptureDataProps) => void;
|
||||
@@ -42,6 +45,9 @@ function CeleryTaskGraph({
|
||||
openTracesButton?: boolean;
|
||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||
applyCeleryTaskFilter?: boolean;
|
||||
customErrorMessage?: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
@@ -116,6 +122,9 @@ function CeleryTaskGraph({
|
||||
openTracesButton={openTracesButton}
|
||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||
version={ENTITY_VERSION_V4}
|
||||
customErrorMessage={customErrorMessage}
|
||||
start={start}
|
||||
end={end}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
@@ -129,6 +138,9 @@ CeleryTaskGraph.defaultProps = {
|
||||
openTracesButton: false,
|
||||
onOpenTraceBtnClick: undefined,
|
||||
applyCeleryTaskFilter: false,
|
||||
customErrorMessage: undefined,
|
||||
start: undefined,
|
||||
end: undefined,
|
||||
};
|
||||
|
||||
export default CeleryTaskGraph;
|
||||
|
||||
@@ -123,11 +123,12 @@ export default function CeleryTaskGraphGrid({
|
||||
key={celeryActiveTasksData.id}
|
||||
widgetData={celeryActiveTasksData}
|
||||
queryEnabled={queryEnabled}
|
||||
customErrorMessage="Enable Flower metrics to view this graph"
|
||||
/>
|
||||
<Card className="celery-task-graph-worker-count">
|
||||
<div className="worker-count-header">
|
||||
<Typography.Text className="worker-count-header-text">
|
||||
Worker Count
|
||||
Worker Online
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="worker-count-text-container">
|
||||
@@ -173,7 +174,7 @@ export default function CeleryTaskGraphGrid({
|
||||
{!collapsedSections.traceBasedGraphs && (
|
||||
<>
|
||||
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
|
||||
<CeleryTaskLatencyGraph onClick={onClick} queryEnabled={queryEnabled} />
|
||||
<CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
|
||||
<div className="celery-task-graph-grid-bottom">
|
||||
{bottomWidgetData.map((widgetData, index) => (
|
||||
<CeleryTaskGraph
|
||||
|
||||
@@ -42,21 +42,7 @@ export const celeryAllStateWidgetData = (
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: 'tasks.tasks.divide',
|
||||
},
|
||||
],
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
@@ -113,7 +99,7 @@ export const celeryRetryStateWidgetData = (
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '6d97eed3',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -179,7 +165,7 @@ export const celeryFailedStateWidgetData = (
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '5983eae2',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -245,7 +231,7 @@ export const celerySuccessStateWidgetData = (
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '000c5a93',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -602,7 +588,7 @@ export const celeryTaskLatencyWidgetData = (
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'p99',
|
||||
timeAggregation: type || 'p99',
|
||||
},
|
||||
],
|
||||
yAxisUnit: 'ns',
|
||||
@@ -686,7 +672,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '9e09c9ed',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -755,7 +741,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: '2330f906',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -822,7 +808,7 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: 'ec3df7b7',
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.state--string--tag--false',
|
||||
@@ -945,33 +931,19 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
aggregateOperator: 'count_distinct',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
id: 'celery.task_name--string--tag--false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: 'celery.task_name',
|
||||
type: 'tag',
|
||||
},
|
||||
op: '=',
|
||||
value: 'tasks.tasks.divide',
|
||||
},
|
||||
],
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
@@ -981,10 +953,10 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
reduceTo: 'last',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
timeAggregation: 'count_distinct',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -998,14 +970,14 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
aggregateOperator: 'count_distinct',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
@@ -1034,10 +1006,10 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
reduceTo: 'last',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
timeAggregation: 'count_distinct',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1051,14 +1023,14 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
aggregateOperator: 'count_distinct',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
@@ -1087,10 +1059,10 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
reduceTo: 'last',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
timeAggregation: 'count_distinct',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1104,13 +1076,13 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
dataType: DataTypes.String,
|
||||
id: 'span_id--string----true',
|
||||
isColumn: true,
|
||||
key: 'span_id',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
aggregateOperator: 'count_distinct',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
@@ -1139,10 +1111,10 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
reduceTo: 'last',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
timeAggregation: 'count_distinct',
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -6,8 +6,11 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/GridCardLayout/config';
|
||||
import GridCard from 'container/GridCardLayout/GridCard';
|
||||
import { Card } from 'container/GridCardLayout/styles';
|
||||
import { Button } from 'container/MetricsApplication/Tabs/styles';
|
||||
import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -16,15 +19,13 @@ import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
|
||||
import {
|
||||
applyCeleryFilterOnWidgetData,
|
||||
createFiltersFromData,
|
||||
getFiltersFromQueryParams,
|
||||
} from '../CeleryUtils';
|
||||
import {
|
||||
celeryTaskLatencyWidgetData,
|
||||
celeryTimeSeriesTablesWidgetData,
|
||||
} from './CeleryTaskGraphUtils';
|
||||
import { useNavigateToTraces } from '../useNavigateToTraces';
|
||||
import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
|
||||
|
||||
interface TabData {
|
||||
label: string;
|
||||
@@ -38,10 +39,8 @@ export enum CeleryTaskGraphState {
|
||||
}
|
||||
|
||||
function CeleryTaskLatencyGraph({
|
||||
onClick,
|
||||
queryEnabled,
|
||||
}: {
|
||||
onClick: (task: CaptureDataProps) => void;
|
||||
queryEnabled: boolean;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
@@ -106,31 +105,51 @@ function CeleryTaskLatencyGraph({
|
||||
[celeryTaskLatencyData, selectedFilters],
|
||||
);
|
||||
|
||||
const onGraphClick = (
|
||||
xValue: number,
|
||||
_yValue: number,
|
||||
_mouseX: number,
|
||||
_mouseY: number,
|
||||
data?: {
|
||||
[key: string]: string;
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
const [entityData, setEntityData] = useState<{
|
||||
entity: string;
|
||||
value: string;
|
||||
}>();
|
||||
|
||||
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
||||
setSelectedTimeStamp(selectTime);
|
||||
}, []);
|
||||
|
||||
const onGraphClick = useCallback(
|
||||
(type: string): OnClickPluginOpts['onClick'] => (
|
||||
xValue,
|
||||
yValue,
|
||||
mouseX,
|
||||
mouseY,
|
||||
data,
|
||||
): Promise<void> => {
|
||||
const [firstDataPoint] = Object.entries(data || {});
|
||||
const [entity, value] = firstDataPoint;
|
||||
setEntityData({
|
||||
entity,
|
||||
value,
|
||||
});
|
||||
|
||||
return onGraphClickHandler(handleSetTimeStamp)(
|
||||
xValue,
|
||||
yValue,
|
||||
mouseX,
|
||||
mouseY,
|
||||
type,
|
||||
);
|
||||
},
|
||||
): void => {
|
||||
const { start, end } = getStartAndEndTimesInMilliseconds(xValue);
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
|
||||
// Extract entity and value from data
|
||||
const [firstDataPoint] = Object.entries(data || {});
|
||||
const [entity, value] = (firstDataPoint || ([] as unknown)) as [
|
||||
string,
|
||||
string,
|
||||
];
|
||||
const navigateToTraces = useNavigateToTraces();
|
||||
|
||||
onClick?.({
|
||||
entity,
|
||||
value,
|
||||
timeRange: [start, end],
|
||||
widgetData: celeryTimeSeriesTablesWidgetData(entity, value, 'Task Latency'),
|
||||
const goToTraces = useCallback(() => {
|
||||
const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
|
||||
const filters = createFiltersFromData({
|
||||
[entityData?.entity as string]: entityData?.value,
|
||||
});
|
||||
};
|
||||
navigateToTraces(filters, start, end, true);
|
||||
}, [entityData, navigateToTraces, selectedTimeStamp]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -161,32 +180,62 @@ function CeleryTaskLatencyGraph({
|
||||
</Row>
|
||||
<div className="celery-task-graph-grid-content">
|
||||
{graphState === CeleryTaskGraphState.P99 && (
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_p99_latency_button"
|
||||
onClick={goToTraces}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p99_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{graphState === CeleryTaskGraphState.P95 && (
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_p95_latency_button"
|
||||
onClick={goToTraces}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p95_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{graphState === CeleryTaskGraphState.P90 && (
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_p90_latency_button"
|
||||
onClick={goToTraces}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={updatedWidgetData}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={onGraphClick('Celery_p90_latency')}
|
||||
isQueryEnabled={queryEnabled}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
import './CeleryTaskGraph.style.scss';
|
||||
|
||||
import { Col, Row } from 'antd';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
applyCeleryFilterOnWidgetData,
|
||||
getFiltersFromQueryParams,
|
||||
} from '../CeleryUtils';
|
||||
import {
|
||||
celeryAllStateCountWidgetData,
|
||||
celeryFailedStateCountWidgetData,
|
||||
@@ -42,16 +48,29 @@ function CeleryTaskStateGraphConfig({
|
||||
setBarState(key as CeleryTaskState);
|
||||
};
|
||||
|
||||
const { values, isLoading, isError } = useGetValueFromWidget(
|
||||
[
|
||||
celeryAllStateCountWidgetData,
|
||||
celeryFailedStateCountWidgetData,
|
||||
celeryRetryStateCountWidgetData,
|
||||
celerySuccessStateCountWidgetData,
|
||||
],
|
||||
['celery-task-states'],
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const selectedFilters = useMemo(
|
||||
() =>
|
||||
getFiltersFromQueryParams(
|
||||
QueryParams.taskName,
|
||||
urlQuery,
|
||||
'celery.task_name',
|
||||
),
|
||||
[urlQuery],
|
||||
);
|
||||
|
||||
const widgetData = [
|
||||
celeryAllStateCountWidgetData,
|
||||
celeryFailedStateCountWidgetData,
|
||||
celeryRetryStateCountWidgetData,
|
||||
celerySuccessStateCountWidgetData,
|
||||
].map((data) => applyCeleryFilterOnWidgetData(selectedFilters || [], data));
|
||||
|
||||
const { values, isLoading, isError } = useGetValueFromWidget(widgetData, [
|
||||
'celery-task-states',
|
||||
]);
|
||||
|
||||
return (
|
||||
<Row className="celery-task-states">
|
||||
{tabs.map((tab, index) => (
|
||||
@@ -66,7 +85,13 @@ function CeleryTaskStateGraphConfig({
|
||||
<div className="celery-task-states__label-wrapper">
|
||||
<div className="celery-task-states__label">{tab.label}</div>
|
||||
<div className="celery-task-states__value">
|
||||
{isLoading ? '-' : isError ? '-' : values[index]}
|
||||
{isLoading
|
||||
? '-'
|
||||
: isError
|
||||
? '-'
|
||||
: Number.isNaN(values[index])
|
||||
? '-'
|
||||
: Math.round(Number(values[index]))}
|
||||
</div>
|
||||
</div>
|
||||
{tab.key === barState && <div className="celery-task-states__indicator" />}
|
||||
|
||||
@@ -90,3 +90,40 @@ export const paths = (
|
||||
|
||||
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
|
||||
};
|
||||
|
||||
export const createFiltersFromData = (
|
||||
data: Record<string, any>,
|
||||
): Array<{
|
||||
id: string;
|
||||
key: {
|
||||
key: string;
|
||||
dataType: DataTypes;
|
||||
type: string;
|
||||
isColumn: boolean;
|
||||
isJSON: boolean;
|
||||
id: string;
|
||||
};
|
||||
op: string;
|
||||
value: string;
|
||||
}> => {
|
||||
const excludeKeys = ['A', 'A_without_unit'];
|
||||
|
||||
return (
|
||||
Object.entries(data)
|
||||
.filter(([key]) => !excludeKeys.includes(key))
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
.map(([key, value]) => ({
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--string--tag--false`,
|
||||
},
|
||||
op: '=',
|
||||
value: value.toString(),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ export function useNavigateToTraces(): (
|
||||
filters: TagFilterItem[],
|
||||
startTime?: number,
|
||||
endTime?: number,
|
||||
sameTab?: boolean,
|
||||
) => void {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
@@ -38,7 +39,12 @@ export function useNavigateToTraces(): (
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
(filters: TagFilterItem[], startTime?: number, endTime?: number): void => {
|
||||
(
|
||||
filters: TagFilterItem[],
|
||||
startTime?: number,
|
||||
endTime?: number,
|
||||
sameTab?: boolean,
|
||||
): void => {
|
||||
const urlParams = new URLSearchParams();
|
||||
if (startTime && endTime) {
|
||||
urlParams.set(QueryParams.startTime, startTime.toString());
|
||||
@@ -58,7 +64,7 @@ export function useNavigateToTraces(): (
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
window.open(newTraceExplorerPath, sameTab ? '_self' : '_blank');
|
||||
},
|
||||
[minTime, maxTime, prepareQuery],
|
||||
);
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
.go-to-docs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 64px;
|
||||
gap: 44px;
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -23,4 +23,5 @@ export enum FeatureKeys {
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
AWS_INTEGRATION = 'AWS_INTEGRATION',
|
||||
}
|
||||
|
||||
@@ -41,5 +41,6 @@ export const REACT_QUERY_KEY = {
|
||||
AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG',
|
||||
AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG',
|
||||
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
|
||||
AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS',
|
||||
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
|
||||
};
|
||||
|
||||
@@ -65,6 +65,9 @@ const ROUTES = {
|
||||
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
|
||||
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
|
||||
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview',
|
||||
METRICS_EXPLORER: '/metrics-explorer/summary',
|
||||
METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer',
|
||||
METRICS_EXPLORER_VIEWS: '/metrics-explorer/views',
|
||||
} as const;
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -250,7 +250,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (
|
||||
!isFetchingActiveLicenseV3 &&
|
||||
!isNull(activeLicenseV3) &&
|
||||
activeLicenseV3?.event_queue?.event === LicenseEvent.FAILED_PAYMENT
|
||||
activeLicenseV3?.event_queue?.event === LicenseEvent.DEFAULT
|
||||
) {
|
||||
setShowPaymentFailedWarning(true);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ function Header(): JSX.Element {
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<div className="cloud-header__breadcrumb-title">AWS web services</div>
|
||||
<div className="cloud-header__breadcrumb-title">
|
||||
Amazon Web Services
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -21,7 +21,7 @@ function HeroSection(): JSX.Element {
|
||||
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
|
||||
</div>
|
||||
<div className="hero-section__details">
|
||||
<div className="title">AWS Web Services</div>
|
||||
<div className="title">Amazon Web Services</div>
|
||||
<div className="description">
|
||||
One-click setup for AWS monitoring with SigNoz
|
||||
</div>
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
.hero-section__actions {
|
||||
margin-top: 12px;
|
||||
.hero-section {
|
||||
&__actions {
|
||||
margin-top: 12px;
|
||||
|
||||
&-with-account {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
.hero-section__action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.hero-section__action-button {
|
||||
font-family: 'Inter';
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
padding: 8px 17px;
|
||||
|
||||
&.primary {
|
||||
background: var(--bg-robin-500);
|
||||
border: none;
|
||||
color: var(--bg-vanilla-100);
|
||||
&-with-account {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
&__input-skeleton {
|
||||
width: 300px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__new-account-button-skeleton {
|
||||
width: 180px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&__account-settings-button-skeleton {
|
||||
width: 140px;
|
||||
}
|
||||
&__action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--bg-ink-300);
|
||||
color: var(--bg-vanilla-100);
|
||||
gap: 8px;
|
||||
}
|
||||
&__action-button {
|
||||
font-family: 'Inter';
|
||||
border-radius: 2px;
|
||||
background: var(--bg-slate-400);
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
padding: 8px 17px;
|
||||
|
||||
&.primary {
|
||||
background: var(--bg-robin-500);
|
||||
border: none;
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--bg-ink-300);
|
||||
color: var(--bg-vanilla-100);
|
||||
border-radius: 2px;
|
||||
background: var(--bg-slate-400);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import './AccountActions.style.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Select } from 'antd';
|
||||
import { Button, Select, Skeleton } from 'antd';
|
||||
import { SelectProps } from 'antd/lib';
|
||||
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
@@ -53,15 +53,100 @@ const getAccountById = (
|
||||
): CloudAccount | null =>
|
||||
accounts.find((account) => account.cloud_account_id === accountId) || null;
|
||||
|
||||
function AccountActionsRenderer({
|
||||
accounts,
|
||||
isLoading,
|
||||
activeAccount,
|
||||
selectOptions,
|
||||
onAccountChange,
|
||||
onIntegrationModalOpen,
|
||||
onAccountSettingsModalOpen,
|
||||
}: {
|
||||
accounts: CloudAccount[] | undefined;
|
||||
isLoading: boolean;
|
||||
activeAccount: CloudAccount | null;
|
||||
selectOptions: SelectProps['options'];
|
||||
onAccountChange: (value: string) => void;
|
||||
onIntegrationModalOpen: () => void;
|
||||
onAccountSettingsModalOpen: () => void;
|
||||
}): JSX.Element {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="hero-section__actions-with-account">
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="large"
|
||||
block
|
||||
className="hero-section__input-skeleton"
|
||||
/>
|
||||
<div className="hero-section__action-buttons">
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="large"
|
||||
className="hero-section__new-account-button-skeleton"
|
||||
/>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="large"
|
||||
className="hero-section__account-settings-button-skeleton"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (accounts?.length) {
|
||||
return (
|
||||
<div className="hero-section__actions-with-account">
|
||||
<Select
|
||||
value={`Account: ${activeAccount?.cloud_account_id}`}
|
||||
options={selectOptions}
|
||||
rootClassName="cloud-account-selector"
|
||||
placeholder="Select AWS Account"
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
optionRender={(option): JSX.Element =>
|
||||
renderOption(option, activeAccount?.cloud_account_id)
|
||||
}
|
||||
onChange={onAccountChange}
|
||||
/>
|
||||
<div className="hero-section__action-buttons">
|
||||
<Button
|
||||
type="primary"
|
||||
className="hero-section__action-button primary"
|
||||
onClick={onIntegrationModalOpen}
|
||||
>
|
||||
Add New AWS Account
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
className="hero-section__action-button secondary"
|
||||
onClick={onAccountSettingsModalOpen}
|
||||
>
|
||||
Account Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
className="hero-section__action-button primary"
|
||||
onClick={onIntegrationModalOpen}
|
||||
>
|
||||
Integrate Now
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountActions(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const navigate = useNavigate();
|
||||
const { data: accounts } = useAwsAccounts();
|
||||
const { data: accounts, isLoading } = useAwsAccounts();
|
||||
|
||||
const initialAccount = useMemo(
|
||||
() =>
|
||||
accounts?.length
|
||||
? getAccountById(accounts, urlQuery.get('accountId') || '') || accounts[0]
|
||||
? getAccountById(accounts, urlQuery.get('cloudAccountId') || '') ||
|
||||
accounts[0]
|
||||
: null,
|
||||
[accounts, urlQuery],
|
||||
);
|
||||
@@ -74,7 +159,7 @@ function AccountActions(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (initialAccount !== null) {
|
||||
setActiveAccount(initialAccount);
|
||||
urlQuery.set('accountId', initialAccount.cloud_account_id);
|
||||
urlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
|
||||
navigate({ search: urlQuery.toString() });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -98,60 +183,35 @@ function AccountActions(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="hero-section__actions">
|
||||
{accounts?.length ? (
|
||||
<div className="hero-section__actions-with-account">
|
||||
<Select
|
||||
value={`Account: ${activeAccount?.cloud_account_id}`}
|
||||
options={selectOptions}
|
||||
rootClassName="cloud-account-selector"
|
||||
placeholder="Select AWS Account"
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
optionRender={(option): JSX.Element =>
|
||||
renderOption(option, activeAccount?.cloud_account_id)
|
||||
}
|
||||
onChange={(value): void => {
|
||||
setActiveAccount(getAccountById(accounts, value));
|
||||
urlQuery.set('accountId', value);
|
||||
navigate({ search: urlQuery.toString() });
|
||||
}}
|
||||
/>
|
||||
<div className="hero-section__action-buttons">
|
||||
<Button
|
||||
type="primary"
|
||||
className="hero-section__action-button primary"
|
||||
onClick={(): void => setIsIntegrationModalOpen(true)}
|
||||
>
|
||||
Add New AWS Account
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
className="hero-section__action-button secondary"
|
||||
onClick={(): void => setIsAccountSettingsModalOpen(true)}
|
||||
>
|
||||
Account Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
className="hero-section__action-button primary"
|
||||
onClick={(): void => setIsIntegrationModalOpen(true)}
|
||||
>
|
||||
Integrate Now
|
||||
</Button>
|
||||
<AccountActionsRenderer
|
||||
accounts={accounts}
|
||||
isLoading={isLoading}
|
||||
activeAccount={activeAccount}
|
||||
selectOptions={selectOptions}
|
||||
onAccountChange={(value): void => {
|
||||
if (accounts) {
|
||||
setActiveAccount(getAccountById(accounts, value));
|
||||
urlQuery.set('cloudAccountId', value);
|
||||
navigate({ search: urlQuery.toString() });
|
||||
}
|
||||
}}
|
||||
onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)}
|
||||
onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)}
|
||||
/>
|
||||
|
||||
{isIntegrationModalOpen && (
|
||||
<CloudAccountSetupModal
|
||||
onClose={(): void => setIsIntegrationModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CloudAccountSetupModal
|
||||
isOpen={isIntegrationModalOpen}
|
||||
onClose={(): void => setIsIntegrationModalOpen(false)}
|
||||
/>
|
||||
|
||||
<AccountSettingsModal
|
||||
isOpen={isAccountSettingsModalOpen}
|
||||
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
||||
account={activeAccount as CloudAccount}
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
{isAccountSettingsModalOpen && (
|
||||
<AccountSettingsModal
|
||||
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
||||
account={activeAccount as CloudAccount}
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,27 +2,27 @@ import './AccountSettingsModal.style.scss';
|
||||
|
||||
import { Form, Select, Switch } from 'antd';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
getRegionPreviewText,
|
||||
useAccountSettingsModal,
|
||||
} from 'hooks/integrations/aws/useAccountSettingsModal';
|
||||
import IntergrationsUninstallBar from 'pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar';
|
||||
import { ConnectionStates } from 'pages/Integrations/IntegrationDetailPage/TestConnection';
|
||||
import { AWS_INTEGRATION } from 'pages/Integrations/IntegrationsList';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
import { CloudAccount } from '../../ServicesSection/types';
|
||||
import { RegionSelector } from './RegionSelector';
|
||||
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
|
||||
|
||||
interface AccountSettingsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
account: CloudAccount;
|
||||
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
||||
}
|
||||
|
||||
function AccountSettingsModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
account,
|
||||
setActiveAccount,
|
||||
@@ -42,6 +42,16 @@ function AccountSettingsModal({
|
||||
handleClose,
|
||||
} = useAccountSettingsModal({ onClose, account, setActiveAccount });
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const handleRemoveIntegrationAccountSuccess = (): void => {
|
||||
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
|
||||
urlQuery.delete('cloudAccountId');
|
||||
handleClose();
|
||||
history.replace({ search: urlQuery.toString() });
|
||||
};
|
||||
|
||||
const renderRegionSelector = useCallback(() => {
|
||||
if (isRegionSelectOpen) {
|
||||
return (
|
||||
@@ -120,7 +130,7 @@ function AccountSettingsModal({
|
||||
|
||||
return (
|
||||
<SignozModal
|
||||
open={isOpen}
|
||||
open
|
||||
title={modalTitle}
|
||||
onCancel={handleClose}
|
||||
onOk={handleSubmit}
|
||||
@@ -164,12 +174,9 @@ function AccountSettingsModal({
|
||||
</Form.Item>
|
||||
|
||||
<div className="integration-detail-content">
|
||||
<IntergrationsUninstallBar
|
||||
integrationTitle={AWS_INTEGRATION.title}
|
||||
integrationId={AWS_INTEGRATION.id}
|
||||
onUnInstallSuccess={handleClose}
|
||||
removeIntegrationTitle="Remove"
|
||||
connectionStatus={ConnectionStates.Connected}
|
||||
<RemoveIntegrationAccount
|
||||
accountId={account?.id}
|
||||
onRemoveIntegrationAccountSuccess={handleRemoveIntegrationAccountSuccess}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,9 @@ import './CloudAccountSetupModal.style.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
|
||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
import {
|
||||
ActiveViewEnum,
|
||||
@@ -18,7 +16,6 @@ import { RegionSelector } from './RegionSelector';
|
||||
import { SuccessView } from './SuccessView';
|
||||
|
||||
function CloudAccountSetupModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: IntegrationModalProps): JSX.Element {
|
||||
const {
|
||||
@@ -41,6 +38,8 @@ function CloudAccountSetupModal({
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
} = useIntegrationModal({ onClose });
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
@@ -71,6 +70,8 @@ function CloudAccountSetupModal({
|
||||
accountId={accountId}
|
||||
selectedDeploymentRegion={selectedDeploymentRegion}
|
||||
handleRegionChange={handleRegionChange}
|
||||
connectionParams={connectionParams}
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
@@ -86,6 +87,8 @@ function CloudAccountSetupModal({
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
]);
|
||||
@@ -96,11 +99,6 @@ function CloudAccountSetupModal({
|
||||
[selectedRegions, allRegions],
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const handleGoToDashboards = useCallback((): void => {
|
||||
navigate(ROUTES.ALL_DASHBOARD);
|
||||
}, [navigate]);
|
||||
|
||||
const getModalConfig = useCallback(() => {
|
||||
// Handle success state first
|
||||
if (modalState === ModalStateEnum.SUCCESS) {
|
||||
@@ -108,11 +106,11 @@ function CloudAccountSetupModal({
|
||||
title: 'AWS Webservice Integration',
|
||||
okText: (
|
||||
<div className="cloud-account-setup-success-view__footer-button">
|
||||
Go to Dashboards
|
||||
Continue
|
||||
</div>
|
||||
),
|
||||
block: true,
|
||||
onOk: handleGoToDashboards,
|
||||
onOk: handleClose,
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
disabled: false,
|
||||
};
|
||||
@@ -151,7 +149,7 @@ function CloudAccountSetupModal({
|
||||
isLoading,
|
||||
isGeneratingUrl,
|
||||
activeView,
|
||||
handleGoToDashboards,
|
||||
handleClose,
|
||||
setActiveView,
|
||||
]);
|
||||
|
||||
@@ -159,7 +157,7 @@ function CloudAccountSetupModal({
|
||||
|
||||
return (
|
||||
<SignozModal
|
||||
open={isOpen}
|
||||
open
|
||||
className="cloud-account-setup-modal"
|
||||
title={modalConfig.title}
|
||||
onCancel={handleClose}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
MonitoringRegionsSection,
|
||||
RegionDeploymentSection,
|
||||
} from './IntegrateNowFormSections';
|
||||
import RenderConnectionFields from './RenderConnectionParams';
|
||||
|
||||
const allRegions = (): string[] =>
|
||||
regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
|
||||
@@ -35,6 +36,8 @@ export function RegionForm({
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
}: RegionFormProps): JSX.Element {
|
||||
const startTimeRef = useRef(Date.now());
|
||||
const refetchInterval = 10 * 1000;
|
||||
@@ -88,6 +91,11 @@ export function RegionForm({
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
<ComplianceNote />
|
||||
<RenderConnectionFields
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
connectionParams={connectionParams}
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
.remove-integration-account {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(218, 85, 101, 0.2);
|
||||
background: rgba(218, 85, 101, 0.06);
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--bg-cherry-500);
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
color: var(--bg-cherry-300);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-cherry-500);
|
||||
border: none;
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 13.3px; /* 110.833% */
|
||||
padding: 9px 13px;
|
||||
.ant-btn-icon {
|
||||
margin-inline-end: 4px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&.ant-btn-default {
|
||||
color: var(--bg-vanilla-300) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import './RemoveIntegrationAccount.scss';
|
||||
|
||||
import { Button, Modal } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import removeAwsIntegrationAccount from 'api/Integrations/removeAwsIntegrationAccount';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { X } from 'lucide-react';
|
||||
import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils';
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
|
||||
function RemoveIntegrationAccount({
|
||||
accountId,
|
||||
onRemoveIntegrationAccountSuccess,
|
||||
}: {
|
||||
accountId: string;
|
||||
onRemoveIntegrationAccountSuccess: () => void;
|
||||
}): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const showModal = (): void => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const {
|
||||
mutate: removeIntegration,
|
||||
isLoading: isRemoveIntegrationLoading,
|
||||
} = useMutation(removeAwsIntegrationAccount, {
|
||||
onSuccess: () => {
|
||||
onRemoveIntegrationAccountSuccess?.();
|
||||
setIsModalOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
const handleOk = (): void => {
|
||||
logEvent(INTEGRATION_TELEMETRY_EVENTS.AWS_INTEGRATION_ACCOUNT_REMOVED, {
|
||||
accountId,
|
||||
});
|
||||
removeIntegration(accountId);
|
||||
};
|
||||
|
||||
const handleCancel = (): void => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="remove-integration-account">
|
||||
<div className="remove-integration-account__header">
|
||||
<div className="remove-integration-account__title">Remove Integration</div>
|
||||
<div className="remove-integration-account__subtitle">
|
||||
Removing this integration won't delete any existing data but will stop
|
||||
collecting new data from AWS.
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="remove-integration-account__button"
|
||||
icon={<X size={14} />}
|
||||
onClick={(): void => showModal()}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
<Modal
|
||||
className="remove-integration-modal"
|
||||
open={isModalOpen}
|
||||
title="Remove integration"
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okText="Remove Integration"
|
||||
okButtonProps={{
|
||||
danger: true,
|
||||
disabled: isRemoveIntegrationLoading,
|
||||
}}
|
||||
>
|
||||
<div className="remove-integration-modal__text">
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your AWS account within the next ~15 minutes
|
||||
(cloudformation stacks named signoz-integration-telemetry-collection in
|
||||
enabled regions). <br />
|
||||
<br />
|
||||
After that, you can delete the cloudformation stack that was created
|
||||
manually when connecting this account.
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RemoveIntegrationAccount;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||
|
||||
function RenderConnectionFields({
|
||||
isConnectionParamsLoading,
|
||||
connectionParams,
|
||||
isFormDisabled,
|
||||
}: {
|
||||
isConnectionParamsLoading?: boolean;
|
||||
connectionParams?: ConnectionParams | null;
|
||||
isFormDisabled?: boolean;
|
||||
}): JSX.Element | null {
|
||||
if (
|
||||
isConnectionParamsLoading ||
|
||||
(!!connectionParams?.ingestion_url &&
|
||||
!!connectionParams?.ingestion_key &&
|
||||
!!connectionParams?.signoz_api_url)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Item name="connection_params">
|
||||
{!connectionParams?.ingestion_url && (
|
||||
<Form.Item
|
||||
name="ingestion_url"
|
||||
label="Ingestion URL"
|
||||
rules={[{ required: true, message: 'Please enter ingestion URL' }]}
|
||||
>
|
||||
<Input placeholder="Enter ingestion URL" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{!connectionParams?.ingestion_key && (
|
||||
<Form.Item
|
||||
name="ingestion_key"
|
||||
label="Ingestion Key"
|
||||
rules={[{ required: true, message: 'Please enter ingestion key' }]}
|
||||
>
|
||||
<Input placeholder="Enter ingestion key" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{!connectionParams?.signoz_api_url && (
|
||||
<Form.Item
|
||||
name="signoz_api_url"
|
||||
label="SigNoz API URL"
|
||||
rules={[{ required: true, message: 'Please enter SigNoz API URL' }]}
|
||||
>
|
||||
<Input placeholder="Enter SigNoz API URL" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
RenderConnectionFields.defaultProps = {
|
||||
connectionParams: null,
|
||||
isFormDisabled: false,
|
||||
isConnectionParamsLoading: false,
|
||||
};
|
||||
|
||||
export default RenderConnectionFields;
|
||||
@@ -53,23 +53,18 @@ export function SuccessView(): JSX.Element {
|
||||
WHAT NEXT
|
||||
</h4>
|
||||
<div className="what-next-items-wrapper">
|
||||
{[
|
||||
'Understand your AWS services with SigNoz’s out-of-the-box dashboards',
|
||||
'Set up alerts for real-time monitoring.',
|
||||
'Track logs and traces.',
|
||||
].map((item) => (
|
||||
<Alert
|
||||
key={item}
|
||||
message={
|
||||
<div className="what-next-items-wrapper__item">
|
||||
<div className="what-next-item-bullet-icon">•</div>
|
||||
<div className="what-next-item-text">{item}</div>
|
||||
<Alert
|
||||
message={
|
||||
<div className="what-next-items-wrapper__item">
|
||||
<div className="what-next-item-bullet-icon">•</div>
|
||||
<div className="what-next-item-text">
|
||||
Set up your AWS services effortlessly under your enabled account.
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
className="what-next-items-wrapper__item"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
className="what-next-items-wrapper__item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FormInstance } from 'antd';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||
|
||||
export enum ActiveViewEnum {
|
||||
SELECT_REGIONS = 'select-regions',
|
||||
@@ -25,9 +26,10 @@ export interface RegionFormProps {
|
||||
accountId?: string;
|
||||
selectedDeploymentRegion: string | undefined;
|
||||
handleRegionChange: (value: string) => void;
|
||||
connectionParams?: ConnectionParams;
|
||||
isConnectionParamsLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface IntegrationModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ServiceData } from './types';
|
||||
|
||||
function DashboardItem({
|
||||
@@ -5,8 +7,8 @@ function DashboardItem({
|
||||
}: {
|
||||
dashboard: ServiceData['assets']['dashboards'][number];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="cloud-service-dashboard-item">
|
||||
const content = (
|
||||
<>
|
||||
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
|
||||
<div className="cloud-service-dashboard-item__preview">
|
||||
<img
|
||||
@@ -15,6 +17,18 @@ function DashboardItem({
|
||||
className="cloud-service-dashboard-item__preview-image"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="cloud-service-dashboard-item">
|
||||
{dashboard.url ? (
|
||||
<Link to={dashboard.url} className="cloud-service-dashboard-item__link">
|
||||
{content}
|
||||
</Link>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tabs, TabsProps } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import Spinner from 'components/Spinner';
|
||||
@@ -8,8 +7,7 @@ import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/t
|
||||
import dayjs from 'dayjs';
|
||||
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Wrench } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import ConfigureServiceModal from './ConfigureServiceModal';
|
||||
|
||||
@@ -38,7 +36,7 @@ const getStatus = (
|
||||
function ServiceStatus({
|
||||
serviceStatus,
|
||||
}: {
|
||||
serviceStatus: IServiceStatus | null;
|
||||
serviceStatus: IServiceStatus | undefined;
|
||||
}): JSX.Element {
|
||||
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
|
||||
const metricsLastReceivedTimestamp =
|
||||
@@ -54,7 +52,7 @@ function ServiceStatus({
|
||||
|
||||
function ServiceDetails(): JSX.Element | null {
|
||||
const urlQuery = useUrlQuery();
|
||||
const accountId = urlQuery.get('accountId');
|
||||
const cloudAccountId = urlQuery.get('cloudAccountId');
|
||||
const serviceId = urlQuery.get('service');
|
||||
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
|
||||
false,
|
||||
@@ -62,7 +60,24 @@ function ServiceDetails(): JSX.Element | null {
|
||||
|
||||
const { data: serviceDetailsData, isLoading } = useServiceDetails(
|
||||
serviceId || '',
|
||||
accountId || undefined,
|
||||
cloudAccountId || undefined,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { config, supported_signals } = serviceDetailsData ?? {};
|
||||
|
||||
const totalSupportedSignals = Object.entries(supported_signals || {}).filter(
|
||||
([, value]) => !!value,
|
||||
).length;
|
||||
const enabledSignals = useMemo(
|
||||
() =>
|
||||
Object.values(config || {}).filter((item) => item && item.enabled).length,
|
||||
[config],
|
||||
);
|
||||
|
||||
const isAnySignalConfigured = useMemo(
|
||||
() => !!config?.logs?.enabled || !!config?.metrics?.enabled,
|
||||
[config],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -96,16 +111,22 @@ function ServiceDetails(): JSX.Element | null {
|
||||
<div className="service-details__title-bar">
|
||||
<div className="service-details__details-title">Details</div>
|
||||
<div className="service-details__right-actions">
|
||||
{serviceDetailsData?.status && (
|
||||
<ServiceStatus serviceStatus={serviceDetailsData.status} />
|
||||
)}
|
||||
{!!accountId && (
|
||||
<ServiceStatus serviceStatus={serviceDetailsData.status} />
|
||||
|
||||
{!!cloudAccountId && isAnySignalConfigured ? (
|
||||
<Button
|
||||
className="configure-button"
|
||||
className="configure-button configure-button--default"
|
||||
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||
>
|
||||
<Wrench size={12} color={Color.BG_VANILLA_400} />
|
||||
Configure
|
||||
Configure ({enabledSignals}/{totalSupportedSignals})
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
className="configure-button configure-button--primary"
|
||||
onClick={(): void => setIsConfigureServiceModalOpen(true)}
|
||||
>
|
||||
Enable Service
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -119,15 +140,17 @@ function ServiceDetails(): JSX.Element | null {
|
||||
<div className="service-details__tabs">
|
||||
<Tabs items={tabItems} />
|
||||
</div>
|
||||
<ConfigureServiceModal
|
||||
isOpen={isConfigureServiceModalOpen}
|
||||
onClose={(): void => setIsConfigureServiceModalOpen(false)}
|
||||
serviceName={serviceDetailsData.title}
|
||||
serviceId={serviceId || ''}
|
||||
cloudAccountId={accountId || ''}
|
||||
initialConfig={serviceDetailsData.config}
|
||||
supportedSignals={serviceDetailsData.supported_signals || {}}
|
||||
/>
|
||||
{isConfigureServiceModalOpen && (
|
||||
<ConfigureServiceModal
|
||||
isOpen
|
||||
onClose={(): void => setIsConfigureServiceModalOpen(false)}
|
||||
serviceName={serviceDetailsData.title}
|
||||
serviceId={serviceId || ''}
|
||||
cloudAccountId={cloudAccountId || ''}
|
||||
initialConfig={serviceDetailsData.config}
|
||||
supportedSignals={serviceDetailsData.supported_signals || {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
|
||||
import ServiceItem from './ServiceItem';
|
||||
|
||||
interface ServicesListProps {
|
||||
accountId: string;
|
||||
cloudAccountId: string;
|
||||
filter: 'all_services' | 'enabled' | 'available';
|
||||
}
|
||||
|
||||
function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
||||
function ServicesList({
|
||||
cloudAccountId,
|
||||
filter,
|
||||
}: ServicesListProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const navigate = useNavigate();
|
||||
const { data: services = [], isLoading } = useGetAccountServices(accountId);
|
||||
const { data: services = [], isLoading } = useGetAccountServices(
|
||||
cloudAccountId,
|
||||
);
|
||||
const activeService = urlQuery.get('service');
|
||||
|
||||
const handleServiceClick = (serviceId: string): void => {
|
||||
urlQuery.set('service', serviceId);
|
||||
navigate({ search: urlQuery.toString() });
|
||||
};
|
||||
const handleActiveService = useCallback(
|
||||
(serviceId: string): void => {
|
||||
urlQuery.set('service', serviceId);
|
||||
navigate({ search: urlQuery.toString() });
|
||||
},
|
||||
[navigate, urlQuery],
|
||||
);
|
||||
|
||||
const filteredServices = useMemo(() => {
|
||||
if (filter === 'all_services') return services;
|
||||
@@ -32,6 +40,12 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
||||
});
|
||||
}, [services, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeService || !services?.length) return;
|
||||
|
||||
handleActiveService(services[0].id);
|
||||
}, [services, activeService, handleActiveService]);
|
||||
|
||||
if (isLoading) return <Spinner size="large" height="25vh" />;
|
||||
if (!services) return <div>No services found</div>;
|
||||
|
||||
@@ -41,7 +55,7 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
|
||||
<ServiceItem
|
||||
key={service.id}
|
||||
service={service}
|
||||
onClick={handleServiceClick}
|
||||
onClick={handleActiveService}
|
||||
isActive={service.id === activeService}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -135,18 +135,25 @@
|
||||
color: var(--bg-cherry-400);
|
||||
}
|
||||
}
|
||||
|
||||
.configure-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--bg-vanilla-400);
|
||||
background: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 10px; /* 83.333% */
|
||||
letter-spacing: 0.12px;
|
||||
font-weight: 500;
|
||||
width: 116px;
|
||||
box-shadow: none;
|
||||
&--default {
|
||||
color: var(--bg-vanilla-400);
|
||||
background: var(--bg-slate-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
&--primary {
|
||||
background-color: var(--bg-robin-500);
|
||||
color: var(--bg-vanilla-100);
|
||||
font-weight: 500;
|
||||
color: var(--Vanilla-100, #fff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ export enum ServiceFilterType {
|
||||
}
|
||||
|
||||
interface ServicesFilterProps {
|
||||
accountId: string;
|
||||
cloudAccountId: string;
|
||||
onFilterChange: (value: ServiceFilterType) => void;
|
||||
}
|
||||
|
||||
function ServicesFilter({
|
||||
accountId,
|
||||
cloudAccountId,
|
||||
onFilterChange,
|
||||
}: ServicesFilterProps): JSX.Element | null {
|
||||
const { data: services, isLoading } = useQuery(
|
||||
[REACT_QUERY_KEY.AWS_SERVICES, accountId],
|
||||
() => getAwsServices(accountId),
|
||||
[REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId],
|
||||
() => getAwsServices(cloudAccountId),
|
||||
);
|
||||
|
||||
const { enabledCount, availableCount } = useMemo(() => {
|
||||
@@ -77,7 +77,7 @@ function ServicesFilter({
|
||||
|
||||
function ServicesSection(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const accountId = urlQuery.get('accountId') || '';
|
||||
const cloudAccountId = urlQuery.get('cloudAccountId') || '';
|
||||
|
||||
const [activeFilter, setActiveFilter] = useState<
|
||||
'all_services' | 'enabled' | 'available'
|
||||
@@ -86,8 +86,11 @@ function ServicesSection(): JSX.Element {
|
||||
return (
|
||||
<div className="services-section">
|
||||
<div className="services-section__sidebar">
|
||||
<ServicesFilter accountId={accountId} onFilterChange={setActiveFilter} />
|
||||
<ServicesList accountId={accountId} filter={activeFilter} />
|
||||
<ServicesFilter
|
||||
cloudAccountId={cloudAccountId}
|
||||
onFilterChange={setActiveFilter}
|
||||
/>
|
||||
<ServicesList cloudAccountId={cloudAccountId} filter={activeFilter} />
|
||||
</div>
|
||||
<div className="services-section__content">
|
||||
<ServiceDetails />
|
||||
|
||||
@@ -48,6 +48,7 @@ function WidgetGraphComponent({
|
||||
openTracesButton,
|
||||
onOpenTraceBtnClick,
|
||||
customSeries,
|
||||
customErrorMessage,
|
||||
}: WidgetGraphComponentProps): JSX.Element {
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
@@ -317,6 +318,13 @@ function WidgetGraphComponent({
|
||||
setSearchTerm={setSearchTerm}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{queryResponse.error && customErrorMessage && (
|
||||
<div className="error-message-container">
|
||||
<Typography.Text type="warning">{customErrorMessage}</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
|
||||
<Skeleton />
|
||||
)}
|
||||
|
||||
@@ -41,9 +41,15 @@ function GridCardGraph({
|
||||
openTracesButton,
|
||||
onOpenTraceBtnClick,
|
||||
customSeries,
|
||||
customErrorMessage,
|
||||
start,
|
||||
end,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const {
|
||||
toScrollWidgetId,
|
||||
setToScrollWidgetId,
|
||||
@@ -178,6 +184,8 @@ function GridCardGraph({
|
||||
variables: getDashboardVariables(variables),
|
||||
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
|
||||
globalSelectedInterval,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
@@ -207,6 +215,11 @@ function GridCardGraph({
|
||||
refetchOnMount: false,
|
||||
onError: (error) => {
|
||||
setErrorMessage(error.message);
|
||||
if (customErrorMessage) {
|
||||
setIsInternalServerError(
|
||||
String(error.message).includes('API responded with 500'),
|
||||
);
|
||||
}
|
||||
setDashboardQueryRangeCalled(true);
|
||||
},
|
||||
onSettled: (data) => {
|
||||
@@ -256,6 +269,7 @@ function GridCardGraph({
|
||||
openTracesButton={openTracesButton}
|
||||
onOpenTraceBtnClick={onOpenTraceBtnClick}
|
||||
customSeries={customSeries}
|
||||
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface WidgetGraphComponentProps {
|
||||
openTracesButton?: boolean;
|
||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||
customErrorMessage?: string;
|
||||
}
|
||||
|
||||
export interface GridCardGraphProps {
|
||||
@@ -54,6 +55,9 @@ export interface GridCardGraphProps {
|
||||
openTracesButton?: boolean;
|
||||
onOpenTraceBtnClick?: (record: RowData) => void;
|
||||
customSeries?: (data: QueryData[]) => uPlot.Series[];
|
||||
customErrorMessage?: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||
|
||||
@@ -106,6 +106,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.error-message-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 0;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.row-settings {
|
||||
.ant-popover-inner {
|
||||
width: 191px;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import './InfraMonitoring.styles.scss';
|
||||
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
function HostsListControls({
|
||||
handleFiltersChange,
|
||||
}: {
|
||||
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||
}): JSX.Element {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
const updatedCurrentQuery = useMemo(
|
||||
() => ({
|
||||
...currentQuery,
|
||||
|
||||
@@ -141,13 +141,9 @@ function ClusterDetails({
|
||||
[cluster?.meta.k8s_cluster_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -161,8 +157,7 @@ function ClusterDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogsAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -181,6 +176,10 @@ function ClusterDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Clusters list details tab changed', {
|
||||
cluster: cluster?.clusterUID,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -204,6 +203,7 @@ function ClusterDetails({
|
||||
logEvent('Infra Monitoring: Clusters list details time updated', {
|
||||
cluster: cluster?.clusterUID,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -212,7 +212,7 @@ function ClusterDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||
);
|
||||
@@ -244,7 +244,7 @@ function ClusterDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||
);
|
||||
@@ -320,8 +320,8 @@ function ClusterDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logsAndTracesFilters,
|
||||
items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -355,7 +355,7 @@ function ClusterDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logsAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -519,7 +519,7 @@ function ClusterDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logsAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="clusterLogs"
|
||||
category={K8sCategory.CLUSTERS}
|
||||
@@ -532,9 +532,10 @@ function ClusterDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logsAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="clusterTraces"
|
||||
queryKeyFilters={[QUERY_KEYS.K8S_CLUSTER_NAME]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -240,6 +240,11 @@ function K8sClustersList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -250,6 +255,11 @@ function K8sClustersList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s clusters list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -261,7 +271,7 @@ function K8sClustersList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -275,15 +285,13 @@ function K8sClustersList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s clusters list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s clusters list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedClusterData = useMemo(() => {
|
||||
@@ -442,6 +450,7 @@ function K8sClustersList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
logEvent('Infra Monitoring: K8s clusters list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -457,6 +466,16 @@ function K8sClustersList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s clusters list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -482,10 +501,7 @@ function K8sClustersList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -155,13 +155,9 @@ function DaemonSetDetails({
|
||||
[daemonSet?.meta.k8s_daemonset_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -175,8 +171,7 @@ function DaemonSetDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -195,6 +190,10 @@ function DaemonSetDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: DaemonSets list details tab changed', {
|
||||
daemonSet: daemonSet?.daemonSetName,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -218,6 +217,7 @@ function DaemonSetDetails({
|
||||
logEvent('Infra Monitoring: DaemonSets list details time updated', {
|
||||
daemonSet: daemonSet?.daemonSetName,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -226,7 +226,7 @@ function DaemonSetDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -259,7 +259,7 @@ function DaemonSetDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -339,8 +339,8 @@ function DaemonSetDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -374,7 +374,7 @@ function DaemonSetDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -551,7 +551,7 @@ function DaemonSetDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
category={K8sCategory.DAEMONSETS}
|
||||
queryKey="daemonsetLogs"
|
||||
@@ -567,9 +567,13 @@ function DaemonSetDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="daemonsetTraces"
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_DAEMON_SET_NAME,
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -243,6 +243,11 @@ function K8sDaemonSetsList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sDaemonSetsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -253,6 +258,11 @@ function K8sDaemonSetsList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s daemonSets list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -264,7 +274,7 @@ function K8sDaemonSetsList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -278,15 +288,13 @@ function K8sDaemonSetsList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s daemonSets list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s daemonSets list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedDaemonSetData = useMemo(() => {
|
||||
@@ -448,6 +456,8 @@ function K8sDaemonSetsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s daemonSets list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -463,6 +473,16 @@ function K8sDaemonSetsList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s daemonSets list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -490,10 +510,7 @@ function K8sDaemonSetsList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -157,13 +157,9 @@ function DeploymentDetails({
|
||||
[deployment?.meta.k8s_deployment_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -177,8 +173,7 @@ function DeploymentDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -197,6 +192,10 @@ function DeploymentDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Deployments list details tab changed', {
|
||||
deployment: deployment?.deploymentName,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -220,6 +219,7 @@ function DeploymentDetails({
|
||||
logEvent('Infra Monitoring: Deployments list details time updated', {
|
||||
deployment: deployment?.deploymentName,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -228,7 +228,7 @@ function DeploymentDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -266,7 +266,7 @@ function DeploymentDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -350,8 +350,8 @@ function DeploymentDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -385,7 +385,7 @@ function DeploymentDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -562,7 +562,7 @@ function DeploymentDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||
@@ -578,9 +578,13 @@ function DeploymentDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="deploymentTraces"
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -245,6 +245,11 @@ function K8sDeploymentsList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -255,6 +260,11 @@ function K8sDeploymentsList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s deployments list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -266,7 +276,7 @@ function K8sDeploymentsList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -280,15 +290,13 @@ function K8sDeploymentsList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s deployments list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s deployments list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedDeploymentData = useMemo(() => {
|
||||
@@ -452,6 +460,8 @@ function K8sDeploymentsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s deployments list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -467,6 +477,16 @@ function K8sDeploymentsList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s deployments list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -494,10 +514,7 @@ function K8sDeploymentsList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useMemo } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { filterOutPrimaryFilters } from '../utils';
|
||||
import EntityLogs from './EntityLogs';
|
||||
|
||||
interface Props {
|
||||
@@ -58,14 +59,14 @@ function EntityLogsDetailedView({
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
items: filterOutPrimaryFilters(logFilters.items, queryKeyFilters),
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, logFilters.items, queryKeyFilters],
|
||||
);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
filterOutPrimaryFilters,
|
||||
getEntityTracesQueryPayload,
|
||||
selectedEntityTracesColumns,
|
||||
} from '../utils';
|
||||
@@ -44,6 +45,7 @@ interface Props {
|
||||
tracesFilters: IBuilderQuery['filters'];
|
||||
selectedInterval: Time;
|
||||
queryKey: string;
|
||||
queryKeyFilters: string[];
|
||||
}
|
||||
|
||||
function EntityTraces({
|
||||
@@ -54,6 +56,7 @@ function EntityTraces({
|
||||
tracesFilters,
|
||||
selectedInterval,
|
||||
queryKey,
|
||||
queryKeyFilters,
|
||||
}: Props): JSX.Element {
|
||||
const [traces, setTraces] = useState<any[]>([]);
|
||||
const [offset] = useState<number>(0);
|
||||
@@ -73,14 +76,14 @@ function EntityTraces({
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
items: filterOutPrimaryFilters(tracesFilters.items, queryKeyFilters),
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, queryKeyFilters, tracesFilters.items],
|
||||
);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { nanoToMilli } from 'utils/timeUtils';
|
||||
@@ -301,3 +304,12 @@ export const getEntityTracesQueryPayload = (
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const filterOutPrimaryFilters = (
|
||||
filters: TagFilterItem[],
|
||||
primaryKeys: string[],
|
||||
): TagFilterItem[] =>
|
||||
filters.filter(
|
||||
(filter) =>
|
||||
!primaryKeys.includes(filter.key?.key ?? '') && filter.key?.key !== 'id',
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { CollapseProps } from 'antd';
|
||||
import { Collapse, Tooltip, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
@@ -68,6 +69,11 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
||||
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
||||
setQuickFiltersLastUpdated(Date.now());
|
||||
|
||||
logEvent(
|
||||
`Infra Monitoring: K8s ${selectedCategory} list quick filters applied`,
|
||||
{},
|
||||
);
|
||||
};
|
||||
|
||||
const items: CollapseProps['items'] = [
|
||||
|
||||
@@ -152,13 +152,9 @@ function JobDetails({
|
||||
[job?.meta.k8s_job_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -172,8 +168,7 @@ function JobDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -192,6 +187,10 @@ function JobDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Jobs list details tab changed', {
|
||||
job: job?.jobName,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -215,6 +214,7 @@ function JobDetails({
|
||||
logEvent('Infra Monitoring: Jobs list details time updated', {
|
||||
job: job?.jobName,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -223,17 +223,16 @@ function JobDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
[QUERY_KEYS.K8S_JOB_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
),
|
||||
);
|
||||
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
|
||||
const newFilters = value.items.filter(
|
||||
(item) =>
|
||||
item.key?.key !== 'id' &&
|
||||
item.key?.key !== QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_JOB_NAME,
|
||||
);
|
||||
|
||||
logEvent('Infra Monitoring: Jobs list details logs filters applied', {
|
||||
@@ -256,9 +255,9 @@ function JobDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
[QUERY_KEYS.K8S_JOB_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
),
|
||||
);
|
||||
@@ -272,7 +271,7 @@ function JobDetails({
|
||||
items: [
|
||||
...primaryFilters,
|
||||
...value.items.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_JOB_NAME,
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
@@ -330,8 +329,8 @@ function JobDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -365,7 +364,7 @@ function JobDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -531,7 +530,7 @@ function JobDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
category={K8sCategory.JOBS}
|
||||
queryKey="jobLogs"
|
||||
@@ -547,9 +546,13 @@ function JobDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="jobTraces"
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_JOB_NAME,
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -234,6 +234,11 @@ function K8sJobsList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sJobsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -242,6 +247,11 @@ function K8sJobsList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s jobs list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -253,7 +263,7 @@ function K8sJobsList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -267,15 +277,13 @@ function K8sJobsList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s jobs list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s jobs list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedJobData = useMemo(() => {
|
||||
@@ -424,6 +432,8 @@ function K8sJobsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s jobs list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -439,6 +449,16 @@ function K8sJobsList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s jobs list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -466,10 +486,7 @@ function K8sJobsList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
import './InfraMonitoringK8s.styles.scss';
|
||||
|
||||
import { Button, Select } from 'antd';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { Filter, SlidersHorizontal } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { K8sCategory } from './constants';
|
||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||
@@ -47,7 +48,7 @@ function K8sHeader({
|
||||
}: K8sHeaderProps): JSX.Element {
|
||||
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
|
||||
const updatedCurrentQuery = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -242,6 +242,11 @@ function K8sNamespacesList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sNamespacesRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -252,6 +257,11 @@ function K8sNamespacesList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s namespaces list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -263,7 +273,7 @@ function K8sNamespacesList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -277,15 +287,13 @@ function K8sNamespacesList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s namespaces list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s namespaces list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedNamespaceData = useMemo(() => {
|
||||
@@ -449,6 +457,8 @@ function K8sNamespacesList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s namespaces list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -464,6 +474,16 @@ function K8sNamespacesList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s namespaces list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -489,10 +509,7 @@ function K8sNamespacesList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -143,13 +143,9 @@ function NamespaceDetails({
|
||||
[namespace?.namespaceName],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -163,8 +159,7 @@ function NamespaceDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -183,6 +178,10 @@ function NamespaceDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Namespaces list details tab changed', {
|
||||
namespace: namespace?.namespaceName,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -206,6 +205,7 @@ function NamespaceDetails({
|
||||
logEvent('Infra Monitoring: Namespaces list details time updated', {
|
||||
namespace: namespace?.namespaceName,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -214,7 +214,7 @@ function NamespaceDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -246,7 +246,7 @@ function NamespaceDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -326,8 +326,8 @@ function NamespaceDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -361,7 +361,7 @@ function NamespaceDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -527,7 +527,7 @@ function NamespaceDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="namespaceLogs"
|
||||
category={K8sCategory.NAMESPACES}
|
||||
@@ -540,9 +540,10 @@ function NamespaceDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="namespaceTraces"
|
||||
queryKeyFilters={[QUERY_KEYS.K8S_NAMESPACE_NAME]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -233,6 +233,11 @@ function K8sNodesList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sNodesRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -241,6 +246,11 @@ function K8sNodesList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s nodes list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -252,7 +262,7 @@ function K8sNodesList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -266,15 +276,13 @@ function K8sNodesList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s nodes list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s nodes list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedNodeData = useMemo(() => {
|
||||
@@ -427,6 +435,8 @@ function K8sNodesList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s nodes list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -442,6 +452,16 @@ function K8sNodesList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s nodes list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -467,10 +487,7 @@ function K8sNodesList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -141,13 +141,9 @@ function NodeDetails({
|
||||
[node?.meta.k8s_node_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -161,8 +157,7 @@ function NodeDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -181,6 +176,10 @@ function NodeDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Nodes list details tab changed', {
|
||||
node: node?.nodeUID,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -204,6 +203,7 @@ function NodeDetails({
|
||||
logEvent('Infra Monitoring: Nodes list details time updated', {
|
||||
node: node?.nodeUID,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -212,7 +212,7 @@ function NodeDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -246,7 +246,7 @@ function NodeDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -322,8 +322,8 @@ function NodeDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -357,7 +357,7 @@ function NodeDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -521,7 +521,7 @@ function NodeDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]}
|
||||
queryKey="nodeLogs"
|
||||
@@ -534,8 +534,9 @@ function NodeDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]}
|
||||
queryKey="nodeTraces"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -248,6 +248,11 @@ function K8sPodsList({
|
||||
groupBy,
|
||||
]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sPodsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -256,6 +261,11 @@ function K8sPodsList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s pods list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -267,7 +277,7 @@ function K8sPodsList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -281,9 +291,7 @@ function K8sPodsList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s pods list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
@@ -308,12 +316,14 @@ function K8sPodsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s pods list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s pods list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedPodData = useMemo(() => {
|
||||
@@ -350,7 +360,7 @@ function K8sPodsList({
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
|
||||
logEvent('Infra Monitoring: K8s list item clicked', {
|
||||
logEvent('Infra Monitoring: K8s pods list item clicked', {
|
||||
podUID: record.podUID,
|
||||
});
|
||||
};
|
||||
@@ -499,6 +509,16 @@ function K8sPodsList({
|
||||
);
|
||||
};
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s pods list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -530,10 +550,7 @@ function K8sPodsList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
loading={{
|
||||
spinning: isFetching || isLoading,
|
||||
|
||||
@@ -158,13 +158,9 @@ function PodDetails({
|
||||
[pod?.meta.k8s_pod_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -178,8 +174,7 @@ function PodDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogsAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -198,6 +193,10 @@ function PodDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: Pods list details tab changed', {
|
||||
pod: pod?.podUID,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -221,6 +220,7 @@ function PodDetails({
|
||||
logEvent('Infra Monitoring: Pods list details time updated', {
|
||||
pod: pod?.podUID,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -229,7 +229,7 @@ function PodDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[
|
||||
QUERY_KEYS.K8S_POD_NAME,
|
||||
@@ -265,7 +265,7 @@ function PodDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[
|
||||
QUERY_KEYS.K8S_POD_NAME,
|
||||
@@ -343,8 +343,8 @@ function PodDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logsAndTracesFilters,
|
||||
items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -378,7 +378,7 @@ function PodDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logsAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -559,7 +559,7 @@ function PodDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logsAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_POD_NAME,
|
||||
@@ -576,9 +576,14 @@ function PodDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logsAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="podTraces"
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_POD_NAME,
|
||||
QUERY_KEYS.K8S_CLUSTER_NAME,
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -245,6 +245,11 @@ function K8sStatefulSetsList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sStatefulSetsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -255,6 +260,11 @@ function K8sStatefulSetsList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s statefulSets list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -266,7 +276,7 @@ function K8sStatefulSetsList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -280,15 +290,13 @@ function K8sStatefulSetsList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s statefulSets list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s statefulSets list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedStatefulSetData = useMemo(() => {
|
||||
@@ -449,6 +457,8 @@ function K8sStatefulSetsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s statefulSets list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -464,6 +474,16 @@ function K8sStatefulSetsList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s statefulSets list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -491,10 +511,7 @@ function K8sStatefulSetsList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
@@ -158,13 +158,9 @@ function StatefulSetDetails({
|
||||
[statefulSet?.meta.k8s_statefulset_name],
|
||||
);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
|
||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
);
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
>(initialFilters);
|
||||
|
||||
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialEventsFilters,
|
||||
@@ -178,8 +174,7 @@ function StatefulSetDetails({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLogFilters(initialFilters);
|
||||
setTracesFilters(initialFilters);
|
||||
setLogAndTracesFilters(initialFilters);
|
||||
setEventsFilters(initialEventsFilters);
|
||||
}, [initialFilters, initialEventsFilters]);
|
||||
|
||||
@@ -198,6 +193,10 @@ function StatefulSetDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
logEvent('Infra Monitoring: StatefulSets list details tab changed', {
|
||||
statefulSet: statefulSet?.statefulSetName,
|
||||
view: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleTimeChange = useCallback(
|
||||
@@ -221,6 +220,7 @@ function StatefulSetDetails({
|
||||
logEvent('Infra Monitoring: StatefulSets list details time updated', {
|
||||
statefulSet: statefulSet?.statefulSetName,
|
||||
interval,
|
||||
view: selectedView,
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -229,7 +229,7 @@ function StatefulSetDetails({
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -265,7 +265,7 @@ function StatefulSetDetails({
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
item.key?.key ?? '',
|
||||
@@ -345,8 +345,8 @@ function StatefulSetDetails({
|
||||
|
||||
if (selectedView === VIEW_TYPES.LOGS) {
|
||||
const filtersWithoutPagination = {
|
||||
...logFilters,
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
...logAndTracesFilters,
|
||||
items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
|
||||
};
|
||||
|
||||
const compositeQuery = {
|
||||
@@ -380,7 +380,7 @@ function StatefulSetDetails({
|
||||
{
|
||||
...initialQueryBuilderFormValuesMap.traces,
|
||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
||||
filters: tracesFilters,
|
||||
filters: logAndTracesFilters,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -546,7 +546,7 @@ function StatefulSetDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeLogFilters={handleChangeLogFilters}
|
||||
logFilters={logFilters}
|
||||
logFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="statefulsetLogs"
|
||||
category={K8sCategory.STATEFULSETS}
|
||||
@@ -562,9 +562,13 @@ function StatefulSetDetails({
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
handleTimeChange={handleTimeChange}
|
||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
||||
tracesFilters={tracesFilters}
|
||||
tracesFilters={logAndTracesFilters}
|
||||
selectedInterval={selectedInterval}
|
||||
queryKey="statefulsetTraces"
|
||||
queryKeyFilters={[
|
||||
QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{selectedView === VIEW_TYPES.EVENTS && (
|
||||
|
||||
@@ -237,6 +237,11 @@ function K8sVolumesList({
|
||||
}
|
||||
}, [selectedRowData, fetchGroupedByRowData]);
|
||||
|
||||
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
|
||||
totalCount,
|
||||
pageSize,
|
||||
]);
|
||||
|
||||
const handleTableChange: TableProps<K8sVolumesRowData>['onChange'] = useCallback(
|
||||
(
|
||||
pagination: TablePaginationConfig,
|
||||
@@ -245,6 +250,11 @@ function K8sVolumesList({
|
||||
): void => {
|
||||
if (pagination.current) {
|
||||
setCurrentPage(pagination.current);
|
||||
logEvent('Infra Monitoring: K8s volumes list page number changed', {
|
||||
page: pagination.current,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
@@ -256,7 +266,7 @@ function K8sVolumesList({
|
||||
setOrderBy(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
[numberOfPages, pageSize],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -270,15 +280,13 @@ function K8sVolumesList({
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
|
||||
logEvent('Infra Monitoring: K8s list filters applied', {
|
||||
filters: value,
|
||||
});
|
||||
logEvent('Infra Monitoring: K8s volumes list filters applied', {});
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Infra Monitoring: K8s list page visited', {});
|
||||
logEvent('Infra Monitoring: K8s volumes list page visited', {});
|
||||
}, []);
|
||||
|
||||
const selectedVolumeData = useMemo(() => {
|
||||
@@ -434,6 +442,8 @@ function K8sVolumesList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent('Infra Monitoring: K8s volumes list group by changed', {});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
);
|
||||
@@ -449,6 +459,16 @@ function K8sVolumesList({
|
||||
}
|
||||
}, [groupByFiltersData]);
|
||||
|
||||
const onPaginationChange = (page: number, pageSize: number): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
logEvent('Infra Monitoring: K8s volumes list page number changed', {
|
||||
page,
|
||||
pageSize,
|
||||
numberOfPages,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="k8s-list">
|
||||
<K8sHeader
|
||||
@@ -476,10 +496,7 @@ function K8sVolumesList({
|
||||
total: totalCount,
|
||||
showSizeChanger: true,
|
||||
hideOnSinglePage: false,
|
||||
onChange: (page, pageSize): void => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
onChange: onPaginationChange,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
loading={{
|
||||
|
||||
12
frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx
Normal file
12
frontend/src/container/MetricsExplorer/Explorer/Explorer.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
function Explorer(): JSX.Element {
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
Explorer
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default Explorer;
|
||||
3
frontend/src/container/MetricsExplorer/Explorer/index.ts
Normal file
3
frontend/src/container/MetricsExplorer/Explorer/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Explorer from './Explorer';
|
||||
|
||||
export default Explorer;
|
||||
12
frontend/src/container/MetricsExplorer/Summary/Summary.tsx
Normal file
12
frontend/src/container/MetricsExplorer/Summary/Summary.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
function Summary(): JSX.Element {
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
Summary
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default Summary;
|
||||
3
frontend/src/container/MetricsExplorer/Summary/index.ts
Normal file
3
frontend/src/container/MetricsExplorer/Summary/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Summary from './Summary';
|
||||
|
||||
export default Summary;
|
||||
12
frontend/src/container/MetricsExplorer/Views/Views.tsx
Normal file
12
frontend/src/container/MetricsExplorer/Views/Views.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
function Views(): JSX.Element {
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
Views
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
export default Views;
|
||||
3
frontend/src/container/MetricsExplorer/Views/index.ts
Normal file
3
frontend/src/container/MetricsExplorer/Views/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Views from './Views';
|
||||
|
||||
export default Views;
|
||||
@@ -266,7 +266,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
demo-app
|
||||
</div>
|
||||
@@ -277,7 +277,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
4.35 s
|
||||
</div>
|
||||
@@ -292,7 +292,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
customer
|
||||
</div>
|
||||
@@ -303,7 +303,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
431 ms
|
||||
</div>
|
||||
@@ -318,7 +318,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
mysql
|
||||
</div>
|
||||
@@ -329,7 +329,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
431 ms
|
||||
</div>
|
||||
@@ -344,7 +344,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
frontend
|
||||
</div>
|
||||
@@ -355,7 +355,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
287 ms
|
||||
</div>
|
||||
@@ -370,7 +370,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
driver
|
||||
</div>
|
||||
@@ -381,7 +381,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
230 ms
|
||||
</div>
|
||||
@@ -396,7 +396,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
route
|
||||
</div>
|
||||
@@ -407,7 +407,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
66.4 ms
|
||||
</div>
|
||||
@@ -422,7 +422,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
redis
|
||||
</div>
|
||||
@@ -433,7 +433,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-clamped-text"
|
||||
class="line-clamped-wrapper__text"
|
||||
>
|
||||
31.3 ms
|
||||
</div>
|
||||
|
||||
@@ -82,6 +82,13 @@ const menuItems: SidebarItem[] = [
|
||||
label: 'Logs',
|
||||
icon: <ScrollText size={16} />,
|
||||
},
|
||||
// TODO - Enable this when the metrics explorer feature is read for release
|
||||
// {
|
||||
// key: ROUTES.METRICS_EXPLORER,
|
||||
// label: 'Metrics',
|
||||
// icon: <BarChart2 size={16} />,
|
||||
// isNew: true,
|
||||
// },
|
||||
{
|
||||
key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||
label: 'Infra Monitoring',
|
||||
|
||||
@@ -219,6 +219,9 @@ export const routesToSkip = [
|
||||
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||
ROUTES.SOMETHING_WENT_WRONG,
|
||||
ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
||||
ROUTES.METRICS_EXPLORER,
|
||||
ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
ROUTES.METRICS_EXPLORER_VIEWS,
|
||||
];
|
||||
|
||||
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
|
||||
import { formUrlParams } from 'container/TraceDetail/utils';
|
||||
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
@@ -113,7 +114,9 @@ export const getListColumns = (
|
||||
|
||||
return (
|
||||
<BlockLink to={getTraceLink(item)} openInNewTab={false}>
|
||||
<Typography data-testid={key}>{value}</Typography>
|
||||
<Typography data-testid={key}>
|
||||
<LineClampedText text={value} lines={3} />
|
||||
</Typography>
|
||||
</BlockLink>
|
||||
);
|
||||
},
|
||||
|
||||
17
frontend/src/hooks/integrations/aws/useConnectionParams.ts
Normal file
17
frontend/src/hooks/integrations/aws/useConnectionParams.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { getConnectionParams } from 'api/integrations/aws';
|
||||
import { AxiosError } from 'axios';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||
|
||||
export function useConnectionParams({
|
||||
options,
|
||||
}: {
|
||||
options?: UseQueryOptions<ConnectionParams, AxiosError>;
|
||||
}): UseQueryResult<ConnectionParams, AxiosError> {
|
||||
return useQuery<ConnectionParams, AxiosError>(
|
||||
[REACT_QUERY_KEY.AWS_GET_CONNECTION_PARAMS],
|
||||
getConnectionParams,
|
||||
options,
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ActiveViewEnum,
|
||||
ModalStateEnum,
|
||||
} from 'container/CloudIntegrationPage/HeroSection/types';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
@@ -13,11 +14,13 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
ConnectionParams,
|
||||
ConnectionUrlResponse,
|
||||
GenerateConnectionUrlPayload,
|
||||
} from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import { useConnectionParams } from './useConnectionParams';
|
||||
import { useGenerateConnectionUrl } from './useGenerateConnectionUrl';
|
||||
|
||||
interface UseIntegrationModalProps {
|
||||
@@ -44,6 +47,8 @@ interface UseIntegrationModal {
|
||||
accountId?: string;
|
||||
selectedDeploymentRegion: string | undefined;
|
||||
handleRegionChange: (value: string) => void;
|
||||
connectionParams?: ConnectionParams;
|
||||
isConnectionParamsLoading: boolean;
|
||||
}
|
||||
|
||||
export function useIntegrationModal({
|
||||
@@ -102,6 +107,12 @@ export function useIntegrationModal({
|
||||
isLoading: isGeneratingUrl,
|
||||
} = useGenerateConnectionUrl();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
const {
|
||||
data: connectionParams,
|
||||
isLoading: isConnectionParamsLoading,
|
||||
} = useConnectionParams({ options: { onError: handleError } });
|
||||
|
||||
const handleGenerateUrl = useCallback(
|
||||
(payload: GenerateConnectionUrlPayload): void => {
|
||||
generateUrl(payload, {
|
||||
@@ -126,6 +137,9 @@ export function useIntegrationModal({
|
||||
const payload: GenerateConnectionUrlPayload = {
|
||||
agent_config: {
|
||||
region: values.region,
|
||||
ingestion_url: connectionParams?.ingestion_url || values.ingestion_url,
|
||||
ingestion_key: connectionParams?.ingestion_key || values.ingestion_key,
|
||||
signoz_api_url: connectionParams?.signoz_api_url || values.signoz_api_url,
|
||||
},
|
||||
account_config: {
|
||||
regions: includeAllRegions ? ['all'] : selectedRegions,
|
||||
@@ -138,7 +152,13 @@ export function useIntegrationModal({
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [form, includeAllRegions, selectedRegions, handleGenerateUrl]);
|
||||
}, [
|
||||
form,
|
||||
includeAllRegions,
|
||||
selectedRegions,
|
||||
handleGenerateUrl,
|
||||
connectionParams,
|
||||
]);
|
||||
|
||||
return {
|
||||
form,
|
||||
@@ -160,5 +180,7 @@ export function useIntegrationModal({
|
||||
setModalState,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import { useQuery, UseQueryResult } from 'react-query';
|
||||
|
||||
export function useServiceDetails(
|
||||
serviceId: string,
|
||||
accountId?: string,
|
||||
cloudAccountId?: string,
|
||||
): UseQueryResult<ServiceData> {
|
||||
return useQuery(
|
||||
[REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, accountId],
|
||||
() => getServiceDetails(serviceId, accountId),
|
||||
[REACT_QUERY_KEY.AWS_SERVICE_DETAILS, serviceId, cloudAccountId],
|
||||
() => getServiceDetails(serviceId, cloudAccountId),
|
||||
{
|
||||
enabled: !!serviceId,
|
||||
},
|
||||
|
||||
@@ -243,13 +243,15 @@ export const useFetchKeysAndValues = (
|
||||
fetchingSuggestionsStatus === 'success' &&
|
||||
suggestionsData?.payload?.attributes
|
||||
) {
|
||||
setKeys(suggestionsData.payload.attributes);
|
||||
setSourceKeys((prevState) =>
|
||||
uniqWith(
|
||||
[...(suggestionsData.payload.attributes ?? []), ...prevState],
|
||||
isEqual,
|
||||
),
|
||||
);
|
||||
if (!isInfraMonitoring) {
|
||||
setKeys(suggestionsData.payload.attributes);
|
||||
setSourceKeys((prevState) =>
|
||||
uniqWith(
|
||||
[...(suggestionsData.payload.attributes ?? []), ...prevState],
|
||||
isEqual,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setKeys([]);
|
||||
}
|
||||
@@ -265,6 +267,7 @@ export const useFetchKeysAndValues = (
|
||||
suggestionsData?.payload?.attributes,
|
||||
fetchingSuggestionsStatus,
|
||||
suggestionsData?.payload?.example_queries,
|
||||
isInfraMonitoring,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function CeleryOverview(): JSX.Element {
|
||||
<p className="celery-overview-content-header-title">
|
||||
Messaging Queue Overview
|
||||
</p>
|
||||
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal={false} />
|
||||
</div>
|
||||
<CeleryOverviewConfigOptions />
|
||||
<CeleryOverviewTable onRowClick={onRowClick} />
|
||||
|
||||
@@ -116,8 +116,8 @@ export default function OverviewRightPanelGraph({
|
||||
isDarkMode: false,
|
||||
drawStyle: 'bars',
|
||||
colorMapping: {
|
||||
False: Color.BG_CHERRY_500,
|
||||
True: Color.BG_FOREST_400,
|
||||
True: Color.BG_CHERRY_500,
|
||||
False: Color.BG_FOREST_400,
|
||||
None: Color.BG_SLATE_200,
|
||||
'Request Rate': Color.BG_ROBIN_500,
|
||||
},
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import './CeleryTask.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import CeleryTaskConfigOptions from 'components/CeleryTask/CeleryTaskConfigOptions/CeleryTaskConfigOptions';
|
||||
import CeleryTaskDetail, {
|
||||
CaptureDataProps,
|
||||
} from 'components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail';
|
||||
import CeleryTaskGraphGrid from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { Check, Share2 } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
|
||||
export default function CeleryTask(): JSX.Element {
|
||||
const [task, setTask] = useState<CaptureDataProps | null>(null);
|
||||
@@ -19,36 +15,13 @@ export default function CeleryTask(): JSX.Element {
|
||||
setTask(captureData);
|
||||
};
|
||||
|
||||
const [isURLCopied, setIsURLCopied] = useState(false);
|
||||
|
||||
const [, handleCopyToClipboard] = useCopyToClipboard();
|
||||
|
||||
return (
|
||||
<div className="celery-task-container">
|
||||
<div className="celery-content">
|
||||
<div className="celery-content-header">
|
||||
<p className="celery-content-header-title">Celery</p>
|
||||
<div className="celery-content-header-right">
|
||||
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
||||
<Tooltip title="Share this" arrow={false}>
|
||||
<Button
|
||||
className="periscope-btn copy-url-btn"
|
||||
onClick={(): void => {
|
||||
handleCopyToClipboard(window.location.href);
|
||||
setIsURLCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsURLCopied(false);
|
||||
}, 1000);
|
||||
}}
|
||||
icon={
|
||||
isURLCopied ? (
|
||||
<Check size={14} color={Color.BG_FOREST_500} />
|
||||
) : (
|
||||
<Share2 size={14} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<DateTimeSelectionV2 showAutoRefresh hideShareModal={false} />
|
||||
</div>
|
||||
</div>
|
||||
<CeleryTaskGraphGrid
|
||||
|
||||
@@ -4,8 +4,10 @@ import './Integrations.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, List, Typography } from 'antd';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useGetAllIntegrations } from 'hooks/Integrations/useGetAllIntegrations';
|
||||
import { MoveUpRight, RotateCw } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import { IntegrationsProps } from 'types/api/integrations/types';
|
||||
import { isCloudUser } from 'utils/app';
|
||||
@@ -14,9 +16,15 @@ import { handleContactSupport, INTEGRATION_TYPES } from './utils';
|
||||
|
||||
export const AWS_INTEGRATION = {
|
||||
id: INTEGRATION_TYPES.AWS_INTEGRATION,
|
||||
title: 'AWS Web Services',
|
||||
title: 'Amazon Web Services',
|
||||
description: 'One-click setup for AWS monitoring with SigNoz',
|
||||
author: {
|
||||
name: 'SigNoz',
|
||||
email: 'integrations@signoz.io',
|
||||
homepage: 'https://signoz.io',
|
||||
},
|
||||
icon: `Logos/aws-dark.svg`,
|
||||
is_installed: false,
|
||||
is_new: true,
|
||||
};
|
||||
|
||||
@@ -38,13 +46,20 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
|
||||
refetch,
|
||||
} = useGetAllIntegrations();
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const isAwsIntegrationEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.AWS_INTEGRATION)
|
||||
?.active || false;
|
||||
|
||||
const filteredDataList = useMemo(() => {
|
||||
let integrationsList: IntegrationsProps[] = [];
|
||||
|
||||
// Temporarily hide AWS Integration from the list, uncomment when the BE changes are finalized
|
||||
// if (AWS_INTEGRATION.title.toLowerCase().includes(searchTerm.toLowerCase())) {
|
||||
// integrationsList.push(AWS_INTEGRATION);
|
||||
// }
|
||||
if (
|
||||
isAwsIntegrationEnabled &&
|
||||
AWS_INTEGRATION.title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
) {
|
||||
integrationsList.push(AWS_INTEGRATION);
|
||||
}
|
||||
|
||||
// Add other integrations
|
||||
if (data?.data.data.integrations) {
|
||||
@@ -57,7 +72,7 @@ function IntegrationsList(props: IntegrationsListProps): JSX.Element {
|
||||
}
|
||||
|
||||
return integrationsList;
|
||||
}, [data?.data.data.integrations, searchTerm]);
|
||||
}, [data?.data.data.integrations, isAwsIntegrationEnabled, searchTerm]);
|
||||
|
||||
const loading = isLoading || isFetching || isRefetching;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user