Compare commits

...

32 Commits

Author SHA1 Message Date
SagarRajput-7
d22ecb9f7c feat: allowed user to edit panel while the API is loading (#7007)
* feat: allowed user to edit panel while the API is loading

* feat: added test case
2025-02-11 18:32:13 +05:30
SagarRajput-7
02c2b55d5e feat: added new and cloned panel at the bottom of the page (#6993)
* feat: added new and cloned panel at the bottom of the page

* feat: added common util and took possible space available in last row in account

* feat: added changes for empty layout

* feat: added different test cases

* feat: remove console.log

* feat: added default value to widgetWidth
2025-02-11 17:01:17 +05:30
Vishal Sharma
9a75e27ec3 chore: add customerio identity calls (#7079) 2025-02-11 11:20:15 +00:00
SagarRajput-7
1fb3953614 feat: added create alerts option for panel in locked dashboard (#7071) 2025-02-11 16:32:25 +05:30
Vishal Sharma
37558facbe chore: add customerio to frontend (#7062) 2025-02-07 20:13:35 +05:30
Vikrant Gupta
12f65f4a72 Revert "chore: add related values (#6619)" (#7067)
This reverts commit c5219ac157.
2025-02-07 11:51:11 +00:00
Srikanth Chekuri
398760006b fix: address the first point increase without previous measurement value (#7054) 2025-02-07 07:44:18 +00:00
Raj Kamal Singh
37323a64cf chore: also use signoz_api_key from connection params (#7061) 2025-02-07 11:26:42 +05:30
Srikanth Chekuri
6d8d2e6b11 chore: use any of [cpu.usage, cpu.utilization] for cpu (#7017) 2025-02-06 11:56:58 +00:00
Srikanth Chekuri
a8e8f31b00 chore: parse string values for __value filter (#7035) 2025-02-06 11:47:29 +00:00
Shaheer Kochai
c3164912e6 AWS Integration changes (#7025)
* fix: update AWS accounts API response to return accounts list

* feat: display skeleton UI for account actions and refactored rendering logic

* chore: update AWS service naming from "AWS Web Services" to "Amazon Web Services"

* feat: aws integration success modal changes

* feat: auto-select first service when no service is active

* feat: display 'enable service' if service hasn't been configured and 'Configure (x/2)' if configured

* fix: display no data yet if status is not available

* feat: properly handle remove integration account flow

* fix: rename accountId param to cloudAccountId

* fix: update the aws service list and details api parameter from account_id to cloud_account_id

* fix: fix the issue of stale service config modal enabled/disabled state

* chore: improve the UI of configure button

* feat: add connection parameters support for AWS cloud integration

* feat: add optional link support for cloud service dashboards

* fix: get the correct supported signals count + a minor refactoring

* fix: remove cloudAccountId on success of account remove

* chore: update the remove integration copy

* refactor: add react query key for AWS connection parameters

* fix: correct typo in integration loading state variable name

* refactor: move skeleton inline styles to style file and do overall refactoring

* chore: address the requested changes
2025-02-06 15:13:19 +04:30
Shaheer Kochai
b215c6a0ce feat: aws integration feature flag changes (#7033)
* feat: aws integration feature flag changes

* fix: fix the failing build
2025-02-06 09:26:10 +00:00
Ekansh Gupta
94c2398a08 feat: bug fix for segregating entrypoint spans and root spans in span filtering (#7046) 2025-02-06 13:26:18 +05:30
Raj Kamal Singh
acd9b97ee3 Feat: aws integrations: service connection status (#7032)
* feat: add ability to get latest metric received ts by labelValues filter

* feat: svc metrics connection status check

* feat: aws integration svc logs connection check

* chore: fix broken test

* chore: address PR review comments

* chore: address PR feedback

* chore: fix broken test expectation

* fix: use resource filter for logs connection status
2025-02-06 13:08:47 +05:30
Srikanth Chekuri
c5219ac157 chore: add related values (#6619) 2025-02-06 05:09:01 +00:00
Vikrant Gupta
2b32ce190f task(licenses): update the license events and the state names (#7034)
* Revert "Revert "chore(licenses): update the license events and the state name…"

This reverts commit 66adc7fbf9.

* chore(license): fix comment
2025-02-05 18:25:36 +00:00
Amlan Kumar Nandy
c7c7b25651 feat: metrics explorer base setup (#7024) 2025-02-05 12:57:12 +00:00
Amlan Kumar Nandy
f548afe284 chore: share filters across logs and traces views in entity details (#7014) 2025-02-05 18:18:35 +05:30
primus-bot[bot]
586f5255f0 chore(release): bump to v0.71.0 (#7031)
#### Summary
 - Release SigNoz v0.71.0
 - Bump SigNoz OTel Collector to v0.111.26

 Created by [Primus-Bot](https://github.com/apps/primus-bot)
2025-02-05 15:13:58 +05:30
SagarRajput-7
62064f136d feat: corrected the color map for error % and added throughput unit (#7030) 2025-02-05 14:56:31 +05:30
Vikrant Gupta
66adc7fbf9 Revert "chore(licenses): update the license events and the state names (#7021)" (#7029)
This reverts commit e414215786.
2025-02-05 12:38:56 +05:30
Yunus M
f6b2d5a519 feat: change grid to flex - flower metrics section (#7027) 2025-02-05 11:48:44 +05:30
Vibhu Pandey
035999250e fix(add_pats): fix name of column (#7026) 2025-02-05 11:02:17 +05:30
Shaheer Kochai
aecbb71ce4 feat: truncate very long lines in traces explorer list view (#6987)
* feat: truncate very long lines in traces explorer list view

* fix: update snapshot tests with new line-clamped text class to fix the failing test
2025-02-04 13:29:23 +00:00
Ekansh Gupta
01b6e22bbd feat: bug fix for the issue in span filtering feature. No spans returned in traces tab (#7023)
* feat: bug fix for the issue in span filtering feature. No spans returned in traces tab

* feat: bug fix for the issue in span filtering feature. No spans returned in traces tab
2025-02-04 18:36:07 +05:30
Vibhu Pandey
dc15ee8176 feat(sqlmigration): consolidate all sqlmigrations into one package (#7018)
* feat(sqlmigration): add sqlmigrations

* feat(sqlmigration): test sqlmigrations

* feat(sqlmigration): add remaining factories

* feat(sqlmigration): consolidate into single package

* fix(telemetrystore): remove existing env variables

* fix(telemetrystore): fix DSN
2025-02-04 09:23:36 +00:00
Vikrant Gupta
e414215786 chore(licenses): update the license events and the state names (#7021) 2025-02-04 14:11:13 +05:30
Amlan Kumar Nandy
5fe04078e5 chore: update logs in infra monitoring for analytics (#6994) 2025-02-04 06:01:37 +00:00
SagarRajput-7
cf95b15ba1 feat: celery - misc feedback fixes (#7019)
* feat: celery - misc feedback fixes

* feat: enabled better sharing and auto-refresh

* feat: made task name filter more visible
2025-02-04 09:41:27 +05:30
Raj Kamal Singh
3b550c485d Feat: cloud integrations: agent check-in api (#7004)
* chore: cloudintegrations: rename rds def

* feat: cloudintegrations: shape of agent check in response for v0 release

* chore: cloudintegrations: add validation for response expected after agent check in

* chore: cloudintegrations: accumulate teletry collection strategies for enabled services

* chore: cloudintegrations: use map struct to parse from map to struct with json tags

* chore: cloudintegrations: telemetry collection strategy for services

* chore: cloudintegrations: wrap up test for agent check in resp

* chore: some cleanup

* chore: some cleanup

* chore: some minor renaming

* chore: address review comment

* chore: some cleanup

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-02-03 20:52:15 +05:30
Amlan Kumar Nandy
784dccf298 feat: fix incorrect suggestions while moving between explores (#7008) 2025-02-03 14:31:01 +05:30
Shaheer Kochai
aa26dc77af fix(styles): fix the gap between text and button in logs quick filters (#7006) 2025-02-03 04:54:21 +00:00
198 changed files with 3391 additions and 1883 deletions

View File

@@ -68,6 +68,8 @@ jobs:
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
- name: Setup golang - name: Setup golang
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:

View File

@@ -181,7 +181,7 @@ services:
- query-service - query-service
query-service: query-service:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/query-service:0.70.1 image: signoz/query-service:0.71.0
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
- --use-logs-new-schema=true - --use-logs-new-schema=true
@@ -214,7 +214,7 @@ services:
retries: 3 retries: 3
frontend: frontend:
!!merge <<: *common !!merge <<: *common
image: signoz/frontend:0.70.1 image: signoz/frontend:0.71.0
depends_on: depends_on:
- alertmanager - alertmanager
- query-service - query-service
@@ -224,7 +224,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.25 image: signoz/signoz-otel-collector:0.111.26
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml - --manager-config=/etc/manager-config.yaml

View File

@@ -117,7 +117,7 @@ services:
- query-service - query-service
query-service: query-service:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/query-service:0.70.1 image: signoz/query-service:0.71.0
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
- --use-logs-new-schema=true - --use-logs-new-schema=true
@@ -150,7 +150,7 @@ services:
retries: 3 retries: 3
frontend: frontend:
!!merge <<: *common !!merge <<: *common
image: signoz/frontend:0.70.1 image: signoz/frontend:0.71.0
depends_on: depends_on:
- alertmanager - alertmanager
- query-service - query-service
@@ -160,7 +160,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:0.111.25 image: signoz/signoz-otel-collector:0.111.26
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml - --manager-config=/etc/manager-config.yaml

View File

@@ -188,7 +188,7 @@ services:
condition: service_healthy condition: service_healthy
query-service: query-service:
!!merge <<: *db-depend !!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 container_name: signoz-query-service
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -222,7 +222,7 @@ services:
retries: 3 retries: 3
frontend: frontend:
!!merge <<: *common !!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.70.1} image: signoz/frontend:${DOCKER_TAG:-0.71.0}
container_name: signoz-frontend container_name: signoz-frontend
depends_on: depends_on:
- alertmanager - alertmanager
@@ -234,7 +234,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing? # TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector: otel-collector:
!!merge <<: *db-depend !!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 container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml

View File

@@ -121,7 +121,7 @@ services:
condition: service_healthy condition: service_healthy
query-service: query-service:
!!merge <<: *db-depend !!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 container_name: signoz-query-service
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -157,7 +157,7 @@ services:
retries: 3 retries: 3
frontend: frontend:
!!merge <<: *common !!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.70.1} image: signoz/frontend:${DOCKER_TAG:-0.71.0}
container_name: signoz-frontend container_name: signoz-frontend
depends_on: depends_on:
- alertmanager - alertmanager
@@ -168,7 +168,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
!!merge <<: *db-depend !!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 container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml

View File

@@ -121,7 +121,7 @@ services:
condition: service_healthy condition: service_healthy
query-service: query-service:
!!merge <<: *db-depend !!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 container_name: signoz-query-service
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -155,7 +155,7 @@ services:
retries: 3 retries: 3
frontend: frontend:
!!merge <<: *common !!merge <<: *common
image: signoz/frontend:${DOCKER_TAG:-0.70.1} image: signoz/frontend:${DOCKER_TAG:-0.71.0}
container_name: signoz-frontend container_name: signoz-frontend
depends_on: depends_on:
- alertmanager - alertmanager
@@ -166,7 +166,7 @@ services:
- ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/signoz/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
!!merge <<: *db-depend !!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 container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml

View File

@@ -11,7 +11,6 @@ import (
"net" "net"
"net/http" "net/http"
_ "net/http/pprof" // http profiler _ "net/http/pprof" // http profiler
"os"
"regexp" "regexp"
"time" "time"
@@ -149,25 +148,20 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
var reader interfaces.DataConnector var reader interfaces.DataConnector
storage := os.Getenv("STORAGE") qb := db.NewDataConnector(
if storage == "clickhouse" { serverOptions.SigNoz.SQLStore.SQLxDB(),
zap.L().Info("Using ClickHouse as datastore ...") serverOptions.SigNoz.TelemetryStore.ClickHouseDB(),
qb := db.NewDataConnector( serverOptions.PromConfigPath,
serverOptions.SigNoz.SQLStore.SQLxDB(), lm,
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(), serverOptions.Cluster,
serverOptions.PromConfigPath, serverOptions.UseLogsNewSchema,
lm, serverOptions.UseTraceNewSchema,
serverOptions.Cluster, fluxIntervalForTraceDetail,
serverOptions.UseLogsNewSchema, serverOptions.SigNoz.Cache,
serverOptions.UseTraceNewSchema, )
fluxIntervalForTraceDetail, go qb.Start(readerReady)
serverOptions.SigNoz.Cache, reader = qb
)
go qb.Start(readerReady)
reader = qb
} else {
return nil, fmt.Errorf("storage type: %s is not supported in query service", storage)
}
skipConfig := &basemodel.SkipConfig{} skipConfig := &basemodel.SkipConfig{}
if serverOptions.SkipTopLvlOpsPath != "" { if serverOptions.SkipTopLvlOpsPath != "" {
// read skip config // read skip config

View File

@@ -7,7 +7,6 @@ import (
basedao "go.signoz.io/signoz/pkg/query-service/dao" basedao "go.signoz.io/signoz/pkg/query-service/dao"
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite" basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces" baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
"go.uber.org/zap"
) )
type modelDao struct { type modelDao struct {
@@ -29,41 +28,6 @@ func (m *modelDao) checkFeature(key string) error {
return m.flags.CheckFeature(key) 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, &notnull, &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 // InitDB creates and extends base model DB repository
func InitDB(inputDB *sqlx.DB) (*modelDao, error) { func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
dao, err := basedsql.InitDB(inputDB) 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 // set package variable so dependent base methods (e.g. AuthCache) will work
basedao.SetDB(dao) basedao.SetDB(dao)
m := &modelDao{ModelDaoSqlite: 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 return m, nil
} }

View File

@@ -10,7 +10,6 @@ import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3" "github.com/mattn/go-sqlite3"
"go.signoz.io/signoz/ee/query-service/license/sqlite"
"go.signoz.io/signoz/ee/query-service/model" "go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap" "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) { func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
licensesData := []model.LicenseDB{} licensesData := []model.LicenseDB{}
licenseV3Data := []*model.LicenseV3{} licenseV3Data := []*model.LicenseV3{}

View File

@@ -2,7 +2,6 @@ package license
import ( import (
"context" "context"
"fmt"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -50,11 +49,6 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)
} }
repo := NewLicenseRepo(db) repo := NewLicenseRepo(db)
err := repo.InitDB(db)
if err != nil {
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
}
m := &Manager{ m := &Manager{
repo: &repo, repo: &repo,
} }

View File

@@ -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
}

View File

@@ -18,7 +18,6 @@ import (
"go.signoz.io/signoz/pkg/config/fileprovider" "go.signoz.io/signoz/pkg/config/fileprovider"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
baseconst "go.signoz.io/signoz/pkg/query-service/constants" 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/query-service/version"
"go.signoz.io/signoz/pkg/signoz" "go.signoz.io/signoz/pkg/signoz"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -183,12 +182,6 @@ func main() {
zap.L().Info("JWT secret key set successfully.") 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) server, err := app.NewServer(serverOptions)
if err != nil { if err != nil {
zap.L().Fatal("Failed to create server", zap.Error(err)) zap.L().Fatal("Failed to create server", zap.Error(err))

View File

@@ -139,8 +139,8 @@ func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if license status is inactive then default it to basic // if license status is invalid then default it to basic
if status == LicenseStatusInactive { if status == LicenseStatusInvalid {
planName = PlanNameBasic planName = PlanNameBasic
} }

View File

@@ -21,7 +21,7 @@ var (
) )
var ( var (
LicenseStatusInactive = "INACTIVE" LicenseStatusInvalid = "INVALID"
) )
const DisableUpsell = "DISABLE_UPSELL" const DisableUpsell = "DISABLE_UPSELL"
@@ -157,6 +157,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
} }
var ProPlan = basemodel.FeatureSet{ var ProPlan = basemodel.FeatureSet{
@@ -279,6 +286,13 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
} }
var EnterprisePlan = basemodel.FeatureSet{ var EnterprisePlan = basemodel.FeatureSet{
@@ -415,4 +429,11 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.AWSIntegration,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
} }

View File

@@ -92,7 +92,7 @@
"overlayscrollbars": "^2.8.1", "overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6", "overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1", "papaparse": "5.4.1",
"posthog-js": "1.160.3", "posthog-js": "1.215.5",
"rc-tween-one": "3.0.6", "rc-tween-one": "3.0.6",
"react": "18.2.0", "react": "18.2.0",
"react-addons-update": "15.6.3", "react-addons-update": "15.6.3",

View File

@@ -57,5 +57,8 @@
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview", "ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
"MESSAGING_QUEUES": "SigNoz | Messaging Queues", "MESSAGING_QUEUES": "SigNoz | Messaging Queues",
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring", "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"
} }

View File

@@ -156,7 +156,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const currentRoute = mapRoutes.get('current'); const currentRoute = mapRoutes.get('current');
const shouldSuspendWorkspace = const shouldSuspendWorkspace =
activeLicenseV3.status === LicenseStatus.SUSPENDED && activeLicenseV3.status === LicenseStatus.SUSPENDED &&
activeLicenseV3.state === LicenseState.PAYMENT_FAILED; activeLicenseV3.state === LicenseState.DEFAULTED;
if (shouldSuspendWorkspace && currentRoute) { if (shouldSuspendWorkspace && currentRoute) {
navigateToWorkSpaceSuspended(currentRoute); navigateToWorkSpaceSuspended(currentRoute);

View File

@@ -110,6 +110,18 @@ function App(): JSX.Element {
source: 'signoz-ui', source: 'signoz-ui',
isPaidUser: !!licenses?.trialConvertedToSubscription, isPaidUser: !!licenses?.trialConvertedToSubscription,
}); });
if (
window.cioanalytics &&
typeof window.cioanalytics.identify === 'function'
) {
window.cioanalytics.reset();
window.cioanalytics.identify(email, {
name: user.name,
email,
role: user.role,
});
}
} }
}, },
[hostname, isFetchingLicenses, licenses, org], [hostname, isFetchingLicenses, licenses, org],

View File

@@ -264,3 +264,8 @@ export const CeleryOverview = Loadable(
/* webpackChunkName: "CeleryOverview" */ 'pages/Celery/CeleryOverview/CeleryOverview' /* webpackChunkName: "CeleryOverview" */ 'pages/Celery/CeleryOverview/CeleryOverview'
), ),
); );
export const MetricsExplorer = Loadable(
() =>
import(/* webpackChunkName: "MetricsExplorer" */ 'pages/MetricsExplorer'),
);

View File

@@ -28,6 +28,7 @@ import {
LogsExplorer, LogsExplorer,
LogsIndexToFields, LogsIndexToFields,
LogsSaveViews, LogsSaveViews,
MetricsExplorer,
MySettings, MySettings,
NewDashboardPage, NewDashboardPage,
OldLogsExplorer, OldLogsExplorer,
@@ -435,6 +436,27 @@ const routes: AppRoutes[] = [
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES', key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
isPrivate: true, 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 = { export const SUPPORT_ROUTE: AppRoutes = {

View 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;

View File

@@ -9,19 +9,22 @@ import {
import { import {
AccountConfigPayload, AccountConfigPayload,
AccountConfigResponse, AccountConfigResponse,
ConnectionParams,
ConnectionUrlResponse, ConnectionUrlResponse,
} from 'types/api/integrations/aws'; } from 'types/api/integrations/aws';
export const getAwsAccounts = async (): Promise<CloudAccount[]> => { export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
const response = await axios.get('/cloud-integrations/aws/accounts'); const response = await axios.get('/cloud-integrations/aws/accounts');
return response.data.data; return response.data.data.accounts;
}; };
export const getAwsServices = async ( export const getAwsServices = async (
accountId?: string, cloudAccountId?: string,
): Promise<Service[]> => { ): 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', { const response = await axios.get('/cloud-integrations/aws/services', {
params, params,
}); });
@@ -31,9 +34,11 @@ export const getAwsServices = async (
export const getServiceDetails = async ( export const getServiceDetails = async (
serviceId: string, serviceId: string,
accountId?: string, cloudAccountId?: string,
): Promise<ServiceData> => { ): Promise<ServiceData> => {
const params = accountId ? { account_id: accountId } : undefined; const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get( const response = await axios.get(
`/cloud-integrations/aws/services/${serviceId}`, `/cloud-integrations/aws/services/${serviceId}`,
{ params }, { params },
@@ -74,3 +79,10 @@ export const updateServiceConfig = async (
); );
return response.data; 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;
};

View File

@@ -1,7 +1,6 @@
import './CeleryOverviewConfigOptions.styles.scss'; import './CeleryOverviewConfigOptions.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Row, Select, Spin } from 'antd';
import { Button, Row, Select, Spin, Tooltip } from 'antd';
import { import {
getValuesFromQueryParams, getValuesFromQueryParams,
setQueryParamsFromOptions, setQueryParamsFromOptions,
@@ -10,10 +9,7 @@ import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOpt
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon'; import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { Check, Share2 } from 'lucide-react';
import { useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
interface SelectOptionConfig { interface SelectOptionConfig {
placeholder: string; placeholder: string;
@@ -66,10 +62,6 @@ function FilterSelect({
} }
function CeleryOverviewConfigOptions(): JSX.Element { function CeleryOverviewConfigOptions(): JSX.Element {
const [isURLCopied, setIsURLCopied] = useState(false);
const [, handleCopyToClipboard] = useCopyToClipboard();
const selectConfigs: SelectOptionConfig[] = [ const selectConfigs: SelectOptionConfig[] = [
{ {
placeholder: 'Service Name', 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 ( return (
<div className="celery-overview-filters"> <div className="celery-overview-filters">
<Row className="celery-filters"> <Row className="celery-filters">
@@ -118,19 +102,6 @@ function CeleryOverviewConfigOptions(): JSX.Element {
/> />
))} ))}
</Row> </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> </div>
); );
} }

View File

@@ -37,7 +37,6 @@
font-weight: 600; font-weight: 600;
line-height: 18px; /* 163.636% */ line-height: 18px; /* 163.636% */
letter-spacing: 0.44px; letter-spacing: 0.44px;
text-transform: uppercase;
&::before { &::before {
background-color: transparent; background-color: transparent;

View File

@@ -218,35 +218,44 @@ function getColumns(data: RowData[]): TableColumnsType<RowData> {
showTitle: false, showTitle: false,
}, },
width: 200, width: 200,
sorter: (a: RowData, b: RowData): number => sorter: (a: RowData, b: RowData): number => {
String(a.error_percentage).localeCompare(String(b.error_percentage)), const aValue = Number(a.error_percentage);
const bValue = Number(b.error_percentage);
return aValue - bValue;
},
render: ProgressRender, render: ProgressRender,
}, },
{ {
title: 'LATENCY (P95)', title: 'LATENCY (P95) in ms',
dataIndex: 'p95_latency', dataIndex: 'p95_latency',
key: 'p95_latency', key: 'p95_latency',
ellipsis: { ellipsis: {
showTitle: false, showTitle: false,
}, },
width: 100, width: 100,
sorter: (a: RowData, b: RowData): number => sorter: (a: RowData, b: RowData): number => {
String(a.p95_latency).localeCompare(String(b.p95_latency)), const aValue = Number(a.p95_latency);
const bValue = Number(b.p95_latency);
return aValue - bValue;
},
render: (value: number | string): string => { render: (value: number | string): string => {
if (!isNumber(value)) return value.toString(); if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3); return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);
}, },
}, },
{ {
title: 'THROUGHPUT', title: 'THROUGHPUT (ops/s)',
dataIndex: 'throughput', dataIndex: 'throughput',
key: 'throughput', key: 'throughput',
ellipsis: { ellipsis: {
showTitle: false, showTitle: false,
}, },
width: 100, width: 100,
sorter: (a: RowData, b: RowData): number => sorter: (a: RowData, b: RowData): number => {
String(a.throughput).localeCompare(String(b.throughput)), const aValue = Number(a.throughput);
const bValue = Number(b.throughput);
return aValue - bValue;
},
render: (value: number | string): string => { render: (value: number | string): string => {
if (!isNumber(value)) return value.toString(); if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3); return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);

View File

@@ -2,24 +2,16 @@ import './CeleryTaskDetail.style.scss';
import { Color, Spacing } from '@signozhq/design-tokens'; import { Color, Spacing } from '@signozhq/design-tokens';
import { Divider, Drawer, Typography } from 'antd'; import { Divider, Drawer, Typography } from 'antd';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useEffect, useState } from 'react'; import { 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 { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; 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 CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
import { createFiltersFromData } from '../CeleryUtils';
import { useNavigateToTraces } from '../useNavigateToTraces'; import { useNavigateToTraces } from '../useNavigateToTraces';
export type CeleryTaskData = { export type CeleryTaskData = {
@@ -39,40 +31,6 @@ export type CeleryTaskDetailProps = {
drawerOpen: boolean; 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({ export default function CeleryTaskDetail({
widgetData, widgetData,
taskData, taskData,
@@ -85,7 +43,7 @@ export default function CeleryTaskDetail({
!!taskData.entity && !!taskData.timeRange[0] && drawerOpen; !!taskData.entity && !!taskData.timeRange[0] && drawerOpen;
const formatTimestamp = (timestamp: number): string => 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); const [totalTask, setTotalTask] = useState(0);
@@ -93,52 +51,9 @@ export default function CeleryTaskDetail({
setTotalTask((graphData?.result?.[0] as any)?.table?.rows.length); 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 startTime = taskData.timeRange[0];
const endTime = taskData.timeRange[1]; 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(); const navigateToTrace = useNavigateToTraces();
return ( return (
@@ -149,10 +64,8 @@ export default function CeleryTaskDetail({
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text> <Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
<div> <div>
<Typography.Text className="subtitle"> <Typography.Text className="subtitle">
{`${formatTimestamp(taskData.timeRange[0])} ${ {`${formatTimestamp(startTime)} ${
taskData.timeRange[1] endTime ? `- ${formatTimestamp(endTime)}` : ''
? `- ${formatTimestamp(taskData.timeRange[1])}`
: ''
}`} }`}
</Typography.Text> </Typography.Text>
<Divider type="vertical" /> <Divider type="vertical" />
@@ -185,8 +98,10 @@ export default function CeleryTaskDetail({
...rowData, ...rowData,
[taskData.entity]: taskData.value, [taskData.entity]: taskData.value,
}); });
navigateToTrace(filters); navigateToTrace(filters, startTime, endTime);
}} }}
start={startTime}
end={endTime}
/> />
</Drawer> </Drawer>
); );

View File

@@ -19,6 +19,10 @@ import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail'; import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import {
applyCeleryFilterOnWidgetData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import { useGetGraphCustomSeries } from '../useGetGraphCustomSeries'; import { useGetGraphCustomSeries } from '../useGetGraphCustomSeries';
import { import {
celeryAllStateWidgetData, celeryAllStateWidgetData,
@@ -72,26 +76,60 @@ function CeleryTaskBar({
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All); const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(
QueryParams.taskName,
urlQuery,
'celery.task_name',
),
[urlQuery],
);
const celeryAllStateData = useMemo( const celeryAllStateData = useMemo(
() => celeryAllStateWidgetData(minTime, maxTime), () => celeryAllStateWidgetData(minTime, maxTime),
[minTime, maxTime], [minTime, maxTime],
); );
const celeryAllStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryAllStateData),
[selectedFilters, celeryAllStateData],
);
const celeryFailedStateData = useMemo( const celeryFailedStateData = useMemo(
() => celeryFailedStateWidgetData(minTime, maxTime), () => celeryFailedStateWidgetData(minTime, maxTime),
[minTime, maxTime], [minTime, maxTime],
); );
const celeryFailedStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryFailedStateData),
[selectedFilters, celeryFailedStateData],
);
const celeryRetryStateData = useMemo( const celeryRetryStateData = useMemo(
() => celeryRetryStateWidgetData(minTime, maxTime), () => celeryRetryStateWidgetData(minTime, maxTime),
[minTime, maxTime], [minTime, maxTime],
); );
const celeryRetryStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryRetryStateData),
[selectedFilters, celeryRetryStateData],
);
const celerySuccessStateData = useMemo( const celerySuccessStateData = useMemo(
() => celerySuccessStateWidgetData(minTime, maxTime), () => celerySuccessStateWidgetData(minTime, maxTime),
[minTime, maxTime], [minTime, maxTime],
); );
const celerySuccessStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celerySuccessStateData),
[selectedFilters, celerySuccessStateData],
);
const onGraphClick = ( const onGraphClick = (
widgetData: Widgets, widgetData: Widgets,
xValue: number, xValue: number,
@@ -141,7 +179,7 @@ function CeleryTaskBar({
<div className="celery-task-graph-grid-content"> <div className="celery-task-graph-grid-content">
{barState === CeleryTaskState.All && ( {barState === CeleryTaskState.All && (
<GridCard <GridCard
widget={celeryAllStateData} widget={celeryAllStateFilteredData}
headerMenuList={[...ViewMenuAction]} headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled} isQueryEnabled={queryEnabled}
@@ -153,7 +191,7 @@ function CeleryTaskBar({
)} )}
{barState === CeleryTaskState.Failed && ( {barState === CeleryTaskState.Failed && (
<GridCard <GridCard
widget={celeryFailedStateData} widget={celeryFailedStateFilteredData}
headerMenuList={[...ViewMenuAction]} headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled} isQueryEnabled={queryEnabled}
@@ -165,7 +203,7 @@ function CeleryTaskBar({
)} )}
{barState === CeleryTaskState.Retry && ( {barState === CeleryTaskState.Retry && (
<GridCard <GridCard
widget={celeryRetryStateData} widget={celeryRetryStateFilteredData}
headerMenuList={[...ViewMenuAction]} headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled} isQueryEnabled={queryEnabled}
@@ -177,7 +215,7 @@ function CeleryTaskBar({
)} )}
{barState === CeleryTaskState.Successful && ( {barState === CeleryTaskState.Successful && (
<GridCard <GridCard
widget={celerySuccessStateData} widget={celerySuccessStateFilteredData}
headerMenuList={[...ViewMenuAction]} headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled} isQueryEnabled={queryEnabled}

View File

@@ -116,9 +116,8 @@
gap: 10px; gap: 10px;
.metric-page-grid { .metric-page-grid {
display: grid; display: flex;
grid-template-columns: 50% 50%; flex-direction: row;
align-items: flex-start;
gap: 10px; gap: 10px;
width: 100%; width: 100%;
@@ -144,6 +143,11 @@
gap: 16px; gap: 16px;
align-items: center; 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 { .configure-option-Info-text {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
font-size: 12px; font-size: 12px;
@@ -241,11 +245,13 @@
.lightMode { .lightMode {
.celery-task-graph-grid-container { .celery-task-graph-grid-container {
.celery-task-graph-grid { .celery-task-graph-worker-count {
.celery-task-graph-worker-count { border: 1px solid var(--bg-vanilla-300);
border: 1px solid var(--bg-vanilla-300); background: unset;
background: unset; }
}
.row-panel .row-panel-section .section-title {
color: var(--bg-ink-400);
} }
} }
@@ -274,4 +280,8 @@
background-color: var(--bg-ink-400); background-color: var(--bg-ink-400);
} }
} }
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
}
} }

View File

@@ -32,6 +32,9 @@ function CeleryTaskGraph({
openTracesButton, openTracesButton,
onOpenTraceBtnClick, onOpenTraceBtnClick,
applyCeleryTaskFilter, applyCeleryTaskFilter,
customErrorMessage,
start,
end,
}: { }: {
widgetData: Widgets; widgetData: Widgets;
onClick?: (task: CaptureDataProps) => void; onClick?: (task: CaptureDataProps) => void;
@@ -42,6 +45,9 @@ function CeleryTaskGraph({
openTracesButton?: boolean; openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void; onOpenTraceBtnClick?: (record: RowData) => void;
applyCeleryTaskFilter?: boolean; applyCeleryTaskFilter?: boolean;
customErrorMessage?: string;
start?: number;
end?: number;
}): JSX.Element { }): JSX.Element {
const history = useHistory(); const history = useHistory();
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -116,6 +122,9 @@ function CeleryTaskGraph({
openTracesButton={openTracesButton} openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick} onOpenTraceBtnClick={onOpenTraceBtnClick}
version={ENTITY_VERSION_V4} version={ENTITY_VERSION_V4}
customErrorMessage={customErrorMessage}
start={start}
end={end}
/> />
</Card> </Card>
); );
@@ -129,6 +138,9 @@ CeleryTaskGraph.defaultProps = {
openTracesButton: false, openTracesButton: false,
onOpenTraceBtnClick: undefined, onOpenTraceBtnClick: undefined,
applyCeleryTaskFilter: false, applyCeleryTaskFilter: false,
customErrorMessage: undefined,
start: undefined,
end: undefined,
}; };
export default CeleryTaskGraph; export default CeleryTaskGraph;

View File

@@ -123,11 +123,12 @@ export default function CeleryTaskGraphGrid({
key={celeryActiveTasksData.id} key={celeryActiveTasksData.id}
widgetData={celeryActiveTasksData} widgetData={celeryActiveTasksData}
queryEnabled={queryEnabled} queryEnabled={queryEnabled}
customErrorMessage="Enable Flower metrics to view this graph"
/> />
<Card className="celery-task-graph-worker-count"> <Card className="celery-task-graph-worker-count">
<div className="worker-count-header"> <div className="worker-count-header">
<Typography.Text className="worker-count-header-text"> <Typography.Text className="worker-count-header-text">
Worker Count Worker Online
</Typography.Text> </Typography.Text>
</div> </div>
<div className="worker-count-text-container"> <div className="worker-count-text-container">
@@ -173,7 +174,7 @@ export default function CeleryTaskGraphGrid({
{!collapsedSections.traceBasedGraphs && ( {!collapsedSections.traceBasedGraphs && (
<> <>
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} /> <CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
<CeleryTaskLatencyGraph onClick={onClick} queryEnabled={queryEnabled} /> <CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
<div className="celery-task-graph-grid-bottom"> <div className="celery-task-graph-grid-bottom">
{bottomWidgetData.map((widgetData, index) => ( {bottomWidgetData.map((widgetData, index) => (
<CeleryTaskGraph <CeleryTaskGraph

View File

@@ -42,21 +42,7 @@ export const celeryAllStateWidgetData = (
disabled: false, disabled: false,
expression: 'A', expression: 'A',
filters: { filters: {
items: [ 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',
},
],
op: 'AND', op: 'AND',
}, },
functions: [], functions: [],
@@ -113,7 +99,7 @@ export const celeryRetryStateWidgetData = (
filters: { filters: {
items: [ items: [
{ {
id: '6d97eed3', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -179,7 +165,7 @@ export const celeryFailedStateWidgetData = (
filters: { filters: {
items: [ items: [
{ {
id: '5983eae2', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -245,7 +231,7 @@ export const celerySuccessStateWidgetData = (
filters: { filters: {
items: [ items: [
{ {
id: '000c5a93', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -602,7 +588,7 @@ export const celeryTaskLatencyWidgetData = (
reduceTo: 'avg', reduceTo: 'avg',
spaceAggregation: 'sum', spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime), stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'p99', timeAggregation: type || 'p99',
}, },
], ],
yAxisUnit: 'ns', yAxisUnit: 'ns',
@@ -686,7 +672,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
filters: { filters: {
items: [ items: [
{ {
id: '9e09c9ed', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -755,7 +741,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
filters: { filters: {
items: [ items: [
{ {
id: '2330f906', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -822,7 +808,7 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
filters: { filters: {
items: [ items: [
{ {
id: 'ec3df7b7', id: uuidv4(),
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
id: 'celery.state--string--tag--false', id: 'celery.state--string--tag--false',
@@ -945,33 +931,19 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
queryData: [ queryData: [
{ {
aggregateAttribute: { aggregateAttribute: {
dataType: DataTypes.EMPTY, dataType: DataTypes.String,
id: '------false', id: 'span_id--string----true',
isColumn: false, isColumn: true,
isJSON: false, isJSON: false,
key: '', key: 'span_id',
type: '', type: '',
}, },
aggregateOperator: 'count', aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES, dataSource: DataSource.TRACES,
disabled: false, disabled: false,
expression: 'A', expression: 'A',
filters: { filters: {
items: [ 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',
},
],
op: 'AND', op: 'AND',
}, },
functions: [], functions: [],
@@ -981,10 +953,10 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
limit: null, limit: null,
orderBy: [], orderBy: [],
queryName: 'A', queryName: 'A',
reduceTo: 'avg', reduceTo: 'last',
spaceAggregation: 'sum', spaceAggregation: 'sum',
stepInterval: 60, stepInterval: 60,
timeAggregation: 'rate', timeAggregation: 'count_distinct',
}, },
], ],
}), }),
@@ -998,14 +970,14 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
queryData: [ queryData: [
{ {
aggregateAttribute: { aggregateAttribute: {
dataType: DataTypes.EMPTY, dataType: DataTypes.String,
id: '------false', id: 'span_id--string----true',
isColumn: false, isColumn: true,
isJSON: false, isJSON: false,
key: '', key: 'span_id',
type: '', type: '',
}, },
aggregateOperator: 'count', aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES, dataSource: DataSource.TRACES,
disabled: false, disabled: false,
expression: 'A', expression: 'A',
@@ -1034,10 +1006,10 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
limit: null, limit: null,
orderBy: [], orderBy: [],
queryName: 'A', queryName: 'A',
reduceTo: 'avg', reduceTo: 'last',
spaceAggregation: 'sum', spaceAggregation: 'sum',
stepInterval: 60, stepInterval: 60,
timeAggregation: 'rate', timeAggregation: 'count_distinct',
}, },
], ],
}), }),
@@ -1051,14 +1023,14 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
queryData: [ queryData: [
{ {
aggregateAttribute: { aggregateAttribute: {
dataType: DataTypes.EMPTY, dataType: DataTypes.String,
id: '------false', id: 'span_id--string----true',
isColumn: false, isColumn: true,
isJSON: false, isJSON: false,
key: '', key: 'span_id',
type: '', type: '',
}, },
aggregateOperator: 'count', aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES, dataSource: DataSource.TRACES,
disabled: false, disabled: false,
expression: 'A', expression: 'A',
@@ -1087,10 +1059,10 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
limit: null, limit: null,
orderBy: [], orderBy: [],
queryName: 'A', queryName: 'A',
reduceTo: 'avg', reduceTo: 'last',
spaceAggregation: 'sum', spaceAggregation: 'sum',
stepInterval: 60, stepInterval: 60,
timeAggregation: 'rate', timeAggregation: 'count_distinct',
}, },
], ],
}), }),
@@ -1104,13 +1076,13 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
queryData: [ queryData: [
{ {
aggregateAttribute: { aggregateAttribute: {
dataType: DataTypes.EMPTY, dataType: DataTypes.String,
id: '------false', id: 'span_id--string----true',
isColumn: false, isColumn: true,
key: '', key: 'span_id',
type: '', type: '',
}, },
aggregateOperator: 'count', aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES, dataSource: DataSource.TRACES,
disabled: false, disabled: false,
expression: 'A', expression: 'A',
@@ -1139,10 +1111,10 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
limit: null, limit: null,
orderBy: [], orderBy: [],
queryName: 'A', queryName: 'A',
reduceTo: 'avg', reduceTo: 'last',
spaceAggregation: 'sum', spaceAggregation: 'sum',
stepInterval: 60, stepInterval: 60,
timeAggregation: 'rate', timeAggregation: 'count_distinct',
}, },
], ],
}), }),

View File

@@ -6,8 +6,11 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { ViewMenuAction } from 'container/GridCardLayout/config'; import { ViewMenuAction } from 'container/GridCardLayout/config';
import GridCard from 'container/GridCardLayout/GridCard'; import GridCard from 'container/GridCardLayout/GridCard';
import { Card } from 'container/GridCardLayout/styles'; 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 { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
@@ -16,15 +19,13 @@ import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import { import {
applyCeleryFilterOnWidgetData, applyCeleryFilterOnWidgetData,
createFiltersFromData,
getFiltersFromQueryParams, getFiltersFromQueryParams,
} from '../CeleryUtils'; } from '../CeleryUtils';
import { import { useNavigateToTraces } from '../useNavigateToTraces';
celeryTaskLatencyWidgetData, import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
celeryTimeSeriesTablesWidgetData,
} from './CeleryTaskGraphUtils';
interface TabData { interface TabData {
label: string; label: string;
@@ -38,10 +39,8 @@ export enum CeleryTaskGraphState {
} }
function CeleryTaskLatencyGraph({ function CeleryTaskLatencyGraph({
onClick,
queryEnabled, queryEnabled,
}: { }: {
onClick: (task: CaptureDataProps) => void;
queryEnabled: boolean; queryEnabled: boolean;
}): JSX.Element { }): JSX.Element {
const history = useHistory(); const history = useHistory();
@@ -106,31 +105,51 @@ function CeleryTaskLatencyGraph({
[celeryTaskLatencyData, selectedFilters], [celeryTaskLatencyData, selectedFilters],
); );
const onGraphClick = ( const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
xValue: number, const [entityData, setEntityData] = useState<{
_yValue: number, entity: string;
_mouseX: number, value: string;
_mouseY: number, }>();
data?: {
[key: string]: 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 => { [handleSetTimeStamp],
const { start, end } = getStartAndEndTimesInMilliseconds(xValue); );
// Extract entity and value from data const navigateToTraces = useNavigateToTraces();
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = (firstDataPoint || ([] as unknown)) as [
string,
string,
];
onClick?.({ const goToTraces = useCallback(() => {
entity, const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
value, const filters = createFiltersFromData({
timeRange: [start, end], [entityData?.entity as string]: entityData?.value,
widgetData: celeryTimeSeriesTablesWidgetData(entity, value, 'Task Latency'),
}); });
}; navigateToTraces(filters, start, end, true);
}, [entityData, navigateToTraces, selectedTimeStamp]);
return ( return (
<Card <Card
@@ -161,32 +180,62 @@ function CeleryTaskLatencyGraph({
</Row> </Row>
<div className="celery-task-graph-grid-content"> <div className="celery-task-graph-grid-content">
{graphState === CeleryTaskGraphState.P99 && ( {graphState === CeleryTaskGraphState.P99 && (
<GridCard <>
widget={updatedWidgetData} <Button
headerMenuList={[...ViewMenuAction]} type="default"
onDragSelect={onDragSelect} size="small"
onClickHandler={onGraphClick} id="Celery_p99_latency_button"
isQueryEnabled={queryEnabled} onClick={goToTraces}
/> >
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p99_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)} )}
{graphState === CeleryTaskGraphState.P95 && ( {graphState === CeleryTaskGraphState.P95 && (
<GridCard <>
widget={updatedWidgetData} <Button
headerMenuList={[...ViewMenuAction]} type="default"
onDragSelect={onDragSelect} size="small"
onClickHandler={onGraphClick} id="Celery_p95_latency_button"
isQueryEnabled={queryEnabled} onClick={goToTraces}
/> >
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p95_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)} )}
{graphState === CeleryTaskGraphState.P90 && ( {graphState === CeleryTaskGraphState.P90 && (
<GridCard <>
widget={updatedWidgetData} <Button
headerMenuList={[...ViewMenuAction]} type="default"
onDragSelect={onDragSelect} size="small"
onClickHandler={onGraphClick} id="Celery_p90_latency_button"
isQueryEnabled={queryEnabled} onClick={goToTraces}
/> >
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p90_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)} )}
</div> </div>
</Card> </Card>

View File

@@ -2,8 +2,14 @@
import './CeleryTaskGraph.style.scss'; import './CeleryTaskGraph.style.scss';
import { Col, Row } from 'antd'; 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 { import {
celeryAllStateCountWidgetData, celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData, celeryFailedStateCountWidgetData,
@@ -42,16 +48,29 @@ function CeleryTaskStateGraphConfig({
setBarState(key as CeleryTaskState); setBarState(key as CeleryTaskState);
}; };
const { values, isLoading, isError } = useGetValueFromWidget( const urlQuery = useUrlQuery();
[
celeryAllStateCountWidgetData, const selectedFilters = useMemo(
celeryFailedStateCountWidgetData, () =>
celeryRetryStateCountWidgetData, getFiltersFromQueryParams(
celerySuccessStateCountWidgetData, QueryParams.taskName,
], urlQuery,
['celery-task-states'], '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 ( return (
<Row className="celery-task-states"> <Row className="celery-task-states">
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
@@ -66,7 +85,13 @@ function CeleryTaskStateGraphConfig({
<div className="celery-task-states__label-wrapper"> <div className="celery-task-states__label-wrapper">
<div className="celery-task-states__label">{tab.label}</div> <div className="celery-task-states__label">{tab.label}</div>
<div className="celery-task-states__value"> <div className="celery-task-states__value">
{isLoading ? '-' : isError ? '-' : values[index]} {isLoading
? '-'
: isError
? '-'
: Number.isNaN(values[index])
? '-'
: Math.round(Number(values[index]))}
</div> </div>
</div> </div>
{tab.key === barState && <div className="celery-task-states__indicator" />} {tab.key === barState && <div className="celery-task-states__indicator" />}

View File

@@ -90,3 +90,40 @@ export const paths = (
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip); 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(),
}))
);
};

View File

@@ -12,6 +12,7 @@ export function useNavigateToTraces(): (
filters: TagFilterItem[], filters: TagFilterItem[],
startTime?: number, startTime?: number,
endTime?: number, endTime?: number,
sameTab?: boolean,
) => void { ) => void {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>( const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
@@ -38,7 +39,12 @@ export function useNavigateToTraces(): (
); );
return useCallback( return useCallback(
(filters: TagFilterItem[], startTime?: number, endTime?: number): void => { (
filters: TagFilterItem[],
startTime?: number,
endTime?: number,
sameTab?: boolean,
): void => {
const urlParams = new URLSearchParams(); const urlParams = new URLSearchParams();
if (startTime && endTime) { if (startTime && endTime) {
urlParams.set(QueryParams.startTime, startTime.toString()); urlParams.set(QueryParams.startTime, startTime.toString());
@@ -58,7 +64,7 @@ export function useNavigateToTraces(): (
QueryParams.compositeQuery QueryParams.compositeQuery
}=${JSONCompositeQuery}`; }=${JSONCompositeQuery}`;
window.open(newTraceExplorerPath, '_blank'); window.open(newTraceExplorerPath, sameTab ? '_self' : '_blank');
}, },
[minTime, maxTime, prepareQuery], [minTime, maxTime, prepareQuery],
); );

View File

@@ -133,7 +133,7 @@
.go-to-docs { .go-to-docs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 64px; gap: 44px;
&__container { &__container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -23,4 +23,5 @@ export enum FeatureKeys {
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT', PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2', QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
ANOMALY_DETECTION = 'ANOMALY_DETECTION', ANOMALY_DETECTION = 'ANOMALY_DETECTION',
AWS_INTEGRATION = 'AWS_INTEGRATION',
} }

View File

@@ -41,5 +41,6 @@ export const REACT_QUERY_KEY = {
AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG', AWS_UPDATE_ACCOUNT_CONFIG: 'AWS_UPDATE_ACCOUNT_CONFIG',
AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG', AWS_UPDATE_SERVICE_CONFIG: 'AWS_UPDATE_SERVICE_CONFIG',
AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL', AWS_GENERATE_CONNECTION_URL: 'AWS_GENERATE_CONNECTION_URL',
AWS_GET_CONNECTION_PARAMS: 'AWS_GET_CONNECTION_PARAMS',
GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES', GET_ATTRIBUTE_VALUES: 'GET_ATTRIBUTE_VALUES',
}; };

View File

@@ -65,6 +65,9 @@ const ROUTES = {
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes', INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task', MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview', 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; } as const;
export default ROUTES; export default ROUTES;

View File

@@ -250,7 +250,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if ( if (
!isFetchingActiveLicenseV3 && !isFetchingActiveLicenseV3 &&
!isNull(activeLicenseV3) && !isNull(activeLicenseV3) &&
activeLicenseV3?.event_queue?.event === LicenseEvent.FAILED_PAYMENT activeLicenseV3?.event_queue?.event === LicenseEvent.DEFAULT
) { ) {
setShowPaymentFailedWarning(true); setShowPaymentFailedWarning(true);
} }

View File

@@ -24,7 +24,9 @@ function Header(): JSX.Element {
}, },
{ {
title: ( title: (
<div className="cloud-header__breadcrumb-title">AWS web services</div> <div className="cloud-header__breadcrumb-title">
Amazon Web Services
</div>
), ),
}, },
]} ]}

View File

@@ -21,7 +21,7 @@ function HeroSection(): JSX.Element {
<img src="/Logos/aws-dark.svg" alt="aws-logo" /> <img src="/Logos/aws-dark.svg" alt="aws-logo" />
</div> </div>
<div className="hero-section__details"> <div className="hero-section__details">
<div className="title">AWS Web Services</div> <div className="title">Amazon Web Services</div>
<div className="description"> <div className="description">
One-click setup for AWS monitoring with SigNoz One-click setup for AWS monitoring with SigNoz
</div> </div>

View File

@@ -1,41 +1,56 @@
.hero-section__actions { .hero-section {
margin-top: 12px; &__actions {
margin-top: 12px;
&-with-account { &-with-account {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; 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);
} }
&.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; display: flex;
align-items: center; align-items: center;
border: 1px solid var(--bg-ink-300); gap: 8px;
color: var(--bg-vanilla-100); }
&__action-button {
font-family: 'Inter';
border-radius: 2px; border-radius: 2px;
background: var(--bg-slate-400); cursor: pointer;
box-shadow: none; 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;
}
} }
} }

View File

@@ -1,7 +1,7 @@
import './AccountActions.style.scss'; import './AccountActions.style.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Select } from 'antd'; import { Button, Select, Skeleton } from 'antd';
import { SelectProps } from 'antd/lib'; import { SelectProps } from 'antd/lib';
import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts'; import { useAwsAccounts } from 'hooks/integrations/aws/useAwsAccounts';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
@@ -53,15 +53,100 @@ const getAccountById = (
): CloudAccount | null => ): CloudAccount | null =>
accounts.find((account) => account.cloud_account_id === accountId) || 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 { function AccountActions(): JSX.Element {
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const navigate = useNavigate(); const navigate = useNavigate();
const { data: accounts } = useAwsAccounts(); const { data: accounts, isLoading } = useAwsAccounts();
const initialAccount = useMemo( const initialAccount = useMemo(
() => () =>
accounts?.length accounts?.length
? getAccountById(accounts, urlQuery.get('accountId') || '') || accounts[0] ? getAccountById(accounts, urlQuery.get('cloudAccountId') || '') ||
accounts[0]
: null, : null,
[accounts, urlQuery], [accounts, urlQuery],
); );
@@ -74,7 +159,7 @@ function AccountActions(): JSX.Element {
useEffect(() => { useEffect(() => {
if (initialAccount !== null) { if (initialAccount !== null) {
setActiveAccount(initialAccount); setActiveAccount(initialAccount);
urlQuery.set('accountId', initialAccount.cloud_account_id); urlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
navigate({ search: urlQuery.toString() }); navigate({ search: urlQuery.toString() });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -98,60 +183,35 @@ function AccountActions(): JSX.Element {
return ( return (
<div className="hero-section__actions"> <div className="hero-section__actions">
{accounts?.length ? ( <AccountActionsRenderer
<div className="hero-section__actions-with-account"> accounts={accounts}
<Select isLoading={isLoading}
value={`Account: ${activeAccount?.cloud_account_id}`} activeAccount={activeAccount}
options={selectOptions} selectOptions={selectOptions}
rootClassName="cloud-account-selector" onAccountChange={(value): void => {
placeholder="Select AWS Account" if (accounts) {
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />} setActiveAccount(getAccountById(accounts, value));
optionRender={(option): JSX.Element => urlQuery.set('cloudAccountId', value);
renderOption(option, activeAccount?.cloud_account_id) navigate({ search: urlQuery.toString() });
} }
onChange={(value): void => { }}
setActiveAccount(getAccountById(accounts, value)); onIntegrationModalOpen={(): void => setIsIntegrationModalOpen(true)}
urlQuery.set('accountId', value); onAccountSettingsModalOpen={(): void => setIsAccountSettingsModalOpen(true)}
navigate({ search: urlQuery.toString() }); />
}}
/> {isIntegrationModalOpen && (
<div className="hero-section__action-buttons"> <CloudAccountSetupModal
<Button onClose={(): void => setIsIntegrationModalOpen(false)}
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>
)} )}
<CloudAccountSetupModal {isAccountSettingsModalOpen && (
isOpen={isIntegrationModalOpen} <AccountSettingsModal
onClose={(): void => setIsIntegrationModalOpen(false)} onClose={(): void => setIsAccountSettingsModalOpen(false)}
/> account={activeAccount as CloudAccount}
setActiveAccount={setActiveAccount}
<AccountSettingsModal />
isOpen={isAccountSettingsModalOpen} )}
onClose={(): void => setIsAccountSettingsModalOpen(false)}
account={activeAccount as CloudAccount}
setActiveAccount={setActiveAccount}
/>
</div> </div>
); );
} }

View File

@@ -2,27 +2,27 @@ import './AccountSettingsModal.style.scss';
import { Form, Select, Switch } from 'antd'; import { Form, Select, Switch } from 'antd';
import SignozModal from 'components/SignozModal/SignozModal'; import SignozModal from 'components/SignozModal/SignozModal';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { import {
getRegionPreviewText, getRegionPreviewText,
useAccountSettingsModal, useAccountSettingsModal,
} from 'hooks/integrations/aws/useAccountSettingsModal'; } from 'hooks/integrations/aws/useAccountSettingsModal';
import IntergrationsUninstallBar from 'pages/Integrations/IntegrationDetailPage/IntegrationsUninstallBar'; import useUrlQuery from 'hooks/useUrlQuery';
import { ConnectionStates } from 'pages/Integrations/IntegrationDetailPage/TestConnection'; import history from 'lib/history';
import { AWS_INTEGRATION } from 'pages/Integrations/IntegrationsList';
import { Dispatch, SetStateAction, useCallback } from 'react'; import { Dispatch, SetStateAction, useCallback } from 'react';
import { useQueryClient } from 'react-query';
import { CloudAccount } from '../../ServicesSection/types'; import { CloudAccount } from '../../ServicesSection/types';
import { RegionSelector } from './RegionSelector'; import { RegionSelector } from './RegionSelector';
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
interface AccountSettingsModalProps { interface AccountSettingsModalProps {
isOpen: boolean;
onClose: () => void; onClose: () => void;
account: CloudAccount; account: CloudAccount;
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>; setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
} }
function AccountSettingsModal({ function AccountSettingsModal({
isOpen,
onClose, onClose,
account, account,
setActiveAccount, setActiveAccount,
@@ -42,6 +42,16 @@ function AccountSettingsModal({
handleClose, handleClose,
} = useAccountSettingsModal({ onClose, account, setActiveAccount }); } = 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(() => { const renderRegionSelector = useCallback(() => {
if (isRegionSelectOpen) { if (isRegionSelectOpen) {
return ( return (
@@ -120,7 +130,7 @@ function AccountSettingsModal({
return ( return (
<SignozModal <SignozModal
open={isOpen} open
title={modalTitle} title={modalTitle}
onCancel={handleClose} onCancel={handleClose}
onOk={handleSubmit} onOk={handleSubmit}
@@ -164,12 +174,9 @@ function AccountSettingsModal({
</Form.Item> </Form.Item>
<div className="integration-detail-content"> <div className="integration-detail-content">
<IntergrationsUninstallBar <RemoveIntegrationAccount
integrationTitle={AWS_INTEGRATION.title} accountId={account?.id}
integrationId={AWS_INTEGRATION.id} onRemoveIntegrationAccountSuccess={handleRemoveIntegrationAccountSuccess}
onUnInstallSuccess={handleClose}
removeIntegrationTitle="Remove"
connectionStatus={ConnectionStates.Connected}
/> />
</div> </div>
</div> </div>

View File

@@ -2,11 +2,9 @@ import './CloudAccountSetupModal.style.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import SignozModal from 'components/SignozModal/SignozModal'; import SignozModal from 'components/SignozModal/SignozModal';
import ROUTES from 'constants/routes';
import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal'; import { useIntegrationModal } from 'hooks/integrations/aws/useIntegrationModal';
import { SquareArrowOutUpRight } from 'lucide-react'; import { SquareArrowOutUpRight } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { import {
ActiveViewEnum, ActiveViewEnum,
@@ -18,7 +16,6 @@ import { RegionSelector } from './RegionSelector';
import { SuccessView } from './SuccessView'; import { SuccessView } from './SuccessView';
function CloudAccountSetupModal({ function CloudAccountSetupModal({
isOpen,
onClose, onClose,
}: IntegrationModalProps): JSX.Element { }: IntegrationModalProps): JSX.Element {
const { const {
@@ -41,6 +38,8 @@ function CloudAccountSetupModal({
accountId, accountId,
selectedDeploymentRegion, selectedDeploymentRegion,
handleRegionChange, handleRegionChange,
connectionParams,
isConnectionParamsLoading,
} = useIntegrationModal({ onClose }); } = useIntegrationModal({ onClose });
const renderContent = useCallback(() => { const renderContent = useCallback(() => {
@@ -71,6 +70,8 @@ function CloudAccountSetupModal({
accountId={accountId} accountId={accountId}
selectedDeploymentRegion={selectedDeploymentRegion} selectedDeploymentRegion={selectedDeploymentRegion}
handleRegionChange={handleRegionChange} handleRegionChange={handleRegionChange}
connectionParams={connectionParams}
isConnectionParamsLoading={isConnectionParamsLoading}
/> />
); );
}, [ }, [
@@ -86,6 +87,8 @@ function CloudAccountSetupModal({
accountId, accountId,
selectedDeploymentRegion, selectedDeploymentRegion,
handleRegionChange, handleRegionChange,
connectionParams,
isConnectionParamsLoading,
setSelectedRegions, setSelectedRegions,
setIncludeAllRegions, setIncludeAllRegions,
]); ]);
@@ -96,11 +99,6 @@ function CloudAccountSetupModal({
[selectedRegions, allRegions], [selectedRegions, allRegions],
); );
const navigate = useNavigate();
const handleGoToDashboards = useCallback((): void => {
navigate(ROUTES.ALL_DASHBOARD);
}, [navigate]);
const getModalConfig = useCallback(() => { const getModalConfig = useCallback(() => {
// Handle success state first // Handle success state first
if (modalState === ModalStateEnum.SUCCESS) { if (modalState === ModalStateEnum.SUCCESS) {
@@ -108,11 +106,11 @@ function CloudAccountSetupModal({
title: 'AWS Webservice Integration', title: 'AWS Webservice Integration',
okText: ( okText: (
<div className="cloud-account-setup-success-view__footer-button"> <div className="cloud-account-setup-success-view__footer-button">
Go to Dashboards Continue
</div> </div>
), ),
block: true, block: true,
onOk: handleGoToDashboards, onOk: handleClose,
cancelButtonProps: { style: { display: 'none' } }, cancelButtonProps: { style: { display: 'none' } },
disabled: false, disabled: false,
}; };
@@ -151,7 +149,7 @@ function CloudAccountSetupModal({
isLoading, isLoading,
isGeneratingUrl, isGeneratingUrl,
activeView, activeView,
handleGoToDashboards, handleClose,
setActiveView, setActiveView,
]); ]);
@@ -159,7 +157,7 @@ function CloudAccountSetupModal({
return ( return (
<SignozModal <SignozModal
open={isOpen} open
className="cloud-account-setup-modal" className="cloud-account-setup-modal"
title={modalConfig.title} title={modalConfig.title}
onCancel={handleClose} onCancel={handleClose}

View File

@@ -12,6 +12,7 @@ import {
MonitoringRegionsSection, MonitoringRegionsSection,
RegionDeploymentSection, RegionDeploymentSection,
} from './IntegrateNowFormSections'; } from './IntegrateNowFormSections';
import RenderConnectionFields from './RenderConnectionParams';
const allRegions = (): string[] => const allRegions = (): string[] =>
regions.flatMap((r) => r.subRegions.map((sr) => sr.name)); regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
@@ -35,6 +36,8 @@ export function RegionForm({
accountId, accountId,
selectedDeploymentRegion, selectedDeploymentRegion,
handleRegionChange, handleRegionChange,
connectionParams,
isConnectionParamsLoading,
}: RegionFormProps): JSX.Element { }: RegionFormProps): JSX.Element {
const startTimeRef = useRef(Date.now()); const startTimeRef = useRef(Date.now());
const refetchInterval = 10 * 1000; const refetchInterval = 10 * 1000;
@@ -88,6 +91,11 @@ export function RegionForm({
isFormDisabled={isFormDisabled} isFormDisabled={isFormDisabled}
/> />
<ComplianceNote /> <ComplianceNote />
<RenderConnectionFields
isConnectionParamsLoading={isConnectionParamsLoading}
connectionParams={connectionParams}
isFormDisabled={isFormDisabled}
/>
</div> </div>
</Form> </Form>
); );

View File

@@ -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;
}
}
}
}

View File

@@ -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&apos;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;

View File

@@ -0,0 +1,71 @@
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 &&
!!connectionParams?.signoz_api_key)
) {
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>
)}
{!connectionParams?.signoz_api_key && (
<Form.Item
name="signoz_api_key"
label="SigNoz API KEY"
rules={[{ required: true, message: 'Please enter SigNoz API Key' }]}
>
<Input placeholder="Enter SigNoz API Key" disabled={isFormDisabled} />
</Form.Item>
)}
</Form.Item>
);
}
RenderConnectionFields.defaultProps = {
connectionParams: null,
isFormDisabled: false,
isConnectionParamsLoading: false,
};
export default RenderConnectionFields;

View File

@@ -53,23 +53,18 @@ export function SuccessView(): JSX.Element {
WHAT NEXT WHAT NEXT
</h4> </h4>
<div className="what-next-items-wrapper"> <div className="what-next-items-wrapper">
{[ <Alert
'Understand your AWS services with SigNozs out-of-the-box dashboards', message={
'Set up alerts for real-time monitoring.', <div className="what-next-items-wrapper__item">
'Track logs and traces.', <div className="what-next-item-bullet-icon"></div>
].map((item) => ( <div className="what-next-item-text">
<Alert Set up your AWS services effortlessly under your enabled account.
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>
</div> </div>
} </div>
type="info" }
className="what-next-items-wrapper__item" type="info"
/> className="what-next-items-wrapper__item"
))} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,6 @@
import { FormInstance } from 'antd'; import { FormInstance } from 'antd';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { ConnectionParams } from 'types/api/integrations/aws';
export enum ActiveViewEnum { export enum ActiveViewEnum {
SELECT_REGIONS = 'select-regions', SELECT_REGIONS = 'select-regions',
@@ -25,9 +26,10 @@ export interface RegionFormProps {
accountId?: string; accountId?: string;
selectedDeploymentRegion: string | undefined; selectedDeploymentRegion: string | undefined;
handleRegionChange: (value: string) => void; handleRegionChange: (value: string) => void;
connectionParams?: ConnectionParams;
isConnectionParamsLoading?: boolean;
} }
export interface IntegrationModalProps { export interface IntegrationModalProps {
isOpen: boolean;
onClose: () => void; onClose: () => void;
} }

View File

@@ -1,3 +1,5 @@
import { Link } from 'react-router-dom';
import { ServiceData } from './types'; import { ServiceData } from './types';
function DashboardItem({ function DashboardItem({
@@ -5,8 +7,8 @@ function DashboardItem({
}: { }: {
dashboard: ServiceData['assets']['dashboards'][number]; dashboard: ServiceData['assets']['dashboards'][number];
}): JSX.Element { }): JSX.Element {
return ( const content = (
<div className="cloud-service-dashboard-item"> <>
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div> <div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
<div className="cloud-service-dashboard-item__preview"> <div className="cloud-service-dashboard-item__preview">
<img <img
@@ -15,6 +17,18 @@ function DashboardItem({
className="cloud-service-dashboard-item__preview-image" className="cloud-service-dashboard-item__preview-image"
/> />
</div> </div>
</>
);
return (
<div className="cloud-service-dashboard-item">
{dashboard.url ? (
<Link to={dashboard.url} className="cloud-service-dashboard-item__link">
{content}
</Link>
) : (
content
)}
</div> </div>
); );
} }

View File

@@ -1,4 +1,3 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Tabs, TabsProps } from 'antd'; import { Button, Tabs, TabsProps } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
@@ -8,8 +7,7 @@ import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/t
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails'; import { useServiceDetails } from 'hooks/integrations/aws/useServiceDetails';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { Wrench } from 'lucide-react'; import { useMemo, useState } from 'react';
import { useState } from 'react';
import ConfigureServiceModal from './ConfigureServiceModal'; import ConfigureServiceModal from './ConfigureServiceModal';
@@ -38,7 +36,7 @@ const getStatus = (
function ServiceStatus({ function ServiceStatus({
serviceStatus, serviceStatus,
}: { }: {
serviceStatus: IServiceStatus | null; serviceStatus: IServiceStatus | undefined;
}): JSX.Element { }): JSX.Element {
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms; const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
const metricsLastReceivedTimestamp = const metricsLastReceivedTimestamp =
@@ -54,7 +52,7 @@ function ServiceStatus({
function ServiceDetails(): JSX.Element | null { function ServiceDetails(): JSX.Element | null {
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const accountId = urlQuery.get('accountId'); const cloudAccountId = urlQuery.get('cloudAccountId');
const serviceId = urlQuery.get('service'); const serviceId = urlQuery.get('service');
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState( const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
false, false,
@@ -62,7 +60,24 @@ function ServiceDetails(): JSX.Element | null {
const { data: serviceDetailsData, isLoading } = useServiceDetails( const { data: serviceDetailsData, isLoading } = useServiceDetails(
serviceId || '', 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) { if (isLoading) {
@@ -96,16 +111,22 @@ function ServiceDetails(): JSX.Element | null {
<div className="service-details__title-bar"> <div className="service-details__title-bar">
<div className="service-details__details-title">Details</div> <div className="service-details__details-title">Details</div>
<div className="service-details__right-actions"> <div className="service-details__right-actions">
{serviceDetailsData?.status && ( <ServiceStatus serviceStatus={serviceDetailsData.status} />
<ServiceStatus serviceStatus={serviceDetailsData.status} />
)} {!!cloudAccountId && isAnySignalConfigured ? (
{!!accountId && (
<Button <Button
className="configure-button" className="configure-button configure-button--default"
onClick={(): void => setIsConfigureServiceModalOpen(true)} onClick={(): void => setIsConfigureServiceModalOpen(true)}
> >
<Wrench size={12} color={Color.BG_VANILLA_400} /> Configure ({enabledSignals}/{totalSupportedSignals})
Configure </Button>
) : (
<Button
type="primary"
className="configure-button configure-button--primary"
onClick={(): void => setIsConfigureServiceModalOpen(true)}
>
Enable Service
</Button> </Button>
)} )}
</div> </div>
@@ -119,15 +140,17 @@ function ServiceDetails(): JSX.Element | null {
<div className="service-details__tabs"> <div className="service-details__tabs">
<Tabs items={tabItems} /> <Tabs items={tabItems} />
</div> </div>
<ConfigureServiceModal {isConfigureServiceModalOpen && (
isOpen={isConfigureServiceModalOpen} <ConfigureServiceModal
onClose={(): void => setIsConfigureServiceModalOpen(false)} isOpen
serviceName={serviceDetailsData.title} onClose={(): void => setIsConfigureServiceModalOpen(false)}
serviceId={serviceId || ''} serviceName={serviceDetailsData.title}
cloudAccountId={accountId || ''} serviceId={serviceId || ''}
initialConfig={serviceDetailsData.config} cloudAccountId={cloudAccountId || ''}
supportedSignals={serviceDetailsData.supported_signals || {}} initialConfig={serviceDetailsData.config}
/> supportedSignals={serviceDetailsData.supported_signals || {}}
/>
)}
</div> </div>
); );
} }

View File

@@ -1,26 +1,34 @@
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices'; import { useGetAccountServices } from 'hooks/integrations/aws/useGetAccountServices';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat'; import { useNavigate } from 'react-router-dom-v5-compat';
import ServiceItem from './ServiceItem'; import ServiceItem from './ServiceItem';
interface ServicesListProps { interface ServicesListProps {
accountId: string; cloudAccountId: string;
filter: 'all_services' | 'enabled' | 'available'; filter: 'all_services' | 'enabled' | 'available';
} }
function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element { function ServicesList({
cloudAccountId,
filter,
}: ServicesListProps): JSX.Element {
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const navigate = useNavigate(); const navigate = useNavigate();
const { data: services = [], isLoading } = useGetAccountServices(accountId); const { data: services = [], isLoading } = useGetAccountServices(
cloudAccountId,
);
const activeService = urlQuery.get('service'); const activeService = urlQuery.get('service');
const handleServiceClick = (serviceId: string): void => { const handleActiveService = useCallback(
urlQuery.set('service', serviceId); (serviceId: string): void => {
navigate({ search: urlQuery.toString() }); urlQuery.set('service', serviceId);
}; navigate({ search: urlQuery.toString() });
},
[navigate, urlQuery],
);
const filteredServices = useMemo(() => { const filteredServices = useMemo(() => {
if (filter === 'all_services') return services; if (filter === 'all_services') return services;
@@ -32,6 +40,12 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
}); });
}, [services, filter]); }, [services, filter]);
useEffect(() => {
if (activeService || !services?.length) return;
handleActiveService(services[0].id);
}, [services, activeService, handleActiveService]);
if (isLoading) return <Spinner size="large" height="25vh" />; if (isLoading) return <Spinner size="large" height="25vh" />;
if (!services) return <div>No services found</div>; if (!services) return <div>No services found</div>;
@@ -41,7 +55,7 @@ function ServicesList({ accountId, filter }: ServicesListProps): JSX.Element {
<ServiceItem <ServiceItem
key={service.id} key={service.id}
service={service} service={service}
onClick={handleServiceClick} onClick={handleActiveService}
isActive={service.id === activeService} isActive={service.id === activeService}
/> />
))} ))}

View File

@@ -135,18 +135,25 @@
color: var(--bg-cherry-400); color: var(--bg-cherry-400);
} }
} }
.configure-button { .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; border-radius: 2px;
font-size: 12px; font-size: 12px;
font-weight: 400;
line-height: 10px; /* 83.333% */
letter-spacing: 0.12px; 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);
}
} }
} }
} }

View File

@@ -20,17 +20,17 @@ export enum ServiceFilterType {
} }
interface ServicesFilterProps { interface ServicesFilterProps {
accountId: string; cloudAccountId: string;
onFilterChange: (value: ServiceFilterType) => void; onFilterChange: (value: ServiceFilterType) => void;
} }
function ServicesFilter({ function ServicesFilter({
accountId, cloudAccountId,
onFilterChange, onFilterChange,
}: ServicesFilterProps): JSX.Element | null { }: ServicesFilterProps): JSX.Element | null {
const { data: services, isLoading } = useQuery( const { data: services, isLoading } = useQuery(
[REACT_QUERY_KEY.AWS_SERVICES, accountId], [REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId],
() => getAwsServices(accountId), () => getAwsServices(cloudAccountId),
); );
const { enabledCount, availableCount } = useMemo(() => { const { enabledCount, availableCount } = useMemo(() => {
@@ -77,7 +77,7 @@ function ServicesFilter({
function ServicesSection(): JSX.Element { function ServicesSection(): JSX.Element {
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const accountId = urlQuery.get('accountId') || ''; const cloudAccountId = urlQuery.get('cloudAccountId') || '';
const [activeFilter, setActiveFilter] = useState< const [activeFilter, setActiveFilter] = useState<
'all_services' | 'enabled' | 'available' 'all_services' | 'enabled' | 'available'
@@ -86,8 +86,11 @@ function ServicesSection(): JSX.Element {
return ( return (
<div className="services-section"> <div className="services-section">
<div className="services-section__sidebar"> <div className="services-section__sidebar">
<ServicesFilter accountId={accountId} onFilterChange={setActiveFilter} /> <ServicesFilter
<ServicesList accountId={accountId} filter={activeFilter} /> cloudAccountId={cloudAccountId}
onFilterChange={setActiveFilter}
/>
<ServicesList cloudAccountId={cloudAccountId} filter={activeFilter} />
</div> </div>
<div className="services-section__content"> <div className="services-section__content">
<ServiceDetails /> <ServiceDetails />

View File

@@ -0,0 +1,233 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { AppProvider } from 'providers/App/App';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import { Provider } from 'react-redux';
import store from 'store';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import {
MENUITEM_KEYS_VS_LABELS,
MenuItemKeys,
} from '../WidgetHeader/contants';
import { WidgetGraphComponentProps } from './types';
import WidgetGraphComponent from './WidgetGraphComponent';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.DASHBOARD}/624652db-6097-42f5-bbca-e9012901db00`,
}),
}));
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),
bars: jest.fn(),
};
const uplotMock = jest.fn(() => ({
paths,
}));
return {
paths,
default: uplotMock,
};
});
// Mock data
const mockProps: WidgetGraphComponentProps = {
widget: {
bucketCount: 30,
bucketWidth: 0,
columnUnits: {},
description: '',
fillSpans: false,
id: '17f905f6-d355-46bd-a78e-cbc87e6f58cc',
isStacked: false,
mergeAllActiveQueries: false,
nullZeroValues: 'zero',
opacity: '1',
panelTypes: PANEL_TYPES.VALUE,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: 'span_id',
type: '',
},
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'count_distinct',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '47449208-2c76-4465-9c62-a37fb4f5f11f',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
selectedLogFields: [
{
dataType: 'string',
name: 'body',
type: '',
},
{
dataType: 'string',
name: 'timestamp',
type: '',
},
],
selectedTracesFields: [],
softMax: 0,
softMin: 0,
stackedBarChart: false,
thresholds: [],
timePreferance: 'GLOBAL_TIME',
title: 'Test Dashboard',
yAxisUnit: 'none',
},
queryResponse: {
status: 'loading',
isLoading: true,
isSuccess: false,
isError: false,
isIdle: false,
dataUpdatedAt: 0,
error: null,
errorUpdatedAt: 0,
failureCount: 0,
errorUpdateCount: 0,
isFetched: false,
isFetchedAfterMount: false,
isFetching: true,
isRefetching: false,
isLoadingError: false,
isPlaceholderData: false,
isPreviousData: false,
isRefetchError: false,
isStale: true,
data: undefined,
refetch: jest.fn(),
remove: jest.fn(),
},
errorMessage: '',
version: 'v4',
headerMenuList: [
MenuItemKeys.View,
MenuItemKeys.Clone,
MenuItemKeys.Delete,
MenuItemKeys.Edit,
MenuItemKeys.CreateAlerts,
],
isWarning: false,
isFetchingResponse: false,
setRequestData: jest.fn(),
onClickHandler: jest.fn(),
onDragSelect: jest.fn(),
openTracesButton: false,
onOpenTraceBtnClick: jest.fn(),
};
// Mock useDashabord hook
jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboard: (): any => ({
selectedDashboard: {
data: {
variables: [],
},
},
}),
}));
describe('WidgetGraphComponent', () => {
it('should show correct menu items when hovering over more options while loading', async () => {
const { getByTestId, findByRole, getByText, container } = render(
<MockQueryClientProvider>
<Provider store={store}>
<AppProvider>
<WidgetGraphComponent
widget={mockProps.widget}
queryResponse={mockProps.queryResponse}
errorMessage={mockProps.errorMessage}
version={mockProps.version}
headerMenuList={mockProps.headerMenuList}
isWarning={mockProps.isWarning}
isFetchingResponse={mockProps.isFetchingResponse}
setRequestData={mockProps.setRequestData}
onClickHandler={mockProps.onClickHandler}
onDragSelect={mockProps.onDragSelect}
openTracesButton={mockProps.openTracesButton}
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
/>
</AppProvider>
</Provider>
</MockQueryClientProvider>,
);
expect(getByText('Test Dashboard')).toBeInTheDocument();
// check if skeleton is rendered
const skeleton = container.querySelector('.ant-skeleton');
expect(skeleton).toBeInTheDocument();
const moreOptionsButton = getByTestId('widget-header-options');
fireEvent.mouseEnter(moreOptionsButton);
const menu = await findByRole('menu');
expect(menu).toBeInTheDocument();
// Check if all menu items are present
const expectedMenuItems = [
MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Clone],
MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Delete],
MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Edit],
MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts],
];
// check that menu is visible
expectedMenuItems.forEach((item) => {
expect(screen.getByText(item)).toBeInTheDocument();
});
});
});

View File

@@ -6,6 +6,7 @@ import { ToggleGraphProps } from 'components/Graph/types';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
import PanelWrapper from 'container/PanelWrapper/PanelWrapper'; import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@@ -48,6 +49,7 @@ function WidgetGraphComponent({
openTracesButton, openTracesButton,
onOpenTraceBtnClick, onOpenTraceBtnClick,
customSeries, customSeries,
customErrorMessage,
}: WidgetGraphComponentProps): JSX.Element { }: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
@@ -132,18 +134,14 @@ function WidgetGraphComponent({
(l) => l.i === widget.id, (l) => l.i === widget.id,
); );
// added the cloned panel on the top as it is given most priority when arranging const newLayoutItem = placeWidgetAtBottom(
// in the layout. React_grid_layout assigns priority from top, hence no random position for cloned panel uuid,
const layout = [ selectedDashboard?.data.layout || [],
{ originalPanelLayout?.w || 6,
i: uuid, originalPanelLayout?.h || 6,
w: originalPanelLayout?.w || 6, );
x: 0,
h: originalPanelLayout?.h || 6, const layout = [...(selectedDashboard.data.layout || []), newLayoutItem];
y: 0,
},
...(selectedDashboard.data.layout || []),
];
updateDashboardMutation.mutateAsync( updateDashboardMutation.mutateAsync(
{ {
@@ -231,21 +229,6 @@ function WidgetGraphComponent({
const [searchTerm, setSearchTerm] = useState<string>(''); const [searchTerm, setSearchTerm] = useState<string>('');
const loadingState =
(queryResponse.isLoading || queryResponse.status === 'idle') &&
widget.panelTypes !== PANEL_TYPES.LIST;
if (loadingState) {
return (
<Skeleton
style={{
height: '100%',
padding: '16px',
}}
/>
);
}
return ( return (
<div <div
style={{ style={{
@@ -317,6 +300,13 @@ function WidgetGraphComponent({
setSearchTerm={setSearchTerm} setSearchTerm={setSearchTerm}
/> />
</div> </div>
{queryResponse.error && customErrorMessage && (
<div className="error-message-container">
<Typography.Text type="warning">{customErrorMessage}</Typography.Text>
</div>
)}
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && ( {queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
<Skeleton /> <Skeleton />
)} )}

View File

@@ -41,9 +41,15 @@ function GridCardGraph({
openTracesButton, openTracesButton,
onOpenTraceBtnClick, onOpenTraceBtnClick,
customSeries, customSeries,
customErrorMessage,
start,
end,
}: GridCardGraphProps): JSX.Element { }: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
false,
);
const { const {
toScrollWidgetId, toScrollWidgetId,
setToScrollWidgetId, setToScrollWidgetId,
@@ -178,6 +184,8 @@ function GridCardGraph({
variables: getDashboardVariables(variables), variables: getDashboardVariables(variables),
selectedTime: widget.timePreferance || 'GLOBAL_TIME', selectedTime: widget.timePreferance || 'GLOBAL_TIME',
globalSelectedInterval, globalSelectedInterval,
start,
end,
}, },
version || DEFAULT_ENTITY_VERSION, version || DEFAULT_ENTITY_VERSION,
{ {
@@ -207,6 +215,11 @@ function GridCardGraph({
refetchOnMount: false, refetchOnMount: false,
onError: (error) => { onError: (error) => {
setErrorMessage(error.message); setErrorMessage(error.message);
if (customErrorMessage) {
setIsInternalServerError(
String(error.message).includes('API responded with 500'),
);
}
setDashboardQueryRangeCalled(true); setDashboardQueryRangeCalled(true);
}, },
onSettled: (data) => { onSettled: (data) => {
@@ -256,6 +269,7 @@ function GridCardGraph({
openTracesButton={openTracesButton} openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick} onOpenTraceBtnClick={onOpenTraceBtnClick}
customSeries={customSeries} customSeries={customSeries}
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
/> />
)} )}
</div> </div>

View File

@@ -37,6 +37,7 @@ export interface WidgetGraphComponentProps {
openTracesButton?: boolean; openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void; onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[]; customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
} }
export interface GridCardGraphProps { export interface GridCardGraphProps {
@@ -54,6 +55,9 @@ export interface GridCardGraphProps {
openTracesButton?: boolean; openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void; onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[]; customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
start?: number;
end?: number;
} }
export interface GetGraphVisibilityStateOnLegendClickProps { export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@@ -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 { .row-settings {
.ant-popover-inner { .ant-popover-inner {
width: 191px; width: 191px;

View File

@@ -45,6 +45,7 @@ import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState';
import GridCard from './GridCard'; import GridCard from './GridCard';
import { Card, CardContainer, ReactGridLayout } from './styles'; import { Card, CardContainer, ReactGridLayout } from './styles';
import { removeUndefinedValuesFromLayout } from './utils'; import { removeUndefinedValuesFromLayout } from './utils';
import { MenuItemKeys } from './WidgetHeader/contants';
import { WidgetRowHeader } from './WidgetRow'; import { WidgetRowHeader } from './WidgetRow';
interface GraphLayoutProps { interface GraphLayoutProps {
@@ -190,7 +191,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
const widgetActions = !isDashboardLocked const widgetActions = !isDashboardLocked
? [...ViewMenuAction, ...EditMenuAction] ? [...ViewMenuAction, ...EditMenuAction]
: [...ViewMenuAction]; : [...ViewMenuAction, MenuItemKeys.CreateAlerts];
const handleLayoutChange = (layout: Layout[]): void => { const handleLayoutChange = (layout: Layout[]): void => {
const filterLayout = removeUndefinedValuesFromLayout(layout); const filterLayout = removeUndefinedValuesFromLayout(layout);

View File

@@ -1,18 +1,19 @@
import './InfraMonitoring.styles.scss'; import './InfraMonitoring.styles.scss';
import { initialQueriesMap } from 'constants/queryBuilder';
import { K8sCategory } from 'container/InfraMonitoringK8s/constants'; import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
function HostsListControls({ function HostsListControls({
handleFiltersChange, handleFiltersChange,
}: { }: {
handleFiltersChange: (value: IBuilderQuery['filters']) => void; handleFiltersChange: (value: IBuilderQuery['filters']) => void;
}): JSX.Element { }): JSX.Element {
const { currentQuery } = useQueryBuilder(); const currentQuery = initialQueriesMap[DataSource.METRICS];
const updatedCurrentQuery = useMemo( const updatedCurrentQuery = useMemo(
() => ({ () => ({
...currentQuery, ...currentQuery,

View File

@@ -141,13 +141,9 @@ function ClusterDetails({
[cluster?.meta.k8s_cluster_name], [cluster?.meta.k8s_cluster_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -161,8 +157,7 @@ function ClusterDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogsAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -181,6 +176,10 @@ function ClusterDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Clusters list details tab changed', {
cluster: cluster?.clusterUID,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -204,6 +203,7 @@ function ClusterDetails({
logEvent('Infra Monitoring: Clusters list details time updated', { logEvent('Infra Monitoring: Clusters list details time updated', {
cluster: cluster?.clusterUID, cluster: cluster?.clusterUID,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -212,7 +212,7 @@ function ClusterDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), [QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
); );
@@ -244,7 +244,7 @@ function ClusterDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''), [QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
); );
@@ -320,8 +320,8 @@ function ClusterDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logsAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -355,7 +355,7 @@ function ClusterDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logsAndTracesFilters,
}, },
], ],
}, },
@@ -519,7 +519,7 @@ function ClusterDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logsAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="clusterLogs" queryKey="clusterLogs"
category={K8sCategory.CLUSTERS} category={K8sCategory.CLUSTERS}
@@ -532,9 +532,10 @@ function ClusterDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logsAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="clusterTraces" queryKey="clusterTraces"
queryKeyFilters={[QUERY_KEYS.K8S_CLUSTER_NAME]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -240,6 +240,11 @@ function K8sClustersList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sClustersRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -250,6 +255,11 @@ function K8sClustersList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -261,7 +271,7 @@ function K8sClustersList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -275,15 +285,13 @@ function K8sClustersList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s clusters list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s clusters list page visited', {});
}, []); }, []);
const selectedClusterData = useMemo(() => { const selectedClusterData = useMemo(() => {
@@ -442,6 +450,7 @@ function K8sClustersList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s clusters list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -457,6 +466,16 @@ function K8sClustersList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -482,10 +501,7 @@ function K8sClustersList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -155,13 +155,9 @@ function DaemonSetDetails({
[daemonSet?.meta.k8s_daemonset_name], [daemonSet?.meta.k8s_daemonset_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -175,8 +171,7 @@ function DaemonSetDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -195,6 +190,10 @@ function DaemonSetDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: DaemonSets list details tab changed', {
daemonSet: daemonSet?.daemonSetName,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -218,6 +217,7 @@ function DaemonSetDetails({
logEvent('Infra Monitoring: DaemonSets list details time updated', { logEvent('Infra Monitoring: DaemonSets list details time updated', {
daemonSet: daemonSet?.daemonSetName, daemonSet: daemonSet?.daemonSetName,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -226,7 +226,7 @@ function DaemonSetDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -259,7 +259,7 @@ function DaemonSetDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -339,8 +339,8 @@ function DaemonSetDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -374,7 +374,7 @@ function DaemonSetDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -551,7 +551,7 @@ function DaemonSetDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
category={K8sCategory.DAEMONSETS} category={K8sCategory.DAEMONSETS}
queryKey="daemonsetLogs" queryKey="daemonsetLogs"
@@ -567,9 +567,13 @@ function DaemonSetDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="daemonsetTraces" queryKey="daemonsetTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_DAEMON_SET_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -243,6 +243,11 @@ function K8sDaemonSetsList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sDaemonSetsRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sDaemonSetsRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -253,6 +258,11 @@ function K8sDaemonSetsList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -264,7 +274,7 @@ function K8sDaemonSetsList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -278,15 +288,13 @@ function K8sDaemonSetsList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s daemonSets list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s daemonSets list page visited', {});
}, []); }, []);
const selectedDaemonSetData = useMemo(() => { const selectedDaemonSetData = useMemo(() => {
@@ -448,6 +456,8 @@ function K8sDaemonSetsList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s daemonSets list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -463,6 +473,16 @@ function K8sDaemonSetsList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -490,10 +510,7 @@ function K8sDaemonSetsList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -157,13 +157,9 @@ function DeploymentDetails({
[deployment?.meta.k8s_deployment_name], [deployment?.meta.k8s_deployment_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -177,8 +173,7 @@ function DeploymentDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -197,6 +192,10 @@ function DeploymentDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Deployments list details tab changed', {
deployment: deployment?.deploymentName,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -220,6 +219,7 @@ function DeploymentDetails({
logEvent('Infra Monitoring: Deployments list details time updated', { logEvent('Infra Monitoring: Deployments list details time updated', {
deployment: deployment?.deploymentName, deployment: deployment?.deploymentName,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -228,7 +228,7 @@ function DeploymentDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -266,7 +266,7 @@ function DeploymentDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -350,8 +350,8 @@ function DeploymentDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -385,7 +385,7 @@ function DeploymentDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -562,7 +562,7 @@ function DeploymentDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKeyFilters={[ queryKeyFilters={[
QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_DEPLOYMENT_NAME,
@@ -578,9 +578,13 @@ function DeploymentDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="deploymentTraces" queryKey="deploymentTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_DEPLOYMENT_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -245,6 +245,11 @@ function K8sDeploymentsList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sDeploymentsRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -255,6 +260,11 @@ function K8sDeploymentsList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -266,7 +276,7 @@ function K8sDeploymentsList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -280,15 +290,13 @@ function K8sDeploymentsList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s deployments list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s deployments list page visited', {});
}, []); }, []);
const selectedDeploymentData = useMemo(() => { const selectedDeploymentData = useMemo(() => {
@@ -452,6 +460,8 @@ function K8sDeploymentsList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s deployments list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -467,6 +477,16 @@ function K8sDeploymentsList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -494,10 +514,7 @@ function K8sDeploymentsList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -12,6 +12,7 @@ import { useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { filterOutPrimaryFilters } from '../utils';
import EntityLogs from './EntityLogs'; import EntityLogs from './EntityLogs';
interface Props { interface Props {
@@ -58,14 +59,14 @@ function EntityLogsDetailedView({
...currentQuery.builder.queryData[0].aggregateAttribute, ...currentQuery.builder.queryData[0].aggregateAttribute,
}, },
filters: { filters: {
items: [], items: filterOutPrimaryFilters(logFilters.items, queryKeyFilters),
op: 'AND', op: 'AND',
}, },
}, },
], ],
}, },
}), }),
[currentQuery], [currentQuery, logFilters.items, queryKeyFilters],
); );
const query = updatedCurrentQuery?.builder?.queryData[0] || null; const query = updatedCurrentQuery?.builder?.queryData[0] || null;

View File

@@ -26,6 +26,7 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { import {
filterOutPrimaryFilters,
getEntityTracesQueryPayload, getEntityTracesQueryPayload,
selectedEntityTracesColumns, selectedEntityTracesColumns,
} from '../utils'; } from '../utils';
@@ -44,6 +45,7 @@ interface Props {
tracesFilters: IBuilderQuery['filters']; tracesFilters: IBuilderQuery['filters'];
selectedInterval: Time; selectedInterval: Time;
queryKey: string; queryKey: string;
queryKeyFilters: string[];
} }
function EntityTraces({ function EntityTraces({
@@ -54,6 +56,7 @@ function EntityTraces({
tracesFilters, tracesFilters,
selectedInterval, selectedInterval,
queryKey, queryKey,
queryKeyFilters,
}: Props): JSX.Element { }: Props): JSX.Element {
const [traces, setTraces] = useState<any[]>([]); const [traces, setTraces] = useState<any[]>([]);
const [offset] = useState<number>(0); const [offset] = useState<number>(0);
@@ -73,14 +76,14 @@ function EntityTraces({
...currentQuery.builder.queryData[0].aggregateAttribute, ...currentQuery.builder.queryData[0].aggregateAttribute,
}, },
filters: { filters: {
items: [], items: filterOutPrimaryFilters(tracesFilters.items, queryKeyFilters),
op: 'AND', op: 'AND',
}, },
}, },
], ],
}, },
}), }),
[currentQuery], [currentQuery, queryKeyFilters, tracesFilters.items],
); );
const query = updatedCurrentQuery?.builder?.queryData[0] || null; const query = updatedCurrentQuery?.builder?.queryData[0] || null;

View File

@@ -7,7 +7,10 @@ import {
BaseAutocompleteData, BaseAutocompleteData,
DataTypes, DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse'; } 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 { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { nanoToMilli } from 'utils/timeUtils'; 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',
);

View File

@@ -4,6 +4,7 @@ import { VerticalAlignTopOutlined } from '@ant-design/icons';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import type { CollapseProps } from 'antd'; import type { CollapseProps } from 'antd';
import { Collapse, Tooltip, Typography } from 'antd'; import { Collapse, Tooltip, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import QuickFilters from 'components/QuickFilters/QuickFilters'; import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types'; import { QuickFiltersSource } from 'components/QuickFilters/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; 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 // in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
handleChangeQueryData('filters', query.builder.queryData[0].filters); handleChangeQueryData('filters', query.builder.queryData[0].filters);
setQuickFiltersLastUpdated(Date.now()); setQuickFiltersLastUpdated(Date.now());
logEvent(
`Infra Monitoring: K8s ${selectedCategory} list quick filters applied`,
{},
);
}; };
const items: CollapseProps['items'] = [ const items: CollapseProps['items'] = [

View File

@@ -152,13 +152,9 @@ function JobDetails({
[job?.meta.k8s_job_name], [job?.meta.k8s_job_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -172,8 +168,7 @@ function JobDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -192,6 +187,10 @@ function JobDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Jobs list details tab changed', {
job: job?.jobName,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -215,6 +214,7 @@ function JobDetails({
logEvent('Infra Monitoring: Jobs list details time updated', { logEvent('Infra Monitoring: Jobs list details time updated', {
job: job?.jobName, job: job?.jobName,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -223,17 +223,16 @@ function JobDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => 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 ?? '', item.key?.key ?? '',
), ),
); );
const paginationFilter = value.items.find((item) => item.key?.key === 'id'); const paginationFilter = value.items.find((item) => item.key?.key === 'id');
const newFilters = value.items.filter( const newFilters = value.items.filter(
(item) => (item) =>
item.key?.key !== 'id' && item.key?.key !== 'id' && item.key?.key !== QUERY_KEYS.K8S_JOB_NAME,
item.key?.key !== QUERY_KEYS.K8S_STATEFUL_SET_NAME,
); );
logEvent('Infra Monitoring: Jobs list details logs filters applied', { logEvent('Infra Monitoring: Jobs list details logs filters applied', {
@@ -256,9 +255,9 @@ function JobDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => 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 ?? '', item.key?.key ?? '',
), ),
); );
@@ -272,7 +271,7 @@ function JobDetails({
items: [ items: [
...primaryFilters, ...primaryFilters,
...value.items.filter( ...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), ].filter((item): item is TagFilterItem => item !== undefined),
}; };
@@ -330,8 +329,8 @@ function JobDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -365,7 +364,7 @@ function JobDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -531,7 +530,7 @@ function JobDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
category={K8sCategory.JOBS} category={K8sCategory.JOBS}
queryKey="jobLogs" queryKey="jobLogs"
@@ -547,9 +546,13 @@ function JobDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="jobTraces" queryKey="jobTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_JOB_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -234,6 +234,11 @@ function K8sJobsList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sJobsRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sJobsRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -242,6 +247,11 @@ function K8sJobsList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -253,7 +263,7 @@ function K8sJobsList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -267,15 +277,13 @@ function K8sJobsList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s jobs list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s jobs list page visited', {});
}, []); }, []);
const selectedJobData = useMemo(() => { const selectedJobData = useMemo(() => {
@@ -424,6 +432,8 @@ function K8sJobsList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s jobs list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -439,6 +449,16 @@ function K8sJobsList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -466,10 +486,7 @@ function K8sJobsList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -2,13 +2,14 @@
import './InfraMonitoringK8s.styles.scss'; import './InfraMonitoringK8s.styles.scss';
import { Button, Select } from 'antd'; import { Button, Select } from 'antd';
import { initialQueriesMap } from 'constants/queryBuilder';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Filter, SlidersHorizontal } from 'lucide-react'; import { Filter, SlidersHorizontal } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { K8sCategory } from './constants'; import { K8sCategory } from './constants';
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel'; import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
@@ -47,7 +48,7 @@ function K8sHeader({
}: K8sHeaderProps): JSX.Element { }: K8sHeaderProps): JSX.Element {
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false); const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
const { currentQuery } = useQueryBuilder(); const currentQuery = initialQueriesMap[DataSource.METRICS];
const updatedCurrentQuery = useMemo( const updatedCurrentQuery = useMemo(
() => ({ () => ({

View File

@@ -242,6 +242,11 @@ function K8sNamespacesList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sNamespacesRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sNamespacesRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -252,6 +257,11 @@ function K8sNamespacesList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -263,7 +273,7 @@ function K8sNamespacesList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -277,15 +287,13 @@ function K8sNamespacesList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s namespaces list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s namespaces list page visited', {});
}, []); }, []);
const selectedNamespaceData = useMemo(() => { const selectedNamespaceData = useMemo(() => {
@@ -449,6 +457,8 @@ function K8sNamespacesList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s namespaces list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -464,6 +474,16 @@ function K8sNamespacesList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -489,10 +509,7 @@ function K8sNamespacesList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -143,13 +143,9 @@ function NamespaceDetails({
[namespace?.namespaceName], [namespace?.namespaceName],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -163,8 +159,7 @@ function NamespaceDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -183,6 +178,10 @@ function NamespaceDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Namespaces list details tab changed', {
namespace: namespace?.namespaceName,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -206,6 +205,7 @@ function NamespaceDetails({
logEvent('Infra Monitoring: Namespaces list details time updated', { logEvent('Infra Monitoring: Namespaces list details time updated', {
namespace: namespace?.namespaceName, namespace: namespace?.namespaceName,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -214,7 +214,7 @@ function NamespaceDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( [QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -246,7 +246,7 @@ function NamespaceDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( [QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -326,8 +326,8 @@ function NamespaceDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -361,7 +361,7 @@ function NamespaceDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -527,7 +527,7 @@ function NamespaceDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="namespaceLogs" queryKey="namespaceLogs"
category={K8sCategory.NAMESPACES} category={K8sCategory.NAMESPACES}
@@ -540,9 +540,10 @@ function NamespaceDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="namespaceTraces" queryKey="namespaceTraces"
queryKeyFilters={[QUERY_KEYS.K8S_NAMESPACE_NAME]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -233,6 +233,11 @@ function K8sNodesList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sNodesRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sNodesRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -241,6 +246,11 @@ function K8sNodesList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -252,7 +262,7 @@ function K8sNodesList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -266,15 +276,13 @@ function K8sNodesList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s nodes list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s nodes list page visited', {});
}, []); }, []);
const selectedNodeData = useMemo(() => { const selectedNodeData = useMemo(() => {
@@ -427,6 +435,8 @@ function K8sNodesList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s nodes list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -442,6 +452,16 @@ function K8sNodesList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -467,10 +487,7 @@ function K8sNodesList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -141,13 +141,9 @@ function NodeDetails({
[node?.meta.k8s_node_name], [node?.meta.k8s_node_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -161,8 +157,7 @@ function NodeDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -181,6 +176,10 @@ function NodeDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Nodes list details tab changed', {
node: node?.nodeUID,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -204,6 +203,7 @@ function NodeDetails({
logEvent('Infra Monitoring: Nodes list details time updated', { logEvent('Infra Monitoring: Nodes list details time updated', {
node: node?.nodeUID, node: node?.nodeUID,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -212,7 +212,7 @@ function NodeDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -246,7 +246,7 @@ function NodeDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes( [QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -322,8 +322,8 @@ function NodeDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -357,7 +357,7 @@ function NodeDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -521,7 +521,7 @@ function NodeDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]} queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]}
queryKey="nodeLogs" queryKey="nodeLogs"
@@ -534,8 +534,9 @@ function NodeDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKeyFilters={[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME]}
queryKey="nodeTraces" queryKey="nodeTraces"
/> />
)} )}

View File

@@ -248,6 +248,11 @@ function K8sPodsList({
groupBy, groupBy,
]); ]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sPodsRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sPodsRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -256,6 +261,11 @@ function K8sPodsList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -267,7 +277,7 @@ function K8sPodsList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -281,9 +291,7 @@ function K8sPodsList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s pods list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
@@ -308,12 +316,14 @@ function K8sPodsList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s pods list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s pods list page visited', {});
}, []); }, []);
const selectedPodData = useMemo(() => { const selectedPodData = useMemo(() => {
@@ -350,7 +360,7 @@ function K8sPodsList({
handleGroupByRowClick(record); handleGroupByRowClick(record);
} }
logEvent('Infra Monitoring: K8s list item clicked', { logEvent('Infra Monitoring: K8s pods list item clicked', {
podUID: record.podUID, 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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -530,10 +550,7 @@ function K8sPodsList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
loading={{ loading={{
spinning: isFetching || isLoading, spinning: isFetching || isLoading,

View File

@@ -158,13 +158,9 @@ function PodDetails({
[pod?.meta.k8s_pod_name], [pod?.meta.k8s_pod_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -178,8 +174,7 @@ function PodDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogsAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -198,6 +193,10 @@ function PodDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: Pods list details tab changed', {
pod: pod?.podUID,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -221,6 +220,7 @@ function PodDetails({
logEvent('Infra Monitoring: Pods list details time updated', { logEvent('Infra Monitoring: Pods list details time updated', {
pod: pod?.podUID, pod: pod?.podUID,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -229,7 +229,7 @@ function PodDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[ [
QUERY_KEYS.K8S_POD_NAME, QUERY_KEYS.K8S_POD_NAME,
@@ -265,7 +265,7 @@ function PodDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogsAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[ [
QUERY_KEYS.K8S_POD_NAME, QUERY_KEYS.K8S_POD_NAME,
@@ -343,8 +343,8 @@ function PodDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logsAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logsAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -378,7 +378,7 @@ function PodDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logsAndTracesFilters,
}, },
], ],
}, },
@@ -559,7 +559,7 @@ function PodDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logsAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKeyFilters={[ queryKeyFilters={[
QUERY_KEYS.K8S_POD_NAME, QUERY_KEYS.K8S_POD_NAME,
@@ -576,9 +576,14 @@ function PodDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logsAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="podTraces" queryKey="podTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_POD_NAME,
QUERY_KEYS.K8S_CLUSTER_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/> />
)} )}

View File

@@ -245,6 +245,11 @@ function K8sStatefulSetsList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sStatefulSetsRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sStatefulSetsRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -255,6 +260,11 @@ function K8sStatefulSetsList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -266,7 +276,7 @@ function K8sStatefulSetsList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -280,15 +290,13 @@ function K8sStatefulSetsList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s statefulSets list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s statefulSets list page visited', {});
}, []); }, []);
const selectedStatefulSetData = useMemo(() => { const selectedStatefulSetData = useMemo(() => {
@@ -449,6 +457,8 @@ function K8sStatefulSetsList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s statefulSets list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -464,6 +474,16 @@ function K8sStatefulSetsList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -491,10 +511,7 @@ function K8sStatefulSetsList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View File

@@ -158,13 +158,9 @@ function StatefulSetDetails({
[statefulSet?.meta.k8s_statefulset_name], [statefulSet?.meta.k8s_statefulset_name],
); );
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>( const [logAndTracesFilters, setLogAndTracesFilters] = useState<
initialFilters, IBuilderQuery['filters']
); >(initialFilters);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>( const [eventsFilters, setEventsFilters] = useState<IBuilderQuery['filters']>(
initialEventsFilters, initialEventsFilters,
@@ -178,8 +174,7 @@ function StatefulSetDetails({
}, []); }, []);
useEffect(() => { useEffect(() => {
setLogFilters(initialFilters); setLogAndTracesFilters(initialFilters);
setTracesFilters(initialFilters);
setEventsFilters(initialEventsFilters); setEventsFilters(initialEventsFilters);
}, [initialFilters, initialEventsFilters]); }, [initialFilters, initialEventsFilters]);
@@ -198,6 +193,10 @@ function StatefulSetDetails({
const handleTabChange = (e: RadioChangeEvent): void => { const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value); setSelectedView(e.target.value);
logEvent('Infra Monitoring: StatefulSets list details tab changed', {
statefulSet: statefulSet?.statefulSetName,
view: e.target.value,
});
}; };
const handleTimeChange = useCallback( const handleTimeChange = useCallback(
@@ -221,6 +220,7 @@ function StatefulSetDetails({
logEvent('Infra Monitoring: StatefulSets list details time updated', { logEvent('Infra Monitoring: StatefulSets list details time updated', {
statefulSet: statefulSet?.statefulSetName, statefulSet: statefulSet?.statefulSetName,
interval, interval,
view: selectedView,
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -229,7 +229,7 @@ function StatefulSetDetails({
const handleChangeLogFilters = useCallback( const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setLogFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -265,7 +265,7 @@ function StatefulSetDetails({
const handleChangeTracesFilters = useCallback( const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters']) => { (value: IBuilderQuery['filters']) => {
setTracesFilters((prevFilters) => { setLogAndTracesFilters((prevFilters) => {
const primaryFilters = prevFilters.items.filter((item) => const primaryFilters = prevFilters.items.filter((item) =>
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes( [QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
item.key?.key ?? '', item.key?.key ?? '',
@@ -345,8 +345,8 @@ function StatefulSetDetails({
if (selectedView === VIEW_TYPES.LOGS) { if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = { const filtersWithoutPagination = {
...logFilters, ...logAndTracesFilters,
items: logFilters.items.filter((item) => item.key?.key !== 'id'), items: logAndTracesFilters.items.filter((item) => item.key?.key !== 'id'),
}; };
const compositeQuery = { const compositeQuery = {
@@ -380,7 +380,7 @@ function StatefulSetDetails({
{ {
...initialQueryBuilderFormValuesMap.traces, ...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP, aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters, filters: logAndTracesFilters,
}, },
], ],
}, },
@@ -546,7 +546,7 @@ function StatefulSetDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters} handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters} logFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="statefulsetLogs" queryKey="statefulsetLogs"
category={K8sCategory.STATEFULSETS} category={K8sCategory.STATEFULSETS}
@@ -562,9 +562,13 @@ function StatefulSetDetails({
isModalTimeSelection={isModalTimeSelection} isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange} handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters} handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters} tracesFilters={logAndTracesFilters}
selectedInterval={selectedInterval} selectedInterval={selectedInterval}
queryKey="statefulsetTraces" queryKey="statefulsetTraces"
queryKeyFilters={[
QUERY_KEYS.K8S_STATEFUL_SET_NAME,
QUERY_KEYS.K8S_NAMESPACE_NAME,
]}
/> />
)} )}
{selectedView === VIEW_TYPES.EVENTS && ( {selectedView === VIEW_TYPES.EVENTS && (

View File

@@ -237,6 +237,11 @@ function K8sVolumesList({
} }
}, [selectedRowData, fetchGroupedByRowData]); }, [selectedRowData, fetchGroupedByRowData]);
const numberOfPages = useMemo(() => Math.ceil(totalCount / pageSize), [
totalCount,
pageSize,
]);
const handleTableChange: TableProps<K8sVolumesRowData>['onChange'] = useCallback( const handleTableChange: TableProps<K8sVolumesRowData>['onChange'] = useCallback(
( (
pagination: TablePaginationConfig, pagination: TablePaginationConfig,
@@ -245,6 +250,11 @@ function K8sVolumesList({
): void => { ): void => {
if (pagination.current) { if (pagination.current) {
setCurrentPage(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) { if ('field' in sorter && sorter.order) {
@@ -256,7 +266,7 @@ function K8sVolumesList({
setOrderBy(null); setOrderBy(null);
} }
}, },
[], [numberOfPages, pageSize],
); );
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
@@ -270,15 +280,13 @@ function K8sVolumesList({
handleChangeQueryData('filters', value); handleChangeQueryData('filters', value);
setCurrentPage(1); setCurrentPage(1);
logEvent('Infra Monitoring: K8s list filters applied', { logEvent('Infra Monitoring: K8s volumes list filters applied', {});
filters: value,
});
}, },
[handleChangeQueryData], [handleChangeQueryData],
); );
useEffect(() => { useEffect(() => {
logEvent('Infra Monitoring: K8s list page visited', {}); logEvent('Infra Monitoring: K8s volumes list page visited', {});
}, []); }, []);
const selectedVolumeData = useMemo(() => { const selectedVolumeData = useMemo(() => {
@@ -434,6 +442,8 @@ function K8sVolumesList({
setCurrentPage(1); setCurrentPage(1);
setGroupBy(groupBy); setGroupBy(groupBy);
setExpandedRowKeys([]); setExpandedRowKeys([]);
logEvent('Infra Monitoring: K8s volumes list group by changed', {});
}, },
[groupByFiltersData], [groupByFiltersData],
); );
@@ -449,6 +459,16 @@ function K8sVolumesList({
} }
}, [groupByFiltersData]); }, [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 ( return (
<div className="k8s-list"> <div className="k8s-list">
<K8sHeader <K8sHeader
@@ -476,10 +496,7 @@ function K8sVolumesList({
total: totalCount, total: totalCount,
showSizeChanger: true, showSizeChanger: true,
hideOnSinglePage: false, hideOnSinglePage: false,
onChange: (page, pageSize): void => { onChange: onPaginationChange,
setCurrentPage(page);
setPageSize(pageSize);
},
}} }}
scroll={{ x: true }} scroll={{ x: true }}
loading={{ loading={{

View 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;

View File

@@ -0,0 +1,3 @@
import Explorer from './Explorer';
export default Explorer;

View 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;

View File

@@ -0,0 +1,3 @@
import Summary from './Summary';
export default Summary;

View 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;

View File

@@ -0,0 +1,3 @@
import Views from './Views';
export default Views;

View File

@@ -0,0 +1,92 @@
// This test suite covers several important scenarios:
// - Empty layout - widget should be placed at origin (0,0)
// - Empty layout with custom dimensions
// - Placing widget next to an existing widget when there's space in the last row
// - Placing widget at bottom when the last row is full
// - Handling multiple rows correctly
// - Handling widgets with different heights
import { placeWidgetAtBottom } from '../utils';
describe('placeWidgetAtBottom', () => {
it('should place widget at (0,0) when layout is empty', () => {
const result = placeWidgetAtBottom('widget1', []);
expect(result).toEqual({
i: 'widget1',
x: 0,
y: 0,
w: 6,
h: 6,
});
});
it('should place widget at (0,0) with custom dimensions when layout is empty', () => {
const result = placeWidgetAtBottom('widget1', [], 4, 8);
expect(result).toEqual({
i: 'widget1',
x: 0,
y: 0,
w: 4,
h: 8,
});
});
it('should place widget next to existing widget in last row if space available', () => {
const existingLayout = [{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 }];
const result = placeWidgetAtBottom('widget2', existingLayout);
expect(result).toEqual({
i: 'widget2',
x: 6,
y: 0,
w: 6,
h: 6,
});
});
it('should place widget at bottom when last row is full', () => {
const existingLayout = [
{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 },
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
];
const result = placeWidgetAtBottom('widget3', existingLayout);
expect(result).toEqual({
i: 'widget3',
x: 0,
y: 6,
w: 6,
h: 6,
});
});
it('should handle multiple rows correctly', () => {
const existingLayout = [
{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 },
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
{ i: 'widget3', x: 0, y: 6, w: 6, h: 6 },
];
const result = placeWidgetAtBottom('widget4', existingLayout);
expect(result).toEqual({
i: 'widget4',
x: 6,
y: 6,
w: 6,
h: 6,
});
});
it('should handle widgets with different heights', () => {
const existingLayout = [
{ i: 'widget1', x: 0, y: 0, w: 6, h: 8 },
{ i: 'widget2', x: 6, y: 0, w: 6, h: 4 },
];
const result = placeWidgetAtBottom('widget3', existingLayout);
// y = 2 here as later the react-grid-layout will add 2px to the y value while adjusting the layout
expect(result).toEqual({
i: 'widget3',
x: 6,
y: 2,
w: 6,
h: 6,
});
});
});

View File

@@ -58,6 +58,7 @@ import {
getDefaultWidgetData, getDefaultWidgetData,
getIsQueryModified, getIsQueryModified,
handleQueryChange, handleQueryChange,
placeWidgetAtBottom,
} from './utils'; } from './utils';
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
@@ -363,20 +364,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
return; return;
} }
const widgetId = query.get('widgetId'); const widgetId = query.get('widgetId') || '';
let updatedLayout = selectedDashboard.data.layout || []; let updatedLayout = selectedDashboard.data.layout || [];
if (isNewDashboard) { if (isNewDashboard) {
updatedLayout = [ const newLayoutItem = placeWidgetAtBottom(widgetId, updatedLayout);
{ updatedLayout = [...updatedLayout, newLayoutItem];
i: widgetId || '',
w: 6,
x: 0,
h: 6,
y: 0,
},
...updatedLayout,
];
} }
const dashboard: Dashboard = { const dashboard: Dashboard = {
...selectedDashboard, ...selectedDashboard,
uuid: selectedDashboard.uuid, uuid: selectedDashboard.uuid,

View File

@@ -10,7 +10,8 @@ import {
PANEL_TYPES_INITIAL_QUERY, PANEL_TYPES_INITIAL_QUERY,
} from 'container/NewDashboard/ComponentsSlider/constants'; } from 'container/NewDashboard/ComponentsSlider/constants';
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config'; import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
import { cloneDeep, isEmpty, isEqual, set, unset } from 'lodash-es'; import { cloneDeep, defaultTo, isEmpty, isEqual, set, unset } from 'lodash-es';
import { Layout } from 'react-grid-layout';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
@@ -575,3 +576,58 @@ export const unitOptions = (columnUnit: string): DefaultOptionType[] => {
options: getCategorySelectOptionByName(filteredCategory), options: getCategorySelectOptionByName(filteredCategory),
})); }));
}; };
export const placeWidgetAtBottom = (
widgetId: string,
layout: Layout[],
widgetWidth?: number,
widgetHeight?: number,
): Layout => {
if (layout.length === 0) {
return { i: widgetId, x: 0, y: 0, w: widgetWidth || 6, h: widgetHeight || 6 };
}
// Find the maximum Y coordinate and height
const { maxY } = layout.reduce(
(acc, curr) => ({
maxY: Math.max(acc.maxY, curr.y + curr.h),
}),
{ maxY: 0 },
);
// Check for available space in the last row
const lastRowWidgets = layout.filter((item) => item.y + item.h === maxY);
const occupiedXInLastRow = lastRowWidgets.reduce(
(acc, widget) => acc + widget.w,
0,
);
// If there's space in the last row (total width < 12)
if (occupiedXInLastRow < 12) {
// Find the rightmost X coordinate in the last row
const maxXInLastRow = lastRowWidgets.reduce(
(acc, widget) => Math.max(acc, widget.x + widget.w),
0,
);
// If there's enough space for a 6-width widget
if (maxXInLastRow + defaultTo(widgetWidth, 6) <= 12) {
return {
i: widgetId,
x: maxXInLastRow,
y: maxY - (widgetHeight || 6), // Align with the last row
w: widgetWidth || 6,
h: widgetHeight || 6,
};
}
}
// If no space in last row, place at the bottom
return {
i: widgetId,
x: 0,
y: maxY,
w: widgetWidth || 6,
h: widgetHeight || 6,
};
};

View File

@@ -266,7 +266,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
demo-app demo-app
</div> </div>
@@ -277,7 +277,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
4.35 s 4.35 s
</div> </div>
@@ -292,7 +292,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
customer customer
</div> </div>
@@ -303,7 +303,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
431 ms 431 ms
</div> </div>
@@ -318,7 +318,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
mysql mysql
</div> </div>
@@ -329,7 +329,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
431 ms 431 ms
</div> </div>
@@ -344,7 +344,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
frontend frontend
</div> </div>
@@ -355,7 +355,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
287 ms 287 ms
</div> </div>
@@ -370,7 +370,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
driver driver
</div> </div>
@@ -381,7 +381,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
230 ms 230 ms
</div> </div>
@@ -396,7 +396,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
route route
</div> </div>
@@ -407,7 +407,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
66.4 ms 66.4 ms
</div> </div>
@@ -422,7 +422,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
redis redis
</div> </div>
@@ -433,7 +433,7 @@ exports[`Table panel wrappper tests table should render fine with the query resp
> >
<div> <div>
<div <div
class="line-clamped-text" class="line-clamped-wrapper__text"
> >
31.3 ms 31.3 ms
</div> </div>

View File

@@ -82,6 +82,13 @@ const menuItems: SidebarItem[] = [
label: 'Logs', label: 'Logs',
icon: <ScrollText size={16} />, 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, key: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
label: 'Infra Monitoring', label: 'Infra Monitoring',

View File

@@ -219,6 +219,9 @@ export const routesToSkip = [
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS, ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
ROUTES.SOMETHING_WENT_WRONG, ROUTES.SOMETHING_WENT_WRONG,
ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES, ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
ROUTES.METRICS_EXPLORER,
ROUTES.METRICS_EXPLORER_EXPLORER,
ROUTES.METRICS_EXPLORER_VIEWS,
]; ];
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS]; export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];

View File

@@ -6,6 +6,7 @@ import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
import { formUrlParams } from 'container/TraceDetail/utils'; import { formUrlParams } from 'container/TraceDetail/utils';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter'; import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ILog } from 'types/api/logs/log'; import { ILog } from 'types/api/logs/log';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
@@ -113,7 +114,9 @@ export const getListColumns = (
return ( return (
<BlockLink to={getTraceLink(item)} openInNewTab={false}> <BlockLink to={getTraceLink(item)} openInNewTab={false}>
<Typography data-testid={key}>{value}</Typography> <Typography data-testid={key}>
<LineClampedText text={value} lines={3} />
</Typography>
</BlockLink> </BlockLink>
); );
}, },

View File

@@ -1,5 +1,6 @@
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { convertKeysToColumnFields } from 'container/LogsExplorerList/utils'; import { convertKeysToColumnFields } from 'container/LogsExplorerList/utils';
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -22,20 +23,16 @@ export const addEmptyWidgetInDashboardJSONWithQuery = (
...convertKeysToColumnFields(selectedColumns || []), ...convertKeysToColumnFields(selectedColumns || []),
]; ];
const newLayoutItem = placeWidgetAtBottom(
widgetId,
dashboard?.data?.layout || [],
);
return { return {
...dashboard, ...dashboard,
data: { data: {
...dashboard.data, ...dashboard.data,
layout: [ layout: [...(dashboard?.data?.layout || []), newLayoutItem],
{
i: widgetId,
w: 6,
x: 0,
h: 6,
y: 0,
},
...(dashboard?.data?.layout || []),
],
widgets: [ widgets: [
...(dashboard?.data?.widgets || []), ...(dashboard?.data?.widgets || []),
{ {

Some files were not shown because too many files have changed in this diff Show More