Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9d542a294 | ||
|
|
e75e5bdbdb | ||
|
|
0d03203977 | ||
|
|
28f6f42ac4 | ||
|
|
92f8e4d5b9 | ||
|
|
037eea5262 | ||
|
|
cd4df6280f | ||
|
|
ad2d4ed56c | ||
|
|
7955497a8d | ||
|
|
6ed30318bd | ||
|
|
c32dd9f17e | ||
|
|
c58cf67eb0 | ||
|
|
440c3d8386 | ||
|
|
d683b94344 | ||
|
|
6a629623bc | ||
|
|
982688ccc9 | ||
|
|
74bbb26033 | ||
|
|
3bb9e05681 | ||
|
|
61b2f8cb31 | ||
|
|
9d397d0867 | ||
|
|
5fb4206a99 | ||
|
|
dd11ba9f48 | ||
|
|
f9cb9f10be | ||
|
|
b6180f6957 | ||
|
|
51d3ca16f7 | ||
|
|
91cbd17275 | ||
|
|
68effaf232 | ||
|
|
c08d1bccaf | ||
|
|
1d77780c70 | ||
|
|
80ded899c7 | ||
|
|
4733af974e | ||
|
|
1ab6c7177f |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -11,5 +11,5 @@
|
||||
/pkg/errors/ @grandwizard28
|
||||
/pkg/factory/ @grandwizard28
|
||||
/pkg/types/ @grandwizard28
|
||||
/pkg/sqlmigration/ @vikrantgupta25
|
||||
.golangci.yml @grandwizard28
|
||||
**/(zeus|licensing|sqlmigration)/ @vikrantgupta25
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -66,6 +66,7 @@ e2e/.auth
|
||||
# go
|
||||
vendor/
|
||||
**/main/**
|
||||
__debug_bin**
|
||||
|
||||
# git-town
|
||||
.git-branches.toml
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p align="center">All your logs, metrics, and traces in one place. Monitor your application, spot issues before they occur and troubleshoot downtime quickly with rich context. SigNoz is a cost-effective open-source alternative to Datadog and New Relic. Visit <a href="https://signoz.io" target="_blank">signoz.io</a> for the full documentation, tutorials, and guide.</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Docker Downloads"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/signoz.svg?label=Docker%20Downloads"> </a>
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
|
||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||
|
||||
@@ -207,3 +207,11 @@ emailing:
|
||||
key_file_path:
|
||||
# The path to the certificate file.
|
||||
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
|
||||
|
||||
@@ -174,7 +174,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.85.2
|
||||
image: signoz/signoz:v0.86.2
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.85.2
|
||||
image: signoz/signoz:v0.86.2
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -177,7 +177,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.85.2}
|
||||
image: signoz/signoz:${VERSION:-v0.86.2}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.85.2}
|
||||
image: signoz/signoz:${VERSION:-v0.86.2}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
@@ -18,23 +18,31 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
store licensetypes.Store
|
||||
zeus zeus.Zeus
|
||||
config licensing.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
stopChan chan struct{}
|
||||
store licensetypes.Store
|
||||
zeus zeus.Zeus
|
||||
config licensing.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
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 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")
|
||||
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 {
|
||||
@@ -66,20 +74,13 @@ func (provider *provider) Stop(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 {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, organizationID := range organizations {
|
||||
err := provider.Refresh(ctx, organizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(organizations) == 0 {
|
||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||
for _, organization := range organizations {
|
||||
err := provider.Refresh(ctx, organization.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -105,11 +106,6 @@ func (provider *provider) Activate(ctx context.Context, organizationID valuer.UU
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.InitFeatures(ctx, license.Features)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -129,28 +125,24 @@ func (provider *provider) GetActive(ctx context.Context, organizationID valuer.U
|
||||
|
||||
func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUID) error {
|
||||
activeLicense, err := provider.GetActive(ctx, organizationID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil
|
||||
}
|
||||
provider.settings.Logger().ErrorContext(ctx, "license validation failed", "org_id", organizationID.StringValue())
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil && errors.Ast(err, errors.TypeNotFound) {
|
||||
provider.settings.Logger().DebugContext(ctx, "no active license found, defaulting to basic plan", "org_id", organizationID.StringValue())
|
||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
||||
if err != nil {
|
||||
if time.Since(activeLicense.LastValidatedAt) > time.Duration(provider.config.FailureThreshold)*provider.config.PollInterval {
|
||||
provider.settings.Logger().ErrorContext(ctx, "license validation failed for consecutive poll intervals, defaulting to basic plan", "failure_threshold", provider.config.FailureThreshold, "license_id", activeLicense.ID.StringValue(), "org_id", organizationID.StringValue())
|
||||
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||
activeLicense.UpdateFeatures(licensetypes.BasicPlan)
|
||||
updatedStorableLicense := licensetypes.NewStorableLicenseFromLicense(activeLicense)
|
||||
err = provider.store.Update(ctx, organizationID, updatedStorableLicense)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
@@ -208,73 +200,14 @@ func (provider *provider) Portal(ctx context.Context, organizationID valuer.UUID
|
||||
return &licensetypes.GettableSubscription{RedirectURL: gjson.GetBytes(response, "url").String()}, nil
|
||||
}
|
||||
|
||||
// feature surrogate
|
||||
func (provider *provider) CheckFeature(ctx context.Context, key string) error {
|
||||
feature, err := provider.store.GetFeature(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if feature.Active {
|
||||
return nil
|
||||
}
|
||||
return errors.Newf(errors.TypeUnsupported, licensing.ErrCodeFeatureUnavailable, "feature unavailable: %s", key)
|
||||
}
|
||||
|
||||
func (provider *provider) GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error) {
|
||||
featureStatus, err := provider.store.GetFeature(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &featuretypes.GettableFeature{
|
||||
Name: featureStatus.Name,
|
||||
Active: featureStatus.Active,
|
||||
Usage: int64(featureStatus.Usage),
|
||||
UsageLimit: int64(featureStatus.UsageLimit),
|
||||
Route: featureStatus.Route,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
|
||||
storableFeatures, err := provider.store.GetAllFeatures(ctx)
|
||||
func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error) {
|
||||
license, err := provider.GetActive(ctx, organizationID)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return licensetypes.BasicPlan, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gettableFeatures := make([]*featuretypes.GettableFeature, len(storableFeatures))
|
||||
for idx, gettableFeature := range storableFeatures {
|
||||
gettableFeatures[idx] = &featuretypes.GettableFeature{
|
||||
Name: gettableFeature.Name,
|
||||
Active: gettableFeature.Active,
|
||||
Usage: int64(gettableFeature.Usage),
|
||||
UsageLimit: int64(gettableFeature.UsageLimit),
|
||||
Route: gettableFeature.Route,
|
||||
}
|
||||
}
|
||||
|
||||
return gettableFeatures, nil
|
||||
}
|
||||
|
||||
func (provider *provider) InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error {
|
||||
featureStatus := make([]*featuretypes.StorableFeature, len(features))
|
||||
for i, f := range features {
|
||||
featureStatus[i] = &featuretypes.StorableFeature{
|
||||
Name: f.Name,
|
||||
Active: f.Active,
|
||||
Usage: int(f.Usage),
|
||||
UsageLimit: int(f.UsageLimit),
|
||||
Route: f.Route,
|
||||
}
|
||||
}
|
||||
|
||||
return provider.store.InitFeatures(ctx, featureStatus)
|
||||
}
|
||||
|
||||
func (provider *provider) UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error {
|
||||
return provider.store.UpdateFeature(ctx, &featuretypes.StorableFeature{
|
||||
Name: feature.Name,
|
||||
Active: feature.Active,
|
||||
Usage: int(feature.Usage),
|
||||
UsageLimit: int(feature.UsageLimit),
|
||||
Route: feature.Route,
|
||||
})
|
||||
return license.Features, nil
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"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/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
@@ -81,106 +79,3 @@ func (store *store) Update(ctx context.Context, organizationID valuer.UUID, stor
|
||||
|
||||
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 {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(storableFeature).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "feature with name:%s already exists", storableFeature.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) GetFeature(ctx context.Context, key string) (*featuretypes.StorableFeature, error) {
|
||||
storableFeature := new(featuretypes.StorableFeature)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(storableFeature).
|
||||
Where("name = ?", key).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "feature with name:%s does not exist", key)
|
||||
}
|
||||
|
||||
return storableFeature, nil
|
||||
}
|
||||
|
||||
func (store *store) GetAllFeatures(ctx context.Context) ([]*featuretypes.StorableFeature, error) {
|
||||
storableFeatures := make([]*featuretypes.StorableFeature, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewSelect().
|
||||
Model(&storableFeatures).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "features do not exist")
|
||||
}
|
||||
|
||||
return storableFeatures, nil
|
||||
}
|
||||
|
||||
func (store *store) InitFeatures(ctx context.Context, storableFeatures []*featuretypes.StorableFeature) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewInsert().
|
||||
Model(&storableFeatures).
|
||||
On("CONFLICT (name) DO UPDATE").
|
||||
Set("active = EXCLUDED.active").
|
||||
Set("usage = EXCLUDED.usage").
|
||||
Set("usage_limit = EXCLUDED.usage_limit").
|
||||
Set("route = EXCLUDED.route").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to initialise features")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) UpdateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
NewUpdate().
|
||||
Model(storableFeature).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to update feature with key: %s", storableFeature.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
@@ -86,23 +85,12 @@ func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
||||
return ah.opts.Gateway
|
||||
}
|
||||
|
||||
func (ah *APIHandler) CheckFeature(ctx context.Context, key string) bool {
|
||||
err := ah.Signoz.Licensing.CheckFeature(ctx, key)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// RegisterRoutes registers routes for this handler on the given router
|
||||
func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// note: add ee override methods first
|
||||
|
||||
// routes available only in ee version
|
||||
|
||||
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)
|
||||
router.HandleFunc("/api/v1/features", am.ViewAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||
|
||||
// paid plans specific routes
|
||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
||||
@@ -114,9 +102,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/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
|
||||
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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
pkgError "github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -31,7 +31,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context())
|
||||
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context(), orgID)
|
||||
if err != nil {
|
||||
ah.HandleError(w, err, http.StatusInternalServerError)
|
||||
return
|
||||
@@ -61,7 +61,15 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if ah.opts.PreferSpanMetrics {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == featuretypes.UseSpanMetrics {
|
||||
if feature.Name == licensetypes.UseSpanMetrics {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
@@ -72,7 +80,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
||||
// and returns the FeatureSet.
|
||||
func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature, error) {
|
||||
func fetchZeusFeatures(url, licenseKey string) ([]*licensetypes.Feature, error) {
|
||||
// Check if the URL is empty
|
||||
if url == "" {
|
||||
return nil, fmt.Errorf("url is empty")
|
||||
@@ -131,28 +139,28 @@ func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature,
|
||||
}
|
||||
|
||||
type ZeusFeaturesResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data []*featuretypes.GettableFeature `json:"data"`
|
||||
Status string `json:"status"`
|
||||
Data []*licensetypes.Feature `json:"data"`
|
||||
}
|
||||
|
||||
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
||||
func MergeFeatureSets(zeusFeatures, internalFeatures []*featuretypes.GettableFeature) []*featuretypes.GettableFeature {
|
||||
func MergeFeatureSets(zeusFeatures, internalFeatures []*licensetypes.Feature) []*licensetypes.Feature {
|
||||
// Create a map to store the merged features
|
||||
featureMap := make(map[string]*featuretypes.GettableFeature)
|
||||
featureMap := make(map[string]*licensetypes.Feature)
|
||||
|
||||
// Add all features from the otherFeatures set to the map
|
||||
for _, feature := range internalFeatures {
|
||||
featureMap[feature.Name] = feature
|
||||
featureMap[feature.Name.StringValue()] = feature
|
||||
}
|
||||
|
||||
// Add all features from the zeusFeatures set to the map
|
||||
// If a feature already exists (i.e., same name), the zeusFeature will overwrite it
|
||||
for _, feature := range zeusFeatures {
|
||||
featureMap[feature.Name] = feature
|
||||
featureMap[feature.Name.StringValue()] = feature
|
||||
}
|
||||
|
||||
// Convert the map back to a FeatureSet slice
|
||||
var mergedFeatures []*featuretypes.GettableFeature
|
||||
var mergedFeatures []*licensetypes.Feature
|
||||
for _, feature := range featureMap {
|
||||
mergedFeatures = append(mergedFeatures, feature)
|
||||
}
|
||||
|
||||
@@ -3,78 +3,79 @@ package api
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMergeFeatureSets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
zeusFeatures []*featuretypes.GettableFeature
|
||||
internalFeatures []*featuretypes.GettableFeature
|
||||
expected []*featuretypes.GettableFeature
|
||||
zeusFeatures []*licensetypes.Feature
|
||||
internalFeatures []*licensetypes.Feature
|
||||
expected []*licensetypes.Feature
|
||||
}{
|
||||
{
|
||||
name: "empty zeusFeatures and internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||
internalFeatures: []*featuretypes.GettableFeature{},
|
||||
expected: []*featuretypes.GettableFeature{},
|
||||
zeusFeatures: []*licensetypes.Feature{},
|
||||
internalFeatures: []*licensetypes.Feature{},
|
||||
expected: []*licensetypes.Feature{},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and empty internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
internalFeatures: []*licensetypes.Feature{},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty zeusFeatures and non-empty internalFeatures",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature3", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature3"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature2", Active: true},
|
||||
{Name: "Feature4", Active: false},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature2"), Active: true},
|
||||
{Name: valuer.NewString("Feature4"), Active: false},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: true},
|
||||
{Name: "Feature3", Active: false},
|
||||
{Name: "Feature4", Active: false},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: true},
|
||||
{Name: valuer.NewString("Feature3"), Active: false},
|
||||
{Name: valuer.NewString("Feature4"), Active: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
||||
zeusFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
zeusFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
},
|
||||
internalFeatures: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: false},
|
||||
{Name: "Feature3", Active: true},
|
||||
internalFeatures: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: false},
|
||||
{Name: valuer.NewString("Feature3"), Active: true},
|
||||
},
|
||||
expected: []*featuretypes.GettableFeature{
|
||||
{Name: "Feature1", Active: true},
|
||||
{Name: "Feature2", Active: false},
|
||||
{Name: "Feature3", Active: true},
|
||||
expected: []*licensetypes.Feature{
|
||||
{Name: valuer.NewString("Feature1"), Active: true},
|
||||
{Name: valuer.NewString("Feature2"), Active: false},
|
||||
{Name: valuer.NewString("Feature3"), Active: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"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/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@@ -113,6 +114,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
serverOptions.SigNoz.SQLStore,
|
||||
serverOptions.SigNoz.TelemetryStore,
|
||||
serverOptions.SigNoz.Prometheus,
|
||||
serverOptions.SigNoz.Modules.OrgGetter,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -157,7 +159,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -225,7 +227,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
&opAmpModel.AllAgents, agentConfMgr,
|
||||
)
|
||||
|
||||
orgs, err := apiHandler.Signoz.Modules.Organization.GetAll(context.Background())
|
||||
orgs, err := apiHandler.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -240,11 +242,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
|
||||
|
||||
r := baseapp.NewRouter()
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).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(), s.serverOptions.SigNoz.Sharder).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@@ -275,8 +276,8 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
r := baseapp.NewRouter()
|
||||
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).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(), s.serverOptions.SigNoz.Sharder).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
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.RegisterThirdPartyApiRoutes(r, am)
|
||||
apiHandler.MetricExplorerRoutes(r, am)
|
||||
apiHandler.RegisterTraceFunnelsRoutes(r, am)
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
@@ -450,6 +452,7 @@ func makeRulesManager(
|
||||
sqlstore sqlstore.SQLStore,
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
orgGetter organization.Getter,
|
||||
) (*baserules.Manager, error) {
|
||||
// create manager opts
|
||||
managerOpts := &baserules.ManagerOptions{
|
||||
@@ -465,6 +468,7 @@ func makeRulesManager(
|
||||
PrepareTestRuleFunc: rules.TestNotification,
|
||||
Alertmanager: alertmanager,
|
||||
SQLStore: sqlstore,
|
||||
OrgGetter: orgGetter,
|
||||
}
|
||||
|
||||
// create Manager
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSiteURL = "https://localhost:8080"
|
||||
)
|
||||
|
||||
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
||||
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
||||
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
||||
@@ -20,3 +24,22 @@ func GetOrDefaultEnv(key string, fallback string) string {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
pkglicensing "github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@@ -133,8 +134,8 @@ func main() {
|
||||
zeus.Config(),
|
||||
httpzeus.NewProviderFactory(),
|
||||
licensing.Config(24*time.Hour, 3),
|
||||
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
|
||||
return httplicensing.NewProviderFactory(sqlstore, zeus)
|
||||
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
|
||||
return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter)
|
||||
},
|
||||
signoz.NewEmailingProviderFactories(),
|
||||
signoz.NewCacheProviderFactories(),
|
||||
|
||||
@@ -41,16 +41,16 @@ type Manager struct {
|
||||
|
||||
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{
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
zeus: zeus,
|
||||
organizationModule: organizationModule,
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
zeus: zeus,
|
||||
orgGetter: orgGetter,
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -74,8 +74,7 @@ func (lm *Manager) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
|
||||
organizations, err := lm.organizationModule.GetAll(context.Background())
|
||||
organizations, err := lm.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get organizations", zap.Error(err))
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const checkFirstTimeUser = useCallback((): boolean => {
|
||||
const users = usersData?.data || [];
|
||||
|
||||
const remainingUsers = users.filter(
|
||||
const remainingUsers = (Array.isArray(users) ? users : []).filter(
|
||||
(user) => user.email !== 'admin@signoz.cloud',
|
||||
);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
import { LicenseStatus } from 'types/api/licensesV3/getActive';
|
||||
import { Userpilot } from 'userpilot';
|
||||
import { extractDomain } from 'utils/app';
|
||||
|
||||
@@ -171,11 +172,13 @@ function App(): JSX.Element {
|
||||
user &&
|
||||
!!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 =
|
||||
activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
);
|
||||
(activeLicenseFetchError &&
|
||||
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||
activeLicenseFetchError?.getHttpStatusCode(),
|
||||
)) ||
|
||||
(activeLicense?.status && activeLicense.status === LicenseStatus.INVALID);
|
||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
|
||||
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
||||
@@ -190,6 +193,10 @@ function App(): JSX.Element {
|
||||
updatedRoutes = updatedRoutes.filter(
|
||||
(route) => route?.path !== ROUTES.BILLING,
|
||||
);
|
||||
|
||||
if (isEnterpriseSelfHostedUser) {
|
||||
updatedRoutes.push(LIST_LICENSES);
|
||||
}
|
||||
}
|
||||
// always add support route for cloud users
|
||||
updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE];
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,10 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||
|
||||
const getFeaturesFlags = (): Promise<FeatureFlagProps[]> =>
|
||||
axios
|
||||
.get<ApiResponse<FeatureFlagProps[]>>(`/featureFlags`)
|
||||
.then((response) => response.data.data);
|
||||
|
||||
export default getFeaturesFlags;
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sClustersListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sClustersListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sDaemonSetsListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sDaemonSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sDeploymentsListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sDeploymentsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sJobsListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sJobsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sNamespacesListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sNamespacesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sNamespacesListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapNamespacesMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sNodesListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sNodesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sPodsListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sPodsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sPodsListResponse = response.data;
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapPodsMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sVolumesListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sVolumesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
headers,
|
||||
});
|
||||
const payload: K8sVolumesListResponse = response.data;
|
||||
|
||||
payload.data.records = payload.data.records.map((record) => ({
|
||||
...record,
|
||||
meta: mapVolumesMeta(record.meta as Record<string, unknown>),
|
||||
}));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sStatefulSetsListPayload {
|
||||
filters: TagFilter;
|
||||
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 (
|
||||
props: K8sStatefulSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
|
||||
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,
|
||||
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 {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
params: props,
|
||||
payload,
|
||||
params: requestProps,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
@@ -167,8 +167,8 @@ interface UpdateFunnelDescriptionPayload {
|
||||
export const saveFunnelDescription = async (
|
||||
payload: UpdateFunnelDescriptionPayload,
|
||||
): Promise<SuccessResponse<FunnelData> | ErrorResponse> => {
|
||||
const response: AxiosResponse = await axios.post(
|
||||
`${FUNNELS_BASE_PATH}/save`,
|
||||
const response: AxiosResponse = await axios.put(
|
||||
`${FUNNELS_BASE_PATH}/${payload.funnel_id}`,
|
||||
payload,
|
||||
);
|
||||
|
||||
|
||||
@@ -17,3 +17,19 @@ export const Logout = (): void => {
|
||||
|
||||
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',
|
||||
};
|
||||
|
||||
23
frontend/src/api/v1/dashboards/create.ts
Normal file
23
frontend/src/api/v1/dashboards/create.ts
Normal 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;
|
||||
19
frontend/src/api/v1/dashboards/getAll.ts
Normal file
19
frontend/src/api/v1/dashboards/getAll.ts
Normal 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;
|
||||
21
frontend/src/api/v1/dashboards/id/delete.ts
Normal file
21
frontend/src/api/v1/dashboards/id/delete.ts
Normal 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;
|
||||
20
frontend/src/api/v1/dashboards/id/get.ts
Normal file
20
frontend/src/api/v1/dashboards/id/get.ts
Normal 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;
|
||||
23
frontend/src/api/v1/dashboards/id/lock.ts
Normal file
23
frontend/src/api/v1/dashboards/id/lock.ts
Normal 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;
|
||||
23
frontend/src/api/v1/dashboards/id/update.ts
Normal file
23
frontend/src/api/v1/dashboards/id/update.ts
Normal 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;
|
||||
23
frontend/src/api/v1/features/list.ts
Normal file
23
frontend/src/api/v1/features/list.ts
Normal 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 {
|
||||
FeatureFlagProps,
|
||||
PayloadProps,
|
||||
} from 'types/api/features/getFeaturesFlags';
|
||||
|
||||
const list = async (): Promise<SuccessResponseV2<FeatureFlagProps[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/features`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default list;
|
||||
@@ -7,9 +7,11 @@ import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import {
|
||||
CustomTimeType,
|
||||
FixedDurationSuggestionOptions,
|
||||
Options,
|
||||
RelativeDurationSuggestionOptions,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { isValidTimeFormat } from 'lib/getMinMax';
|
||||
@@ -56,6 +58,10 @@ interface CustomTimePickerProps {
|
||||
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
|
||||
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
|
||||
handleGoLive?: () => void;
|
||||
onTimeChange?: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
function CustomTimePicker({
|
||||
@@ -73,6 +79,7 @@ function CustomTimePicker({
|
||||
setCustomDTPickerVisible,
|
||||
onCustomDateHandler,
|
||||
handleGoLive,
|
||||
onTimeChange,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [
|
||||
selectedTimePlaceholderValue,
|
||||
@@ -336,6 +343,7 @@ function CustomTimePicker({
|
||||
setActiveView={setActiveView}
|
||||
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
||||
isOpenedFromFooter={isOpenedFromFooter}
|
||||
onTimeChange={onTimeChange}
|
||||
/>
|
||||
) : (
|
||||
content
|
||||
@@ -405,4 +413,5 @@ CustomTimePicker.defaultProps = {
|
||||
onCustomDateHandler: noop,
|
||||
handleGoLive: noop,
|
||||
onCustomTimeStatusUpdate: noop,
|
||||
onTimeChange: undefined,
|
||||
};
|
||||
|
||||
@@ -7,9 +7,11 @@ import cx from 'classnames';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import {
|
||||
CustomTimeType,
|
||||
LexicalContext,
|
||||
Option,
|
||||
RelativeDurationSuggestionOptions,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { Clock, PenLine } from 'lucide-react';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
@@ -35,6 +37,10 @@ interface CustomTimePickerPopoverContentProps {
|
||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
||||
isOpenedFromFooter: boolean;
|
||||
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
|
||||
onTimeChange?: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
@@ -51,6 +57,7 @@ function CustomTimePickerPopoverContent({
|
||||
setActiveView,
|
||||
isOpenedFromFooter,
|
||||
setIsOpenedFromFooter,
|
||||
onTimeChange,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
@@ -143,6 +150,7 @@ function CustomTimePickerPopoverContent({
|
||||
setIsOpen={setIsOpen}
|
||||
onCustomDateHandler={onCustomDateHandler}
|
||||
selectedTime={selectedTime}
|
||||
onTimeChange={onTimeChange}
|
||||
/>
|
||||
) : (
|
||||
<div className="relative-times-container">
|
||||
@@ -181,4 +189,8 @@ function CustomTimePickerPopoverContent({
|
||||
);
|
||||
}
|
||||
|
||||
CustomTimePickerPopoverContent.defaultProps = {
|
||||
onTimeChange: undefined,
|
||||
};
|
||||
|
||||
export default CustomTimePickerPopoverContent;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import './RangePickerModal.styles.scss';
|
||||
|
||||
import { DatePicker } from 'antd';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import {
|
||||
CustomTimeType,
|
||||
LexicalContext,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||
@@ -19,6 +24,10 @@ interface RangePickerModalProps {
|
||||
lexicalContext?: LexicalContext | undefined,
|
||||
) => void;
|
||||
selectedTime: string;
|
||||
onTimeChange?: (
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
}
|
||||
|
||||
function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||
@@ -27,6 +36,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||
setIsOpen,
|
||||
onCustomDateHandler,
|
||||
selectedTime,
|
||||
onTimeChange,
|
||||
} = props;
|
||||
const { RangePicker } = DatePicker;
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
@@ -74,13 +84,22 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
||||
date.tz(timezone.value).format(DATE_TIME_FORMATS.ISO_DATETIME)
|
||||
}
|
||||
onOk={onModalOkHandler}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(selectedTime === 'custom' && {
|
||||
value: rangeValue,
|
||||
})}
|
||||
{...(selectedTime === 'custom' &&
|
||||
!onTimeChange && {
|
||||
value: rangeValue,
|
||||
})}
|
||||
// use default value if onTimeChange is provided
|
||||
{...(selectedTime === 'custom' &&
|
||||
onTimeChange && {
|
||||
defaultValue: rangeValue,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RangePickerModal.defaultProps = {
|
||||
onTimeChange: undefined,
|
||||
};
|
||||
|
||||
export default RangePickerModal;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useQuery } from 'react-query';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { VIEWS } from '../constants';
|
||||
import { getHostTracesQueryPayload, selectedColumns } from './constants';
|
||||
import { getListColumns } from './utils';
|
||||
|
||||
@@ -39,7 +40,10 @@ interface Props {
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
||||
handleChangeTracesFilters: (
|
||||
value: IBuilderQuery['filters'],
|
||||
view: VIEWS,
|
||||
) => void;
|
||||
tracesFilters: IBuilderQuery['filters'];
|
||||
selectedInterval: Time;
|
||||
}
|
||||
@@ -70,14 +74,16 @@ function HostMetricTraces({
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
items: tracesFilters.items.filter(
|
||||
(item) => item.key?.key !== 'host.name',
|
||||
),
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, tracesFilters.items],
|
||||
);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
@@ -153,14 +159,16 @@ function HostMetricTraces({
|
||||
{query && (
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeTracesFilters}
|
||||
onChange={(value): void =>
|
||||
handleChangeTracesFilters(value, VIEWS.TRACES)
|
||||
}
|
||||
disableNavigationShortcuts
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -93,8 +95,18 @@ function HostMetricsDetails({
|
||||
);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@@ -111,9 +123,8 @@ function HostMetricsDetails({
|
||||
value: host?.hostName || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[host?.hostName],
|
||||
);
|
||||
};
|
||||
}, [host?.hostName, searchParams]);
|
||||
|
||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
||||
initialFilters,
|
||||
@@ -154,7 +165,13 @@ function HostMetricsDetails({
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
if (host?.hostName) {
|
||||
setSearchParams({ hostName: host?.hostName, view: e.target.value });
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.HostEntity,
|
||||
@@ -191,7 +208,7 @@ function HostMetricsDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogFilters((prevFilters) => {
|
||||
const hostNameFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === 'host.name',
|
||||
@@ -209,7 +226,7 @@ function HostMetricsDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
hostNameFilter,
|
||||
@@ -217,6 +234,15 @@ function HostMetricsDetails({
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -224,7 +250,7 @@ function HostMetricsDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setTracesFilters((prevFilters) => {
|
||||
const hostNameFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === 'host.name',
|
||||
@@ -238,13 +264,23 @@ function HostMetricsDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
hostNameFilter,
|
||||
...value.items.filter((item) => item.key?.key !== 'host.name'),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useMemo } from 'react';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { VIEWS } from '../constants';
|
||||
import HostMetricsLogs from './HostMetricsLogs';
|
||||
|
||||
interface Props {
|
||||
@@ -23,7 +24,7 @@ interface Props {
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
||||
handleChangeLogFilters: (value: IBuilderQuery['filters'], view: VIEWS) => void;
|
||||
logFilters: IBuilderQuery['filters'];
|
||||
selectedInterval: Time;
|
||||
}
|
||||
@@ -51,14 +52,14 @@ function HostMetricLogsDetailedView({
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
items: logFilters.items.filter((item) => item.key?.key !== 'host.name'),
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, logFilters.items],
|
||||
);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
@@ -70,14 +71,14 @@ function HostMetricLogsDetailedView({
|
||||
{query && (
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeLogFilters}
|
||||
onChange={(value): void => handleChangeLogFilters(value, VIEWS.LOGS)}
|
||||
disableNavigationShortcuts
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
|
||||
@@ -18,11 +18,14 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
|
||||
interface MetricsTabProps {
|
||||
timeRange: {
|
||||
startTime: number;
|
||||
@@ -45,9 +48,20 @@ function Metrics({
|
||||
handleTimeChange,
|
||||
isModalTimeSelection,
|
||||
}: MetricsTabProps): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
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(
|
||||
@@ -68,6 +82,45 @@ function Metrics({
|
||||
[queries],
|
||||
);
|
||||
|
||||
const [graphTimeIntervals, setGraphTimeIntervals] = useState<
|
||||
{
|
||||
start: number;
|
||||
end: number;
|
||||
}[]
|
||||
>(
|
||||
new Array(queries.length).fill({
|
||||
start: timeRange.startTime,
|
||||
end: timeRange.endTime,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setGraphTimeIntervals(
|
||||
new Array(queries.length).fill({
|
||||
start: timeRange.startTime,
|
||||
end: timeRange.endTime,
|
||||
}),
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timeRange]);
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number, graphIndex: number) => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
setGraphTimeIntervals((prev) => {
|
||||
const newIntervals = [...prev];
|
||||
newIntervals[graphIndex] = {
|
||||
start: Math.floor(startTimestamp / 1000),
|
||||
end: Math.floor(endTimestamp / 1000),
|
||||
};
|
||||
return newIntervals;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
queries.map(({ data }, idx) =>
|
||||
@@ -78,12 +131,12 @@ function Metrics({
|
||||
yAxisUnit: hostWidgetInfo[idx].yAxisUnit,
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
minTimeScale: timeRange.startTime,
|
||||
maxTimeScale: timeRange.endTime,
|
||||
enableZoom: true,
|
||||
minTimeScale: graphTimeIntervals[idx].start,
|
||||
maxTimeScale: graphTimeIntervals[idx].end,
|
||||
onDragSelect: (start, end) => onDragSelect(start, end, idx),
|
||||
}),
|
||||
),
|
||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
||||
[queries, isDarkMode, dimensions, graphTimeIntervals, onDragSelect],
|
||||
);
|
||||
|
||||
const renderCardContent = (
|
||||
|
||||
@@ -16,6 +16,7 @@ import JSONView from 'container/LogDetailedView/JsonView';
|
||||
import Overview from 'container/LogDetailedView/Overview';
|
||||
import {
|
||||
aggregateAttributesResourcesToString,
|
||||
escapeHtml,
|
||||
removeEscapeCharacters,
|
||||
unescapeString,
|
||||
} from 'container/LogDetailedView/utils';
|
||||
@@ -118,7 +119,7 @@ function LogDetail({
|
||||
const htmlBody = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(log?.body || ''), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import cx from 'classnames';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
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 dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
@@ -58,7 +58,7 @@ function LogGeneralField({
|
||||
const html = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(fieldValue), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DrawerProps } from 'antd';
|
||||
import LogDetail from 'components/LogDetail';
|
||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||
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 dompurify from 'dompurify';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
@@ -177,7 +177,7 @@ function RawLogView({
|
||||
const html = useMemo(
|
||||
() => ({
|
||||
__html: convert.toHtml(
|
||||
dompurify.sanitize(unescapeString(text), {
|
||||
dompurify.sanitize(unescapeString(escapeHtml(text)), {
|
||||
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -26,6 +26,18 @@ export interface UplotProps {
|
||||
resetScales?: boolean;
|
||||
}
|
||||
|
||||
function isAlignedData(data: unknown): data is uPlot.AlignedData {
|
||||
return Array.isArray(data) && data.length > 0;
|
||||
}
|
||||
|
||||
function isUplotOptions(options: unknown): options is uPlot.Options {
|
||||
return options !== null && typeof options === 'object';
|
||||
}
|
||||
|
||||
function isHTMLElement(el: unknown): el is HTMLElement {
|
||||
return el instanceof HTMLElement;
|
||||
}
|
||||
|
||||
const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
|
||||
(
|
||||
{ options, data, onDelete, onCreate, resetScales = true },
|
||||
@@ -78,6 +90,19 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!isUplotOptions(propOptionsRef.current) ||
|
||||
!isAlignedData(propDataRef.current) ||
|
||||
!isHTMLElement(targetRef.current)
|
||||
) {
|
||||
console.error('Uplot: Invalid options, data, or target element', {
|
||||
options: propOptionsRef.current,
|
||||
data: propDataRef.current,
|
||||
target: targetRef.current,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newChart = new UPlot(
|
||||
propOptionsRef.current,
|
||||
propDataRef.current,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// keep this consistent with backend constants.go
|
||||
// keep this consistent with backend plan.go
|
||||
export enum FeatureKeys {
|
||||
SSO = 'SSO',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
ONBOARDING = 'ONBOARDING',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
GATEWAY = 'GATEWAY',
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
||||
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
||||
SSO = 'sso',
|
||||
USE_SPAN_METRICS = 'use_span_metrics',
|
||||
ONBOARDING = 'onboarding',
|
||||
CHAT_SUPPORT = 'chat_support',
|
||||
GATEWAY = 'gateway',
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
ONBOARDING_V3 = 'onboarding_v3',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
}
|
||||
|
||||
@@ -316,7 +316,6 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
expect(screen.getByText('Microsoft Teams')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// TODO[vikrantgupta25]: check with Shaheer
|
||||
it.skip('Should check if the upgrade plan message is shown', () => {
|
||||
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
|
||||
expect(
|
||||
|
||||
@@ -21,7 +21,10 @@ import ROUTES from 'constants/routes';
|
||||
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
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 useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
@@ -38,6 +41,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Exception, PayloadProps } from 'types/api/errors/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { FilterDropdownExtendsProps } from './types';
|
||||
import {
|
||||
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(
|
||||
(
|
||||
paginations: TablePaginationConfig,
|
||||
@@ -438,7 +448,7 @@ function AllErrors(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!isUndefined(errorCountResponse.data?.payload)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === 'resource_deployment_environment',
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('Exception: List page visited', {
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Provider, useSelector } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import store from 'store';
|
||||
|
||||
import * as appContextHooks from '../../../providers/App/App';
|
||||
import { LicenseEvent } from '../../../types/api/licensesV3/getActive';
|
||||
import AllErrors from '../index';
|
||||
import {
|
||||
INIT_URL_WITH_COMMON_QUERY,
|
||||
@@ -28,6 +30,30 @@ jest.mock('react-redux', () => ({
|
||||
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 {
|
||||
return (
|
||||
<MemoryRouter initialEntries={initUrl ?? ['/exceptions']}>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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 { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation } from 'react-query';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { ExportPanelProps } from '.';
|
||||
import {
|
||||
@@ -33,26 +34,28 @@ function ExportPanelContainer({
|
||||
refetch,
|
||||
} = useGetAllDashboard();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const {
|
||||
mutate: createNewDashboard,
|
||||
isLoading: createDashboardLoading,
|
||||
} = useMutation(createDashboard, {
|
||||
onSuccess: (data) => {
|
||||
if (data.payload) {
|
||||
onExport(data?.payload, true);
|
||||
if (data.data) {
|
||||
onExport(data?.data, true);
|
||||
}
|
||||
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 currentSelectedDashboard = data?.find(
|
||||
({ uuid }) => uuid === selectedDashboardId,
|
||||
const currentSelectedDashboard = data?.data?.find(
|
||||
({ id }) => id === selectedDashboardId,
|
||||
);
|
||||
|
||||
onExport(currentSelectedDashboard || null, false);
|
||||
@@ -66,14 +69,18 @@ function ExportPanelContainer({
|
||||
);
|
||||
|
||||
const handleNewDashboard = useCallback(async () => {
|
||||
createNewDashboard({
|
||||
title: t('new_dashboard_title', {
|
||||
ns: 'dashboard',
|
||||
}),
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
}, [t, createNewDashboard]);
|
||||
try {
|
||||
await createNewDashboard({
|
||||
title: t('new_dashboard_title', {
|
||||
ns: 'dashboard',
|
||||
}),
|
||||
uploadedGrafana: false,
|
||||
version: ENTITY_VERSION_V4,
|
||||
});
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
}, [createNewDashboard, t, showErrorModal]);
|
||||
|
||||
const isDashboardLoading = isAllDashboardsLoading || createDashboardLoading;
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { SelectProps } from 'antd';
|
||||
import { PayloadProps as AllDashboardsData } from 'types/api/dashboard/getAll';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
export const getSelectOptions = (
|
||||
data: AllDashboardsData,
|
||||
): SelectProps['options'] =>
|
||||
data.map(({ uuid, data }) => ({
|
||||
export const getSelectOptions = (data: Dashboard[]): SelectProps['options'] =>
|
||||
data.map(({ id, data }) => ({
|
||||
label: data.title,
|
||||
value: uuid,
|
||||
value: id,
|
||||
}));
|
||||
|
||||
export const filterOptions: SelectProps['filterOption'] = (
|
||||
|
||||
@@ -36,7 +36,6 @@ function QuerySection({
|
||||
const { t } = useTranslation('alerts');
|
||||
const [currentTab, setCurrentTab] = useState(queryCategory);
|
||||
|
||||
// TODO[vikrantgupta25] : check if this is still required ??
|
||||
const handleQueryCategoryChange = (queryType: string): void => {
|
||||
setQueryCategory(queryType as EQueryType);
|
||||
setCurrentTab(queryType as EQueryType);
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
setSelectedRowWidgetId(null);
|
||||
handleToggleDashboardSlider(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { AppProvider } from 'providers/App/App';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from 'store';
|
||||
@@ -189,24 +190,26 @@ describe('WidgetGraphComponent', () => {
|
||||
it('should show correct menu items when hovering over more options while loading', async () => {
|
||||
const { getByTestId, findByRole, getByText, container } = render(
|
||||
<MockQueryClientProvider>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<WidgetGraphComponent
|
||||
widget={mockProps.widget}
|
||||
queryResponse={mockProps.queryResponse}
|
||||
errorMessage={mockProps.errorMessage}
|
||||
version={mockProps.version}
|
||||
headerMenuList={mockProps.headerMenuList}
|
||||
isWarning={mockProps.isWarning}
|
||||
isFetchingResponse={mockProps.isFetchingResponse}
|
||||
setRequestData={mockProps.setRequestData}
|
||||
onClickHandler={mockProps.onClickHandler}
|
||||
onDragSelect={mockProps.onDragSelect}
|
||||
openTracesButton={mockProps.openTracesButton}
|
||||
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
||||
/>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
<ErrorModalProvider>
|
||||
<Provider store={store}>
|
||||
<AppProvider>
|
||||
<WidgetGraphComponent
|
||||
widget={mockProps.widget}
|
||||
queryResponse={mockProps.queryResponse}
|
||||
errorMessage={mockProps.errorMessage}
|
||||
version={mockProps.version}
|
||||
headerMenuList={mockProps.headerMenuList}
|
||||
isWarning={mockProps.isWarning}
|
||||
isFetchingResponse={mockProps.isFetchingResponse}
|
||||
setRequestData={mockProps.setRequestData}
|
||||
onClickHandler={mockProps.onClickHandler}
|
||||
onDragSelect={mockProps.onDragSelect}
|
||||
openTracesButton={mockProps.openTracesButton}
|
||||
onOpenTraceBtnClick={mockProps.onOpenTraceBtnClick}
|
||||
/>
|
||||
</AppProvider>
|
||||
</Provider>
|
||||
</ErrorModalProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Skeleton, Tooltip, Typography } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
|
||||
import { ToggleGraphProps } from 'components/Graph/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
@@ -31,7 +30,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
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 { v4 } from 'uuid';
|
||||
|
||||
@@ -119,29 +118,23 @@ function WidgetGraphComponent({
|
||||
const updatedLayout =
|
||||
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
layout: updatedLayout,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
id: selectedDashboard.id,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
setDeleteModal(false);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -166,7 +159,8 @@ function WidgetGraphComponent({
|
||||
|
||||
updateDashboardMutation.mutateAsync(
|
||||
{
|
||||
...selectedDashboard,
|
||||
id: selectedDashboard.id,
|
||||
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
layout,
|
||||
@@ -183,9 +177,9 @@ function WidgetGraphComponent({
|
||||
},
|
||||
{
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
notifications.success({
|
||||
message: 'Panel cloned successfully, redirecting to new copy.',
|
||||
@@ -252,7 +246,11 @@ function WidgetGraphComponent({
|
||||
|
||||
const graphClick = useGraphClickToShowButton({
|
||||
graphRef: currentGraphRef?.current ? currentGraphRef : graphRef,
|
||||
isButtonEnabled: (widget?.query?.builder?.queryData ?? []).some(
|
||||
isButtonEnabled: (widget?.query?.builder?.queryData &&
|
||||
Array.isArray(widget.query.builder.queryData)
|
||||
? widget.query.builder.queryData
|
||||
: []
|
||||
).some(
|
||||
(q) =>
|
||||
q.dataSource === DataSource.TRACES || q.dataSource === DataSource.LOGS,
|
||||
),
|
||||
|
||||
@@ -160,8 +160,6 @@ function GridCardGraph({
|
||||
};
|
||||
});
|
||||
|
||||
// TODO [vikrantgupta25] remove this useEffect with refactor as this is prone to race condition
|
||||
// this is added to tackle the case of async communication between VariableItem.tsx and GridCard.tsx
|
||||
useEffect(() => {
|
||||
if (variablesToGetUpdated.length > 0) {
|
||||
queryClient.cancelQueries([
|
||||
|
||||
@@ -31,7 +31,9 @@ export const getLocalStorageGraphVisibilityState = ({
|
||||
name: string;
|
||||
}): GraphVisibilityLegendEntryProps => {
|
||||
const visibilityStateAndLegendEntry: GraphVisibilityLegendEntryProps = {
|
||||
graphVisibilityStates: Array(apiResponse.length + 1).fill(true),
|
||||
graphVisibilityStates: Array(
|
||||
(Array.isArray(apiResponse) ? apiResponse.length : 0) + 1,
|
||||
).fill(true),
|
||||
legendEntry: [
|
||||
{
|
||||
label: 'Timestamp',
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Button, Form, Input, Modal, Typography } from 'antd';
|
||||
import { useForm } from 'antd/es/form/Form';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
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 useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { defaultTo, isUndefined } from 'lodash-es';
|
||||
@@ -36,7 +34,8 @@ import { ItemCallback, Layout } from 'react-grid-layout';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
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 { ComponentTypes } from 'utils/permission';
|
||||
|
||||
@@ -107,7 +106,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
|
||||
const updateDashboardMutation = useUpdateDashboard();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
||||
@@ -158,20 +156,20 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && !isUndefined(data)) {
|
||||
logEvent('Dashboard Detail: Opened', {
|
||||
dashboardId: data.uuid,
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: data.title,
|
||||
numberOfPanels: data.widgets?.length,
|
||||
numberOfVariables: Object.keys(data?.variables || {}).length || 0,
|
||||
});
|
||||
logEventCalledRef.current = true;
|
||||
}
|
||||
}, [data]);
|
||||
}, [data, selectedDashboard?.id]);
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
if (!selectedDashboard) return;
|
||||
|
||||
const updatedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
panelMap: { ...currentPanelMap },
|
||||
@@ -186,24 +184,18 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
return widget;
|
||||
}),
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutate(updatedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
setSelectedRowWidgetId(null);
|
||||
if (updatedDashboard.payload) {
|
||||
if (updatedDashboard.payload.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (updatedDashboard.data) {
|
||||
if (updatedDashboard.data.data.layout)
|
||||
setLayouts(sortLayout(updatedDashboard.data.data.layout));
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
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);
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
if (setPanelMap)
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
form.setFieldValue('title', '');
|
||||
setIsSettingsModalOpen(false);
|
||||
setCurrentSelectRowId(null);
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -415,12 +399,14 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
};
|
||||
|
||||
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {
|
||||
if (currentPanelMap[oldItem.i]) {
|
||||
if (oldItem?.i && currentPanelMap?.[oldItem.i]) {
|
||||
const differenceY = newItem.y - oldItem.y;
|
||||
const widgetsInsideRow = currentPanelMap[oldItem.i].widgets.map((w) => ({
|
||||
...w,
|
||||
y: w.y + differenceY,
|
||||
}));
|
||||
const widgetsInsideRow = (currentPanelMap[oldItem.i]?.widgets ?? []).map(
|
||||
(w) => ({
|
||||
...w,
|
||||
y: w.y + differenceY,
|
||||
}),
|
||||
);
|
||||
setCurrentPanelMap((prev) => ({
|
||||
...prev,
|
||||
[oldItem.i]: {
|
||||
@@ -447,34 +433,26 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
const updatedPanelMap = { ...currentPanelMap };
|
||||
delete updatedPanelMap[currentSelectRowId];
|
||||
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
const updatedSelectedDashboard: Props = {
|
||||
id: selectedDashboard.id,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
widgets: updatedWidgets,
|
||||
layout: updatedLayout,
|
||||
panelMap: updatedPanelMap,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
};
|
||||
|
||||
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||
onSuccess: (updatedDashboard) => {
|
||||
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||
setSelectedDashboard(updatedDashboard.payload);
|
||||
if (setLayouts) setLayouts(updatedDashboard.data?.data?.layout || []);
|
||||
if (setSelectedDashboard && updatedDashboard.data) {
|
||||
setSelectedDashboard(updatedDashboard.data);
|
||||
}
|
||||
if (setPanelMap)
|
||||
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||
if (setPanelMap) setPanelMap(updatedDashboard.data?.data?.panelMap || {});
|
||||
setIsDeleteModalOpen(false);
|
||||
setCurrentSelectRowId(null);
|
||||
},
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
const isDashboardEmpty = useMemo(
|
||||
|
||||
@@ -102,11 +102,11 @@ export function updateStepInterval(
|
||||
return {
|
||||
...query,
|
||||
builder: {
|
||||
...query.builder,
|
||||
...query?.builder,
|
||||
queryData: [
|
||||
...query.builder.queryData.map((queryData) => ({
|
||||
...(query?.builder?.queryData ?? []).map((queryData) => ({
|
||||
...queryData,
|
||||
stepInterval: stepIntervalPoints || queryData.stepInterval || 60,
|
||||
stepInterval: stepIntervalPoints || queryData?.stepInterval || 60,
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -110,24 +110,24 @@ export function getQueryLegend(
|
||||
switch (currentQuery.queryType) {
|
||||
case EQueryType.QUERY_BUILDER:
|
||||
// check if the value is present in the queries
|
||||
legend = currentQuery.builder.queryData.find(
|
||||
legend = currentQuery?.builder?.queryData?.find(
|
||||
(query) => query.queryName === queryName,
|
||||
)?.legend;
|
||||
|
||||
if (!legend) {
|
||||
// check if the value is present in the formula
|
||||
legend = currentQuery.builder.queryFormulas.find(
|
||||
legend = currentQuery?.builder?.queryFormulas?.find(
|
||||
(query) => query.queryName === queryName,
|
||||
)?.legend;
|
||||
}
|
||||
break;
|
||||
case EQueryType.CLICKHOUSE:
|
||||
legend = currentQuery.clickhouse_sql.find(
|
||||
legend = currentQuery?.clickhouse_sql?.find(
|
||||
(query) => query.name === queryName,
|
||||
)?.legend;
|
||||
break;
|
||||
case EQueryType.PROM:
|
||||
legend = currentQuery.promql.find((query) => query.name === queryName)
|
||||
legend = currentQuery?.promql?.find((query) => query.name === queryName)
|
||||
?.legend;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function Dashboards({
|
||||
useEffect(() => {
|
||||
if (!dashboardsList) return;
|
||||
|
||||
const sortedDashboards = dashboardsList.sort((a, b) => {
|
||||
const sortedDashboards = dashboardsList.data.sort((a, b) => {
|
||||
const aUpdateAt = new Date(a.updatedAt).getTime();
|
||||
const bUpdateAt = new Date(b.updatedAt).getTime();
|
||||
return bUpdateAt - aUpdateAt;
|
||||
@@ -103,7 +103,7 @@ export default function Dashboards({
|
||||
<div className="home-dashboards-list-container home-data-item-container">
|
||||
<div className="dashboards-list">
|
||||
{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 => {
|
||||
event.stopPropagation();
|
||||
@@ -134,7 +134,7 @@ export default function Dashboards({
|
||||
<div className="dashboard-item-name-container home-data-item-name-container">
|
||||
<img
|
||||
src={
|
||||
dashboard.id % 2 === 0
|
||||
Math.random() % 2 === 0
|
||||
? '/Icons/eight-ball.svg'
|
||||
: '/Icons/circus-tent.svg'
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import getAllUserPreferences from 'api/preferences/getAllUserPreference';
|
||||
import updateUserPreferenceAPI from 'api/preferences/updateUserPreference';
|
||||
import Header from 'components/Header/Header';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
@@ -161,10 +162,20 @@ export default function Home(): JSX.Element {
|
||||
enabled: !!query,
|
||||
});
|
||||
|
||||
const { data: k8sPodsData } = useGetK8sPodsList(query as K8sPodsListPayload, {
|
||||
queryKey: ['K8sPodsList', query],
|
||||
enabled: !!query,
|
||||
});
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
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 [isTracesIngestionActive, setIsTracesIngestionActive] = useState(false);
|
||||
|
||||
@@ -30,6 +30,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
@@ -210,6 +211,11 @@ function ServiceMetrics({
|
||||
|
||||
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(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
@@ -217,12 +223,14 @@ function ServiceMetrics({
|
||||
minTime: timeRange.startTime * 1e6,
|
||||
maxTime: timeRange.endTime * 1e6,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[
|
||||
globalSelectedInterval,
|
||||
timeRange.endTime,
|
||||
timeRange.startTime,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -25,9 +25,11 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import HostsListControls from './HostsListControls';
|
||||
import HostsListTable from './HostsListTable';
|
||||
import { getHostListsQuery, HostsQuickFiltersConfig } from './utils';
|
||||
import { getHostListsQuery, GetHostsQuickFiltersConfig } from './utils';
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function HostsList(): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
@@ -114,6 +116,11 @@ function HostsList(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
const isNewFilterAdded = value.items.length !== filters.items.length;
|
||||
@@ -182,7 +189,7 @@ function HostsList(): JSX.Element {
|
||||
</div>
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={HostsQuickFiltersConfig}
|
||||
config={GetHostsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
|
||||
@@ -58,7 +58,7 @@ function HostsListControls({
|
||||
|
||||
<div className="time-selector">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
/>
|
||||
|
||||
@@ -198,3 +198,48 @@ export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function GetHostsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
// These keys don’t 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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -59,11 +61,26 @@ function K8sClustersList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
@@ -115,9 +132,16 @@ function K8sClustersList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sClustersRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -177,6 +201,8 @@ function K8sClustersList({
|
||||
queryKey: ['clusterList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -185,8 +211,10 @@ function K8sClustersList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
K8sEntityToAggregateAttributeMapping[K8sCategory.CLUSTERS],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.CLUSTERS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -232,6 +260,8 @@ function K8sClustersList({
|
||||
queryKey: ['clusterList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
@@ -309,7 +339,11 @@ function K8sClustersList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -319,7 +353,8 @@ function K8sClustersList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -136,6 +136,11 @@ export const getK8sClustersListColumns = (
|
||||
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 = (
|
||||
cluster: K8sClustersData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -143,7 +148,13 @@ const getGroupByEle = (
|
||||
const groupByValues: string[] = [];
|
||||
|
||||
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 (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -61,11 +63,26 @@ function K8sDaemonSetsList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
@@ -117,9 +134,16 @@ function K8sDaemonSetsList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sDaemonSetsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -179,6 +203,8 @@ function K8sDaemonSetsList({
|
||||
queryKey: ['daemonSetList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -187,8 +213,10 @@ function K8sDaemonSetsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
K8sEntityToAggregateAttributeMapping[K8sCategory.DAEMONSETS],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DAEMONSETS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -229,6 +257,8 @@ function K8sDaemonSetsList({
|
||||
queryKey: ['daemonSetList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const daemonSetsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
@@ -313,7 +343,11 @@ function K8sDaemonSetsList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -323,7 +357,8 @@ function K8sDaemonSetsList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -236,6 +236,12 @@ export const getK8sDaemonSetsListColumns = (
|
||||
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 = (
|
||||
daemonSet: K8sDaemonSetsData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -243,7 +249,13 @@ const getGroupByEle = (
|
||||
const groupByValues: string[] = [];
|
||||
|
||||
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 (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -61,10 +63,26 @@ function K8sDeploymentsList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
@@ -117,9 +135,16 @@ function K8sDeploymentsList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sDeploymentsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -179,6 +204,8 @@ function K8sDeploymentsList({
|
||||
queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -187,8 +214,10 @@ function K8sDeploymentsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
K8sEntityToAggregateAttributeMapping[K8sCategory.DEPLOYMENTS],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DEPLOYMENTS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -229,6 +258,8 @@ function K8sDeploymentsList({
|
||||
queryKey: ['deploymentList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
@@ -315,7 +346,11 @@ function K8sDeploymentsList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -325,7 +360,8 @@ function K8sDeploymentsList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -226,6 +226,12 @@ export const getK8sDeploymentsListColumns = (
|
||||
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 = (
|
||||
deployment: K8sDeploymentsData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -233,9 +239,14 @@ const getGroupByEle = (
|
||||
const groupByValues: string[] = [];
|
||||
|
||||
groupBy.forEach((group) => {
|
||||
groupByValues.push(
|
||||
deployment.meta[group.key as keyof typeof deployment.meta],
|
||||
);
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// 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 (
|
||||
|
||||
@@ -259,7 +259,7 @@ export default function Events({
|
||||
</div>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
|
||||
@@ -86,7 +86,7 @@ function EntityLogsDetailedView({
|
||||
</div>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
|
||||
@@ -23,12 +23,15 @@ import {
|
||||
} from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Options } from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
|
||||
interface EntityMetricsProps<T> {
|
||||
timeRange: {
|
||||
startTime: number;
|
||||
@@ -49,6 +52,7 @@ interface EntityMetricsProps<T> {
|
||||
node: T,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
) => GetQueryResultsProps[];
|
||||
queryKey: string;
|
||||
category: K8sCategory;
|
||||
@@ -65,9 +69,25 @@ function EntityMetrics<T>({
|
||||
queryKey,
|
||||
category,
|
||||
}: EntityMetricsProps<T>): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
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(
|
||||
@@ -94,6 +114,45 @@ function EntityMetrics<T>({
|
||||
[queries],
|
||||
);
|
||||
|
||||
const [graphTimeIntervals, setGraphTimeIntervals] = useState<
|
||||
{
|
||||
start: number;
|
||||
end: number;
|
||||
}[]
|
||||
>(
|
||||
new Array(queries.length).fill({
|
||||
start: timeRange.startTime,
|
||||
end: timeRange.endTime,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setGraphTimeIntervals(
|
||||
new Array(queries.length).fill({
|
||||
start: timeRange.startTime,
|
||||
end: timeRange.endTime,
|
||||
}),
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timeRange]);
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number, graphIndex: number) => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
setGraphTimeIntervals((prev) => {
|
||||
const newIntervals = [...prev];
|
||||
newIntervals[graphIndex] = {
|
||||
start: Math.floor(startTimestamp / 1000),
|
||||
end: Math.floor(endTimestamp / 1000),
|
||||
};
|
||||
return newIntervals;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
queries.map(({ data }, idx) => {
|
||||
@@ -108,9 +167,9 @@ function EntityMetrics<T>({
|
||||
yAxisUnit: entityWidgetInfo[idx].yAxisUnit,
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
minTimeScale: timeRange.startTime,
|
||||
maxTimeScale: timeRange.endTime,
|
||||
enableZoom: true,
|
||||
minTimeScale: graphTimeIntervals[idx].start,
|
||||
maxTimeScale: graphTimeIntervals[idx].end,
|
||||
onDragSelect: (start, end) => onDragSelect(start, end, idx),
|
||||
});
|
||||
}),
|
||||
[
|
||||
@@ -118,8 +177,8 @@ function EntityMetrics<T>({
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
entityWidgetInfo,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
graphTimeIntervals,
|
||||
onDragSelect,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ function EntityTraces({
|
||||
</div>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
isModalTimeSelection={isModalTimeSelection}
|
||||
|
||||
@@ -26,19 +26,21 @@ import { useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import K8sClustersList from './Clusters/K8sClustersList';
|
||||
import {
|
||||
ClustersQuickFiltersConfig,
|
||||
DaemonSetsQuickFiltersConfig,
|
||||
DeploymentsQuickFiltersConfig,
|
||||
GetClustersQuickFiltersConfig,
|
||||
GetDaemonsetsQuickFiltersConfig,
|
||||
GetDeploymentsQuickFiltersConfig,
|
||||
GetJobsQuickFiltersConfig,
|
||||
GetNamespaceQuickFiltersConfig,
|
||||
GetNodesQuickFiltersConfig,
|
||||
GetPodsQuickFiltersConfig,
|
||||
GetStatefulsetsQuickFiltersConfig,
|
||||
GetVolumesQuickFiltersConfig,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
JobsQuickFiltersConfig,
|
||||
K8sCategories,
|
||||
NamespaceQuickFiltersConfig,
|
||||
NodesQuickFiltersConfig,
|
||||
PodsQuickFiltersConfig,
|
||||
StatefulsetsQuickFiltersConfig,
|
||||
VolumesQuickFiltersConfig,
|
||||
} from './constants';
|
||||
import K8sDaemonSetsList from './DaemonSets/K8sDaemonSetsList';
|
||||
import K8sDeploymentsList from './Deployments/K8sDeploymentsList';
|
||||
@@ -74,6 +76,11 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const handleFilterChange = (query: Query): void => {
|
||||
// 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
|
||||
@@ -109,7 +116,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={PodsQuickFiltersConfig}
|
||||
config={GetPodsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -129,7 +136,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={NodesQuickFiltersConfig}
|
||||
config={GetNodesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -152,7 +159,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={NamespaceQuickFiltersConfig}
|
||||
config={GetNamespaceQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -172,7 +179,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={ClustersQuickFiltersConfig}
|
||||
config={GetClustersQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -192,7 +199,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={DeploymentsQuickFiltersConfig}
|
||||
config={GetDeploymentsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -212,7 +219,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={JobsQuickFiltersConfig}
|
||||
config={GetJobsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -232,7 +239,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={DaemonSetsQuickFiltersConfig}
|
||||
config={GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -255,7 +262,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={StatefulsetsQuickFiltersConfig}
|
||||
config={GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -275,7 +282,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={VolumesQuickFiltersConfig}
|
||||
config={GetVolumesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
|
||||
@@ -30,395 +30,415 @@ export const getJobMetricsQueryPayload = (
|
||||
job: K8sJobsData,
|
||||
start: number,
|
||||
end: number,
|
||||
): GetQueryResultsProps[] => [
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
query: {
|
||||
builder: {
|
||||
queryData: [
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.utilization'
|
||||
: 'k8s_pod_cpu_utilization';
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? '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,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
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',
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
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: [
|
||||
id: v4(),
|
||||
promql: [
|
||||
{
|
||||
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,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
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',
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
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,
|
||||
},
|
||||
variables: {},
|
||||
formatForWeb: false,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
query: {
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
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: 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,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
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',
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
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: [
|
||||
id: v4(),
|
||||
promql: [
|
||||
{
|
||||
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,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
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',
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
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,
|
||||
},
|
||||
variables: {},
|
||||
formatForWeb: false,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
];
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
graphType: PANEL_TYPES.TIME_SERIES,
|
||||
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,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -29,11 +29,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -60,11 +62,26 @@ function K8sJobsList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
@@ -112,9 +129,16 @@ function K8sJobsList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sJobsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -168,10 +192,15 @@ function K8sJobsList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sJobsList(fetchGroupedByRowDataQuery as K8sJobsListPayload, {
|
||||
queryKey: ['jobList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sJobsList(
|
||||
fetchGroupedByRowDataQuery as K8sJobsListPayload,
|
||||
{
|
||||
queryKey: ['jobList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -179,7 +208,10 @@ function K8sJobsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.JOBS],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.JOBS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -225,6 +257,8 @@ function K8sJobsList({
|
||||
queryKey: ['jobList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const jobsData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
@@ -300,7 +334,11 @@ function K8sJobsList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -310,7 +348,8 @@ function K8sJobsList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -263,6 +263,12 @@ export const getK8sJobsListColumns = (
|
||||
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 = (
|
||||
job: K8sJobsData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -270,7 +276,13 @@ const getGroupByEle = (
|
||||
const groupByValues: string[] = [];
|
||||
|
||||
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 (
|
||||
|
||||
@@ -136,7 +136,7 @@ function K8sHeader({
|
||||
|
||||
<div className="k8s-list-controls-right">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
/>
|
||||
|
||||
@@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -60,11 +62,26 @@ function K8sNamespacesList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
@@ -116,9 +133,16 @@ function K8sNamespacesList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sNamespacesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -178,6 +202,8 @@ function K8sNamespacesList({
|
||||
queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -186,8 +212,10 @@ function K8sNamespacesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute:
|
||||
K8sEntityToAggregateAttributeMapping[K8sCategory.NAMESPACES],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NAMESPACES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -228,6 +256,8 @@ function K8sNamespacesList({
|
||||
queryKey: ['namespaceList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const namespacesData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
@@ -312,7 +342,11 @@ function K8sNamespacesList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -322,7 +356,8 @@ function K8sNamespacesList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -122,6 +122,11 @@ export const getK8sNamespacesListColumns = (
|
||||
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 = (
|
||||
namespace: K8sNamespacesData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -129,7 +134,13 @@ const getGroupByEle = (
|
||||
const groupByValues: string[] = [];
|
||||
|
||||
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 (
|
||||
|
||||
@@ -28,11 +28,13 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
@@ -59,12 +61,26 @@ function K8sNodesList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
@@ -111,9 +127,16 @@ function K8sNodesList({
|
||||
|
||||
// Reset pagination every time quick filters are changed
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sNodesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -167,10 +190,15 @@ function K8sNodesList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sNodesList(fetchGroupedByRowDataQuery as K8sNodesListPayload, {
|
||||
queryKey: ['nodeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sNodesList(
|
||||
fetchGroupedByRowDataQuery as K8sNodesListPayload,
|
||||
{
|
||||
queryKey: ['nodeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -178,7 +206,10 @@ function K8sNodesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: K8sEntityToAggregateAttributeMapping[K8sCategory.NODES],
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NODES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -224,6 +255,8 @@ function K8sNodesList({
|
||||
queryKey: ['nodeList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
@@ -299,7 +332,11 @@ function K8sNodesList({
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
handleChangeQueryData('filters', value);
|
||||
setCurrentPage(1);
|
||||
if (filtersInitialised) {
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setFiltersInitialised(true);
|
||||
}
|
||||
|
||||
if (value.items.length > 0) {
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
@@ -309,7 +346,8 @@ function K8sNodesList({
|
||||
});
|
||||
}
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user