mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-22 18:00:25 +01:00
Compare commits
29 Commits
refactor/i
...
feat/insta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3611c10c79 | ||
|
|
ec79eab7ca | ||
|
|
40811b4cad | ||
|
|
3ab7ab612c | ||
|
|
3d9ab97856 | ||
|
|
4e6384c0c1 | ||
|
|
b05d16242a | ||
|
|
fb2edfd770 | ||
|
|
34a2767396 | ||
|
|
43c72b8f4d | ||
|
|
95be5f12f3 | ||
|
|
ce141576d6 | ||
|
|
fab7fd001f | ||
|
|
02634894d9 | ||
|
|
83f1070b8a | ||
|
|
147005077c | ||
|
|
d720f76819 | ||
|
|
d76e15d444 | ||
|
|
dc1a5cce76 | ||
|
|
37c97528f3 | ||
|
|
6a8e6e94e9 | ||
|
|
5c480272f3 | ||
|
|
ab26fd3d8c | ||
|
|
80c0801b2e | ||
|
|
bd190b8d88 | ||
|
|
7e5f5bfac4 | ||
|
|
7f72ca19d3 | ||
|
|
231229d73e | ||
|
|
bb88cde296 |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -118,9 +118,6 @@ go.mod @therealpandey
|
||||
|
||||
/tests/integration/ @therealpandey
|
||||
|
||||
# e2e tests
|
||||
/tests/e2e/ @AshwinBhatkal
|
||||
|
||||
# Flagger Owners
|
||||
|
||||
/pkg/flagger/ @therealpandey
|
||||
@@ -165,7 +162,3 @@ go.mod @therealpandey
|
||||
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend
|
||||
/frontend/src/components/NewSelect/ @SigNoz/pulse-frontend
|
||||
|
||||
## Dashboard V2
|
||||
/frontend/src/pages/DashboardPageV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/DashboardsListPageV2/ @SigNoz/pulse-frontend
|
||||
|
||||
@@ -115,7 +115,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
||||
return querier.NewHandler(ps, q, a)
|
||||
},
|
||||
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
func(_ sqlstore.SQLStore, _ dashboard.Module, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
return implcloudintegration.NewModule(), nil
|
||||
},
|
||||
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
|
||||
|
||||
@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
communityHandler := querier.NewHandler(ps, q, a)
|
||||
return eequerier.NewHandler(ps, q, communityHandler)
|
||||
},
|
||||
func(sqlStore sqlstore.SQLStore, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
func(sqlStore sqlstore.SQLStore, dashboardModule dashboard.Module, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
defStore := pkgcloudintegration.NewServiceDefinitionStore()
|
||||
awsCloudProviderModule, err := implcloudprovider.NewAWSCloudProvider(defStore)
|
||||
if err != nil {
|
||||
@@ -179,7 +179,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
|
||||
}
|
||||
|
||||
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
|
||||
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), dashboardModule, global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
|
||||
},
|
||||
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
|
||||
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
|
||||
|
||||
@@ -54,11 +54,6 @@ func (provider *awscloudprovider) GetServiceDefinition(ctx context.Context, serv
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAWS, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,6 @@ func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, se
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id.
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package implcloudintegration
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
type module struct {
|
||||
store cloudintegrationtypes.Store
|
||||
dashboardModule dashboard.Module
|
||||
gateway gateway.Gateway
|
||||
zeus zeus.Zeus
|
||||
licensing licensing.Licensing
|
||||
@@ -34,6 +35,7 @@ type module struct {
|
||||
|
||||
func NewModule(
|
||||
store cloudintegrationtypes.Store,
|
||||
dashboardModule dashboard.Module,
|
||||
global global.Global,
|
||||
zeus zeus.Zeus,
|
||||
gateway gateway.Gateway,
|
||||
@@ -44,6 +46,7 @@ func NewModule(
|
||||
) (cloudintegration.Module, error) {
|
||||
return &module{
|
||||
store: store,
|
||||
dashboardModule: dashboardModule,
|
||||
global: global,
|
||||
zeus: zeus,
|
||||
gateway: gateway,
|
||||
@@ -254,7 +257,41 @@ func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID,
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
services, err := module.store.ListServices(ctx, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sharedServices, err := module.store.ListSharedServices(ctx, orgID, provider, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, svc := range services {
|
||||
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !svcCfg.IsMetricsEnabled(provider) {
|
||||
continue
|
||||
}
|
||||
|
||||
if isServiceSharedWithMetricsEnabled(provider, sharedServices[svc.Type]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := module.deprovisionDashboards(ctx, orgID, provider, svc.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := module.store.DeleteServicesByCloudIntegrationID(ctx, orgID, accountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
|
||||
})
|
||||
}
|
||||
|
||||
func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) ([]*cloudintegrationtypes.ServiceMetadata, error) {
|
||||
@@ -331,12 +368,16 @@ func (module *module) GetService(ctx context.Context, orgID valuer.UUID, service
|
||||
|
||||
integrationService = cloudintegrationtypes.NewCloudIntegrationServiceFromStorable(storedService, serviceConfig)
|
||||
}
|
||||
|
||||
if err := module.enrichDashboardIDs(ctx, orgID, provider, serviceID, serviceDefinition); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewService(*serviceDefinition, integrationService), nil
|
||||
}
|
||||
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -357,10 +398,21 @@ func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, serv
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.CreateService(ctx, cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON)))
|
||||
metricsEnabled := service.Config.IsMetricsEnabled(provider)
|
||||
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON))
|
||||
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.CreateService(ctx, storableService); err != nil {
|
||||
return err
|
||||
}
|
||||
if metricsEnabled {
|
||||
return module.provisionDashboards(ctx, orgID, createdBy, creator, provider, service, serviceDefinition)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
@@ -381,43 +433,28 @@ func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, inte
|
||||
return err
|
||||
}
|
||||
|
||||
metricsEnabled := integrationService.Config.IsMetricsEnabled(provider)
|
||||
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(integrationService, string(configJSON))
|
||||
|
||||
return module.store.UpdateService(ctx, storableService)
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
_, _, _, err = cloudintegrationtypes.ParseCloudIntegrationDashboardID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allDashboards, err := module.listDashboards(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range allDashboards {
|
||||
if d.ID == id {
|
||||
return d, nil
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.UpdateService(ctx, storableService); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration dashboard not found")
|
||||
}
|
||||
if metricsEnabled {
|
||||
return module.provisionDashboards(ctx, orgID, createdBy, creator, provider, integrationService, serviceDefinition)
|
||||
}
|
||||
|
||||
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
sharedServices, err := module.store.ListSharedServices(ctx, orgID, provider, integrationService.CloudIntegrationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isServiceSharedWithMetricsEnabled(provider, sharedServices[integrationService.Type]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return module.listDashboards(ctx, orgID)
|
||||
return module.deprovisionDashboards(ctx, orgID, provider, integrationService.Type)
|
||||
})
|
||||
}
|
||||
|
||||
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
@@ -493,52 +530,89 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
|
||||
return factorAPIKey.Key, nil
|
||||
}
|
||||
|
||||
func (module *module) listDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
var allDashboards []*dashboardtypes.Dashboard
|
||||
// provisionDashboards creates dashboard and integration_dashboard rows for each dashboard in the service definition.
|
||||
// Must be called within a transaction (ctx carries the tx).
|
||||
func (module *module) provisionDashboards(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, provider cloudintegrationtypes.CloudProviderType, service *cloudintegrationtypes.CloudIntegrationService, serviceDefinition *cloudintegrationtypes.ServiceDefinition) error {
|
||||
// TODO: DB calls are in for loop, can be optimized later.
|
||||
for _, dashboard := range serviceDefinition.Assets.Dashboards {
|
||||
slug := cloudintegrationtypes.IntegrationDashboardSlug(provider, service.Type, dashboard.ID)
|
||||
|
||||
for provider := range module.cloudProvidersMap {
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
existing, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
if existing != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connectedAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
|
||||
createdDashboard, err := module.dashboardModule.Create(ctx, orgID, createdBy, creator, dashboardtypes.SourceIntegration, dashboardtypes.PostableDashboard(dashboard.Definition))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
for _, storableAccount := range connectedAccounts {
|
||||
storedServices, err := module.store.ListServices(ctx, storableAccount.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, storedSvc := range storedServices {
|
||||
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedSvc.Config)
|
||||
if err != nil || !serviceConfig.IsMetricsEnabled(provider) {
|
||||
continue
|
||||
}
|
||||
|
||||
svcDef, err := cloudProvider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil || svcDef == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dashboards := cloudintegrationtypes.GetDashboardsFromAssets(
|
||||
storedSvc.Type.StringValue(),
|
||||
orgID,
|
||||
provider,
|
||||
storableAccount.CreatedAt,
|
||||
svcDef.Assets,
|
||||
)
|
||||
allDashboards = append(allDashboards, dashboards...)
|
||||
}
|
||||
integrationDashboard := cloudintegrationtypes.NewStorableIntegrationDashboard(createdDashboard.ID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err := module.store.CreateIntegrationDashboard(ctx, integrationDashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(allDashboards, func(i, j int) bool {
|
||||
return allDashboards[i].ID < allDashboards[j].ID
|
||||
})
|
||||
|
||||
return allDashboards, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// deprovisionDashboards deletes all dashboard and integration_dashboard rows for the given service.
|
||||
// make sure to call this within a transaction.
|
||||
func (module *module) deprovisionDashboards(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, serviceID cloudintegrationtypes.ServiceID) error {
|
||||
slugPrefix := cloudintegrationtypes.IntegrationDashboardSlugPrefix(provider, serviceID)
|
||||
rows, err := module.store.ListIntegrationDashboardsBySlugPrefix(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slugPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
dashID, err := valuer.NewUUID(row.DashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.store.DeleteIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, row.Slug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.dashboardModule.DeleteUnsafe(ctx, orgID, dashID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isServiceSharedWithMetricsEnabled returns true if any of the provided services has metrics enabled.
|
||||
// It is used to determine whether dashboards for a service type should be deprovisioned when
|
||||
// an account is disconnected or a service is updated.
|
||||
func isServiceSharedWithMetricsEnabled(provider cloudintegrationtypes.CloudProviderType, services []*cloudintegrationtypes.StorableCloudIntegrationService) bool {
|
||||
for _, svc := range services {
|
||||
cfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if cfg.IsMetricsEnabled(provider) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// enrichDashboardIDs replaces the raw dashboard name in each Dashboard.ID with the provisioned UUID,
|
||||
// or sets it to nil if the dashboard has not been provisioned yet.
|
||||
func (module *module) enrichDashboardIDs(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, serviceID cloudintegrationtypes.ServiceID, serviceDefinition *cloudintegrationtypes.ServiceDefinition) error {
|
||||
for i, d := range serviceDefinition.Assets.Dashboards {
|
||||
slug := cloudintegrationtypes.IntegrationDashboardSlug(provider, serviceID, d.ID)
|
||||
row, err := module.store.GetIntegrationDashboardBySlug(ctx, orgID, cloudintegrationtypes.IntegrationDashboardProviderCloudIntegration, slug)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
serviceDefinition.Assets.Dashboards[i].ID = row.DashboardID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -162,24 +162,11 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "dashboard is locked, please unlock the dashboard to be delete it")
|
||||
}
|
||||
|
||||
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
err := module.store.DeletePublic(ctx, id.String())
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
return module.delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
err = module.store.Delete(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (module *module) DeleteUnsafe(ctx context.Context, orgID, id valuer.UUID) error {
|
||||
return module.delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
@@ -221,8 +208,8 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, data)
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.Create(ctx, orgID, createdBy, creator, source, data)
|
||||
}
|
||||
|
||||
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
|
||||
@@ -244,3 +231,12 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) delete(ctx context.Context, orgID, id valuer.UUID) error {
|
||||
return module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := module.store.DeletePublic(ctx, id.String()); err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return err
|
||||
}
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
// initiate opamp
|
||||
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
|
||||
|
||||
integrationsController, err := integrations.NewController(signoz.SQLStore)
|
||||
integrationsController, err := integrations.NewController(signoz.SQLStore, signoz.Modules.Dashboard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"couldn't create integrations controller: %w", err,
|
||||
|
||||
@@ -18,7 +18,6 @@ const BANNED_COMPONENTS = {
|
||||
'Use @signozhq/ui/typography Typography instead of antd Typography.',
|
||||
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
|
||||
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
|
||||
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -51,6 +51,13 @@
|
||||
background: var(--l1-background);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
.ant-progress-bg {
|
||||
height: 8px !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ import {
|
||||
Flex,
|
||||
Input,
|
||||
InputRef,
|
||||
Progress,
|
||||
Space,
|
||||
Spin,
|
||||
TableColumnsType,
|
||||
TableColumnType,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { FilterDropdownProps } from 'antd/lib/table/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -59,7 +59,7 @@ function ProgressRender(item: string | number): JSX.Element {
|
||||
<Progress
|
||||
percent={percent}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const cpuPercent = percent;
|
||||
if (cpuPercent >= 90) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Card, Form } from 'antd';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { X } from '@signozhq/icons';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable sonarjs/no-identical-functions */
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Checkbox, Skeleton } from 'antd';
|
||||
import { Button, Checkbox, Input, Skeleton } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
|
||||
|
||||
@@ -45,10 +45,6 @@
|
||||
.contributors-row {
|
||||
height: 80px;
|
||||
}
|
||||
.top-contributors-progress {
|
||||
--progress-background: transparent;
|
||||
}
|
||||
|
||||
&__content {
|
||||
.ant-table {
|
||||
&-cell {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { HTMLAttributes } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Table, TableColumnsType as ColumnsType } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, Table, TableColumnsType as ColumnsType } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
|
||||
import AlertLabels from 'pages/AlertDetails/AlertHeader/AlertLabels/AlertLabels';
|
||||
@@ -52,8 +51,8 @@ function TopContributorsRows({
|
||||
<Progress
|
||||
percent={(count / totalCurrentTriggers) * 100}
|
||||
showInfo={false}
|
||||
trailColor="rgba(255, 255, 255, 0)"
|
||||
strokeColor={Color.BG_ROBIN_500}
|
||||
className="top-contributors-progress"
|
||||
/>
|
||||
</ConditionalAlertPopover>
|
||||
),
|
||||
|
||||
@@ -141,9 +141,12 @@
|
||||
|
||||
.progress-container {
|
||||
width: 158px;
|
||||
.ant-progress {
|
||||
margin: 0;
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
.ant-progress-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, Skeleton, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
@@ -137,11 +136,12 @@ function DomainMetrics({
|
||||
<Tooltip title={formattedDomainMetricsData.errorRate}>
|
||||
{formattedDomainMetricsData.errorRate !== '-' ? (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(2),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number(
|
||||
Number(formattedDomainMetricsData.errorRate).toFixed(2),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, Skeleton, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
getDisplayValue,
|
||||
@@ -81,9 +80,10 @@ function EndPointMetrics({
|
||||
<Tooltip title={metricsData?.errorRate}>
|
||||
{metricsData?.errorRate !== '-' ? (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(Number(metricsData?.errorRate ?? 0).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number(
|
||||
Number(metricsData?.errorRate ?? 0).toFixed(2),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import { convertFiltersToExpressionWithExistingQuery } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
FiltersType,
|
||||
@@ -258,9 +257,10 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
|
||||
errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate;
|
||||
return (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number((errorRateValue as number).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number((errorRateValue as number).toFixed(2));
|
||||
if (errorRatePercent >= 90) {
|
||||
@@ -1022,13 +1022,14 @@ export const getEndPointsColumnsConfig = (
|
||||
className: `column`,
|
||||
render: (errorRate: number | string): React.ReactNode => (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(
|
||||
(
|
||||
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
|
||||
).toFixed(1),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number((errorRate as number).toFixed(1));
|
||||
if (errorRatePercent >= 90) {
|
||||
@@ -2513,9 +2514,10 @@ export const dependentServicesColumns: ColumnType<DependentServicesData>[] = [
|
||||
render: (errorPercentage: number | string): React.ReactNode =>
|
||||
errorPercentage !== '-' ? (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number((errorPercentage as number).toFixed(2))}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorPercentagePercent = Number(
|
||||
(errorPercentage as number).toFixed(2),
|
||||
@@ -3020,13 +3022,14 @@ export const getAllEndpointsWidgetData = (
|
||||
),
|
||||
F1: (errorRate: any): ReactNode => (
|
||||
<Progress
|
||||
status="active"
|
||||
percent={Number(
|
||||
(
|
||||
(errorRate === 'n/a' || errorRate === '-' ? 0 : errorRate) as number
|
||||
).toFixed(2),
|
||||
)}
|
||||
strokeLinecap="butt"
|
||||
showInfo
|
||||
size="small"
|
||||
strokeColor={((): string => {
|
||||
const errorRatePercent = Number(
|
||||
(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Select, Tooltip } from 'antd';
|
||||
import { Button, Input, Select, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { CircleX, Trash } from '@signozhq/icons';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Collapse } from 'antd';
|
||||
import { Collapse, Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Input, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
|
||||
import './TimeInput.scss';
|
||||
|
||||
export interface TimeInputProps {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Input, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
|
||||
@@ -16,8 +16,7 @@ import {
|
||||
Plus,
|
||||
X,
|
||||
} from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Modal, Popover, Tag, Tooltip } from 'antd';
|
||||
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Button, Input } from 'antd';
|
||||
import { PrecisionOption, PrecisionOptionsEnum } from 'components/Graph/types';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
|
||||
@@ -19,11 +19,11 @@ import {
|
||||
Info,
|
||||
} from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
Divider,
|
||||
Input,
|
||||
Modal,
|
||||
RefSelectProps,
|
||||
Select,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { EmailChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
function EmailForm({ setSelectedConfig }: EmailFormProps): JSX.Element {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
|
||||
import { WebhookChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form, FormInstance, Input, Select } from 'antd';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Form, FormInstance, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { Store } from 'antd/lib/form/interface';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
@@ -6,8 +6,7 @@ import { useIsFetching } from 'react-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form, Modal } from 'antd';
|
||||
import { Button, Form, Input, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -39,5 +39,7 @@
|
||||
|
||||
width: 100% !important;
|
||||
|
||||
--progress-width: 100%;
|
||||
.ant-progress-steps-outer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress } from 'antd';
|
||||
|
||||
import { ChecklistItem } from '../HomeChecklist/HomeChecklist';
|
||||
|
||||
@@ -15,7 +15,9 @@ function StepsProgress({
|
||||
|
||||
const totalChecklistItems = checklistItems.length;
|
||||
|
||||
const progress = (completedChecklistItems.length / totalChecklistItems) * 100;
|
||||
const progress = Math.round(
|
||||
(completedChecklistItems.length / totalChecklistItems) * 100,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="steps-progress-container">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, Tag } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
getHostLists,
|
||||
@@ -80,8 +79,8 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
|
||||
render: (value): React.ReactNode => (
|
||||
<Progress
|
||||
percent={Number(Number(value).toFixed(1))}
|
||||
size="small"
|
||||
strokeColor={getProgressColor(Number(value))}
|
||||
showInfo
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -91,8 +90,8 @@ export const hostDetailsMetadataConfig: K8sDetailsMetadataConfig<HostData>[] = [
|
||||
render: (value): React.ReactNode => (
|
||||
<Progress
|
||||
percent={Number(Number(value).toFixed(1))}
|
||||
size="small"
|
||||
strokeColor={getMemoryProgressColor(Number(value))}
|
||||
showInfo
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -60,6 +60,11 @@
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.ant-progress-bg) {
|
||||
height: 8px !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
|
||||
@@ -103,8 +103,12 @@
|
||||
.progress-container {
|
||||
width: 158px;
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
.ant-progress {
|
||||
margin: 0;
|
||||
|
||||
.ant-progress-text {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +292,10 @@
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
.ant-progress-bg {
|
||||
height: 8px !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress } from 'antd';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import {
|
||||
getMemoryProgressColor,
|
||||
@@ -53,6 +53,7 @@ export function EntityProgressBar({
|
||||
<Progress
|
||||
percent={percentage}
|
||||
strokeLinecap="butt"
|
||||
size="small"
|
||||
status="normal"
|
||||
strokeColor={getStrokeColor(type, value)}
|
||||
className={styles.progressBar}
|
||||
|
||||
@@ -5,12 +5,12 @@ import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Col,
|
||||
Collapse,
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Row,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
function RenderConnectionFields({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form } from 'antd';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import apply from 'api/v3/licenses/post';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { Button, Input, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import ApacheIcon from 'assets/CustomIcons/ApacheIcon';
|
||||
import DockerIcon from 'assets/CustomIcons/DockerIcon';
|
||||
|
||||
@@ -12,11 +12,11 @@ import { useTranslation } from 'react-i18next';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Input,
|
||||
MenuProps,
|
||||
Modal,
|
||||
Popover,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LoaderCircle, Check } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Space } from 'antd';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
|
||||
@@ -2,9 +2,8 @@ import { ReactNode, useState } from 'react';
|
||||
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Collapse, Divider, Tag } from 'antd';
|
||||
import { Collapse, Divider, Input, Tag } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
|
||||
@@ -2,8 +2,7 @@ import { ChangeEvent, useCallback, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CirclePlus, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Col } from 'antd';
|
||||
import { Col, Input } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SquareX, X } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Select } from 'antd';
|
||||
import { Button, Input, Select } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
// TODO(@signozhq/ui-input): migrate this <Input> once @signozhq/ui Input
|
||||
// supports the `onWheel` handler (used to blur on scroll for number inputs).
|
||||
import { Input, Select } from 'antd';
|
||||
import { Select } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { TIME_AGGREGATION_OPTIONS } from './constants';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import type { TableColumnsType as ColumnsType } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Collapse, Select, Spin } from 'antd';
|
||||
import { Button, Collapse, Input, Select, Spin } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
|
||||
@@ -142,6 +142,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
.ant-progress-bg {
|
||||
height: 8px !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
DropResult,
|
||||
} from 'react-beautiful-dnd';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Divider, Dropdown, MenuProps, Tooltip } from 'antd';
|
||||
import { Button, Divider, Dropdown, Input, MenuProps, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { FieldDataType } from 'api/v5/v5';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
// TODO(@signozhq/ui-input): migrate <Input> once @signozhq/ui Input
|
||||
// supports the `spellCheck` prop on the URL input below.
|
||||
import { Button, Col, Form, Input, Input as AntInput, Row } from 'antd';
|
||||
import { Button, Col, Form, Input as AntInput, Input, Row } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { CONTEXT_LINK_FIELDS } from 'container/NewWidget/RightContainer/ContextLinks/constants';
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Blocks, Check, LoaderCircle } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Form, Select, Space } from 'antd';
|
||||
import { Button, Card, Form, Input, Select, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check, Server, LoaderCircle } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Form, Space } from 'antd';
|
||||
import { Button, Card, Form, Input, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Trash2 } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form, FormInstance, Select, Space } from 'antd';
|
||||
import { Button, Form, FormInstance, Input, Select, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
|
||||
|
||||
@@ -87,7 +87,12 @@
|
||||
|
||||
.service-progress-indicator {
|
||||
width: fit-content;
|
||||
--progress-width: 30px;
|
||||
margin-inline-end: 0px !important;
|
||||
margin-bottom: 0px !important;
|
||||
|
||||
.ant-progress-inner {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.percent-value {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { Progress } from '@signozhq/ui/progress';
|
||||
import { Progress, Skeleton, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { AxiosError } from 'axios';
|
||||
import Spinner from 'components/Spinner';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { formValidationRules } from '../../config';
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { ChangeEventHandler, useState } from 'react';
|
||||
// TODO(@signozhq/ui-input): migrate to @signozhq/ui Input once the antd
|
||||
// `InputProps` spread (`size`, etc.) is no longer needed on this wrapper.
|
||||
import { Input, InputProps } from 'antd';
|
||||
|
||||
function CSVInput({ value, onChange, ...otherProps }: InputProps): JSX.Element {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Info } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Flex, Form, Space, Tooltip } from 'antd';
|
||||
import { Flex, Form, Input, Space, Tooltip } from 'antd';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { PREDEFINED_MAPPING } from '../config';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form, Input, Select, Space } from 'antd';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Form, Select, Space } from 'antd';
|
||||
import { ModalFooterTitle } from 'container/PipelinePage/styles';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Plus, Search } from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Flex, Form, Tooltip } from 'antd';
|
||||
import { Button, Flex, Form, Input, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
useDeleteDowntimeScheduleByID,
|
||||
|
||||
@@ -311,7 +311,7 @@ export function PlannedDowntimeForm(
|
||||
default:
|
||||
return `Scheduled for ${formattedStartDate} starting at ${formattedStartTime}.`;
|
||||
}
|
||||
}, [formData, recurrenceType, timezone]);
|
||||
}, [formData, recurrenceType]);
|
||||
|
||||
const endTimeText = useMemo((): string => {
|
||||
const endTime = formData.endTime;
|
||||
@@ -322,7 +322,7 @@ export function PlannedDowntimeForm(
|
||||
const formattedEndTime = endTime.format(TIME_FORMAT);
|
||||
const formattedEndDate = endTime.format(DATE_FORMAT);
|
||||
return `Scheduled to end maintenance on ${formattedEndDate} at ${formattedEndTime}.`;
|
||||
}, [formData, recurrenceType, timezone]);
|
||||
}, [formData, recurrenceType]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Skeleton } from 'antd';
|
||||
import { Input, Skeleton } from 'antd';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Plus, Search } from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Flex, Tooltip } from 'antd';
|
||||
import { Button, Flex, Input, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Collapse, Modal } from 'antd';
|
||||
import { Collapse, Input, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { Diamond } from '@signozhq/icons';
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Skeleton,
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-15m"
|
||||
onClick={(): void => onSelect('15m')}
|
||||
>
|
||||
15m
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-6h"
|
||||
onClick={(): void => onSelect('6h')}
|
||||
>
|
||||
6h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-custom"
|
||||
onClick={(): void => onSelect('custom')}
|
||||
>
|
||||
Custom
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Edge Cases', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Fresh Params at Navigation Time (Core Fix)', () => {
|
||||
it('should read params at navigation time, not render time', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=30m"
|
||||
onUrlUpdate={(event): void => {
|
||||
currentSearchParams = event.searchParams;
|
||||
}}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&externalParam=addedLater',
|
||||
);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('externalParam=addedLater');
|
||||
});
|
||||
|
||||
it('should preserve multiple externally added params', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&yAxisUnit=bytes&groupBy=host&view=table',
|
||||
);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-6h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
expect(navigatedUrl).toContain('yAxisUnit=bytes');
|
||||
expect(navigatedUrl).toContain('groupBy=host');
|
||||
expect(navigatedUrl).toContain('view=table');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Empty and Special Values', () => {
|
||||
it('should handle empty URL params gracefully', async () => {
|
||||
currentSearchParams = new URLSearchParams('');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-15m'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=15m');
|
||||
});
|
||||
|
||||
it('should handle special characters in preserved params', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&filter=name%3D%22test%22',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&filter=name%3D%22test%22">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('filter=');
|
||||
});
|
||||
|
||||
it('should not navigate when selecting custom (opens picker instead)', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-custom'));
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const customNavigationCalls = mockSafeNavigate.mock.calls.filter((call) => {
|
||||
const url = call[0] as string;
|
||||
return url.includes('startTime=') || url.includes('endTime=');
|
||||
});
|
||||
|
||||
expect(customNavigationCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,180 +0,0 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
|
||||
null;
|
||||
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
|
||||
null;
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
onCustomDateHandler,
|
||||
onValidCustomDateChange,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
onCustomDateHandler?: (range: [unknown, unknown]) => void;
|
||||
onValidCustomDateChange?: (data: { timeStr: string }) => void;
|
||||
}): JSX.Element => {
|
||||
mockOnCustomDateHandler = onCustomDateHandler || null;
|
||||
mockOnValidCustomDateChange = onValidCustomDateChange || null;
|
||||
|
||||
return (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Modal Mode', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
mockOnCustomDateHandler = null;
|
||||
mockOnValidCustomDateChange = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
it('should call onTimeChange instead of navigating for relative time', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith('1h');
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onTimeChange with custom and timestamps for custom date', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = { toDate: (): Date => new Date(1700000000000) };
|
||||
const endMoment = { toDate: (): Date => new Date(1700003600000) };
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith(
|
||||
'custom',
|
||||
[1700000000000, 1700003600000],
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onTimeChange for valid custom date string in modal', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnValidCustomDateChange?.({ timeStr: '4h' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith('4h');
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,207 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, Route } from 'react-router-dom';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import store from 'store';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { refetchOnWindowFocus: false, retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
function NuqsParamSetter({ paramValue }: { paramValue: string }): JSX.Element {
|
||||
const [, setYAxisUnit] = useQueryState(
|
||||
'yAxisUnit',
|
||||
parseAsString.withDefault(''),
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="set-nuqs-param"
|
||||
onClick={(): void => {
|
||||
setYAxisUnit(paramValue);
|
||||
}}
|
||||
>
|
||||
Set yAxisUnit
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
initialSearchParams?: string;
|
||||
initialPath?: string;
|
||||
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
|
||||
}
|
||||
|
||||
function TestWrapper({
|
||||
children,
|
||||
initialSearchParams = '',
|
||||
initialPath = '/services',
|
||||
onUrlUpdate,
|
||||
}: WrapperProps): JSX.Element {
|
||||
const initialEntry = initialSearchParams
|
||||
? `${initialPath}?${initialSearchParams}`
|
||||
: initialPath;
|
||||
|
||||
const mockedStore = mockStore({
|
||||
...store.getState(),
|
||||
app: {
|
||||
...store.getState().app,
|
||||
role: 'ADMIN',
|
||||
user: {
|
||||
userId: 'test-user-id',
|
||||
email: 'test@signoz.io',
|
||||
name: 'TestUser',
|
||||
profilePictureURL: '',
|
||||
accessJwt: '',
|
||||
refreshJwt: '',
|
||||
},
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<CompatRouter>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={initialSearchParams}
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockedStore}>
|
||||
<AppContext.Provider value={getAppContextMock('ADMIN')}>
|
||||
<TimezoneProvider>
|
||||
<QueryBuilderProvider>
|
||||
<Route path="*">{children}</Route>
|
||||
</QueryBuilderProvider>
|
||||
</TimezoneProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</NuqsTestingAdapter>
|
||||
</CompatRouter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
describe('REGRESSION: DateTimeSelectionV2 preserves nuqs query params on time change', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
|
||||
// Initialize with test's initial search params
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
it('should preserve yAxisUnit param set via nuqs when changing time selection', async () => {
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=30m"
|
||||
onUrlUpdate={(event): void => {
|
||||
// Sync nuqs URL updates to our mock getter
|
||||
// This simulates how window.location.search would be updated in real browser
|
||||
currentSearchParams = event.searchParams;
|
||||
}}
|
||||
>
|
||||
<NuqsParamSetter paramValue="bytes" />
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('set-nuqs-param'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(currentSearchParams.get('yAxisUnit')).toBe('bytes');
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('yAxisUnit=bytes');
|
||||
});
|
||||
});
|
||||
@@ -1,133 +0,0 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="custom-time-picker" />,
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Route-Specific Behavior', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Alert Pages', () => {
|
||||
it('should set default time for alert overview when no time params', async () => {
|
||||
currentSearchParams = new URLSearchParams('otherParam=value');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="otherParam=value"
|
||||
initialPath={ROUTES.ALERT_OVERVIEW}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
expect(navigatedUrl).toContain('otherParam=value');
|
||||
});
|
||||
|
||||
it('should set default time for alert history when no time params', async () => {
|
||||
currentSearchParams = new URLSearchParams('filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="filter=active"
|
||||
initialPath={ROUTES.ALERT_HISTORY}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
});
|
||||
|
||||
it('should NOT override existing time params on alert pages', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=1h&filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=1h&filter=active"
|
||||
initialPath={ROUTES.ALERT_OVERVIEW}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const calls = mockSafeNavigate.mock.calls;
|
||||
if (calls.length > 0) {
|
||||
const lastUrl = calls[calls.length - 1][0] as string;
|
||||
expect(lastUrl).toContain('relativeTime=1h');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableUrlSync Behavior', () => {
|
||||
it('should not sync URL on mount when disableUrlSync is true', async () => {
|
||||
currentSearchParams = new URLSearchParams('');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="" initialPath="/services">
|
||||
<DateTimeSelection showAutoRefresh disableUrlSync />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const syncCalls = mockSafeNavigate.mock.calls.filter((call) => {
|
||||
const url = call[0] as string;
|
||||
return url.includes('relativeTime=') && !url.includes('services?');
|
||||
});
|
||||
|
||||
expect(syncCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,353 +0,0 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper, createMockMoment } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
|
||||
null;
|
||||
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
|
||||
null;
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
onCustomDateHandler,
|
||||
onValidCustomDateChange,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
onCustomDateHandler?: (range: [unknown, unknown]) => void;
|
||||
onValidCustomDateChange?: (data: { timeStr: string }) => void;
|
||||
}): JSX.Element => {
|
||||
mockOnCustomDateHandler = onCustomDateHandler || null;
|
||||
mockOnValidCustomDateChange = onValidCustomDateChange || null;
|
||||
|
||||
return (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-15m"
|
||||
onClick={(): void => onSelect('15m')}
|
||||
>
|
||||
15m
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-6h"
|
||||
onClick={(): void => onSelect('6h')}
|
||||
>
|
||||
6h
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Time Selection', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
mockOnCustomDateHandler = null;
|
||||
mockOnValidCustomDateChange = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Relative Time', () => {
|
||||
it('should update relativeTime and remove startTime/endTime', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'startTime=1000&endTime=2000&otherParam=keep',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="startTime=1000&endTime=2000&otherParam=keep">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-15m'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=15m');
|
||||
expect(navigatedUrl).not.toContain('startTime=');
|
||||
expect(navigatedUrl).not.toContain('endTime=');
|
||||
expect(navigatedUrl).toContain('otherParam=keep');
|
||||
});
|
||||
|
||||
it('should remove activeLogId param on time change', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&activeLogId=log123',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&activeLogId=log123">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).not.toContain('activeLogId');
|
||||
});
|
||||
|
||||
it('should update compositeQuery with new ID when present', async () => {
|
||||
const compositeQuery = encodeURIComponent(
|
||||
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
|
||||
);
|
||||
currentSearchParams = new URLSearchParams(
|
||||
`relativeTime=30m&compositeQuery=${compositeQuery}`,
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-6h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('compositeQuery=');
|
||||
expect(navigatedUrl).not.toContain('old-id');
|
||||
});
|
||||
|
||||
it('should preserve all non-time URL params', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m¶m1=a¶m2=b¶m3=c',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m¶m1=a¶m2=b¶m3=c">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('param1=a');
|
||||
expect(navigatedUrl).toContain('param2=b');
|
||||
expect(navigatedUrl).toContain('param3=c');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom Date Range', () => {
|
||||
it('should set startTime/endTime and remove relativeTime', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m&keepThis=yes');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&keepThis=yes">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = createMockMoment(1700000000000);
|
||||
const endMoment = createMockMoment(1700003600000);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('startTime=1700000000000');
|
||||
expect(navigatedUrl).toContain('endTime=1700003600000');
|
||||
expect(navigatedUrl).not.toContain('relativeTime=');
|
||||
expect(navigatedUrl).toContain('keepThis=yes');
|
||||
});
|
||||
|
||||
it('should update compositeQuery when present for custom date', async () => {
|
||||
const compositeQuery = encodeURIComponent(
|
||||
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
|
||||
);
|
||||
currentSearchParams = new URLSearchParams(
|
||||
`relativeTime=30m&compositeQuery=${compositeQuery}`,
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = createMockMoment(1700000000000);
|
||||
const endMoment = createMockMoment(1700003600000);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('compositeQuery=');
|
||||
expect(navigatedUrl).not.toContain('old-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Valid Custom Date String', () => {
|
||||
it('should handle shorthand date format and preserve params', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m&filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&filter=active">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnValidCustomDateChange?.({ timeStr: '2h' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=2h');
|
||||
expect(navigatedUrl).toContain('filter=active');
|
||||
expect(navigatedUrl).not.toContain('startTime=');
|
||||
expect(navigatedUrl).not.toContain('endTime=');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, Route } from 'react-router-dom';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import store from 'store';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { refetchOnWindowFocus: false, retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
export const mockStore = configureStore([thunk]);
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
initialSearchParams?: string;
|
||||
initialPath?: string;
|
||||
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
|
||||
}
|
||||
|
||||
export function TestWrapper({
|
||||
children,
|
||||
initialSearchParams = '',
|
||||
initialPath = '/services',
|
||||
onUrlUpdate,
|
||||
}: WrapperProps): JSX.Element {
|
||||
const initialEntry = initialSearchParams
|
||||
? `${initialPath}?${initialSearchParams}`
|
||||
: initialPath;
|
||||
|
||||
const mockedStore = mockStore({
|
||||
...store.getState(),
|
||||
app: {
|
||||
...store.getState().app,
|
||||
role: 'ADMIN',
|
||||
user: {
|
||||
userId: 'test-user-id',
|
||||
email: 'test@signoz.io',
|
||||
name: 'TestUser',
|
||||
profilePictureURL: '',
|
||||
accessJwt: '',
|
||||
refreshJwt: '',
|
||||
},
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<CompatRouter>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={initialSearchParams}
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockedStore}>
|
||||
<AppContext.Provider value={getAppContextMock('ADMIN')}>
|
||||
<TimezoneProvider>
|
||||
<QueryBuilderProvider>
|
||||
<Route path="*">{children}</Route>
|
||||
</QueryBuilderProvider>
|
||||
</TimezoneProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</NuqsTestingAdapter>
|
||||
</CompatRouter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export function createMockMoment(timestamp: number): {
|
||||
toDate: () => Date;
|
||||
toISOString: () => string;
|
||||
format: () => string;
|
||||
toString: () => string;
|
||||
} {
|
||||
const date = new Date(timestamp);
|
||||
return {
|
||||
toDate: (): Date => date,
|
||||
toISOString: (): string => date.toISOString(),
|
||||
format: (): string => date.toISOString(),
|
||||
toString: (): string => date.toString(),
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { useNavigationType } from 'react-router-dom-v5-compat';
|
||||
import { useNavigationType, useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { RefreshCw, Undo } from '@signozhq/icons';
|
||||
import { Button } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from 'store/globalTime';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { isValidShortHandDateTimeFormat } from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import { cloneDeep, isObject } from 'lodash-es';
|
||||
@@ -53,7 +54,6 @@ import {
|
||||
Time,
|
||||
TimeRange,
|
||||
} from './types';
|
||||
import { getUnstableCurrentSearchParams } from './utils/getUnstableCurrentSearchParams';
|
||||
|
||||
import './DateTimeSelectionV2.styles.scss';
|
||||
|
||||
@@ -90,12 +90,10 @@ function DateTimeSelection({
|
||||
const [hasSelectedTimeError, setHasSelectedTimeError] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const currentSearchParams = getUnstableCurrentSearchParams();
|
||||
const searchStartTime = currentSearchParams.get(QueryParams.startTime);
|
||||
const searchEndTime = currentSearchParams.get(QueryParams.endTime);
|
||||
const relativeTimeFromUrl = currentSearchParams.get(QueryParams.relativeTime);
|
||||
const hasTimeParamsInUrl =
|
||||
(searchStartTime && searchEndTime) || relativeTimeFromUrl;
|
||||
const urlQuery = useUrlQuery();
|
||||
const searchStartTime = urlQuery.get('startTime');
|
||||
const searchEndTime = urlQuery.get('endTime');
|
||||
const relativeTimeFromUrl = urlQuery.get(QueryParams.relativeTime);
|
||||
|
||||
// Prioritize props for initial modal time, fallback to URL params
|
||||
let initialModalStartTime = 0;
|
||||
@@ -117,6 +115,8 @@ function DateTimeSelection({
|
||||
);
|
||||
const [modalEndTime, setModalEndTime] = useState<number>(initialModalEndTime);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Effect to update modal time state when props change
|
||||
useEffect(() => {
|
||||
if (modalInitialStartTime !== undefined) {
|
||||
@@ -323,15 +323,14 @@ function DateTimeSelection({
|
||||
return;
|
||||
}
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, value);
|
||||
// Remove Hidden Filters from URL query parameters on time change
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
|
||||
if (urlQuery.has(QueryParams.compositeQuery)) {
|
||||
if (searchParams.has(QueryParams.compositeQuery)) {
|
||||
const updatedCompositeQuery = getUpdatedCompositeQuery();
|
||||
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
|
||||
}
|
||||
@@ -350,6 +349,8 @@ function DateTimeSelection({
|
||||
getUpdatedCompositeQuery,
|
||||
updateLocalStorageForRoutes,
|
||||
updateTimeInterval,
|
||||
urlQuery,
|
||||
searchParams,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -413,7 +414,6 @@ function DateTimeSelection({
|
||||
|
||||
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.set(
|
||||
QueryParams.startTime,
|
||||
startTime?.toDate().getTime().toString(),
|
||||
@@ -421,7 +421,7 @@ function DateTimeSelection({
|
||||
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
|
||||
if (urlQuery.has(QueryParams.compositeQuery)) {
|
||||
if (searchParams.has(QueryParams.compositeQuery)) {
|
||||
const updatedCompositeQuery = getUpdatedCompositeQuery();
|
||||
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
|
||||
}
|
||||
@@ -441,9 +441,8 @@ function DateTimeSelection({
|
||||
updateTimeInterval(dateTimeStr);
|
||||
updateLocalStorageForRoutes(dateTimeStr);
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, dateTimeStr);
|
||||
|
||||
@@ -596,12 +595,13 @@ function DateTimeSelection({
|
||||
|
||||
// set the default relative time for alert history and overview pages if relative time is not specified
|
||||
if (
|
||||
!hasTimeParamsInUrl &&
|
||||
(!urlQuery.has(QueryParams.startTime) ||
|
||||
!urlQuery.has(QueryParams.endTime)) &&
|
||||
!urlQuery.has(QueryParams.relativeTime) &&
|
||||
(currentRoute === ROUTES.ALERT_OVERVIEW ||
|
||||
currentRoute === ROUTES.ALERT_HISTORY)
|
||||
) {
|
||||
updateTimeInterval(defaultRelativeTime);
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.set(QueryParams.relativeTime, defaultRelativeTime);
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(generatedUrl);
|
||||
@@ -625,10 +625,9 @@ function DateTimeSelection({
|
||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||
}
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
if (updatedTime !== 'custom') {
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
urlQuery.set(QueryParams.relativeTime, updatedTime);
|
||||
} else {
|
||||
const startTime = preStartTime.toString();
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* This was introduced to fix a sync bug between Nuqs and react-router-dom
|
||||
*
|
||||
* We are using the wrong adapter for nuqs because the correct one only supports v6/v7,
|
||||
* and we are at version v5. This causes the nuqs/react-router-dom to be out of sync.
|
||||
*
|
||||
* We can revert this commit once we migrate react-router-dom to v6, or once we migrate
|
||||
* to DateTimeSelectionV3
|
||||
*/
|
||||
|
||||
/**
|
||||
* This was created to help testing the regression introduced between nuqs/react-router-dom
|
||||
*/
|
||||
type SearchParamsGetter = () => URLSearchParams;
|
||||
let getter: SearchParamsGetter = (): URLSearchParams =>
|
||||
new URLSearchParams(window.location.search);
|
||||
|
||||
/**
|
||||
* This function will return a fresh instance of URLSearchParams every time it's called.
|
||||
*
|
||||
* DO NOT USE IT FOR useEffect/useCallback dependencies, use Nuqs instead.
|
||||
*/
|
||||
export function getUnstableCurrentSearchParams(): URLSearchParams {
|
||||
return getter();
|
||||
}
|
||||
|
||||
// Testing helpers
|
||||
export function __setSearchParamsGetterForTest(fn: SearchParamsGetter): void {
|
||||
getter = fn;
|
||||
}
|
||||
|
||||
export function __resetSearchParamsGetter(): void {
|
||||
getter = (): URLSearchParams => new URLSearchParams(window.location.search);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// TODO(@signozhq/ui-input): migrate this styled(Input) once @signozhq/ui
|
||||
// Input supports `addonAfter` (the consumer renders `<InputComponent addonAfter="ms">`).
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DurationText = styled.div`
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { AutoComplete } from 'antd';
|
||||
import { AutoComplete, Input } from 'antd';
|
||||
import getTagFilters from 'api/trace/getTagFilter';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { AutoComplete, Space } from 'antd';
|
||||
import { AutoComplete, Input, Space } from 'antd';
|
||||
import getTagFilters from 'api/trace/getTagFilter';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { ArrowLeft, Check, Loader, Plus, Search } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Spin } from 'antd';
|
||||
import { Button, Input, Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const InputComponent = styled(Input)`
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
function DashboardPageV2(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard Page V2</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default DashboardPageV2;
|
||||
@@ -1,9 +0,0 @@
|
||||
function DashboardsListPageV2(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboards List Page V2</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardsListPageV2;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Input, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import './DropRateView.styles.scss';
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { ColorPicker, Modal, Table, TableProps } from 'antd';
|
||||
import { ColorPicker, Input, Modal, Table, TableProps } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Checkbox, Select, Skeleton } from 'antd';
|
||||
import { Checkbox, Input, Select, Skeleton } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Spin } from 'antd';
|
||||
import { Input, Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Input } from 'antd';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useRenameFunnel } from 'hooks/TracesFunnels/useFunnels';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Popover, Tooltip } from 'antd';
|
||||
import { Button, Input, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { ArrowDownWideNarrow, Check, Plus, Search } from '@signozhq/icons';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/statsreporter"
|
||||
citypes "github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -43,23 +42,14 @@ type Module interface {
|
||||
GetService(ctx context.Context, orgID valuer.UUID, serviceID citypes.ServiceID, provider citypes.CloudProviderType, integrationID valuer.UUID) (*citypes.Service, error)
|
||||
|
||||
// CreateService creates a new service for a cloud integration account.
|
||||
CreateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
|
||||
CreateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
|
||||
|
||||
// UpdateService updates cloud integration service
|
||||
UpdateService(ctx context.Context, orgID valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
|
||||
UpdateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *citypes.CloudIntegrationService, provider citypes.CloudProviderType) error
|
||||
|
||||
// AgentCheckIn is called by agent to send heartbeat and get latest config in response.
|
||||
AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider citypes.CloudProviderType, req *citypes.AgentCheckInRequest) (*citypes.AgentCheckInResponse, error)
|
||||
|
||||
// GetDashboardByID returns dashboard JSON for a given dashboard id.
|
||||
// this only returns the dashboard when the service (embedded in dashboard id) is enabled
|
||||
// in the org for any cloud integration account
|
||||
GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
// ListDashboards returns list of dashboards across all connected cloud integration accounts
|
||||
// for enabled services in the org. This list gets added to dashboard list page
|
||||
ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error)
|
||||
|
||||
statsreporter.StatsCollector
|
||||
}
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ func (handler *handler) UpdateService(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.CreateService(ctx, orgID, cloudIntegrationService, provider)
|
||||
err = handler.module.CreateService(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), cloudIntegrationService, provider)
|
||||
} else {
|
||||
err = svc.CloudIntegrationService.Update(provider, serviceID, req.Config)
|
||||
if err != nil {
|
||||
@@ -388,7 +388,7 @@ func (handler *handler) UpdateService(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = handler.module.UpdateService(ctx, orgID, svc.CloudIntegrationService, provider)
|
||||
err = handler.module.UpdateService(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), svc.CloudIntegrationService, provider)
|
||||
}
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
@@ -45,7 +44,7 @@ func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID,
|
||||
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "disconnect account is not supported")
|
||||
}
|
||||
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "create service is not supported")
|
||||
}
|
||||
|
||||
@@ -57,7 +56,7 @@ func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUI
|
||||
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "list services metadata is not supported")
|
||||
}
|
||||
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
return errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "update service is not supported")
|
||||
}
|
||||
|
||||
@@ -69,14 +68,6 @@ func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provi
|
||||
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "agent check-in is not supported")
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "get dashboard by ID is not supported")
|
||||
}
|
||||
|
||||
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "list dashboards is not supported")
|
||||
}
|
||||
|
||||
func (module *module) Collect(context.Context, valuer.UUID) (map[string]any, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, cloudintegrationtypes.ErrCodeUnsupported, "stats collection is not supported")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -186,6 +187,41 @@ func (store *store) ListServices(ctx context.Context, cloudIntegrationID valuer.
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (store *store) ListSharedServices(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, cloudIntegrationID valuer.UUID) (map[cloudintegrationtypes.ServiceID][]*cloudintegrationtypes.StorableCloudIntegrationService, error) {
|
||||
// Subquery: service types that belong to the given account
|
||||
ownTypes := store.store.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("cloud_integration_service").
|
||||
ColumnExpr("type").
|
||||
Where("cloud_integration_id = ?", cloudIntegrationID)
|
||||
|
||||
var services []*cloudintegrationtypes.StorableCloudIntegrationService
|
||||
err := store.
|
||||
store.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("cloud_integration_service AS cis").
|
||||
ColumnExpr("cis.*").
|
||||
Join("JOIN cloud_integration AS ci ON ci.id = cis.cloud_integration_id").
|
||||
Where("ci.org_id = ?", orgID).
|
||||
Where("ci.provider = ?", provider).
|
||||
Where("ci.removed_at IS NULL").
|
||||
Where("ci.account_id IS NOT NULL").
|
||||
Where("ci.last_agent_report IS NOT NULL").
|
||||
Where("cis.cloud_integration_id != ?", cloudIntegrationID).
|
||||
Where("cis.type IN (?)", ownTypes).
|
||||
Scan(ctx, &services)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[cloudintegrationtypes.ServiceID][]*cloudintegrationtypes.StorableCloudIntegrationService)
|
||||
for _, svc := range services {
|
||||
result[svc.Type] = append(result[svc.Type], svc)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (store *store) CreateService(ctx context.Context, service *cloudintegrationtypes.StorableCloudIntegrationService) error {
|
||||
_, err := store.
|
||||
store.
|
||||
@@ -200,6 +236,24 @@ func (store *store) CreateService(ctx context.Context, service *cloudintegration
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) DeleteServicesByCloudIntegrationID(ctx context.Context, orgID, cloudIntegrationID valuer.UUID) error {
|
||||
cte := store.store.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("cloud_integration_service AS cis_inner").
|
||||
ColumnExpr("cis_inner.id").
|
||||
Join("JOIN cloud_integration AS ci ON cis_inner.cloud_integration_id = ci.id").
|
||||
Where("ci.org_id = ?", orgID).
|
||||
Where("cis_inner.cloud_integration_id = ?", cloudIntegrationID)
|
||||
|
||||
_, err := store.store.BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(cloudintegrationtypes.StorableCloudIntegrationService)).
|
||||
With("target", cte).
|
||||
Where("id IN (SELECT id FROM target)").
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *store) UpdateService(ctx context.Context, service *cloudintegrationtypes.StorableCloudIntegrationService) error {
|
||||
_, err := store.
|
||||
store.
|
||||
@@ -213,6 +267,62 @@ func (store *store) UpdateService(ctx context.Context, service *cloudintegration
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *store) CreateIntegrationDashboard(ctx context.Context, integrationDashboard *cloudintegrationtypes.StorableIntegrationDashboard) error {
|
||||
_, err := store.store.BunDBCtx(ctx).NewInsert().Model(integrationDashboard).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *store) GetIntegrationDashboardBySlug(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.IntegrationDashboardProviderType, slug string) (*cloudintegrationtypes.StorableIntegrationDashboard, error) {
|
||||
integrationDashboard := new(cloudintegrationtypes.StorableIntegrationDashboard)
|
||||
err := store.store.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(integrationDashboard).
|
||||
Join("JOIN dashboard AS d ON storable_integration_dashboard.dashboard_id = d.id").
|
||||
Where("d.org_id = ?", orgID).
|
||||
Where("storable_integration_dashboard.provider = ?", provider).
|
||||
Where("storable_integration_dashboard.slug = ?", slug).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.store.WrapNotFoundErrf(err, errors.CodeNotFound, "integration dashboard with slug %s not found", slug)
|
||||
}
|
||||
return integrationDashboard, nil
|
||||
}
|
||||
|
||||
func (store *store) ListIntegrationDashboardsBySlugPrefix(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.IntegrationDashboardProviderType, slugPrefix string) ([]*cloudintegrationtypes.StorableIntegrationDashboard, error) {
|
||||
var integrationDashboards []*cloudintegrationtypes.StorableIntegrationDashboard
|
||||
err := store.store.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&integrationDashboards).
|
||||
Join("JOIN dashboard AS d ON storable_integration_dashboard.dashboard_id = d.id").
|
||||
Where("d.org_id = ?", orgID).
|
||||
Where("storable_integration_dashboard.provider = ?", provider).
|
||||
Where("storable_integration_dashboard.slug LIKE ?", slugPrefix+"%").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return integrationDashboards, nil
|
||||
}
|
||||
|
||||
func (store *store) DeleteIntegrationDashboardBySlug(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.IntegrationDashboardProviderType, slug string) error {
|
||||
cte := store.store.BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
TableExpr("integration_dashboard AS id_inner").
|
||||
ColumnExpr("id_inner.id").
|
||||
Join("JOIN dashboard AS d ON id_inner.dashboard_id = d.id").
|
||||
Where("d.org_id = ?", orgID).
|
||||
Where("id_inner.provider = ?", provider).
|
||||
Where("id_inner.slug = ?", slug)
|
||||
|
||||
_, err := store.store.BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(cloudintegrationtypes.StorableIntegrationDashboard)).
|
||||
With("target", cte).
|
||||
Where("id IN (SELECT id FROM target)").
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
|
||||
return store.store.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
|
||||
return cb(ctx)
|
||||
|
||||
@@ -34,7 +34,7 @@ type Module interface {
|
||||
// deletes the public sharing config and disables public sharing for the dashboard
|
||||
DeletePublic(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error)
|
||||
|
||||
@@ -46,6 +46,9 @@ type Module interface {
|
||||
|
||||
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
// DeleteUnsafe deletes a dashboard bypassing the guards. Intended for internal system callers.
|
||||
DeleteUnsafe(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
|
||||
|
||||
GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error)
|
||||
|
||||
statsreporter.StatsCollector
|
||||
|
||||
@@ -60,7 +60,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
||||
dashboardMigrator.Migrate(ctx, req)
|
||||
}
|
||||
|
||||
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), req)
|
||||
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.IdentityID()), dashboardtypes.SourceUser, req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
|
||||
@@ -37,8 +37,8 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, dashboardtypes.SourceUser, postableDashboard)
|
||||
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, source dashboardtypes.Source, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
|
||||
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, source, postableDashboard)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,6 +161,10 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *module) DeleteUnsafe(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
return module.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error) {
|
||||
dashboards, err := module.List(ctx, orgID)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewStore(sqlstore sqlstore.SQLStore) dashboardtypes.Store {
|
||||
func (store *store) Create(ctx context.Context, storabledashboard *dashboardtypes.StorableDashboard) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDB().
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(storabledashboard).
|
||||
Exec(ctx)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user