Merge branch 'main' into SIG-7395-1

This commit is contained in:
SagarRajput-7 2025-06-03 09:56:59 +05:30 committed by GitHub
commit 4e78c91567
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
313 changed files with 64151 additions and 16463 deletions

1
.gitignore vendored
View File

@ -66,6 +66,7 @@ e2e/.auth
# go # go
vendor/ vendor/
**/main/** **/main/**
__debug_bin**
# git-town # git-town
.git-branches.toml .git-branches.toml

View File

@ -207,3 +207,11 @@ emailing:
key_file_path: key_file_path:
# The path to the certificate file. # The path to the certificate file.
cert_file_path: cert_file_path:
##################### Sharder (experimental) #####################
sharder:
# Specifies the sharder provider to use.
provider: noop
single:
# The org id to which this instance belongs to.
org_id: org_id

View File

@ -174,7 +174,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.85.2 image: signoz/signoz:v0.85.3
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
ports: ports:

View File

@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.85.2 image: signoz/signoz:v0.85.3
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
ports: ports:

View File

@ -177,7 +177,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.85.2} image: signoz/signoz:${VERSION:-v0.85.3}
container_name: signoz container_name: signoz
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml

View File

@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.85.2} image: signoz/signoz:${VERSION:-v0.85.3}
container_name: signoz container_name: signoz
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml

View File

@ -5,10 +5,13 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore" "github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing" "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/featuretypes" "github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/licensetypes" "github.com/SigNoz/signoz/pkg/types/licensetypes"
@ -18,23 +21,31 @@ import (
) )
type provider struct { type provider struct {
store licensetypes.Store store licensetypes.Store
zeus zeus.Zeus zeus zeus.Zeus
config licensing.Config config licensing.Config
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
stopChan chan struct{} orgGetter organization.Getter
stopChan chan struct{}
} }
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus) factory.ProviderFactory[licensing.Licensing, licensing.Config] { func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) { return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
return New(ctx, providerSettings, config, store, zeus) return New(ctx, providerSettings, config, store, zeus, orgGetter)
}) })
} }
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus) (licensing.Licensing, error) { func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) (licensing.Licensing, error) {
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing") settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing")
licensestore := sqllicensingstore.New(sqlstore) licensestore := sqllicensingstore.New(sqlstore)
return &provider{store: licensestore, zeus: zeus, config: config, settings: settings, stopChan: make(chan struct{})}, nil return &provider{
store: licensestore,
zeus: zeus,
config: config,
settings: settings,
orgGetter: orgGetter,
stopChan: make(chan struct{}),
}, nil
} }
func (provider *provider) Start(ctx context.Context) error { func (provider *provider) Start(ctx context.Context) error {
@ -66,13 +77,13 @@ func (provider *provider) Stop(ctx context.Context) error {
} }
func (provider *provider) Validate(ctx context.Context) error { func (provider *provider) Validate(ctx context.Context) error {
organizations, err := provider.store.ListOrganizations(ctx) organizations, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil { if err != nil {
return err return err
} }
for _, organizationID := range organizations { for _, organization := range organizations {
err := provider.Refresh(ctx, organizationID) err := provider.Refresh(ctx, organization.ID)
if err != nil { if err != nil {
return err return err
} }
@ -167,6 +178,11 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
return err return err
} }
err = provider.InitFeatures(ctx, activeLicense.Features)
if err != nil {
return err
}
return nil return nil
} }
@ -251,6 +267,13 @@ func (provider *provider) GetFeatureFlags(ctx context.Context) ([]*featuretypes.
} }
} }
if constants.IsDotMetricsEnabled {
gettableFeatures = append(gettableFeatures, &featuretypes.GettableFeature{
Name: featuretypes.DotMetricsEnabled,
Active: true,
})
}
return gettableFeatures, nil return gettableFeatures, nil
} }

View File

@ -5,7 +5,6 @@ import (
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/featuretypes" "github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/licensetypes" "github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
@ -82,31 +81,6 @@ func (store *store) Update(ctx context.Context, organizationID valuer.UUID, stor
return nil return nil
} }
func (store *store) ListOrganizations(ctx context.Context) ([]valuer.UUID, error) {
orgIDStrs := make([]string, 0)
err := store.sqlstore.
BunDB().
NewSelect().
Model(new(types.Organization)).
Column("id").
Scan(ctx, &orgIDStrs)
if err != nil {
return nil, err
}
orgIDs := make([]valuer.UUID, len(orgIDStrs))
for idx, orgIDStr := range orgIDStrs {
orgID, err := valuer.NewUUID(orgIDStr)
if err != nil {
return nil, err
}
orgIDs[idx] = orgID
}
return orgIDs, nil
}
func (store *store) CreateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error { func (store *store) CreateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
_, err := store. _, err := store.
sqlstore. sqlstore.

View File

@ -96,13 +96,7 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
// note: add ee override methods first // note: add ee override methods first
// routes available only in ee version // routes available only in ee version
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet) router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/loginPrecheck", am.OpenAccess(ah.Signoz.Handlers.User.LoginPrecheck)).Methods(http.MethodGet)
// invite
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.Signoz.Handlers.User.GetInvite)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/invite/accept", am.OpenAccess(ah.Signoz.Handlers.User.AcceptInvite)).Methods(http.MethodPost)
// paid plans specific routes // paid plans specific routes
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost) router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
@ -114,9 +108,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet) router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost) router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
// v3 // v3
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost) router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut) router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut)

View File

@ -1,62 +0,0 @@
package api
import (
"net/http"
"strings"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
func (ah *APIHandler) lockDashboard(w http.ResponseWriter, r *http.Request) {
ah.lockUnlockDashboard(w, r, true)
}
func (ah *APIHandler) unlockDashboard(w http.ResponseWriter, r *http.Request) {
ah.lockUnlockDashboard(w, r, false)
}
func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request, lock bool) {
// Locking can only be done by the owner of the dashboard
// or an admin
// - Fetch the dashboard
// - Check if the user is the owner or an admin
// - If yes, lock/unlock the dashboard
// - If no, return 403
// Get the dashboard UUID from the request
uuid := mux.Vars(r)["uuid"]
if strings.HasPrefix(uuid, "integration") {
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "dashboards created by integrations cannot be modified"))
return
}
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "unauthenticated"))
return
}
dashboard, err := ah.Signoz.Modules.Dashboard.Get(r.Context(), claims.OrgID, uuid)
if err != nil {
render.Error(w, err)
return
}
if err := claims.IsAdmin(); err != nil && (dashboard.CreatedBy != claims.Email) {
render.Error(w, errors.Newf(errors.TypeForbidden, errors.CodeForbidden, "You are not authorized to lock/unlock this dashboard"))
return
}
// Lock/Unlock the dashboard
err = ah.Signoz.Modules.Dashboard.LockUnlock(r.Context(), claims.OrgID, uuid, lock)
if err != nil {
render.Error(w, err)
return
}
ah.Respond(w, "Dashboard updated successfully")
}

View File

@ -20,6 +20,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
@ -113,6 +114,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
serverOptions.SigNoz.SQLStore, serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.TelemetryStore, serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus, serverOptions.SigNoz.Prometheus,
serverOptions.SigNoz.Modules.OrgGetter,
) )
if err != nil { if err != nil {
@ -157,7 +159,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
// start the usagemanager // start the usagemanager
usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.Organization) usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.OrgGetter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -225,7 +227,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
&opAmpModel.AllAgents, agentConfMgr, &opAmpModel.AllAgents, agentConfMgr,
) )
orgs, err := apiHandler.Signoz.Modules.Organization.GetAll(context.Background()) orgs, err := apiHandler.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -240,11 +242,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) { func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
r := baseapp.NewRouter() r := baseapp.NewRouter()
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap) r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap) r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(), r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes, s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default, s.serverOptions.Config.APIServer.Timeout.Default,
@ -275,8 +276,8 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
r := baseapp.NewRouter() r := baseapp.NewRouter()
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger()) am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap) r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap) r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(), r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes, s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default, s.serverOptions.Config.APIServer.Timeout.Default,
@ -297,6 +298,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
apiHandler.RegisterMessagingQueuesRoutes(r, am) apiHandler.RegisterMessagingQueuesRoutes(r, am)
apiHandler.RegisterThirdPartyApiRoutes(r, am) apiHandler.RegisterThirdPartyApiRoutes(r, am)
apiHandler.MetricExplorerRoutes(r, am) apiHandler.MetricExplorerRoutes(r, am)
apiHandler.RegisterTraceFunnelsRoutes(r, am)
c := cors.New(cors.Options{ c := cors.New(cors.Options{
AllowedOrigins: []string{"*"}, AllowedOrigins: []string{"*"},
@ -450,6 +452,7 @@ func makeRulesManager(
sqlstore sqlstore.SQLStore, sqlstore sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore, telemetryStore telemetrystore.TelemetryStore,
prometheus prometheus.Prometheus, prometheus prometheus.Prometheus,
orgGetter organization.Getter,
) (*baserules.Manager, error) { ) (*baserules.Manager, error) {
// create manager opts // create manager opts
managerOpts := &baserules.ManagerOptions{ managerOpts := &baserules.ManagerOptions{
@ -465,6 +468,7 @@ func makeRulesManager(
PrepareTestRuleFunc: rules.TestNotification, PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager, Alertmanager: alertmanager,
SQLStore: sqlstore, SQLStore: sqlstore,
OrgGetter: orgGetter,
} }
// create Manager // create Manager

View File

@ -4,6 +4,10 @@ import (
"os" "os"
) )
const (
DefaultSiteURL = "https://localhost:8080"
)
var LicenseSignozIo = "https://license.signoz.io/api/v1" var LicenseSignozIo = "https://license.signoz.io/api/v1"
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "") var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "") var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
@ -20,3 +24,22 @@ func GetOrDefaultEnv(key string, fallback string) string {
} }
return v return v
} }
// constant functions that override env vars
// GetDefaultSiteURL returns default site url, primarily
// used to send saml request and allowing backend to
// handle http redirect
func GetDefaultSiteURL() string {
return GetOrDefaultEnv("SIGNOZ_SITE_URL", DefaultSiteURL)
}
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
var IsDotMetricsEnabled = false
func init() {
if GetOrDefaultEnv(DotMetricsEnabled, "false") == "true" {
IsDotMetricsEnabled = true
}
}

View File

@ -17,6 +17,7 @@ import (
"github.com/SigNoz/signoz/pkg/config/fileprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
pkglicensing "github.com/SigNoz/signoz/pkg/licensing" pkglicensing "github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants" baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
@ -133,8 +134,8 @@ func main() {
zeus.Config(), zeus.Config(),
httpzeus.NewProviderFactory(), httpzeus.NewProviderFactory(),
licensing.Config(24*time.Hour, 3), licensing.Config(24*time.Hour, 3),
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] { func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
return httplicensing.NewProviderFactory(sqlstore, zeus) return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter)
}, },
signoz.NewEmailingProviderFactories(), signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(), signoz.NewCacheProviderFactories(),

View File

@ -41,16 +41,16 @@ type Manager struct {
zeus zeus.Zeus zeus zeus.Zeus
organizationModule organization.Module orgGetter organization.Getter
} }
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, organizationModule organization.Module) (*Manager, error) { func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, orgGetter organization.Getter) (*Manager, error) {
m := &Manager{ m := &Manager{
clickhouseConn: clickhouseConn, clickhouseConn: clickhouseConn,
licenseService: licenseService, licenseService: licenseService,
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
zeus: zeus, zeus: zeus,
organizationModule: organizationModule, orgGetter: orgGetter,
} }
return m, nil return m, nil
} }
@ -74,8 +74,7 @@ func (lm *Manager) Start(ctx context.Context) error {
return nil return nil
} }
func (lm *Manager) UploadUsage(ctx context.Context) { func (lm *Manager) UploadUsage(ctx context.Context) {
organizations, err := lm.orgGetter.ListByOwnedKeyRange(ctx)
organizations, err := lm.organizationModule.GetAll(context.Background())
if err != nil { if err != nil {
zap.L().Error("failed to get organizations", zap.Error(err)) zap.L().Error("failed to get organizations", zap.Error(err))
return return

View File

@ -28,6 +28,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useCallback, useEffect, useState } from 'react'; import { Suspense, useCallback, useEffect, useState } from 'react';
import { Route, Router, Switch } from 'react-router-dom'; import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat'; import { CompatRouter } from 'react-router-dom-v5-compat';
import { LicenseStatus } from 'types/api/licensesV3/getActive';
import { Userpilot } from 'userpilot'; import { Userpilot } from 'userpilot';
import { extractDomain } from 'utils/app'; import { extractDomain } from 'utils/app';
@ -171,11 +172,13 @@ function App(): JSX.Element {
user && user &&
!!user.email !!user.email
) { ) {
// either the active API returns error with 404 or 501 and if it returns a terminated license means it's on basic plan
const isOnBasicPlan = const isOnBasicPlan =
activeLicenseFetchError && (activeLicenseFetchError &&
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes( [StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
activeLicenseFetchError?.getHttpStatusCode(), activeLicenseFetchError?.getHttpStatusCode(),
); )) ||
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER); const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) { if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
@ -190,6 +193,10 @@ function App(): JSX.Element {
updatedRoutes = updatedRoutes.filter( updatedRoutes = updatedRoutes.filter(
(route) => route?.path !== ROUTES.BILLING, (route) => route?.path !== ROUTES.BILLING,
); );
if (isEnterpriseSelfHostedUser) {
updatedRoutes.push(LIST_LICENSES);
}
} }
// always add support route for cloud users // always add support route for cloud users
updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE]; updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE];

View File

@ -1,27 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/create';
const createDashboard = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const url = props.uploadedGrafana ? '/dashboards/grafana' : '/dashboards';
try {
const response = await axios.post(url, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default createDashboard;

View File

@ -1,9 +0,0 @@
import axios from 'api';
import { PayloadProps, Props } from 'types/api/dashboard/delete';
const deleteDashboard = (props: Props): Promise<PayloadProps> =>
axios
.delete<PayloadProps>(`/dashboards/${props.uuid}`)
.then((response) => response.data);
export default deleteDashboard;

View File

@ -1,11 +0,0 @@
import axios from 'api';
import { ApiResponse } from 'types/api';
import { Props } from 'types/api/dashboard/get';
import { Dashboard } from 'types/api/dashboard/getAll';
const getDashboard = (props: Props): Promise<Dashboard> =>
axios
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
.then((res) => res.data.data);
export default getDashboard;

View File

@ -1,8 +0,0 @@
import axios from 'api';
import { ApiResponse } from 'types/api';
import { Dashboard } from 'types/api/dashboard/getAll';
export const getAllDashboardList = (): Promise<Dashboard[]> =>
axios
.get<ApiResponse<Dashboard[]>>('/dashboards')
.then((res) => res.data.data);

View File

@ -1,11 +0,0 @@
import axios from 'api';
import { AxiosResponse } from 'axios';
interface LockDashboardProps {
uuid: string;
}
const lockDashboard = (props: LockDashboardProps): Promise<AxiosResponse> =>
axios.put(`/dashboards/${props.uuid}/lock`);
export default lockDashboard;

View File

@ -1,11 +0,0 @@
import axios from 'api';
import { AxiosResponse } from 'axios';
interface UnlockDashboardProps {
uuid: string;
}
const unlockDashboard = (props: UnlockDashboardProps): Promise<AxiosResponse> =>
axios.put(`/dashboards/${props.uuid}/unlock`);
export default unlockDashboard;

View File

@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/update';
const updateDashboard = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.put(`/dashboards/${props.uuid}`, {
...props.data,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateDashboard;

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sClustersListPayload { export interface K8sClustersListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -40,23 +42,80 @@ export interface K8sClustersListResponse {
}; };
} }
export const clustersMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.cluster.uid', under: 'k8s_cluster_uid' },
] as const;
export function mapClustersMeta(
raw: Record<string, unknown>,
): K8sClustersData['meta'] {
const out: Record<string, unknown> = { ...raw };
clustersMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sClustersData['meta'];
}
export const getK8sClustersList = async ( export const getK8sClustersList = async (
props: K8sClustersListPayload, props: K8sClustersListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/clusters/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/clusters/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sClustersListResponse = response.data;
// one-liner meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapClustersMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sDaemonSetsListPayload { export interface K8sDaemonSetsListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -46,23 +48,82 @@ export interface K8sDaemonSetsListResponse {
}; };
} }
export const daemonSetsMetaMap = [
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapDaemonSetsMeta(
raw: Record<string, unknown>,
): K8sDaemonSetsData['meta'] {
const out: Record<string, unknown> = { ...raw };
daemonSetsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sDaemonSetsData['meta'];
}
export const getK8sDaemonSetsList = async ( export const getK8sDaemonSetsList = async (
props: K8sDaemonSetsListPayload, props: K8sDaemonSetsListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/daemonsets/list', props, { // filter prep (unchanged)…
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/daemonsets/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sDaemonSetsListResponse = response.data;
// single-line meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapDaemonSetsMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sDeploymentsListPayload { export interface K8sDeploymentsListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -46,23 +48,81 @@ export interface K8sDeploymentsListResponse {
}; };
} }
export const deploymentsMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
] as const;
export function mapDeploymentsMeta(
raw: Record<string, unknown>,
): K8sDeploymentsData['meta'] {
const out: Record<string, unknown> = { ...raw };
deploymentsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sDeploymentsData['meta'];
}
export const getK8sDeploymentsList = async ( export const getK8sDeploymentsList = async (
props: K8sDeploymentsListPayload, props: K8sDeploymentsListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/deployments/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/deployments/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sDeploymentsListResponse = response.data;
// single-line mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapDeploymentsMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sJobsListPayload { export interface K8sJobsListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -48,23 +50,79 @@ export interface K8sJobsListResponse {
}; };
} }
export const jobsMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
] as const;
export function mapJobsMeta(raw: Record<string, unknown>): K8sJobsData['meta'] {
const out: Record<string, unknown> = { ...raw };
jobsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sJobsData['meta'];
}
export const getK8sJobsList = async ( export const getK8sJobsList = async (
props: K8sJobsListPayload, props: K8sJobsListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/jobs/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/jobs/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sJobsListResponse = response.data;
// one-liner meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapJobsMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sNamespacesListPayload { export interface K8sNamespacesListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -38,23 +40,79 @@ export interface K8sNamespacesListResponse {
}; };
} }
export const namespacesMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
] as const;
export function mapNamespacesMeta(
raw: Record<string, unknown>,
): K8sNamespacesData['meta'] {
const out: Record<string, unknown> = { ...raw };
namespacesMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sNamespacesData['meta'];
}
export const getK8sNamespacesList = async ( export const getK8sNamespacesList = async (
props: K8sNamespacesListPayload, props: K8sNamespacesListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/namespaces/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/namespaces/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sNamespacesListResponse = response.data;
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapNamespacesMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sNodesListPayload { export interface K8sNodesListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -41,23 +43,81 @@ export interface K8sNodesListResponse {
}; };
} }
export const nodesMetaMap = [
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.node.uid', under: 'k8s_node_uid' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapNodesMeta(
raw: Record<string, unknown>,
): K8sNodesData['meta'] {
const out: Record<string, unknown> = { ...raw };
nodesMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sNodesData['meta'];
}
export const getK8sNodesList = async ( export const getK8sNodesList = async (
props: K8sNodesListPayload, props: K8sNodesListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/nodes/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/nodes/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sNodesListResponse = response.data;
// one-liner to map dot→underscore
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapNodesMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sPodsListPayload { export interface K8sPodsListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -69,23 +71,87 @@ export interface K8sPodsListResponse {
}; };
} }
export const podsMetaMap = [
{ dot: 'k8s.cronjob.name', under: 'k8s_cronjob_name' },
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapPodsMeta(raw: Record<string, unknown>): K8sPodsData['meta'] {
// clone everything
const out: Record<string, unknown> = { ...raw };
// overlay only the dot→under mappings
podsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sPodsData['meta'];
}
// getK8sPodsList
export const getK8sPodsList = async ( export const getK8sPodsList = async (
props: K8sPodsListPayload, props: K8sPodsListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/pods/list', props, { const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/pods/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sPodsListResponse = response.data;
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapPodsMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sVolumesListPayload { export interface K8sVolumesListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -47,23 +49,92 @@ export interface K8sVolumesListResponse {
}; };
} }
export const volumesMetaMap: Array<{
dot: keyof Record<string, unknown>;
under: keyof K8sVolumesData['meta'];
}> = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{
dot: 'k8s.persistentvolumeclaim.name',
under: 'k8s_persistentvolumeclaim_name',
},
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
];
export function mapVolumesMeta(
rawMeta: Record<string, unknown>,
): K8sVolumesData['meta'] {
// start with everything that was already there
const out: Record<string, unknown> = { ...rawMeta };
// for each dot→under rule, if the raw has the dot, overwrite the underscore
volumesMetaMap.forEach(({ dot, under }) => {
if (dot in rawMeta) {
const val = rawMeta[dot];
out[under] = typeof val === 'string' ? val : rawMeta[under];
}
});
return out as K8sVolumesData['meta'];
}
export const getK8sVolumesList = async ( export const getK8sVolumesList = async (
props: K8sVolumesListPayload, props: K8sVolumesListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/pvcs/list', props, { // Prepare filters
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/pvcs/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sVolumesListResponse = response.data;
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapVolumesMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sStatefulSetsListPayload { export interface K8sStatefulSetsListPayload {
filters: TagFilter; filters: TagFilter;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
@ -45,23 +47,78 @@ export interface K8sStatefulSetsListResponse {
}; };
} }
export const statefulSetsMetaMap = [
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
] as const;
export function mapStatefulSetsMeta(
raw: Record<string, unknown>,
): K8sStatefulSetsData['meta'] {
const out: Record<string, unknown> = { ...raw };
statefulSetsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sStatefulSetsData['meta'];
}
export const getK8sStatefulSetsList = async ( export const getK8sStatefulSetsList = async (
props: K8sStatefulSetsListPayload, props: K8sStatefulSetsListPayload,
signal?: AbortSignal, signal?: AbortSignal,
headers?: Record<string, string>, headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => { ): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
try { try {
const response = await axios.post('/statefulsets/list', props, { // Prepare filters
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) return acc;
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/statefulsets/list', requestProps, {
signal, signal,
headers, headers,
}); });
const payload: K8sStatefulSetsListResponse = response.data;
// apply our helper
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapStatefulSetsMeta(record.meta as Record<string, unknown>),
}));
return { return {
statusCode: 200, statusCode: 200,
error: null, error: null,
message: 'Success', message: 'Success',
payload: response.data, payload,
params: props, params: requestProps,
}; };
} catch (error) { } catch (error) {
return ErrorResponseHandler(error as AxiosError); return ErrorResponseHandler(error as AxiosError);

View File

@ -17,3 +17,19 @@ export const Logout = (): void => {
history.push(ROUTES.LOGIN); history.push(ROUTES.LOGIN);
}; };
export const UnderscoreToDotMap: Record<string, string> = {
k8s_cluster_name: 'k8s.cluster.name',
k8s_cluster_uid: 'k8s.cluster.uid',
k8s_namespace_name: 'k8s.namespace.name',
k8s_node_name: 'k8s.node.name',
k8s_node_uid: 'k8s.node.uid',
k8s_pod_name: 'k8s.pod.name',
k8s_pod_uid: 'k8s.pod.uid',
k8s_deployment_name: 'k8s.deployment.name',
k8s_daemonset_name: 'k8s.daemonset.name',
k8s_statefulset_name: 'k8s.statefulset.name',
k8s_cronjob_name: 'k8s.cronjob.name',
k8s_job_name: 'k8s.job.name',
k8s_persistentvolumeclaim_name: 'k8s.persistentvolumeclaim.name',
};

View File

@ -0,0 +1,23 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/create';
import { Dashboard } from 'types/api/dashboard/getAll';
const create = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
try {
const response = await axios.post<PayloadProps>('/dashboards', {
...props,
});
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default create;

View File

@ -0,0 +1,19 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Dashboard, PayloadProps } from 'types/api/dashboard/getAll';
const getAll = async (): Promise<SuccessResponseV2<Dashboard[]>> => {
try {
const response = await axios.get<PayloadProps>('/dashboards');
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getAll;

View File

@ -0,0 +1,21 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/delete';
const deleteDashboard = async (
props: Props,
): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.delete<PayloadProps>(`/dashboards/${props.id}`);
return {
httpStatusCode: response.status,
data: null,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default deleteDashboard;

View File

@ -0,0 +1,20 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/get';
import { Dashboard } from 'types/api/dashboard/getAll';
const get = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
try {
const response = await axios.get<PayloadProps>(`/dashboards/${props.id}`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default get;

View File

@ -0,0 +1,23 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/lockUnlock';
const lock = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.put<PayloadProps>(
`/dashboards/${props.id}/lock`,
{ lock: props.lock },
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default lock;

View File

@ -0,0 +1,23 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Dashboard } from 'types/api/dashboard/getAll';
import { PayloadProps, Props } from 'types/api/dashboard/update';
const update = async (props: Props): Promise<SuccessResponseV2<Dashboard>> => {
try {
const response = await axios.put<PayloadProps>(`/dashboards/${props.id}`, {
...props.data,
});
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default update;

View File

@ -23,6 +23,9 @@ import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
interface MetricsTabProps { interface MetricsTabProps {
timeRange: { timeRange: {
startTime: number; startTime: number;
@ -45,9 +48,20 @@ function Metrics({
handleTimeChange, handleTimeChange,
isModalTimeSelection, isModalTimeSelection,
}: MetricsTabProps): JSX.Element { }: MetricsTabProps): JSX.Element {
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo( const queryPayloads = useMemo(
() => getHostQueryPayload(hostName, timeRange.startTime, timeRange.endTime), () =>
[hostName, timeRange.startTime, timeRange.endTime], getHostQueryPayload(
hostName,
timeRange.startTime,
timeRange.endTime,
dotMetricsEnabled,
),
[hostName, timeRange.startTime, timeRange.endTime, dotMetricsEnabled],
); );
const queries = useQueries( const queries = useQueries(

View File

@ -16,6 +16,7 @@ import JSONView from 'container/LogDetailedView/JsonView';
import Overview from 'container/LogDetailedView/Overview'; import Overview from 'container/LogDetailedView/Overview';
import { import {
aggregateAttributesResourcesToString, aggregateAttributesResourcesToString,
escapeHtml,
removeEscapeCharacters, removeEscapeCharacters,
unescapeString, unescapeString,
} from 'container/LogDetailedView/utils'; } from 'container/LogDetailedView/utils';
@ -118,7 +119,7 @@ function LogDetail({
const htmlBody = useMemo( const htmlBody = useMemo(
() => ({ () => ({
__html: convert.toHtml( __html: convert.toHtml(
dompurify.sanitize(unescapeString(log?.body || ''), { dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}), }),
), ),

View File

@ -7,7 +7,7 @@ import cx from 'classnames';
import LogDetail from 'components/LogDetail'; import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants'; import { VIEW_TYPES } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { unescapeString } from 'container/LogDetailedView/utils'; import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types'; import { FontSize } from 'container/OptionsMenu/types';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog'; import { useActiveLog } from 'hooks/logs/useActiveLog';
@ -58,7 +58,7 @@ function LogGeneralField({
const html = useMemo( const html = useMemo(
() => ({ () => ({
__html: convert.toHtml( __html: convert.toHtml(
dompurify.sanitize(unescapeString(fieldValue), { dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}), }),
), ),

View File

@ -5,7 +5,7 @@ import { DrawerProps } from 'antd';
import LogDetail from 'components/LogDetail'; import LogDetail from 'components/LogDetail';
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants'; import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats'; import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { unescapeString } from 'container/LogDetailedView/utils'; import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
import LogsExplorerContext from 'container/LogsExplorerContext'; import LogsExplorerContext from 'container/LogsExplorerContext';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog'; import { useActiveLog } from 'hooks/logs/useActiveLog';
@ -177,7 +177,7 @@ function RawLogView({
const html = useMemo( const html = useMemo(
() => ({ () => ({
__html: convert.toHtml( __html: convert.toHtml(
dompurify.sanitize(unescapeString(text), { dompurify.sanitize(unescapeString(escapeHtml(text)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}), }),
), ),

View File

@ -10,4 +10,5 @@ export enum FeatureKeys {
ONBOARDING_V3 = 'ONBOARDING_V3', ONBOARDING_V3 = 'ONBOARDING_V3',
THIRD_PARTY_API = 'THIRD_PARTY_API', THIRD_PARTY_API = 'THIRD_PARTY_API',
TRACE_FUNNELS = 'TRACE_FUNNELS', TRACE_FUNNELS = 'TRACE_FUNNELS',
DOT_METRICS_ENABLED = 'DOT_METRICS_ENABLED',
} }

View File

@ -21,7 +21,10 @@ import ROUTES from 'constants/routes';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute'; import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertCompositeQueryToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; import {
convertCompositeQueryToTraceSelectedTags,
getResourceDeploymentKeys,
} from 'hooks/useResourceAttribute/utils';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter'; import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams'; import createQueryParams from 'lib/createQueryParams';
@ -38,6 +41,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { Exception, PayloadProps } from 'types/api/errors/getAll'; import { Exception, PayloadProps } from 'types/api/errors/getAll';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import { FilterDropdownExtendsProps } from './types'; import { FilterDropdownExtendsProps } from './types';
import { import {
extractFilterValues, extractFilterValues,
@ -405,6 +410,11 @@ function AllErrors(): JSX.Element {
}, },
]; ];
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const onChangeHandler: TableProps<Exception>['onChange'] = useCallback( const onChangeHandler: TableProps<Exception>['onChange'] = useCallback(
( (
paginations: TablePaginationConfig, paginations: TablePaginationConfig,
@ -438,7 +448,7 @@ function AllErrors(): JSX.Element {
useEffect(() => { useEffect(() => {
if (!isUndefined(errorCountResponse.data?.payload)) { if (!isUndefined(errorCountResponse.data?.payload)) {
const selectedEnvironments = queries.find( const selectedEnvironments = queries.find(
(val) => val.tagKey === 'resource_deployment_environment', (val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
)?.tagValue; )?.tagValue;
logEvent('Exception: List page visited', { logEvent('Exception: List page visited', {

View File

@ -10,6 +10,8 @@ import { Provider, useSelector } from 'react-redux';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import store from 'store'; import store from 'store';
import * as appContextHooks from '../../../providers/App/App';
import { LicenseEvent } from '../../../types/api/licensesV3/getActive';
import AllErrors from '../index'; import AllErrors from '../index';
import { import {
INIT_URL_WITH_COMMON_QUERY, INIT_URL_WITH_COMMON_QUERY,
@ -28,6 +30,30 @@ jest.mock('react-redux', () => ({
useSelector: jest.fn(), useSelector: jest.fn(),
})); }));
jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
user: {
role: 'admin',
},
activeLicenseV3: {
event_queue: {
created_at: '0',
event: LicenseEvent.NO_EVENT,
scheduled_at: '0',
status: '',
updated_at: '0',
},
license: {
license_key: 'test-license-key',
license_type: 'trial',
org_id: 'test-org-id',
plan_id: 'test-plan-id',
plan_name: 'test-plan-name',
plan_type: 'trial',
plan_version: 'test-plan-version',
},
},
} as any);
function Exceptions({ initUrl }: { initUrl?: string[] }): JSX.Element { function Exceptions({ initUrl }: { initUrl?: string[] }): JSX.Element {
return ( return (
<MemoryRouter initialEntries={initUrl ?? ['/exceptions']}> <MemoryRouter initialEntries={initUrl ?? ['/exceptions']}>

View File

@ -1,11 +1,12 @@
import { Button, Typography } from 'antd'; import { Button, Typography } from 'antd';
import createDashboard from 'api/dashboard/create'; import createDashboard from 'api/v1/dashboards/create';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useAxiosError from 'hooks/useAxiosError'; import { useErrorModal } from 'providers/ErrorModalProvider';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import APIError from 'types/api/error';
import { ExportPanelProps } from '.'; import { ExportPanelProps } from '.';
import { import {
@ -33,26 +34,28 @@ function ExportPanelContainer({
refetch, refetch,
} = useGetAllDashboard(); } = useGetAllDashboard();
const handleError = useAxiosError(); const { showErrorModal } = useErrorModal();
const { const {
mutate: createNewDashboard, mutate: createNewDashboard,
isLoading: createDashboardLoading, isLoading: createDashboardLoading,
} = useMutation(createDashboard, { } = useMutation(createDashboard, {
onSuccess: (data) => { onSuccess: (data) => {
if (data.payload) { if (data.data) {
onExport(data?.payload, true); onExport(data?.data, true);
} }
refetch(); refetch();
}, },
onError: handleError, onError: (error) => {
showErrorModal(error as APIError);
},
}); });
const options = useMemo(() => getSelectOptions(data || []), [data]); const options = useMemo(() => getSelectOptions(data?.data || []), [data]);
const handleExportClick = useCallback((): void => { const handleExportClick = useCallback((): void => {
const currentSelectedDashboard = data?.find( const currentSelectedDashboard = data?.data?.find(
({ uuid }) => uuid === selectedDashboardId, ({ id }) => id === selectedDashboardId,
); );
onExport(currentSelectedDashboard || null, false); onExport(currentSelectedDashboard || null, false);
@ -66,14 +69,18 @@ function ExportPanelContainer({
); );
const handleNewDashboard = useCallback(async () => { const handleNewDashboard = useCallback(async () => {
createNewDashboard({ try {
title: t('new_dashboard_title', { await createNewDashboard({
ns: 'dashboard', title: t('new_dashboard_title', {
}), ns: 'dashboard',
uploadedGrafana: false, }),
version: ENTITY_VERSION_V4, uploadedGrafana: false,
}); version: ENTITY_VERSION_V4,
}, [t, createNewDashboard]); });
} catch (error) {
showErrorModal(error as APIError);
}
}, [createNewDashboard, t, showErrorModal]);
const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading; const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading;

View File

@ -1,12 +1,10 @@
import { SelectProps } from 'antd'; import { SelectProps } from 'antd';
import { PayloadProps as AllDashboardsData } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
export const getSelectOptions = ( export const getSelectOptions = (data: Dashboard[]): SelectProps['options'] =>
data: AllDashboardsData, data.map(({ id, data }) => ({
): SelectProps['options'] =>
data.map(({ uuid, data }) => ({
label: data.title, label: data.title,
value: uuid, value: id,
})); }));
export const filterOptions: SelectProps['filterOption'] = ( export const filterOptions: SelectProps['filterOption'] = (

View File

@ -38,7 +38,7 @@ export default function DashboardEmptyState(): JSX.Element {
setSelectedRowWidgetId(null); setSelectedRowWidgetId(null);
handleToggleDashboardSlider(true); handleToggleDashboardSlider(true);
logEvent('Dashboard Detail: Add new panel clicked', { logEvent('Dashboard Detail: Add new panel clicked', {
dashboardId: selectedDashboard?.uuid, dashboardId: selectedDashboard?.id,
dashboardName: selectedDashboard?.data.title, dashboardName: selectedDashboard?.data.title,
numberOfPanels: selectedDashboard?.data.widgets?.length, numberOfPanels: selectedDashboard?.data.widgets?.length,
}); });

View File

@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { AppProvider } from 'providers/App/App'; import { AppProvider } from 'providers/App/App';
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import store from 'store'; import store from 'store';
@ -189,24 +190,26 @@ describe('WidgetGraphComponent', () => {
it('should show correct menu items when hovering over more options while loading', async () => { it('should show correct menu items when hovering over more options while loading', async () => {
const { getByTestId, findByRole, getByText, container } = render( const { getByTestId, findByRole, getByText, container } = render(
<MockQueryClientProvider> <MockQueryClientProvider>
<Provider store={store}> <ErrorModalProvider>
<AppProvider> <Provider store={store}>
<WidgetGraphComponent <AppProvider>
widget={mockProps.widget} <WidgetGraphComponent
queryResponse={mockProps.queryResponse} widget={mockProps.widget}
errorMessage={mockProps.errorMessage} queryResponse={mockProps.queryResponse}
version={mockProps.version} errorMessage={mockProps.errorMessage}
headerMenuList={mockProps.headerMenuList} version={mockProps.version}
isWarning={mockProps.isWarning} headerMenuList={mockProps.headerMenuList}
isFetchingResponse={mockProps.isFetchingResponse} isWarning={mockProps.isWarning}
setRequestData={mockProps.setRequestData} isFetchingResponse={mockProps.isFetchingResponse}
onClickHandler={mockProps.onClickHandler} setRequestData={mockProps.setRequestData}
onDragSelect={mockProps.onDragSelect} onClickHandler={mockProps.onClickHandler}
openTracesButton={mockProps.openTracesButton} onDragSelect={mockProps.onDragSelect}
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick} openTracesButton={mockProps.openTracesButton}
/> onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
</AppProvider> />
</Provider> </AppProvider>
</Provider>
</ErrorModalProvider>
</MockQueryClientProvider>, </MockQueryClientProvider>,
); );

View File

@ -4,7 +4,6 @@ import { Skeleton, Tooltip, Typography } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer'; import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
import { ToggleGraphProps } from 'components/Graph/types'; import { ToggleGraphProps } from 'components/Graph/types';
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 { placeWidgetAtBottom } from 'container/NewWidget/utils';
@ -31,7 +30,7 @@ import {
useState, useState,
} from 'react'; } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Props } from 'types/api/dashboard/update';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -119,29 +118,23 @@ function WidgetGraphComponent({
const updatedLayout = const updatedLayout =
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || []; selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
const updatedSelectedDashboard: Dashboard = { const updatedSelectedDashboard: Props = {
...selectedDashboard,
data: { data: {
...selectedDashboard.data, ...selectedDashboard.data,
widgets: updatedWidgets, widgets: updatedWidgets,
layout: updatedLayout, layout: updatedLayout,
}, },
uuid: selectedDashboard.uuid, id: selectedDashboard.id,
}; };
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, { updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => { onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []); if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) { if (setSelectedDashboard && updatedDashboard.data) {
setSelectedDashboard(updatedDashboard.payload); setSelectedDashboard(updatedDashboard.data);
} }
setDeleteModal(false); setDeleteModal(false);
}, },
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
}); });
}; };
@ -166,7 +159,8 @@ function WidgetGraphComponent({
updateDashboardMutation.mutateAsync( updateDashboardMutation.mutateAsync(
{ {
...selectedDashboard, id: selectedDashboard.id,
data: { data: {
...selectedDashboard.data, ...selectedDashboard.data,
layout, layout,
@ -183,9 +177,9 @@ function WidgetGraphComponent({
}, },
{ {
onSuccess: (updatedDashboard) => { onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []); if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) { if (setSelectedDashboard && updatedDashboard.data) {
setSelectedDashboard(updatedDashboard.payload); setSelectedDashboard(updatedDashboard.data);
} }
notifications.success({ notifications.success({
message: 'Panel cloned successfully, redirecting to new copy.', message: 'Panel cloned successfully, redirecting to new copy.',

View File

@ -6,7 +6,6 @@ import { Button, Form, Input, Modal, Typography } from 'antd';
import { useForm } from 'antd/es/form/Form'; import { useForm } from 'antd/es/form/Form';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import cx from 'classnames'; import cx from 'classnames';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
@ -14,7 +13,6 @@ import { DEFAULT_ROW_NAME } from 'container/NewDashboard/DashboardDescription/ut
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate'; import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { defaultTo, isUndefined } from 'lodash-es'; import { defaultTo, isUndefined } from 'lodash-es';
@ -36,7 +34,8 @@ import { ItemCallback, Layout } from 'react-grid-layout';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update';
import { ROLES, USER_ROLES } from 'types/roles'; import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission'; import { ComponentTypes } from 'utils/permission';
@ -107,7 +106,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
const updateDashboardMutation = useUpdateDashboard(); const updateDashboardMutation = useUpdateDashboard();
const { notifications } = useNotifications();
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
let permissions: ComponentTypes[] = ['save_layout', 'add_panel']; let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
@ -158,20 +156,20 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
useEffect(() => { useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data)) { if (!logEventCalledRef.current && !isUndefined(data)) {
logEvent('Dashboard Detail: Opened', { logEvent('Dashboard Detail: Opened', {
dashboardId: data.uuid, dashboardId: selectedDashboard?.id,
dashboardName: data.title, dashboardName: data.title,
numberOfPanels: data.widgets?.length, numberOfPanels: data.widgets?.length,
numberOfVariables: Object.keys(data?.variables || {}).length || 0, numberOfVariables: Object.keys(data?.variables || {}).length || 0,
}); });
logEventCalledRef.current = true; logEventCalledRef.current = true;
} }
}, [data]); }, [data, selectedDashboard?.id]);
const onSaveHandler = (): void => { const onSaveHandler = (): void => {
if (!selectedDashboard) return; if (!selectedDashboard) return;
const updatedDashboard: Dashboard = { const updatedDashboard: Props = {
...selectedDashboard, id: selectedDashboard.id,
data: { data: {
...selectedDashboard.data, ...selectedDashboard.data,
panelMap: { ...currentPanelMap }, panelMap: { ...currentPanelMap },
@ -186,24 +184,18 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
return widget; return widget;
}), }),
}, },
uuid: selectedDashboard.uuid,
}; };
updateDashboardMutation.mutate(updatedDashboard, { updateDashboardMutation.mutate(updatedDashboard, {
onSuccess: (updatedDashboard) => { onSuccess: (updatedDashboard) => {
setSelectedRowWidgetId(null); setSelectedRowWidgetId(null);
if (updatedDashboard.payload) { if (updatedDashboard.data) {
if (updatedDashboard.payload.data.layout) if (updatedDashboard.data.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout)); setLayouts(sortLayout(updatedDashboard.data.data.layout));
setSelectedDashboard(updatedDashboard.payload); setSelectedDashboard(updatedDashboard.data);
setPanelMap(updatedDashboard.payload?.data?.panelMap || {}); setPanelMap(updatedDashboard.data?.data?.panelMap || {});
} }
}, },
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
}); });
}; };
@ -286,33 +278,25 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
updatedWidgets?.push(currentWidget); updatedWidgets?.push(currentWidget);
const updatedSelectedDashboard: Dashboard = { const updatedSelectedDashboard: Props = {
...selectedDashboard, id: selectedDashboard.id,
data: { data: {
...selectedDashboard.data, ...selectedDashboard.data,
widgets: updatedWidgets, widgets: updatedWidgets,
}, },
uuid: selectedDashboard.uuid,
}; };
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, { updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => { onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []); if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) { if (setSelectedDashboard && updatedDashboard.data) {
setSelectedDashboard(updatedDashboard.payload); setSelectedDashboard(updatedDashboard.data);
} }
if (setPanelMap) if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
form.setFieldValue('title', ''); form.setFieldValue('title', '');
setIsSettingsModalOpen(false); setIsSettingsModalOpen(false);
setCurrentSelectRowId(null); setCurrentSelectRowId(null);
}, },
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
}); });
}; };
@ -447,34 +431,26 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
const updatedPanelMap = { ...currentPanelMap }; const updatedPanelMap = { ...currentPanelMap };
delete updatedPanelMap[currentSelectRowId]; delete updatedPanelMap[currentSelectRowId];
const updatedSelectedDashboard: Dashboard = { const updatedSelectedDashboard: Props = {
...selectedDashboard, id: selectedDashboard.id,
data: { data: {
...selectedDashboard.data, ...selectedDashboard.data,
widgets: updatedWidgets, widgets: updatedWidgets,
layout: updatedLayout, layout: updatedLayout,
panelMap: updatedPanelMap, panelMap: updatedPanelMap,
}, },
uuid: selectedDashboard.uuid,
}; };
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, { updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => { onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []); if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) { if (setSelectedDashboard && updatedDashboard.data) {
setSelectedDashboard(updatedDashboard.payload); setSelectedDashboard(updatedDashboard.data);
} }
if (setPanelMap) if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
setCurrentSelectRowId(null); setCurrentSelectRowId(null);
}, },
// eslint-disable-next-line sonarjs/no-identical-functions
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
}); });
}; };
const isDashboardEmpty = useMemo( const isDashboardEmpty = useMemo(

View File

@ -33,7 +33,7 @@ export default function Dashboards({
useEffect(() => { useEffect(() => {
if (!dashboardsList) return; if (!dashboardsList) return;
const sortedDashboards = dashboardsList.sort((a, b) => { const sortedDashboards = dashboardsList.data.sort((a, b) => {
const aUpdateAt = new Date(a.updatedAt).getTime(); const aUpdateAt = new Date(a.updatedAt).getTime();
const bUpdateAt = new Date(b.updatedAt).getTime(); const bUpdateAt = new Date(b.updatedAt).getTime();
return bUpdateAt - aUpdateAt; return bUpdateAt - aUpdateAt;
@ -103,7 +103,7 @@ export default function Dashboards({
<div className="home-dashboards-list-container home-data-item-container"> <div className="home-dashboards-list-container home-data-item-container">
<div className="dashboards-list"> <div className="dashboards-list">
{sortedDashboards.slice(0, 5).map((dashboard) => { {sortedDashboards.slice(0, 5).map((dashboard) => {
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.uuid}`; const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => { const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
event.stopPropagation(); event.stopPropagation();
@ -134,7 +134,7 @@ export default function Dashboards({
<div className="dashboard-item-name-container home-data-item-name-container"> <div className="dashboard-item-name-container home-data-item-name-container">
<img <img
src={ src={
dashboard.id % 2 === 0 Math.random() % 2 === 0
? '/Icons/eight-ball.svg' ? '/Icons/eight-ball.svg'
: '/Icons/circus-tent.svg' : '/Icons/circus-tent.svg'
} }

View File

@ -10,6 +10,7 @@ import getAllUserPreferences from 'api/preferences/getAllUserPreference';
import updateUserPreferenceAPI from 'api/preferences/updateUserPreference'; import updateUserPreferenceAPI from 'api/preferences/updateUserPreference';
import Header from 'components/Header/Header'; import Header from 'components/Header/Header';
import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@ -161,10 +162,20 @@ export default function Home(): JSX.Element {
enabled: !!query, enabled: !!query,
}); });
const { data: k8sPodsData } = useGetK8sPodsList(query as K8sPodsListPayload, { const { featureFlags } = useAppContext();
queryKey: ['K8sPodsList', query], const dotMetricsEnabled =
enabled: !!query, featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
}); ?.active || false;
const { data: k8sPodsData } = useGetK8sPodsList(
query as K8sPodsListPayload,
{
queryKey: ['K8sPodsList', query],
enabled: !!query,
},
undefined,
dotMetricsEnabled,
);
const [isLogsIngestionActive, setIsLogsIngestionActive] = useState(false); const [isLogsIngestionActive, setIsLogsIngestionActive] = useState(false);
const [isTracesIngestionActive, setIsTracesIngestionActive] = useState(false); const [isTracesIngestionActive, setIsTracesIngestionActive] = useState(false);

View File

@ -30,6 +30,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace'; import { Tags } from 'types/reducer/trace';
import { USER_ROLES } from 'types/roles'; import { USER_ROLES } from 'types/roles';
import { FeatureKeys } from '../../../constants/features';
import { DOCS_LINKS } from '../constants'; import { DOCS_LINKS } from '../constants';
import { columns, TIME_PICKER_OPTIONS } from './constants'; import { columns, TIME_PICKER_OPTIONS } from './constants';
@ -210,6 +211,11 @@ function ServiceMetrics({
const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]); const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryRangeRequestData = useMemo( const queryRangeRequestData = useMemo(
() => () =>
getQueryRangeRequestData({ getQueryRangeRequestData({
@ -217,12 +223,14 @@ function ServiceMetrics({
minTime: timeRange.startTime * 1e6, minTime: timeRange.startTime * 1e6,
maxTime: timeRange.endTime * 1e6, maxTime: timeRange.endTime * 1e6,
globalSelectedInterval, globalSelectedInterval,
dotMetricsEnabled,
}), }),
[ [
globalSelectedInterval, globalSelectedInterval,
timeRange.endTime, timeRange.endTime,
timeRange.startTime, timeRange.startTime,
topLevelOperations, topLevelOperations,
dotMetricsEnabled,
], ],
); );

View File

@ -25,9 +25,11 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import HostsListControls from './HostsListControls'; import HostsListControls from './HostsListControls';
import HostsListTable from './HostsListTable'; import HostsListTable from './HostsListTable';
import { getHostListsQuery, HostsQuickFiltersConfig } from './utils'; import { getHostListsQuery, GetHostsQuickFiltersConfig } from './utils';
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
function HostsList(): JSX.Element { function HostsList(): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
@ -114,6 +116,11 @@ function HostsList(): JSX.Element {
entityVersion: '', entityVersion: '',
}); });
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const handleFiltersChange = useCallback( const handleFiltersChange = useCallback(
(value: IBuilderQuery['filters']): void => { (value: IBuilderQuery['filters']): void => {
const isNewFilterAdded = value.items.length !== filters.items.length; const isNewFilterAdded = value.items.length !== filters.items.length;
@ -182,7 +189,7 @@ function HostsList(): JSX.Element {
</div> </div>
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={HostsQuickFiltersConfig} config={GetHostsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleQuickFiltersChange} onFilterChange={handleQuickFiltersChange}
/> />

View File

@ -198,3 +198,48 @@ export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
defaultOpen: true, defaultOpen: true,
}, },
]; ];
export function GetHostsQuickFiltersConfig(
dotMetricsEnabled: boolean,
): IQuickFiltersConfig[] {
// These keys dont change with dotMetricsEnabled
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
const osTypeKey = dotMetricsEnabled ? 'os.type' : 'os_type';
// This metric stays the same regardless of notation
const metricName = dotMetricsEnabled
? 'system.cpu.load_average.15m'
: 'system_cpu_load_average_15m';
return [
{
type: FiltersType.CHECKBOX,
title: 'Host Name',
attributeKey: {
key: hostNameKey,
dataType: DataTypes.String,
type: 'resource',
isColumn: false,
isJSON: false,
},
aggregateOperator: 'noop',
aggregateAttribute: metricName,
dataSource: DataSource.METRICS,
defaultOpen: true,
},
{
type: FiltersType.CHECKBOX,
title: 'OS Type',
attributeKey: {
key: osTypeKey,
dataType: DataTypes.String,
type: 'resource',
isColumn: false,
isJSON: false,
},
aggregateOperator: 'noop',
aggregateAttribute: metricName,
dataSource: DataSource.METRICS,
defaultOpen: true,
},
];
}

View File

@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -135,6 +137,11 @@ function K8sClustersList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sClustersRowData, selectedRowData: K8sClustersRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -194,6 +201,8 @@ function K8sClustersList({
queryKey: ['clusterList', fetchGroupedByRowDataQuery], queryKey: ['clusterList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
}, },
undefined,
dotMetricsEnabled,
); );
const { const {
@ -202,8 +211,10 @@ function K8sClustersList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sEntityToAggregateAttributeMapping[K8sCategory.CLUSTERS], K8sCategory.CLUSTERS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -249,6 +260,8 @@ function K8sClustersList({
queryKey: ['clusterList', query], queryKey: ['clusterList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]); const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]);

View File

@ -136,6 +136,11 @@ export const getK8sClustersListColumns = (
return columnsConfig as ColumnType<K8sClustersRowData>[]; return columnsConfig as ColumnType<K8sClustersRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sClustersData['meta']> = {
'k8s.cluster.name': 'k8s_cluster_name',
'k8s.cluster.uid': 'k8s_cluster_uid',
};
const getGroupByEle = ( const getGroupByEle = (
cluster: K8sClustersData, cluster: K8sClustersData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -143,7 +148,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(cluster.meta[group.key as keyof typeof cluster.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof cluster.meta;
const value = cluster.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -137,6 +139,11 @@ function K8sDaemonSetsList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sDaemonSetsRowData, selectedRowData: K8sDaemonSetsRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -196,6 +203,8 @@ function K8sDaemonSetsList({
queryKey: ['daemonSetList', fetchGroupedByRowDataQuery], queryKey: ['daemonSetList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
}, },
undefined,
dotMetricsEnabled,
); );
const { const {
@ -204,8 +213,10 @@ function K8sDaemonSetsList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sEntityToAggregateAttributeMapping[K8sCategory.DAEMONSETS], K8sCategory.DAEMONSETS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -246,6 +257,8 @@ function K8sDaemonSetsList({
queryKey: ['daemonSetList', query], queryKey: ['daemonSetList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const daemonSetsData = useMemo(() => data?.payload?.data?.records || [], [ const daemonSetsData = useMemo(() => data?.payload?.data?.records || [], [

View File

@ -236,6 +236,12 @@ export const getK8sDaemonSetsListColumns = (
return columnsConfig as ColumnType<K8sDaemonSetsRowData>[]; return columnsConfig as ColumnType<K8sDaemonSetsRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sDaemonSetsData['meta']> = {
'k8s.daemonset.name': 'k8s_daemonset_name',
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.cluster.name': 'k8s_cluster_name',
};
const getGroupByEle = ( const getGroupByEle = (
daemonSet: K8sDaemonSetsData, daemonSet: K8sDaemonSetsData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -243,7 +249,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(daemonSet.meta[group.key as keyof typeof daemonSet.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof daemonSet.meta;
const value = daemonSet.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -138,6 +140,11 @@ function K8sDeploymentsList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sDeploymentsRowData, selectedRowData: K8sDeploymentsRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -197,6 +204,8 @@ function K8sDeploymentsList({
queryKey: ['deploymentList', fetchGroupedByRowDataQuery], queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
}, },
undefined,
dotMetricsEnabled,
); );
const { const {
@ -205,8 +214,10 @@ function K8sDeploymentsList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sEntityToAggregateAttributeMapping[K8sCategory.DEPLOYMENTS], K8sCategory.DEPLOYMENTS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -247,6 +258,8 @@ function K8sDeploymentsList({
queryKey: ['deploymentList', query], queryKey: ['deploymentList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [ const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [

View File

@ -226,6 +226,12 @@ export const getK8sDeploymentsListColumns = (
return columnsConfig as ColumnType<K8sDeploymentsRowData>[]; return columnsConfig as ColumnType<K8sDeploymentsRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sDeploymentsData['meta']> = {
'k8s.deployment.name': 'k8s_deployment_name',
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.cluster.name': 'k8s_cluster_name',
};
const getGroupByEle = ( const getGroupByEle = (
deployment: K8sDeploymentsData, deployment: K8sDeploymentsData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -233,9 +239,14 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push( const rawKey = group.key as string;
deployment.meta[group.key as keyof typeof deployment.meta],
); // Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ??
rawKey) as keyof typeof deployment.meta;
const value = deployment.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -29,6 +29,9 @@ import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Options } from 'uplot'; import { Options } from 'uplot';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
interface EntityMetricsProps<T> { interface EntityMetricsProps<T> {
timeRange: { timeRange: {
startTime: number; startTime: number;
@ -49,6 +52,7 @@ interface EntityMetricsProps<T> {
node: T, node: T,
start: number, start: number,
end: number, end: number,
dotMetricsEnabled: boolean,
) => GetQueryResultsProps[]; ) => GetQueryResultsProps[];
queryKey: string; queryKey: string;
category: K8sCategory; category: K8sCategory;
@ -65,9 +69,25 @@ function EntityMetrics<T>({
queryKey, queryKey,
category, category,
}: EntityMetricsProps<T>): JSX.Element { }: EntityMetricsProps<T>): JSX.Element {
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo( const queryPayloads = useMemo(
() => getEntityQueryPayload(entity, timeRange.startTime, timeRange.endTime), () =>
[getEntityQueryPayload, entity, timeRange.startTime, timeRange.endTime], getEntityQueryPayload(
entity,
timeRange.startTime,
timeRange.endTime,
dotMetricsEnabled,
),
[
getEntityQueryPayload,
entity,
timeRange.startTime,
timeRange.endTime,
dotMetricsEnabled,
],
); );
const queries = useQueries( const queries = useQueries(

View File

@ -26,19 +26,21 @@ import { useState } from 'react';
import { useSearchParams } from 'react-router-dom-v5-compat'; import { useSearchParams } from 'react-router-dom-v5-compat';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { FeatureKeys } from '../../constants/features';
import { useAppContext } from '../../providers/App/App';
import K8sClustersList from './Clusters/K8sClustersList'; import K8sClustersList from './Clusters/K8sClustersList';
import { import {
ClustersQuickFiltersConfig, GetClustersQuickFiltersConfig,
DaemonSetsQuickFiltersConfig, GetDaemonsetsQuickFiltersConfig,
DeploymentsQuickFiltersConfig, GetDeploymentsQuickFiltersConfig,
GetJobsQuickFiltersConfig,
GetNamespaceQuickFiltersConfig,
GetNodesQuickFiltersConfig,
GetPodsQuickFiltersConfig,
GetStatefulsetsQuickFiltersConfig,
GetVolumesQuickFiltersConfig,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
JobsQuickFiltersConfig,
K8sCategories, K8sCategories,
NamespaceQuickFiltersConfig,
NodesQuickFiltersConfig,
PodsQuickFiltersConfig,
StatefulsetsQuickFiltersConfig,
VolumesQuickFiltersConfig,
} from './constants'; } from './constants';
import K8sDaemonSetsList from './DaemonSets/K8sDaemonSetsList'; import K8sDaemonSetsList from './DaemonSets/K8sDaemonSetsList';
import K8sDeploymentsList from './Deployments/K8sDeploymentsList'; import K8sDeploymentsList from './Deployments/K8sDeploymentsList';
@ -74,6 +76,11 @@ export default function InfraMonitoringK8s(): JSX.Element {
entityVersion: '', entityVersion: '',
}); });
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const handleFilterChange = (query: Query): void => { const handleFilterChange = (query: Query): void => {
// update the current query with the new filters // update the current query with the new filters
// 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
@ -109,7 +116,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={PodsQuickFiltersConfig} config={GetPodsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -129,7 +136,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={NodesQuickFiltersConfig} config={GetNodesQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -152,7 +159,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={NamespaceQuickFiltersConfig} config={GetNamespaceQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -172,7 +179,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={ClustersQuickFiltersConfig} config={GetClustersQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -192,7 +199,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={DeploymentsQuickFiltersConfig} config={GetDeploymentsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -212,7 +219,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={JobsQuickFiltersConfig} config={GetJobsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -232,7 +239,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={DaemonSetsQuickFiltersConfig} config={GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -255,7 +262,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={StatefulsetsQuickFiltersConfig} config={GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />
@ -275,7 +282,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
children: ( children: (
<QuickFilters <QuickFilters
source={QuickFiltersSource.INFRA_MONITORING} source={QuickFiltersSource.INFRA_MONITORING}
config={VolumesQuickFiltersConfig} config={GetVolumesQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange} handleFilterVisibilityChange={handleFilterVisibilityChange}
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
/> />

View File

@ -30,395 +30,415 @@ export const getJobMetricsQueryPayload = (
job: K8sJobsData, job: K8sJobsData,
start: number, start: number,
end: number, end: number,
): GetQueryResultsProps[] => [ dotMetricsEnabled: boolean,
{ ): GetQueryResultsProps[] => {
selectedTime: 'GLOBAL_TIME', const k8sPodCpuUtilizationKey = dotMetricsEnabled
graphType: PANEL_TYPES.TIME_SERIES, ? 'k8s.pod.cpu.utilization'
query: { : 'k8s_pod_cpu_utilization';
builder: { const k8sPodMemoryUsageKey = dotMetricsEnabled
queryData: [ ? 'k8s.pod.memory.usage'
: 'k8s_pod_memory_usage';
const k8sPodNetworkIoKey = dotMetricsEnabled
? 'k8s.pod.network.io'
: 'k8s_pod_network_io';
const k8sPodNetworkErrorsKey = dotMetricsEnabled
? 'k8s.pod.network.errors'
: 'k8s_pod_network_errors';
const k8sJobNameKey = dotMetricsEnabled ? 'k8s.job.name' : 'k8s_job_name';
const k8sNamespaceNameKey = dotMetricsEnabled
? 'k8s.namespace.name'
: 'k8s_namespace_name';
return [
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: k8sPodCpuUtilizationKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '6b59b690',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: 'usage',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{ {
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_utilization--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: 'k8s_pod_cpu_utilization',
type: 'Gauge',
},
aggregateOperator: 'avg',
dataSource: DataSource.METRICS,
disabled: false, disabled: false,
expression: 'A', legend: '',
filters: { name: 'A',
items: [ query: '',
{
id: '6b59b690',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_job_name',
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_namespace_name',
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: 'usage',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
}, },
], ],
queryFormulas: [], id: v4(),
}, promql: [
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{ {
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
isColumn: true,
isJSON: false,
key: 'k8s_pod_memory_usage',
type: 'Gauge',
},
aggregateOperator: 'avg',
dataSource: DataSource.METRICS,
disabled: false, disabled: false,
expression: 'A', legend: '',
filters: { name: 'A',
items: [ query: '',
{
id: '8c217f4d',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_job_name',
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_namespace_name',
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: 'usage',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
}, },
], ],
queryFormulas: [], queryType: EQueryType.QUERY_BUILDER,
}, },
clickhouse_sql: [ variables: {},
{ formatForWeb: false,
disabled: false, start,
legend: '', end,
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
}, },
variables: {}, {
formatForWeb: false, selectedTime: 'GLOBAL_TIME',
start, graphType: PANEL_TYPES.TIME_SERIES,
end, query: {
}, builder: {
{ queryData: [
selectedTime: 'GLOBAL_TIME', {
graphType: PANEL_TYPES.TIME_SERIES, aggregateAttribute: {
query: { dataType: DataTypes.Float64,
builder: { id: 'k8s_pod_memory_usage--float64--Gauge--true',
queryData: [ isColumn: true,
isJSON: false,
key: k8sPodMemoryUsageKey,
type: 'Gauge',
},
aggregateOperator: 'avg',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '8c217f4d',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: 'usage',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'avg',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{ {
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_io--float64--Sum--true',
isColumn: true,
isJSON: false,
key: 'k8s_pod_network_io',
type: 'Sum',
},
aggregateOperator: 'rate',
dataSource: DataSource.METRICS,
disabled: false, disabled: false,
expression: 'A', legend: '',
filters: { name: 'A',
items: [ query: '',
{
id: '2bbf9d0c',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_job_name',
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_namespace_name',
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'direction--string--tag--false',
isColumn: false,
isJSON: false,
key: 'direction',
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'interface--string--tag--false',
isColumn: false,
isJSON: false,
key: 'interface',
type: 'tag',
},
],
having: [],
legend: '{{direction}} :: {{interface}}',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
}, },
], ],
queryFormulas: [], id: v4(),
}, promql: [
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{ {
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_errors--float64--Sum--true',
isColumn: true,
isJSON: false,
key: 'k8s_pod_network_errors',
type: 'Sum',
},
aggregateOperator: 'increase',
dataSource: DataSource.METRICS,
disabled: false, disabled: false,
expression: 'A', legend: '',
filters: { name: 'A',
items: [ query: '',
{
id: '448e6cf7',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_job_name',
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'k8s_namespace_name',
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'direction--string--tag--false',
isColumn: false,
isJSON: false,
key: 'direction',
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'interface--string--tag--false',
isColumn: false,
isJSON: false,
key: 'interface',
type: 'tag',
},
],
having: [],
legend: '{{direction}} :: {{interface}}',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
}, },
], ],
queryFormulas: [], queryType: EQueryType.QUERY_BUILDER,
}, },
clickhouse_sql: [ variables: {},
{ formatForWeb: false,
disabled: false, start,
legend: '', end,
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
}, },
variables: {}, {
formatForWeb: false, selectedTime: 'GLOBAL_TIME',
start, graphType: PANEL_TYPES.TIME_SERIES,
end, query: {
}, builder: {
]; queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_io--float64--Sum--true',
isColumn: true,
isJSON: false,
key: k8sPodNetworkIoKey,
type: 'Sum',
},
aggregateOperator: 'rate',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '2bbf9d0c',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'direction--string--tag--false',
isColumn: false,
isJSON: false,
key: 'direction',
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'interface--string--tag--false',
isColumn: false,
isJSON: false,
key: 'interface',
type: 'tag',
},
],
having: [],
legend: '{{direction}} :: {{interface}}',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_errors--float64--Sum--true',
isColumn: true,
isJSON: false,
key: k8sPodNetworkErrorsKey,
type: 'Sum',
},
aggregateOperator: 'increase',
dataSource: DataSource.METRICS,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '448e6cf7',
key: {
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sJobNameKey,
type: 'tag',
},
op: '=',
value: job.jobName,
},
{
id: '47b3adae',
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
isColumn: false,
isJSON: false,
key: k8sNamespaceNameKey,
type: 'tag',
},
op: '=',
value: job.meta.k8s_namespace_name,
},
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'direction--string--tag--false',
isColumn: false,
isJSON: false,
key: 'direction',
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'interface--string--tag--false',
isColumn: false,
isJSON: false,
key: 'interface',
type: 'tag',
},
],
having: [],
legend: '{{direction}} :: {{interface}}',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'increase',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: v4(),
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
},
];
};

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -132,6 +134,11 @@ function K8sJobsList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sJobsRowData, selectedRowData: K8sJobsRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -185,10 +192,15 @@ function K8sJobsList({
isLoading: isLoadingGroupedByRowData, isLoading: isLoadingGroupedByRowData,
isError: isErrorGroupedByRowData, isError: isErrorGroupedByRowData,
refetch: fetchGroupedByRowData, refetch: fetchGroupedByRowData,
} = useGetK8sJobsList(fetchGroupedByRowDataQuery as K8sJobsListPayload, { } = useGetK8sJobsList(
queryKey: ['jobList', fetchGroupedByRowDataQuery], fetchGroupedByRowDataQuery as K8sJobsListPayload,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, {
}); queryKey: ['jobList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
dotMetricsEnabled,
);
const { const {
data: groupByFiltersData, data: groupByFiltersData,
@ -196,7 +208,10 @@ function K8sJobsList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.JOBS], aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sCategory.JOBS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -242,6 +257,8 @@ function K8sJobsList({
queryKey: ['jobList', query], queryKey: ['jobList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const jobsData = useMemo(() => data?.payload?.data?.records || [], [data]); const jobsData = useMemo(() => data?.payload?.data?.records || [], [data]);

View File

@ -263,6 +263,12 @@ export const getK8sJobsListColumns = (
return columnsConfig as ColumnType<K8sJobsRowData>[]; return columnsConfig as ColumnType<K8sJobsRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sJobsData['meta']> = {
'k8s.job.name': 'k8s_job_name',
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.cluster.name': 'k8s_cluster_name',
};
const getGroupByEle = ( const getGroupByEle = (
job: K8sJobsData, job: K8sJobsData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -270,7 +276,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(job.meta[group.key as keyof typeof job.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof job.meta;
const value = job.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -136,6 +138,11 @@ function K8sNamespacesList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sNamespacesRowData, selectedRowData: K8sNamespacesRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -195,6 +202,8 @@ function K8sNamespacesList({
queryKey: ['namespaceList', fetchGroupedByRowDataQuery], queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
}, },
undefined,
dotMetricsEnabled,
); );
const { const {
@ -203,8 +212,10 @@ function K8sNamespacesList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sEntityToAggregateAttributeMapping[K8sCategory.NAMESPACES], K8sCategory.NAMESPACES,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -245,6 +256,8 @@ function K8sNamespacesList({
queryKey: ['namespaceList', query], queryKey: ['namespaceList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const namespacesData = useMemo(() => data?.payload?.data?.records || [], [ const namespacesData = useMemo(() => data?.payload?.data?.records || [], [

View File

@ -122,6 +122,11 @@ export const getK8sNamespacesListColumns = (
return columnsConfig as ColumnType<K8sNamespacesRowData>[]; return columnsConfig as ColumnType<K8sNamespacesRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sNamespacesData['meta']> = {
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.cluster.name': 'k8s_cluster_name',
};
const getGroupByEle = ( const getGroupByEle = (
namespace: K8sNamespacesData, namespace: K8sNamespacesData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -129,7 +134,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(namespace.meta[group.key as keyof typeof namespace.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof namespace.meta;
const value = namespace.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -130,6 +132,11 @@ function K8sNodesList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sNodesRowData, selectedRowData: K8sNodesRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -183,10 +190,15 @@ function K8sNodesList({
isLoading: isLoadingGroupedByRowData, isLoading: isLoadingGroupedByRowData,
isError: isErrorGroupedByRowData, isError: isErrorGroupedByRowData,
refetch: fetchGroupedByRowData, refetch: fetchGroupedByRowData,
} = useGetK8sNodesList(fetchGroupedByRowDataQuery as K8sNodesListPayload, { } = useGetK8sNodesList(
queryKey: ['nodeList', fetchGroupedByRowDataQuery], fetchGroupedByRowDataQuery as K8sNodesListPayload,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, {
}); queryKey: ['nodeList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
dotMetricsEnabled,
);
const { const {
data: groupByFiltersData, data: groupByFiltersData,
@ -194,7 +206,10 @@ function K8sNodesList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES], aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sCategory.NODES,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -240,6 +255,8 @@ function K8sNodesList({
queryKey: ['nodeList', query], queryKey: ['nodeList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]); const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]);

View File

@ -152,6 +152,12 @@ export const getK8sNodesListColumns = (
return columnsConfig as ColumnType<K8sNodesRowData>[]; return columnsConfig as ColumnType<K8sNodesRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sNodesData['meta']> = {
'k8s.node.name': 'k8s_node_name',
'k8s.cluster.name': 'k8s_cluster_name',
'k8s.node.uid': 'k8s_node_uid',
};
const getGroupByEle = ( const getGroupByEle = (
node: K8sNodesData, node: K8sNodesData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -159,7 +165,14 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(node.meta[group.key as keyof typeof node.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof node.meta;
const value = node.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -118,13 +120,21 @@ function K8sPodsList({
[currentQuery?.builder?.queryData], [currentQuery?.builder?.queryData],
); );
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const { const {
data: groupByFiltersData, data: groupByFiltersData,
isLoading: isLoadingGroupByFilters, isLoading: isLoadingGroupByFilters,
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.PODS], aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sCategory.PODS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -201,6 +211,8 @@ function K8sPodsList({
queryKey: ['hostList', query], queryKey: ['hostList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
@ -255,10 +267,15 @@ function K8sPodsList({
isLoading: isLoadingGroupedByRowData, isLoading: isLoadingGroupedByRowData,
isError: isErrorGroupedByRowData, isError: isErrorGroupedByRowData,
refetch: fetchGroupedByRowData, refetch: fetchGroupedByRowData,
} = useGetK8sPodsList(fetchGroupedByRowDataQuery as K8sPodsListPayload, { } = useGetK8sPodsList(
queryKey: ['hostList', fetchGroupedByRowDataQuery], fetchGroupedByRowDataQuery as K8sPodsListPayload,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, {
}); queryKey: ['hostList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
dotMetricsEnabled,
);
const podsData = useMemo(() => data?.payload?.data?.records || [], [data]); const podsData = useMemo(() => data?.payload?.data?.records || [], [data]);
const totalCount = data?.payload?.data?.total || 0; const totalCount = data?.payload?.data?.total || 0;

File diff suppressed because it is too large Load Diff

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -137,6 +139,11 @@ function K8sStatefulSetsList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sStatefulSetsRowData, selectedRowData: K8sStatefulSetsRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -196,6 +203,8 @@ function K8sStatefulSetsList({
queryKey: ['statefulSetList', fetchGroupedByRowDataQuery], queryKey: ['statefulSetList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
}, },
undefined,
dotMetricsEnabled,
); );
const { const {
@ -204,8 +213,10 @@ function K8sStatefulSetsList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sEntityToAggregateAttributeMapping[K8sCategory.STATEFULSETS], K8sCategory.STATEFULSETS,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -251,6 +262,8 @@ function K8sStatefulSetsList({
queryKey: ['statefulSetList', query], queryKey: ['statefulSetList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const statefulSetsData = useMemo(() => data?.payload?.data?.records || [], [ const statefulSetsData = useMemo(() => data?.payload?.data?.records || [], [

View File

@ -236,6 +236,11 @@ export const getK8sStatefulSetsListColumns = (
return columnsConfig as ColumnType<K8sStatefulSetsRowData>[]; return columnsConfig as ColumnType<K8sStatefulSetsRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sStatefulSetsData['meta']> = {
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.statefulset.name': 'k8s_statefulset_name',
};
const getGroupByEle = ( const getGroupByEle = (
statefulSet: K8sStatefulSetsData, statefulSet: K8sStatefulSetsData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -243,9 +248,14 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push( const rawKey = group.key as string;
statefulSet.meta[group.key as keyof typeof statefulSet.meta],
); // Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ??
rawKey) as keyof typeof statefulSet.meta;
const value = statefulSet.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { getOrderByFromParams } from '../commonUtils'; import { getOrderByFromParams } from '../commonUtils';
import { import {
GetK8sEntityToAggregateAttribute,
INFRA_MONITORING_K8S_PARAMS_KEYS, INFRA_MONITORING_K8S_PARAMS_KEYS,
K8sCategory, K8sCategory,
K8sEntityToAggregateAttributeMapping,
} from '../constants'; } from '../constants';
import K8sHeader from '../K8sHeader'; import K8sHeader from '../K8sHeader';
import LoadingContainer from '../LoadingContainer'; import LoadingContainer from '../LoadingContainer';
@ -137,6 +139,11 @@ function K8sVolumesList({
} }
}, [quickFiltersLastUpdated]); }, [quickFiltersLastUpdated]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const createFiltersForSelectedRowData = ( const createFiltersForSelectedRowData = (
selectedRowData: K8sVolumesRowData, selectedRowData: K8sVolumesRowData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -190,10 +197,15 @@ function K8sVolumesList({
isLoading: isLoadingGroupedByRowData, isLoading: isLoadingGroupedByRowData,
isError: isErrorGroupedByRowData, isError: isErrorGroupedByRowData,
refetch: fetchGroupedByRowData, refetch: fetchGroupedByRowData,
} = useGetK8sVolumesList(fetchGroupedByRowDataQuery as K8sVolumesListPayload, { } = useGetK8sVolumesList(
queryKey: ['volumeList', fetchGroupedByRowDataQuery], fetchGroupedByRowDataQuery as K8sVolumesListPayload,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData, {
}); queryKey: ['volumeList', fetchGroupedByRowDataQuery],
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
dotMetricsEnabled,
);
const { const {
data: groupByFiltersData, data: groupByFiltersData,
@ -201,7 +213,10 @@ function K8sVolumesList({
} = useGetAggregateKeys( } = useGetAggregateKeys(
{ {
dataSource: currentQuery.builder.queryData[0].dataSource, dataSource: currentQuery.builder.queryData[0].dataSource,
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES], aggregateAttribute: GetK8sEntityToAggregateAttribute(
K8sCategory.NODES,
dotMetricsEnabled,
),
aggregateOperator: 'noop', aggregateOperator: 'noop',
searchText: '', searchText: '',
tagType: '', tagType: '',
@ -247,6 +262,8 @@ function K8sVolumesList({
queryKey: ['volumeList', query], queryKey: ['volumeList', query],
enabled: !!query, enabled: !!query,
}, },
undefined,
dotMetricsEnabled,
); );
const volumesData = useMemo(() => data?.payload?.data?.records || [], [data]); const volumesData = useMemo(() => data?.payload?.data?.records || [], [data]);

View File

@ -142,6 +142,16 @@ export const getK8sVolumesListColumns = (
return columnsConfig as ColumnType<K8sVolumesRowData>[]; return columnsConfig as ColumnType<K8sVolumesRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sVolumesData['meta']> = {
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.node.name': 'k8s_node_name',
'k8s.pod.name': 'k8s_pod_name',
'k8s.pod.uid': 'k8s_pod_uid',
'k8s.statefulset.name': 'k8s_statefulset_name',
'k8s.cluster.name': 'k8s_cluster_name',
'k8s.persistentvolumeclaim.name': 'k8s_persistentvolumeclaim_name',
};
const getGroupByEle = ( const getGroupByEle = (
volume: K8sVolumesData, volume: K8sVolumesData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -149,7 +159,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(volume.meta[group.key as keyof typeof volume.meta]); const rawKey = group.key as string;
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof volume.meta;
const value = volume.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

File diff suppressed because it is too large Load Diff

View File

@ -299,6 +299,19 @@ export const getK8sPodsListColumns = (
return updatedColumnsConfig as ColumnType<K8sPodsRowData>[]; return updatedColumnsConfig as ColumnType<K8sPodsRowData>[];
}; };
const dotToUnder: Record<string, keyof K8sPodsData['meta']> = {
'k8s.cronjob.name': 'k8s_cronjob_name',
'k8s.daemonset.name': 'k8s_daemonset_name',
'k8s.deployment.name': 'k8s_deployment_name',
'k8s.job.name': 'k8s_job_name',
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.node.name': 'k8s_node_name',
'k8s.pod.name': 'k8s_pod_name',
'k8s.pod.uid': 'k8s_pod_uid',
'k8s.statefulset.name': 'k8s_statefulset_name',
'k8s.cluster.name': 'k8s_cluster_name',
};
const getGroupByEle = ( const getGroupByEle = (
pod: K8sPodsData, pod: K8sPodsData,
groupBy: IBuilderQuery['groupBy'], groupBy: IBuilderQuery['groupBy'],
@ -306,7 +319,13 @@ const getGroupByEle = (
const groupByValues: string[] = []; const groupByValues: string[] = [];
groupBy.forEach((group) => { groupBy.forEach((group) => {
groupByValues.push(pod.meta[group.key as keyof typeof pod.meta]); const rawKey = group.key as string;
// Choose mapped key if present, otherwise use rawKey
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof pod.meta;
const value = pod.meta[metaKey];
groupByValues.push(value);
}); });
return ( return (

View File

@ -22,7 +22,7 @@ import {
} from 'antd'; } from 'antd';
import { TableProps } from 'antd/lib'; import { TableProps } from 'antd/lib';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import createDashboard from 'api/dashboard/create'; import createDashboard from 'api/v1/dashboards/create';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import cx from 'classnames'; import cx from 'classnames';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
@ -63,6 +63,7 @@ import {
import { handleContactSupport } from 'pages/Integrations/utils'; import { handleContactSupport } from 'pages/Integrations/utils';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { import {
ChangeEvent, ChangeEvent,
@ -83,6 +84,7 @@ import {
WidgetRow, WidgetRow,
Widgets, Widgets,
} from 'types/api/dashboard/getAll'; } from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal'; import DashboardTemplatesModal from './DashboardTemplates/DashboardTemplatesModal';
import ImportJSON from './ImportJSON'; import ImportJSON from './ImportJSON';
@ -226,7 +228,7 @@ function DashboardsList(): JSX.Element {
useEffect(() => { useEffect(() => {
const filteredDashboards = filterDashboard( const filteredDashboards = filterDashboard(
searchString, searchString,
dashboardListResponse || [], dashboardListResponse?.data || [],
); );
if (sortOrder.columnKey === 'updatedAt') { if (sortOrder.columnKey === 'updatedAt') {
sortDashboardsByUpdatedAt(filteredDashboards || []); sortDashboardsByUpdatedAt(filteredDashboards || []);
@ -256,17 +258,19 @@ function DashboardsList(): JSX.Element {
errorMessage: '', errorMessage: '',
}); });
const { showErrorModal } = useErrorModal();
const data: Data[] = const data: Data[] =
dashboards?.map((e) => ({ dashboards?.map((e) => ({
createdAt: e.createdAt, createdAt: e.createdAt,
description: e.data.description || '', description: e.data.description || '',
id: e.uuid, id: e.id,
lastUpdatedTime: e.updatedAt, lastUpdatedTime: e.updatedAt,
name: e.data.title, name: e.data.title,
tags: e.data.tags || [], tags: e.data.tags || [],
key: e.uuid, key: e.id,
createdBy: e.createdBy, createdBy: e.createdBy,
isLocked: !!e.isLocked || false, isLocked: !!e.locked || false,
lastUpdatedBy: e.updatedBy, lastUpdatedBy: e.updatedBy,
image: e.data.image || Base64Icons[0], image: e.data.image || Base64Icons[0],
variables: e.data.variables, variables: e.data.variables,
@ -292,28 +296,20 @@ function DashboardsList(): JSX.Element {
version: ENTITY_VERSION_V4, version: ENTITY_VERSION_V4,
}); });
if (response.statusCode === 200) { safeNavigate(
safeNavigate( generatePath(ROUTES.DASHBOARD, {
generatePath(ROUTES.DASHBOARD, { dashboardId: response.data.id,
dashboardId: response.payload.uuid, }),
}), );
);
} else {
setNewDashboardState({
...newDashboardState,
loading: false,
error: true,
errorMessage: response.error || 'Something went wrong',
});
}
} catch (error) { } catch (error) {
showErrorModal(error as APIError);
setNewDashboardState({ setNewDashboardState({
...newDashboardState, ...newDashboardState,
error: true, error: true,
errorMessage: (error as AxiosError).toString() || 'Something went Wrong', errorMessage: (error as AxiosError).toString() || 'Something went Wrong',
}); });
} }
}, [newDashboardState, safeNavigate, t]); }, [newDashboardState, safeNavigate, showErrorModal, t]);
const onModalHandler = (uploadedGrafana: boolean): void => { const onModalHandler = (uploadedGrafana: boolean): void => {
logEvent('Dashboard List: Import JSON clicked', {}); logEvent('Dashboard List: Import JSON clicked', {});
@ -327,7 +323,7 @@ function DashboardsList(): JSX.Element {
const searchText = (event as React.BaseSyntheticEvent)?.target?.value || ''; const searchText = (event as React.BaseSyntheticEvent)?.target?.value || '';
const filteredDashboards = filterDashboard( const filteredDashboards = filterDashboard(
searchText, searchText,
dashboardListResponse || [], dashboardListResponse?.data || [],
); );
setDashboards(filteredDashboards); setDashboards(filteredDashboards);
setIsFilteringDashboards(false); setIsFilteringDashboards(false);
@ -677,7 +673,7 @@ function DashboardsList(): JSX.Element {
!isUndefined(dashboardListResponse) !isUndefined(dashboardListResponse)
) { ) {
logEvent('Dashboard List: Page visited', { logEvent('Dashboard List: Page visited', {
number: dashboardListResponse?.length, number: dashboardListResponse?.data?.length,
}); });
logEventCalledRef.current = true; logEventCalledRef.current = true;
} }

View File

@ -14,19 +14,21 @@ import {
UploadProps, UploadProps,
} from 'antd'; } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import createDashboard from 'api/dashboard/create'; import createDashboard from 'api/v1/dashboards/create';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate'; import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout'; import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react'; import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
import { useErrorModal } from 'providers/ErrorModalProvider';
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/ // #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
// See more: https://github.com/lucide-icons/lucide/issues/94 // See more: https://github.com/lucide-icons/lucide/issues/94
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom'; import { generatePath } from 'react-router-dom';
import { DashboardData } from 'types/api/dashboard/getAll'; import { DashboardData } from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
function ImportJSON({ function ImportJSON({
isImportJSONModalVisible, isImportJSONModalVisible,
@ -74,6 +76,8 @@ function ImportJSON({
} }
}; };
const { showErrorModal } = useErrorModal();
const onClickLoadJsonHandler = async (): Promise<void> => { const onClickLoadJsonHandler = async (): Promise<void> => {
try { try {
setDashboardCreating(true); setDashboardCreating(true);
@ -81,11 +85,6 @@ function ImportJSON({
const dashboardData = JSON.parse(editorValue) as DashboardData; const dashboardData = JSON.parse(editorValue) as DashboardData;
// Remove uuid from the dashboard data, in all cases - empty, duplicate or any valid not duplicate uuid
if (dashboardData.uuid !== undefined) {
delete dashboardData.uuid;
}
if (dashboardData?.layout) { if (dashboardData?.layout) {
dashboardData.layout = getUpdatedLayout(dashboardData.layout); dashboardData.layout = getUpdatedLayout(dashboardData.layout);
} else { } else {
@ -97,28 +96,19 @@ function ImportJSON({
uploadedGrafana, uploadedGrafana,
}); });
if (response.statusCode === 200) { safeNavigate(
safeNavigate( generatePath(ROUTES.DASHBOARD, {
generatePath(ROUTES.DASHBOARD, { dashboardId: response.data.id,
dashboardId: response.payload.uuid, }),
}), );
); logEvent('Dashboard List: New dashboard imported successfully', {
logEvent('Dashboard List: New dashboard imported successfully', { dashboardId: response.data?.id,
dashboardId: response.payload?.uuid, dashboardName: response.data?.data?.title,
dashboardName: response.payload?.data?.title, });
});
} else {
setIsCreateDashboardError(true);
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setDashboardCreating(false); setDashboardCreating(false);
} catch (error) { } catch (error) {
showErrorModal(error as APIError);
setDashboardCreating(false); setDashboardCreating(false);
setIsCreateDashboardError(true); setIsCreateDashboardError(true);
notifications.error({ notifications.error({

View File

@ -6,8 +6,7 @@ import { executeSearchQueries } from '../utils';
describe('executeSearchQueries', () => { describe('executeSearchQueries', () => {
const firstDashboard: Dashboard = { const firstDashboard: Dashboard = {
id: 11111, id: uuid(),
uuid: uuid(),
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
createdBy: '', createdBy: '',
@ -18,8 +17,7 @@ describe('executeSearchQueries', () => {
}, },
}; };
const secondDashboard: Dashboard = { const secondDashboard: Dashboard = {
id: 22222, id: uuid(),
uuid: uuid(),
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
createdBy: '', createdBy: '',
@ -30,8 +28,7 @@ describe('executeSearchQueries', () => {
}, },
}; };
const thirdDashboard: Dashboard = { const thirdDashboard: Dashboard = {
id: 333333, id: uuid(),
uuid: uuid(),
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
createdBy: '', createdBy: '',

View File

@ -59,7 +59,7 @@ export function DeleteButton({
onClick: (e) => { onClick: (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
deleteDashboardMutation.mutateAsync(undefined, { deleteDashboardMutation.mutate(undefined, {
onSuccess: () => { onSuccess: () => {
notifications.success({ notifications.success({
message: t('dashboard:delete_dashboard_success', { message: t('dashboard:delete_dashboard_success', {

View File

@ -14,7 +14,7 @@ export const generateSearchData = (
dashboards.forEach((dashboard) => { dashboards.forEach((dashboard) => {
dashboardSearchData.push({ dashboardSearchData.push({
id: dashboard.uuid, id: dashboard.id,
title: dashboard.data.title, title: dashboard.data.title,
description: dashboard.data.description, description: dashboard.data.description,
tags: dashboard.data.tags || [], tags: dashboard.data.tags || [],

View File

@ -15,6 +15,8 @@ import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot'; import uPlot from 'uplot';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { import {
getHostQueryPayload, getHostQueryPayload,
getNodeQueryPayload, getNodeQueryPayload,
@ -49,12 +51,23 @@ function NodeMetrics({
}; };
}, [logLineTimestamp]); }, [logLineTimestamp]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo(() => { const queryPayloads = useMemo(() => {
if (nodeName) { if (nodeName) {
return getNodeQueryPayload(clusterName, nodeName, start, end); return getNodeQueryPayload(
clusterName,
nodeName,
start,
end,
dotMetricsEnabled,
);
} }
return getHostQueryPayload(hostName, start, end); return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
}, [nodeName, hostName, clusterName, start, end]); }, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo; const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
const queries = useQueries( const queries = useQueries(

View File

@ -8,6 +8,7 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useAppContext } from 'providers/App/App';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useQueries, UseQueryResult } from 'react-query'; import { useQueries, UseQueryResult } from 'react-query';
@ -15,6 +16,7 @@ import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot'; import uPlot from 'uplot';
import { FeatureKeys } from '../../../constants/features';
import { getPodQueryPayload, podWidgetInfo } from './constants'; import { getPodQueryPayload, podWidgetInfo } from './constants';
function PodMetrics({ function PodMetrics({
@ -41,9 +43,15 @@ function PodMetrics({
verticalLineTimestamp: logTimestamp.unix(), verticalLineTimestamp: logTimestamp.unix(),
}; };
}, [logLineTimestamp]); }, [logLineTimestamp]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const queryPayloads = useMemo( const queryPayloads = useMemo(
() => getPodQueryPayload(clusterName, podName, start, end), () => getPodQueryPayload(clusterName, podName, start, end, dotMetricsEnabled),
[clusterName, end, podName, start], [clusterName, end, podName, start, dotMetricsEnabled],
); );
const queries = useQueries( const queries = useQueries(
queryPayloads.map((payload) => ({ queryPayloads.map((payload) => ({

View File

@ -21,8 +21,10 @@ import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
import { DataType } from '../TableView'; import { DataType } from '../TableView';
import { import {
escapeHtml,
filterKeyForField, filterKeyForField,
jsonToDataNodes, jsonToDataNodes,
parseFieldValue,
recursiveParseJSON, recursiveParseJSON,
removeEscapeCharacters, removeEscapeCharacters,
unescapeString, unescapeString,
@ -85,7 +87,7 @@ export function TableViewActions(
record.field === 'body' record.field === 'body'
? { ? {
__html: convert.toHtml( __html: convert.toHtml(
dompurify.sanitize(unescapeString(record.value), { dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
}), }),
), ),
@ -155,7 +157,11 @@ export function TableViewActions(
<ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} /> <ArrowDownToDot size={14} style={{ transform: 'rotate(90deg)' }} />
) )
} }
onClick={onClickHandler(OPERATORS['='], fieldFilterKey, fieldData.value)} onClick={onClickHandler(
OPERATORS['='],
fieldFilterKey,
parseFieldValue(fieldData.value),
)}
/> />
</Tooltip> </Tooltip>
<Tooltip title="Filter out value"> <Tooltip title="Filter out value">
@ -171,7 +177,7 @@ export function TableViewActions(
onClick={onClickHandler( onClick={onClickHandler(
OPERATORS['!='], OPERATORS['!='],
fieldFilterKey, fieldFilterKey,
fieldData.value, parseFieldValue(fieldData.value),
)} )}
/> />
</Tooltip> </Tooltip>

View File

@ -259,6 +259,24 @@ export const getDataTypes = (value: unknown): DataTypes => {
return determineType(value); return determineType(value);
}; };
// prevent html rendering in the value
export const escapeHtml = (unsafe: string): string =>
unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
// parse field value to remove escaping characters
export const parseFieldValue = (value: string): string => {
try {
return JSON.parse(value);
} catch (error) {
return value;
}
};
// now we do not want to render colors everywhere like in tooltip and monaco editor hence we remove such codes to make // now we do not want to render colors everywhere like in tooltip and monaco editor hence we remove such codes to make
// the log line readable // the log line readable
export const removeEscapeCharacters = (str: string): string => export const removeEscapeCharacters = (str: string): string =>

View File

@ -28,16 +28,12 @@ import LogsExplorerTable from 'container/LogsExplorerTable';
import { useOptionsMenu } from 'container/OptionsMenu'; import { useOptionsMenu } from 'container/OptionsMenu';
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange'; import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useAxiosError from 'hooks/useAxiosError';
import useClickOutside from 'hooks/useClickOutside'; import useClickOutside from 'hooks/useClickOutside';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange'; import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
import { useSafeNavigate } from 'hooks/useSafeNavigate'; import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQueryData from 'hooks/useUrlQueryData'; import useUrlQueryData from 'hooks/useUrlQueryData';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
@ -98,7 +94,6 @@ function LogsExplorerViews({
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
chartQueryKeyRef: MutableRefObject<any>; chartQueryKeyRef: MutableRefObject<any>;
}): JSX.Element { }): JSX.Element {
const { notifications } = useNotifications();
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
// this is to respect the panel type present in the URL rather than defaulting it to list always. // this is to respect the panel type present in the URL rather than defaulting it to list always.
@ -141,8 +136,6 @@ function LogsExplorerViews({
const [queryId, setQueryId] = useState<string>(v4()); const [queryId, setQueryId] = useState<string>(v4());
const [queryStats, setQueryStats] = useState<WsDataEvent>(); const [queryStats, setQueryStats] = useState<WsDataEvent>();
const handleAxisError = useAxiosError();
const listQuery = useMemo(() => { const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null; if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
@ -396,11 +389,6 @@ function LogsExplorerViews({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.payload]); }, [data?.payload]);
const {
mutate: updateDashboard,
isLoading: isUpdateDashboardLoading,
} = useUpdateDashboard();
const getUpdatedQueryForExport = useCallback((): Query => { const getUpdatedQueryForExport = useCallback((): Query => {
const updatedQuery = cloneDeep(currentQuery); const updatedQuery = cloneDeep(currentQuery);
@ -424,68 +412,22 @@ function LogsExplorerViews({
? getUpdatedQueryForExport() ? getUpdatedQueryForExport()
: exportDefaultQuery; : exportDefaultQuery;
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
dashboard,
query,
widgetId,
panelTypeParam,
options.selectColumns,
);
logEvent('Logs Explorer: Add to dashboard successful', { logEvent('Logs Explorer: Add to dashboard successful', {
panelType, panelType,
isNewDashboard, isNewDashboard,
dashboardName: dashboard?.data?.title, dashboardName: dashboard?.data?.title,
}); });
updateDashboard(updatedDashboard, { const dashboardEditView = generateExportToDashboardLink({
onSuccess: (data) => { query,
if (data.error) { panelType: panelTypeParam,
const message = dashboardId: dashboard.id,
data.error === 'feature usage exceeded' ? ( widgetId,
<span>
Panel limit exceeded for {DataSource.LOGS} in community edition. Please
checkout our paid plans{' '}
<a
href="https://signoz.io/pricing/?utm_source=product&utm_medium=dashboard-limit"
rel="noreferrer noopener"
target="_blank"
>
here
</a>
</span>
) : (
data.error
);
notifications.error({
message,
});
return;
}
const dashboardEditView = generateExportToDashboardLink({
query,
panelType: panelTypeParam,
dashboardId: data.payload?.uuid || '',
widgetId,
});
safeNavigate(dashboardEditView);
},
onError: handleAxisError,
}); });
safeNavigate(dashboardEditView);
}, },
[ [getUpdatedQueryForExport, exportDefaultQuery, safeNavigate, panelType],
getUpdatedQueryForExport,
exportDefaultQuery,
options.selectColumns,
safeNavigate,
notifications,
panelType,
updateDashboard,
handleAxisError,
],
); );
useEffect(() => { useEffect(() => {
@ -811,7 +753,6 @@ function LogsExplorerViews({
<ExplorerOptionWrapper <ExplorerOptionWrapper
disabled={!stagedQuery} disabled={!stagedQuery}
query={exportDefaultQuery} query={exportDefaultQuery}
isLoading={isUpdateDashboardLoading}
onExport={handleExport} onExport={handleExport}
sourcepage={DataSource.LOGS} sourcepage={DataSource.LOGS}
/> />

View File

@ -21,6 +21,7 @@ export const databaseCallsRPS = ({
servicename, servicename,
legend, legend,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: DatabaseCallsRPSProps): QueryBuilderData => { }: DatabaseCallsRPSProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [ const autocompleteData: BaseAutocompleteData[] = [
{ {
@ -34,7 +35,7 @@ export const databaseCallsRPS = ({
{ {
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
key: 'db_system', key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
type: 'tag', type: 'tag',
}, },
]; ];
@ -43,7 +44,9 @@ export const databaseCallsRPS = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,
@ -75,6 +78,7 @@ export const databaseCallsRPS = ({
export const databaseCallsAvgDuration = ({ export const databaseCallsAvgDuration = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: DatabaseCallProps): QueryBuilderData => { }: DatabaseCallProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozDbLatencySum, key: WidgetKeys.SignozDbLatencySum,
@ -93,7 +97,9 @@ export const databaseCallsAvgDuration = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,

View File

@ -33,6 +33,7 @@ export const externalCallErrorPercent = ({
servicename, servicename,
legend, legend,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozExternalCallLatencyCount, key: WidgetKeys.SignozExternalCallLatencyCount,
@ -51,7 +52,9 @@ export const externalCallErrorPercent = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,
@ -62,7 +65,7 @@ export const externalCallErrorPercent = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.StatusCode, key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
dataType: DataTypes.Int64, dataType: DataTypes.Int64,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -76,7 +79,9 @@ export const externalCallErrorPercent = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,
@ -121,6 +126,7 @@ export const externalCallErrorPercent = ({
export const externalCallDuration = ({ export const externalCallDuration = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: ExternalCallProps): QueryBuilderData => { }: ExternalCallProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
@ -144,7 +150,9 @@ export const externalCallDuration = ({
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
type: MetricsType.Resource, type: MetricsType.Resource,
}, },
op: OPERATORS.IN, op: OPERATORS.IN,
@ -184,6 +192,7 @@ export const externalCallRpsByAddress = ({
servicename, servicename,
legend, legend,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [ const autocompleteData: BaseAutocompleteData[] = [
{ {
@ -200,7 +209,9 @@ export const externalCallRpsByAddress = ({
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
type: MetricsType.Resource, type: MetricsType.Resource,
}, },
op: OPERATORS.IN, op: OPERATORS.IN,
@ -231,6 +242,7 @@ export const externalCallDurationByAddress = ({
servicename, servicename,
legend, legend,
tagFilterItems, tagFilterItems,
dotMetricsEnabled,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
@ -253,7 +265,9 @@ export const externalCallDurationByAddress = ({
key: { key: {
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
type: MetricsType.Resource, type: MetricsType.Resource,
}, },
op: OPERATORS.IN, op: OPERATORS.IN,

View File

@ -37,10 +37,18 @@ export const latency = ({
tagFilterItems, tagFilterItems,
isSpanMetricEnable = false, isSpanMetricEnable = false,
topLevelOperationsRoute, topLevelOperationsRoute,
dotMetricsEnabled,
}: LatencyProps): QueryBuilderData => { }: LatencyProps): QueryBuilderData => {
const signozLatencyBucketMetrics = dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm;
const signozMetricsServiceName = dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm;
const newAutoCompleteData: BaseAutocompleteData = { const newAutoCompleteData: BaseAutocompleteData = {
key: isSpanMetricEnable key: isSpanMetricEnable
? WidgetKeys.Signoz_latency_bucket ? signozLatencyBucketMetrics
: WidgetKeys.DurationNano, : WidgetKeys.DurationNano,
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
isColumn: true, isColumn: true,
@ -53,7 +61,7 @@ export const latency = ({
{ {
id: '', id: '',
key: { key: {
key: isSpanMetricEnable ? WidgetKeys.Service_name : WidgetKeys.ServiceName, key: isSpanMetricEnable ? signozMetricsServiceName : WidgetKeys.ServiceName,
dataType: DataTypes.String, dataType: DataTypes.String,
type: isSpanMetricEnable ? MetricsType.Resource : MetricsType.Tag, type: isSpanMetricEnable ? MetricsType.Resource : MetricsType.Tag,
isColumn: !isSpanMetricEnable, isColumn: !isSpanMetricEnable,
@ -295,23 +303,30 @@ export const apDexMetricsQueryBuilderQueries = ({
threashold, threashold,
delta, delta,
metricsBuckets, metricsBuckets,
dotMetricsEnabled,
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => { }: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
const autoCompleteDataA: BaseAutocompleteData = { const autoCompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozLatencyCount, key: dotMetricsEnabled
? WidgetKeys.SignozLatencyCount
: WidgetKeys.SignozLatencyCountNorm,
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
isColumn: true, isColumn: true,
type: '', type: '',
}; };
const autoCompleteDataB: BaseAutocompleteData = { const autoCompleteDataB: BaseAutocompleteData = {
key: WidgetKeys.Signoz_latency_bucket, key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
isColumn: true, isColumn: true,
type: '', type: '',
}; };
const autoCompleteDataC: BaseAutocompleteData = { const autoCompleteDataC: BaseAutocompleteData = {
key: WidgetKeys.Signoz_latency_bucket, key: dotMetricsEnabled
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.Signoz_latency_bucket_norm,
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
isColumn: true, isColumn: true,
type: '', type: '',
@ -321,7 +336,9 @@ export const apDexMetricsQueryBuilderQueries = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -347,7 +364,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.StatusCode, key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -369,7 +386,9 @@ export const apDexMetricsQueryBuilderQueries = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -406,7 +425,7 @@ export const apDexMetricsQueryBuilderQueries = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.StatusCode, key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -417,7 +436,9 @@ export const apDexMetricsQueryBuilderQueries = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -482,10 +503,13 @@ export const operationPerSec = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
topLevelOperations, topLevelOperations,
dotMetricsEnabled,
}: OperationPerSecProps): QueryBuilderData => { }: OperationPerSecProps): QueryBuilderData => {
const autocompleteData: BaseAutocompleteData[] = [ const autocompleteData: BaseAutocompleteData[] = [
{ {
key: WidgetKeys.SignozLatencyCount, key: dotMetricsEnabled
? WidgetKeys.SignozLatencyCount
: WidgetKeys.SignozLatencyCountNorm,
dataType: DataTypes.Float64, dataType: DataTypes.Float64,
isColumn: true, isColumn: true,
type: '', type: '',
@ -497,7 +521,9 @@ export const operationPerSec = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,
@ -540,6 +566,7 @@ export const errorPercentage = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
topLevelOperations, topLevelOperations,
dotMetricsEnabled,
}: OperationPerSecProps): QueryBuilderData => { }: OperationPerSecProps): QueryBuilderData => {
const autocompleteDataA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
key: WidgetKeys.SignozCallsTotal, key: WidgetKeys.SignozCallsTotal,
@ -560,7 +587,9 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,
@ -582,7 +611,9 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.StatusCode, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.StatusCodeNorm,
dataType: DataTypes.Int64, dataType: DataTypes.Int64,
isColumn: false, isColumn: false,
type: MetricsType.Tag, type: MetricsType.Tag,
@ -597,7 +628,9 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: { key: {
key: WidgetKeys.Service_name, key: dotMetricsEnabled
? WidgetKeys.Service_name
: WidgetKeys.Service_name_norm,
dataType: DataTypes.String, dataType: DataTypes.String,
isColumn: false, isColumn: false,
type: MetricsType.Resource, type: MetricsType.Resource,

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