Compare commits

..

25 Commits

Author SHA1 Message Date
SagarRajput-7
edf9c6cc1f chore: base path setup and routing and navigation migrations 2026-04-14 19:03:20 +05:30
SagarRajput-7
db6da1784d feat: set React Router basename from <base href> tag 2026-04-14 19:03:20 +05:30
SagarRajput-7
07f412f748 test: add getBasePath() contract test; mark basePath export as internal 2026-04-14 19:03:20 +05:30
SagarRajput-7
0216fd61b8 feat: add getBasePath() utility reading <base href> from DOM 2026-04-14 19:03:20 +05:30
SagarRajput-7
52f097aee8 chore: public dir cleanup, remove duplicate assets since they are moved to src/assets 2026-04-14 16:56:55 +05:30
SagarRajput-7
bda93ee286 Merge branch 'main' into asset-consumption 2026-04-14 16:55:41 +05:30
SagarRajput-7
7edc737076 chore: addressed feedback comments 2026-04-14 16:54:56 +05:30
Tushar Vats
381966adcd fix: flaky integration tests (#10927) 2026-04-14 10:52:16 +00:00
SagarRajput-7
4fa306f94f chore: strengthen the lint rule and migration the gaps found 2026-04-14 16:11:21 +05:30
SagarRajput-7
c415b2a1d7 chore: fmt fix 2026-04-14 15:16:46 +05:30
SagarRajput-7
d513188511 chore: code refactor 2026-04-14 15:16:46 +05:30
SagarRajput-7
e7edc5da82 chore: asset migration - using the src/assets across the codebase, instead of the public dir 2026-04-14 15:16:46 +05:30
SagarRajput-7
dae7a43a52 chore: added eslint and stylelint for the asset migration task 2026-04-14 15:16:46 +05:30
SagarRajput-7
9fb373e36b chore: remove the snapshot update since no migration is done under this pr 2026-04-14 15:16:46 +05:30
SagarRajput-7
b01d4d8a74 chore: jest module resolution fix 2026-04-14 15:16:46 +05:30
Piyush Singariya
df9023c74c chore: upgrade collector version v0.144.3-rc.4 (#10921)
* fix: upgraded collector version

* fix: qbtoexpr tests

* fix: go sum

* chore: upgrade collector version v0.144.3-rc.4

* fix: tests

* ci: test fix
2026-04-14 08:57:28 +00:00
Nikhil Soni
10fc166e97 feat: add docs and agent skill info banner to ClickHouse query editor (#10713)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: add clickhouse query doc and skill links

* fix: switch to using callout component for info

* chore: update banner message

* chore: move styles to a separate file and rename folder

to match conventions

* fix: rename folder in git as well
2026-04-14 06:17:13 +00:00
SagarRajput-7
22b137c92b chore: moved json to ts for the onboarding data sources (#10905)
* chore: moved json to ts for the onboarding data sources

* chore: migrated unsupported occurences in scss files

* chore: jest module resolution fix

* chore: remove the snapshot update since no migration is done under this pr
2026-04-14 05:54:49 +00:00
SagarRajput-7
0b20afc469 chore: duplicated all the assets required for migration from public dir to src/assets dir (#10904)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: duplicated all the assets required for migration from public dir to src/assets dir

* chore: jest module resolution fix

* chore: remove the snapshot update since no migration is done under this pr
2026-04-13 19:12:17 +00:00
swapnil-signoz
dce496d099 refactor: cloud integration modules implementation (#10718)
* feat: adding cloud integration type for refactor

* refactor: store interfaces to use local types and error

* feat: adding sql store implementation

* refactor: removing interface check

* feat: adding updated types for cloud integration

* refactor: using struct for map

* refactor: update cloud integration types and module interface

* fix: correct GetService signature and remove shadowed Data field

* feat: implement cloud integration store

* refactor: adding comments and removed wrong code

* refactor: streamlining types

* refactor: add comments for backward compatibility in PostableAgentCheckInRequest

* refactor: update Dashboard struct comments and remove unused fields

* refactor: split upsert store method

* feat: adding integration test

* refactor: clean up types

* refactor: renaming service type to service id

* refactor: using serviceID type

* feat: adding method for service id creation

* refactor: updating store methods

* refactor: clean up

* refactor: clean up

* refactor: review comments

* refactor: clean up

* feat: adding handlers

* fix: lint and ci issues

* fix: lint issues

* fix: update error code for service not found

* feat: adding handler skeleton

* chore: removing todo comment

* feat: adding frontend openapi schema

* feat: adding module implementation for create account

* fix: returning valid error instead of panic

* fix: module test

* refactor: simplify ingestion key retrieval logic

* feat: adding module implementation for AWS

* refactor: ci lint changes

* refactor: python formatting change

* fix: new storable account func was unsetting provider account id

* refactor: python lint changes

* refactor: adding validation on update account request

* refactor: reverting older tests and adding new tests

* chore: lint changes

* feat: using service account for API key

* refactor: renaming tests and cleanup

* refactor: removing dashboard overview images

* feat: adding service definition store

* chore: adding TODO comments

* feat: adding API for getting connection credentials

* feat: adding openapi spec for the endpoint

* feat: adding tests for credential API

* feat: adding cloud integration config

* refactor: updating test with new env variable for config

* refactor: moving few cloud provider interface methods to types

* refactor: review comments resolution

* refactor: lint changes

* refactor: code clean up

* refactor: removing email domain function

* refactor: review comments and clean up

* refactor: lint fixes

* refactor: review changes

- Added get connected account module method
- Fixed integration tests
- Removed cloud integration store as callback function's param

* refactor: changing wrong dashboard id for EKS definition
2026-04-13 15:16:26 +00:00
aniketio-ctrl
c15e3ca59e feat(billing): use meter api from zeus (#10903)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(meter-api): use meter api from zeus

* feat(meter-api): bug fixes

* feat(meter-api): bug fixes

* feat(meter-api): bug fixes
2026-04-13 13:34:06 +00:00
Vikrant Gupta
9ec6da5e76 fix(authz): unauthenticated error code for deleted service account (#10878)
* fix(authz): populate correct error for deleted service account

* chore(authz): reduce the regex restrictions on service accounts

* chore(authz): reduce the regex restrictions on service accounts

* fix(authz): populate correct error for deleted service account

* fix(authz): populate correct error for deleted service account
2026-04-13 13:19:25 +00:00
Vikrant Gupta
3e241944e7 chore(sqlstore): added connection max lifetime support (#10913)
* chore(sqlstore): added connection max lifetime param support

* chore(sqlstore): fix test

* chore(sqlstore): default to 0 for conn_max_lifetime

* chore(sqlstore): rename the config

* chore(sqlstore): rename the config
2026-04-13 13:09:00 +00:00
SagarRajput-7
5251339a16 chore: config setup for asset migration, allow use of @ alias (#10901)
* chore: config setup for asset migration, allow use of @ alias

* chore: added modulewrapper for jests

* chore: jest module resolution fix

* chore: remove the snapshot update since no migration is done under this pr
2026-04-13 12:31:47 +00:00
Abhi kumar
702a16f80d chore: added test-ids to identify elements better in e2es (#10914) 2026-04-13 12:12:53 +00:00
620 changed files with 14923 additions and 15361 deletions

View File

@@ -18,11 +18,15 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/gateway/noopgateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser"
@@ -100,6 +104,9 @@ 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) {
return implcloudintegration.NewModule(), nil
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

View File

@@ -17,6 +17,8 @@ import (
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration/implcloudprovider"
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
@@ -31,10 +33,14 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz"
@@ -42,6 +48,7 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
)
@@ -127,7 +134,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil, err
}
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
@@ -146,8 +152,21 @@ 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) {
defStore := pkgcloudintegration.NewServiceDefinitionStore()
awsCloudProviderModule, err := implcloudprovider.NewAWSCloudProvider(defStore)
if err != nil {
return nil, err
}
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
}
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))
return err

View File

@@ -82,6 +82,9 @@ sqlstore:
provider: sqlite
# The maximum number of open connections to the database.
max_open_conns: 100
# The maximum amount of time a connection may be reused.
# If max_conn_lifetime == 0, connections are not closed due to a connection's age.
max_conn_lifetime: 0
sqlite:
# The path to the SQLite database file.
path: /var/lib/signoz/signoz.db
@@ -395,3 +398,10 @@ auditor:
max_interval: 30s
# The total maximum time spent retrying.
max_elapsed_time: 60s
##################### Cloud Integration #####################
cloudintegration:
# cloud integration agent configuration
agent:
# The version of the cloud integration agent.
version: v0.0.8

View File

@@ -0,0 +1,132 @@
package implcloudprovider
import (
"context"
"fmt"
"net/url"
"sort"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type awscloudprovider struct {
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
}
func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) (cloudintegration.CloudProviderModule, error) {
return &awscloudprovider{serviceDefinitions: defStore}, nil
}
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.Aws.DeploymentRegion)
u, _ := url.Parse(baseURL)
q := u.Query()
q.Set("region", req.Config.Aws.DeploymentRegion)
u.Fragment = "/stacks/quickcreate"
u.RawQuery = q.Encode()
q = u.Query()
q.Set("stackName", cloudintegrationtypes.AgentCloudFormationBaseStackName.StringValue())
q.Set("templateURL", fmt.Sprintf(cloudintegrationtypes.AgentCloudFormationTemplateS3Path.StringValue(), req.Config.AgentVersion))
q.Set("param_SigNozIntegrationAgentVersion", req.Config.AgentVersion)
q.Set("param_SigNozApiUrl", req.Credentials.SigNozAPIURL)
q.Set("param_SigNozApiKey", req.Credentials.SigNozAPIKey)
q.Set("param_SigNozAccountId", account.ID.StringValue())
q.Set("param_IngestionUrl", req.Credentials.IngestionURL)
q.Set("param_IngestionKey", req.Credentials.IngestionKey)
return &cloudintegrationtypes.ConnectionArtifact{
Aws: &cloudintegrationtypes.AWSConnectionArtifact{
ConnectionURL: u.String() + "?&" + q.Encode(), // this format is required by AWS
},
}, nil
}
func (provider *awscloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAWS)
}
func (provider *awscloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAWS, serviceID)
if err != nil {
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
}
func (provider *awscloudprovider) BuildIntegrationConfig(
ctx context.Context,
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
// Sort services for deterministic output
sort.Slice(services, func(i, j int) bool {
return services[i].Type.StringValue() < services[j].Type.StringValue()
})
compiledMetrics := new(cloudintegrationtypes.AWSMetricsCollectionStrategy)
compiledLogs := new(cloudintegrationtypes.AWSLogsCollectionStrategy)
var compiledS3Buckets map[string][]string
for _, storedSvc := range services {
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAWS, storedSvc.Config)
if err != nil {
return nil, err
}
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
if err != nil {
return nil, err
}
strategy := svcDef.TelemetryCollectionStrategy.AWS
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
// S3Sync: logs come directly from configured S3 buckets, not CloudWatch subscriptions
if storedSvc.Type == cloudintegrationtypes.AWSServiceS3Sync {
if logsEnabled && svcCfg.AWS.Logs.S3Buckets != nil {
compiledS3Buckets = svcCfg.AWS.Logs.S3Buckets
}
// no need to go ahead as the code block specifically checks for the S3Sync service
continue
}
if logsEnabled && strategy.Logs != nil {
compiledLogs.Subscriptions = append(compiledLogs.Subscriptions, strategy.Logs.Subscriptions...)
}
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
if metricsEnabled && strategy.Metrics != nil {
compiledMetrics.StreamFilters = append(compiledMetrics.StreamFilters, strategy.Metrics.StreamFilters...)
}
}
collectionStrategy := new(cloudintegrationtypes.AWSTelemetryCollectionStrategy)
if len(compiledMetrics.StreamFilters) > 0 {
collectionStrategy.Metrics = compiledMetrics
}
if len(compiledLogs.Subscriptions) > 0 {
collectionStrategy.Logs = compiledLogs
}
if compiledS3Buckets != nil {
collectionStrategy.S3Buckets = compiledS3Buckets
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
AWS: &cloudintegrationtypes.AWSIntegrationConfig{
EnabledRegions: account.Config.AWS.Regions,
TelemetryCollectionStrategy: collectionStrategy,
},
}, nil
}

View File

@@ -0,0 +1,34 @@
package implcloudprovider
import (
"context"
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
)
type azurecloudprovider struct{}
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
return &azurecloudprovider{}
}
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
panic("implement me")
}
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
}
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
panic("implement me")
}
func (provider *azurecloudprovider) BuildIntegrationConfig(
ctx context.Context,
account *cloudintegrationtypes.Account,
services []*cloudintegrationtypes.StorableCloudIntegrationService,
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
panic("implement me")
}

View File

@@ -0,0 +1,540 @@
package implcloudintegration
import (
"context"
"fmt"
"sort"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/gateway"
"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/serviceaccount"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
)
type module struct {
store cloudintegrationtypes.Store
gateway gateway.Gateway
zeus zeus.Zeus
licensing licensing.Licensing
global global.Global
serviceAccount serviceaccount.Module
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule
config cloudintegration.Config
}
func NewModule(
store cloudintegrationtypes.Store,
global global.Global,
zeus zeus.Zeus,
gateway gateway.Gateway,
licensing licensing.Licensing,
serviceAccount serviceaccount.Module,
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule,
config cloudintegration.Config,
) (cloudintegration.Module, error) {
return &module{
store: store,
global: global,
zeus: zeus,
gateway: gateway,
licensing: licensing,
serviceAccount: serviceAccount,
cloudProvidersMap: cloudProvidersMap,
config: config,
}, nil
}
// GetConnectionCredentials returns credentials required to generate connection artifact. eg. apiKey, ingestionKey etc.
// It will return creds it can deduce and return empty value for others.
func (module *module) GetConnectionCredentials(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Credentials, error) {
// get license to get the deployment details
license, 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())
}
// get deployment details from zeus, ignore error
respBytes, _ := module.zeus.GetDeployment(ctx, license.Key)
var signozAPIURL string
if len(respBytes) > 0 {
// parse deployment details, ignore error, if client is asking api url every time check for possible error
deployment, _ := zeustypes.NewGettableDeployment(respBytes)
if deployment != nil {
signozAPIURL, _ = cloudintegrationtypes.GetSigNozAPIURLFromDeployment(deployment)
}
}
// ignore error
apiKey, _ := module.getOrCreateAPIKey(ctx, orgID, provider)
// ignore error
ingestionKey, _ := module.getOrCreateIngestionKey(ctx, orgID, provider)
return cloudintegrationtypes.NewCredentials(
signozAPIURL,
apiKey,
module.global.GetConfig(ctx).IngestionURL,
ingestionKey,
), nil
}
func (module *module) CreateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
_, err := module.licensing.GetActive(ctx, account.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())
}
storableCloudIntegration, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return err
}
return module.store.CreateAccount(ctx, storableCloudIntegration)
}
func (module *module) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
_, err := module.licensing.GetActive(ctx, account.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())
}
cloudProviderModule, err := module.getCloudProvider(account.Provider)
if err != nil {
return nil, err
}
req.Config.SetAgentVersion(module.config.Agent.Version)
return cloudProviderModule.GetConnectionArtifact(ctx, account, req)
}
func (module *module) GetAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, 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())
}
storableAccount, err := module.store.GetAccountByID(ctx, orgID, accountID, provider)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAccountFromStorable(storableAccount)
}
func (module *module) GetConnectedAccount(ctx context.Context, orgID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, 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())
}
storableAccount, err := module.store.GetConnectedAccount(ctx, orgID, accountID, provider)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAccountFromStorable(storableAccount)
}
// ListAccounts return only agent connected accounts.
func (module *module) ListAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.Account, 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())
}
storableAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAccountsFromStorables(storableAccounts)
}
func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, req *cloudintegrationtypes.AgentCheckInRequest) (*cloudintegrationtypes.AgentCheckInResponse, 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())
}
connectedAccount, err := module.store.GetConnectedAccountByProviderAccountID(ctx, orgID, req.ProviderAccountID, provider)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
// If a different integration is already connected to this provider account ID, reject the check-in.
// Allow re-check-in from the same integration (e.g. agent restarting).
if connectedAccount != nil && connectedAccount.ID != req.CloudIntegrationID {
errMessage := fmt.Sprintf("provider account id %s is already connected to cloud integration id %s", req.ProviderAccountID, connectedAccount.ID)
return nil, errors.New(errors.TypeAlreadyExists, cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyConnected, errMessage)
}
account, err := module.store.GetAccountByID(ctx, orgID, req.CloudIntegrationID, provider)
if err != nil {
return nil, err
}
// If account has been removed (disconnected), return a minimal response with empty integration config.
// The agent uses this response to clean up resources
if account.RemovedAt != nil {
return cloudintegrationtypes.NewAgentCheckInResponse(
req.ProviderAccountID,
account.ID.StringValue(),
new(cloudintegrationtypes.ProviderIntegrationConfig),
account.RemovedAt,
), nil
}
// update account with cloud provider account id and agent report (heartbeat)
account.Update(&req.ProviderAccountID, cloudintegrationtypes.NewAgentReport(req.Data))
err = module.store.UpdateAccount(ctx, account)
if err != nil {
return nil, err
}
// Get account as domain object for config access (enabled regions, etc.)
domainAccount, err := cloudintegrationtypes.NewAccountFromStorable(account)
if err != nil {
return nil, err
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
storedServices, err := module.store.ListServices(ctx, req.CloudIntegrationID)
if err != nil {
return nil, err
}
// Delegate integration config building entirely to the provider module
integrationConfig, err := cloudProvider.BuildIntegrationConfig(ctx, domainAccount, storedServices)
if err != nil {
return nil, err
}
return cloudintegrationtypes.NewAgentCheckInResponse(
req.ProviderAccountID,
account.ID.StringValue(),
integrationConfig,
account.RemovedAt,
), nil
}
func (module *module) UpdateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
_, err := module.licensing.GetActive(ctx, account.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())
}
storableAccount, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
if err != nil {
return err
}
return module.store.UpdateAccount(ctx, storableAccount)
}
func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, 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())
}
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) {
_, 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())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
serviceDefinitions, err := cloudProvider.ListServiceDefinitions(ctx)
if err != nil {
return nil, err
}
enabledServiceIDs := map[string]bool{}
if !integrationID.IsZero() {
storedServices, err := module.store.ListServices(ctx, integrationID)
if err != nil {
return nil, err
}
for _, svc := range storedServices {
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
if err != nil {
return nil, err
}
if serviceConfig.IsServiceEnabled(provider) {
enabledServiceIDs[svc.Type.StringValue()] = true
}
}
}
resp := make([]*cloudintegrationtypes.ServiceMetadata, 0, len(serviceDefinitions))
for _, serviceDefinition := range serviceDefinitions {
resp = append(resp, cloudintegrationtypes.NewServiceMetadata(*serviceDefinition, enabledServiceIDs[serviceDefinition.ID]))
}
return resp, nil
}
func (module *module) GetService(ctx context.Context, orgID valuer.UUID, serviceID cloudintegrationtypes.ServiceID, provider cloudintegrationtypes.CloudProviderType, cloudIntegrationID valuer.UUID) (*cloudintegrationtypes.Service, 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())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, serviceID)
if err != nil {
return nil, err
}
var integrationService *cloudintegrationtypes.CloudIntegrationService
if !cloudIntegrationID.IsZero() {
storedService, err := module.store.GetServiceByServiceID(ctx, cloudIntegrationID, serviceID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
if storedService != nil {
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedService.Config)
if err != nil {
return nil, err
}
integrationService = cloudintegrationtypes.NewCloudIntegrationServiceFromStorable(storedService, serviceConfig)
}
}
return cloudintegrationtypes.NewService(*serviceDefinition, integrationService), nil
}
func (module *module) CreateService(ctx context.Context, orgID 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())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, service.Type)
if err != nil {
return err
}
configJSON, err := service.Config.ToJSON(provider, service.Type, &serviceDefinition.SupportedSignals)
if err != nil {
return err
}
return module.store.CreateService(ctx, cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON)))
}
func (module *module) UpdateService(ctx context.Context, orgID 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())
}
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return err
}
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, integrationService.Type)
if err != nil {
return err
}
configJSON, err := integrationService.Config.ToJSON(provider, integrationService.Type, &serviceDefinition.SupportedSignals)
if err != nil {
return err
}
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 nil, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration dashboard not found")
}
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())
}
return module.listDashboards(ctx, orgID)
}
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
// get connected accounts for AWS
awsAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAWS)
if err == nil {
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
}
// NOTE: not adding stats for services for now.
// TODO: add more cloud providers when supported
return stats, nil
}
func (module *module) getCloudProvider(provider cloudintegrationtypes.CloudProviderType) (cloudintegration.CloudProviderModule, error) {
if cloudProviderModule, ok := module.cloudProvidersMap[provider]; ok {
return cloudProviderModule, nil
}
return nil, errors.NewInvalidInputf(cloudintegrationtypes.ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
}
func (module *module) getOrCreateIngestionKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
keyName := cloudintegrationtypes.NewIngestionKeyName(provider)
result, err := module.gateway.SearchIngestionKeysByName(ctx, orgID, keyName, 1, 10)
if err != nil {
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't search ingestion keys")
}
// ideally there should be only one key per cloud integration provider
if len(result.Keys) > 0 {
return result.Keys[0].Value, nil
}
createdIngestionKey, err := module.gateway.CreateIngestionKey(ctx, orgID, keyName, []string{"integration"}, time.Time{})
if err != nil {
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't create ingestion key")
}
return createdIngestionKey.Value, nil
}
func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
domain := module.serviceAccount.Config().Email.Domain
serviceAccount := serviceaccounttypes.NewServiceAccount("integration", domain, serviceaccounttypes.ServiceAccountStatusActive, orgID)
serviceAccount, err := module.serviceAccount.GetOrCreate(ctx, orgID, serviceAccount)
if err != nil {
return "", err
}
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
if err != nil {
return "", err
}
factorAPIKey, err := serviceAccount.NewFactorAPIKey(provider.StringValue(), 0)
if err != nil {
return "", err
}
factorAPIKey, err = module.serviceAccount.GetOrCreateFactorAPIKey(ctx, factorAPIKey)
if err != nil {
return "", err
}
return factorAPIKey.Key, nil
}
func (module *module) listDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
var allDashboards []*dashboardtypes.Dashboard
for provider := range module.cloudProvidersMap {
cloudProvider, err := module.getCloudProvider(provider)
if err != nil {
return nil, err
}
connectedAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
if err != nil {
return nil, 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...)
}
}
}
sort.Slice(allDashboards, func(i, j int) bool {
return allDashboards[i].ID < allDashboards[j].ID
})
return allDashboards, nil
}

View File

@@ -7,6 +7,10 @@ import (
"github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type DayWiseBreakdown struct {
@@ -45,15 +49,17 @@ type details struct {
BillTotal float64 `json:"billTotal"`
}
type billingData struct {
BillingPeriodStart int64 `json:"billingPeriodStart"`
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
Details details `json:"details"`
Discount float64 `json:"discount"`
SubscriptionStatus string `json:"subscriptionStatus"`
}
type billingDetails struct {
Status string `json:"status"`
Data struct {
BillingPeriodStart int64 `json:"billingPeriodStart"`
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
Details details `json:"details"`
Discount float64 `json:"discount"`
SubscriptionStatus string `json:"subscriptionStatus"`
} `json:"data"`
Status string `json:"status"`
Data billingData `json:"data"`
}
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
@@ -64,6 +70,33 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
return
}
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
orgID := valuer.MustNewUUID(claims.OrgID)
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
useZeus := ah.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureGetMetersFromZeus, evalCtx)
if useZeus {
data, err := ah.Signoz.Zeus.GetMeters(r.Context(), licenseKey)
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
var billing billingData
if err := json.Unmarshal(data, &billing); err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
ah.Respond(w, billing)
return
}
billingURL := fmt.Sprintf("%s/usage?licenseKey=%s", constants.LicenseSignozIo, licenseKey)
hClient := &http.Client{}
@@ -79,13 +112,11 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
return
}
// decode response body
var billingResponse billingDetails
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
// TODO(srikanthccv):Fetch the current day usage and add it to the response
ah.Respond(w, billingResponse.Data)
}

View File

@@ -54,6 +54,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
// Set the maximum number of open connections
pgConfig.MaxConns = int32(config.Connection.MaxOpenConns)
pgConfig.MaxConnLifetime = config.Connection.MaxConnLifetime
// Use pgxpool to create a connection pool
pool, err := pgxpool.NewWithConfig(ctx, pgConfig)

View File

@@ -109,6 +109,21 @@ func (provider *Provider) GetDeployment(ctx context.Context, key string) ([]byte
return []byte(gjson.GetBytes(response, "data").String()), nil
}
func (provider *Provider) GetMeters(ctx context.Context, key string) ([]byte, error) {
response, err := provider.do(
ctx,
provider.config.URL.JoinPath("/v1/meters"),
http.MethodGet,
key,
nil,
)
if err != nil {
return nil, err
}
return []byte(gjson.GetBytes(response, "data").String()), nil
}
func (provider *Provider) PutMeters(ctx context.Context, key string, data []byte) error {
_, err := provider.do(
ctx,

View File

@@ -1,77 +0,0 @@
---
description: Global vs local mock strategy for Jest tests
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# Mock Decision Strategy
## Global Mocks (20+ test files)
- Core infrastructure: react-router-dom, react-query, antd
- Browser APIs: ResizeObserver, matchMedia, localStorage
- Utility libraries: date-fns, lodash
- Available: `uplot` → `__mocks__/uplotMock.ts`
## Local Mocks (515 test files)
- Business logic dependencies
- API endpoints with specific responses
- Domain-specific components
- Error scenarios and edge cases
## Decision Tree
```
Is it used in 20+ test files?
├─ YES → Use Global Mock
│ ├─ react-router-dom
│ ├─ react-query
│ ├─ antd components
│ └─ browser APIs
└─ NO → Is it business logic?
├─ YES → Use Local Mock
│ ├─ API endpoints
│ ├─ Custom hooks
│ └─ Domain components
└─ NO → Is it test-specific?
├─ YES → Use Local Mock
│ ├─ Error scenarios
│ ├─ Loading states
│ └─ Specific data
└─ NO → Consider Global Mock
└─ If it becomes frequently used
```
## Anti-patterns
❌ Don't mock global dependencies locally:
```ts
jest.mock('react-router-dom', () => ({ ... })); // Already globally mocked
```
❌ Don't create global mocks for test-specific data:
```ts
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => specificTestData) // BAD - should be local
}));
```
✅ Do use global mocks for infrastructure:
```ts
import { useLocation } from 'react-router-dom';
```
✅ Do create local mocks for business logic:
```ts
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => mockTracesData)
}));
```

View File

@@ -1,124 +0,0 @@
---
description: Core Jest/React Testing Library conventions - harness, MSW, interactions, timers
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# Jest Test Conventions
Expert developer with Jest, React Testing Library, MSW, and TypeScript. Focus on critical functionality, mock dependencies before imports, test multiple scenarios, write maintainable tests.
**Auto-detect TypeScript**: Check for TypeScript in the project through tsconfig.json or package.json dependencies. Adjust syntax based on this detection.
## Imports
Always import from our harness:
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
```
For API mocks:
```ts
import { server, rest } from 'mocks-server/server';
```
❌ Do not import directly from `@testing-library/react`.
## Router
Use the router built into render:
```ts
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
```
Only mock `useLocation` / `useParams` if the test depends on them.
## Hook Mocks
```ts
import useFoo from 'hooks/useFoo';
jest.mock('hooks/useFoo');
const mockUseFoo = jest.mocked(useFoo);
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
```
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
## MSW
Global MSW server runs automatically. Override per-test:
```ts
server.use(
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
);
```
Keep large responses in `mocks-server/__mockdata__/`.
## Interactions
- Prefer `userEvent` for real user interactions (click, type, select, tab).
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
- Always await interactions:
```ts
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(screen.getByRole('button', { name: /save/i }));
```
```ts
// Example: virtualized list scroll (no userEvent helper)
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
scroller.scrollTop = targetScrollTop;
act(() => { fireEvent.scroll(scroller); });
```
## Timers
❌ No global fake timers. ✅ Per-test only:
```ts
jest.useFakeTimers();
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
await user.type(screen.getByRole('textbox'), 'query');
jest.advanceTimersByTime(400);
jest.useRealTimers();
```
## Queries
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`). Fallback: visible text. Last resort: `data-testid`.
## Best Practices
- **Critical Functionality**: Prioritize testing business logic and utilities
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
- **Data Scenarios**: Always test valid, invalid, and edge cases
- **Descriptive Names**: Make test intent clear
- **Organization**: Group related tests in describe
- **Consistency**: Match repo conventions
- **Edge Cases**: Test null, undefined, unexpected values
- **Limit Scope**: 35 focused tests per file
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
- **No Any**: Enforce type safety
## Anti-patterns
❌ Importing RTL directly | ❌ Global fake timers | ❌ Wrapping render in `act(...)` | ❌ Mocking infra locally
✅ Use harness | ✅ MSW for API | ✅ userEvent + await | ✅ Pin time only for relative-date tests
## Example
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { server, rest } from 'mocks-server/server';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('renders and interacts', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 }))));
render(<MyComponent />, undefined, { initialRoute: '/foo' });
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /refresh/i }));
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
});
});
```

View File

@@ -1,168 +0,0 @@
---
description: TypeScript type safety for Jest tests - mocks, interfaces, no any
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# TypeScript Type Safety for Jest Tests
**CRITICAL**: All Jest tests MUST be fully type-safe.
## Requirements
- Use proper TypeScript interfaces for all mock data
- Type all Jest mock functions with `jest.MockedFunction<T>`
- Use generic types for React components and hooks
- Define proper return types for mock functions
- Use `as const` for literal types when needed
- Avoid `any` type use proper typing instead
## Mock Function Typing
```ts
// ✅ GOOD
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
// ❌ BAD
const mockFetchUser = jest.fn() as any;
```
## Mock Data with Interfaces
```ts
interface User { id: number; name: string; email: string; }
interface ApiResponse<T> { data: T; status: number; message: string; }
const mockUser: User = { id: 1, name: 'John Doe', email: 'john@example.com' };
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
```
## Component Props Typing
```ts
interface ComponentProps { title: string; data: User[]; onUserSelect: (user: User) => void; }
const mockProps: ComponentProps = {
title: 'Test',
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
};
render(<TestComponent {...mockProps} />);
```
## Hook Testing with Types
```ts
interface UseUserDataReturn {
user: User | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
describe('useUserData', () => {
it('should return user data with proper typing', () => {
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
const { result } = renderHook(() => useUserData(1));
expect(result.current.user).toEqual(mockUser);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});
});
```
## Generic Mock Typing
```ts
interface MockApiResponse<T> { data: T; status: number; }
const mockFetchData = jest.fn() as jest.MockedFunction<
<T>(endpoint: string) => Promise<MockApiResponse<T>>
>;
mockFetchData<User>('/users').mockResolvedValue({ data: { id: 1, name: 'John' }, status: 200 });
```
## React Testing Library with Types
```ts
type TestComponentProps = ComponentProps<typeof TestComponent>;
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
const defaultProps: TestComponentProps = { title: 'Test', data: [], onSelect: jest.fn(), ...props };
return render(<TestComponent {...defaultProps} />);
};
```
## Error Handling with Types
```ts
interface ApiError { message: string; code: number; details?: Record<string, unknown>; }
const mockApiError: ApiError = { message: 'API Error', code: 500, details: { endpoint: '/users' } };
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
```
## Global Mock Type Safety
```ts
// In __mocks__/routerMock.ts
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
pathname: '/traces',
search: '',
hash: '',
state: null,
key: 'test-key',
...overrides,
});
// In test files: const location = useLocation(); // Properly typed from global mock
```
## TypeScript Configuration for Jest
```json
// jest.config.ts
{
"preset": "ts-jest/presets/js-with-ts-esm",
"globals": {
"ts-jest": {
"useESM": true,
"isolatedModules": true,
"tsconfig": "<rootDir>/tsconfig.jest.json"
}
},
"extensionsToTreatAsEsm": [".ts", ".tsx"],
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
}
```
```json
// tsconfig.jest.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "@testing-library/jest-dom"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node"
},
"include": ["src/**/*", "**/*.test.ts", "**/*.test.tsx", "__mocks__/**/*"]
}
```
## Type Safety Checklist
- [ ] All mock functions use `jest.MockedFunction<T>`
- [ ] All mock data has proper interfaces
- [ ] No `any` types in test files
- [ ] Generic types are used where appropriate
- [ ] Error types are properly defined
- [ ] Component props are typed
- [ ] Hook return types are defined
- [ ] API response types are defined
- [ ] Global mocks are type-safe
- [ ] Test utilities are properly typed

View File

@@ -1,5 +1,7 @@
node_modules
build
eslint-rules/
stylelint-rules/
*.typegen.ts
i18-generate-hash.js
src/parser/TraceOperatorParser/**

View File

@@ -1,3 +1,8 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const rulesDirPlugin = require('eslint-plugin-rulesdir');
rulesDirPlugin.RULES_DIR = path.join(__dirname, 'eslint-rules');
/**
* ESLint Configuration for SigNoz Frontend
*/
@@ -32,6 +37,7 @@ module.exports = {
sourceType: 'module',
},
plugins: [
'rulesdir', // Local custom rules
'react', // React-specific rules
'@typescript-eslint', // TypeScript linting
'simple-import-sort', // Auto-sort imports
@@ -56,6 +62,9 @@ module.exports = {
},
},
rules: {
// Asset migration — base-path safety
'rulesdir/no-unsupported-asset-pattern': 'error',
// Code quality rules
'prefer-const': 'error', // Enforces const for variables never reassigned
'no-var': 'error', // Disallows var, enforces let/const
@@ -202,6 +211,23 @@ module.exports = {
message:
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
},
{
selector:
"CallExpression[callee.object.name='window'][callee.property.name='open']",
message:
'Do not call window.open() directly. ' +
"Use openInNewTab() from 'utils/navigation' for internal SigNoz paths. " +
"For intentional external URLs, use openExternalLink() from 'utils/navigation'. " +
'For unavoidable direct calls, add // eslint-disable-next-line with a reason.',
},
{
selector:
"AssignmentExpression[left.object.name='window'][left.property.name='href']",
message:
'Do not assign window.location.href for internal navigation. ' +
"Use history.push() or history.replace() from 'lib/history'. " +
'For external redirects (SSO, logout URLs), add // eslint-disable-next-line with a reason.',
},
],
},
overrides: [
@@ -254,5 +280,14 @@ module.exports = {
'no-restricted-syntax': 'off',
},
},
{
// navigation.ts and useSafeNavigate.ts are the canonical implementations that call
// window.open after computing a base-path-aware href. They are the only places
// allowed to call window.open directly.
files: ['src/utils/navigation.ts', 'src/hooks/useSafeNavigate.ts'],
rules: {
'no-restricted-syntax': 'off',
},
},
],
};

View File

@@ -0,0 +1,9 @@
const path = require('path');
module.exports = {
plugins: [path.join(__dirname, 'stylelint-rules/no-unsupported-asset-url.js')],
customSyntax: 'postcss-scss',
rules: {
'local/no-unsupported-asset-url': true,
},
};

View File

@@ -0,0 +1 @@
export default 'test-file-stub';

View File

@@ -0,0 +1,268 @@
'use strict';
const {
hasAssetExtension,
containsAssetExtension,
extractUrlPath,
isAbsolutePath,
isPublicRelative,
isRelativePublicDir,
isValidAssetImport,
isExternalUrl,
} = require('./shared/asset-patterns');
const PUBLIC_DIR_SEGMENTS = ['/Icons/', '/Images/', '/Logos/', '/svgs/'];
function collectBinaryStringParts(node) {
if (node.type === 'Literal' && typeof node.value === 'string')
return [node.value];
if (node.type === 'BinaryExpression' && node.operator === '+') {
return [
...collectBinaryStringParts(node.left),
...collectBinaryStringParts(node.right),
];
}
if (node.type === 'TemplateLiteral') {
return node.quasis.map((q) => q.value.raw);
}
return [null];
}
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'Disallow Vite-unsafe asset reference patterns that break runtime base-path deployments',
category: 'Asset Migration',
recommended: true,
},
schema: [],
messages: {
absoluteString:
'Absolute asset path "{{ value }}" is not base-path-safe. ' +
"Use an ES import instead: import fooUrl from '@/assets/...' and reference the variable.",
templateLiteral:
'Dynamic asset path with absolute prefix is not base-path-safe. ' +
"Use new URL('./asset.svg', import.meta.url).href for dynamic asset paths.",
absoluteImport:
'Asset imported via absolute path is not supported. ' +
"Use import fooUrl from '@/assets/...' instead.",
publicImport:
"Assets in public/ bypass Vite's module pipeline — their URLs are not base-path-aware and will break when the app is served from a sub-path (e.g. /app/). " +
"Use an ES import instead: import fooUrl from '@/assets/...' so Vite injects the correct base path.",
relativePublicString:
'Relative public-dir path "{{ value }}" is not base-path-safe. ' +
"Use an ES import instead: import fooUrl from '@/assets/...' and reference the variable.",
invalidAssetImport:
"Asset '{{ src }}' must be imported from src/assets/ using either '@/assets/...' " +
'or a relative path into src/assets/.',
},
},
create(context) {
return {
Literal(node) {
if (node.parent && node.parent.type === 'ImportDeclaration') {
return;
}
const value = node.value;
if (typeof value !== 'string') return;
if (isExternalUrl(value)) return;
if (isAbsolutePath(value) && hasAssetExtension(value)) {
context.report({
node,
messageId: 'absoluteString',
data: { value },
});
return;
}
if (isRelativePublicDir(value) && hasAssetExtension(value)) {
context.report({
node,
messageId: 'relativePublicString',
data: { value },
});
return;
}
const urlPath = extractUrlPath(value);
if (urlPath && isExternalUrl(urlPath)) return;
if (urlPath && isAbsolutePath(urlPath) && hasAssetExtension(urlPath)) {
context.report({
node,
messageId: 'absoluteString',
data: { value: urlPath },
});
return;
}
if (urlPath && isRelativePublicDir(urlPath) && hasAssetExtension(urlPath)) {
context.report({
node,
messageId: 'relativePublicString',
data: { value: urlPath },
});
}
},
TemplateLiteral(node) {
const quasis = node.quasis;
if (!quasis || quasis.length === 0) return;
const firstQuasi = quasis[0].value.raw;
if (isExternalUrl(firstQuasi)) return;
const hasAssetExt = quasis.some((q) => containsAssetExtension(q.value.raw));
if (isAbsolutePath(firstQuasi) && hasAssetExt) {
context.report({
node,
messageId: 'templateLiteral',
});
return;
}
if (isRelativePublicDir(firstQuasi) && hasAssetExt) {
context.report({
node,
messageId: 'relativePublicString',
data: { value: firstQuasi },
});
return;
}
// Expression-first template with known public-dir segment: `${base}/Icons/foo.svg`
const hasPublicSegment = quasis.some((q) =>
PUBLIC_DIR_SEGMENTS.some((seg) => q.value.raw.includes(seg)),
);
if (hasPublicSegment && hasAssetExt) {
context.report({
node,
messageId: 'templateLiteral',
});
return;
}
if (quasis.length === 1) {
const urlPath = extractUrlPath(firstQuasi);
if (urlPath && isExternalUrl(urlPath)) return;
if (urlPath && isAbsolutePath(urlPath) && hasAssetExtension(urlPath)) {
context.report({
node,
messageId: 'templateLiteral',
});
return;
}
if (
urlPath &&
isRelativePublicDir(urlPath) &&
hasAssetExtension(urlPath)
) {
context.report({
node,
messageId: 'relativePublicString',
data: { value: urlPath },
});
}
return;
}
if (firstQuasi.includes('url(') && hasAssetExt) {
const urlMatch = firstQuasi.match(/^url\(\s*['"]?\//);
if (urlMatch) {
context.report({
node,
messageId: 'templateLiteral',
});
}
}
},
// String concatenation: "/Icons/" + name + ".svg" or "Icons/" + name + ".svg"
BinaryExpression(node) {
if (node.operator !== '+') return;
const parts = collectBinaryStringParts(node);
const prefixParts = [];
for (const part of parts) {
if (part === null) break;
prefixParts.push(part);
}
const staticPrefix = prefixParts.join('');
if (isExternalUrl(staticPrefix)) return;
const hasExt = parts.some(
(part) => part !== null && containsAssetExtension(part),
);
if (isAbsolutePath(staticPrefix) && hasExt) {
context.report({
node,
messageId: 'templateLiteral',
});
return;
}
if (isRelativePublicDir(staticPrefix) && hasExt) {
context.report({
node,
messageId: 'relativePublicString',
data: { value: staticPrefix },
});
}
},
ImportDeclaration(node) {
const src = node.source.value;
if (typeof src !== 'string') return;
if (!hasAssetExtension(src)) return;
if (isAbsolutePath(src)) {
context.report({ node, messageId: 'absoluteImport' });
return;
}
if (isPublicRelative(src)) {
context.report({ node, messageId: 'publicImport' });
return;
}
if (!isValidAssetImport(src)) {
context.report({
node,
messageId: 'invalidAssetImport',
data: { src },
});
}
},
ImportExpression(node) {
const src = node.source;
if (!src || src.type !== 'Literal' || typeof src.value !== 'string') return;
const value = src.value;
if (!hasAssetExtension(value)) return;
if (isAbsolutePath(value)) {
context.report({ node, messageId: 'absoluteImport' });
return;
}
if (isPublicRelative(value)) {
context.report({ node, messageId: 'publicImport' });
return;
}
if (!isValidAssetImport(value)) {
context.report({
node,
messageId: 'invalidAssetImport',
data: { src: value },
});
}
},
};
},
};

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,114 @@
'use strict';
const ASSET_EXTENSIONS = ['.svg', '.png', '.webp', '.jpg', '.jpeg', '.gif'];
/**
* Returns true if the string ends with an asset extension.
* e.g. "/Icons/foo.svg" → true, "/Icons/foo.svg.bak" → false
*/
function hasAssetExtension(str) {
if (typeof str !== 'string') return false;
return ASSET_EXTENSIONS.some((ext) => str.endsWith(ext));
}
// Like hasAssetExtension but also matches mid-string with boundary check,
// e.g. "/foo.svg?v=1" → true, "/icons.svg-dir/" → true (- is non-alphanumeric boundary)
function containsAssetExtension(str) {
if (typeof str !== 'string') return false;
return ASSET_EXTENSIONS.some((ext) => {
const idx = str.indexOf(ext);
if (idx === -1) return false;
const afterIdx = idx + ext.length;
// Broad boundary (any non-alphanumeric) is intentional — the real guard against
// false positives is the upstream conditions (isAbsolutePath, isRelativePublicDir, etc.)
// that must pass before this is reached. "/icons.svg-dir/" → true (- is a boundary).
return afterIdx >= str.length || /[^a-zA-Z0-9]/.test(str[afterIdx]);
});
}
/**
* Extracts the asset path from a CSS url() wrapper.
* Handles single quotes, double quotes, unquoted, and whitespace variations.
* e.g.
* "url('/Icons/foo.svg')" → "/Icons/foo.svg"
* "url( '../assets/bg.png' )" → "../assets/bg.png"
* "url(/Icons/foo.svg)" → "/Icons/foo.svg"
* Returns null if the string is not a url() wrapper.
*/
function extractUrlPath(str) {
if (typeof str !== 'string') return null;
// Match url( [whitespace] [quote?] path [quote?] [whitespace] )
// Capture group: [^'")\s]+ matches path until quote, closing paren, or whitespace
const match = str.match(/^url\(\s*['"]?([^'")\s]+)['"]?\s*\)$/);
return match ? match[1] : null;
}
/**
* Returns true if the string is an absolute path (starts with /).
* Absolute paths in url() bypass <base href> and fail under any URL prefix.
*/
function isAbsolutePath(str) {
if (typeof str !== 'string') return false;
return str.startsWith('/') && !str.startsWith('//');
}
/**
* Returns true if the path imports from the public/ directory.
* Relative imports into public/ cause asset duplication in dist/.
*/
function isPublicRelative(str) {
if (typeof str !== 'string') return false;
return str.includes('/public/') || str.startsWith('public/');
}
/**
* Returns true if the string is a relative reference into a known public-dir folder.
* e.g. "Icons/foo.svg", `Logos/aws-dark.svg`, "Images/bg.png"
* These bypass Vite's module pipeline even without a leading slash.
*/
const PUBLIC_DIR_SEGMENTS = ['Icons/', 'Images/', 'Logos/', 'svgs/'];
function isRelativePublicDir(str) {
if (typeof str !== 'string') return false;
return PUBLIC_DIR_SEGMENTS.some((seg) => str.startsWith(seg));
}
/**
* Returns true if an asset import path is valid (goes through Vite's module pipeline).
* Valid: @/assets/..., any relative path containing /assets/, or node_modules packages.
* Invalid: absolute paths, public/ dir, or relative paths outside src/assets/.
*/
function isValidAssetImport(str) {
if (typeof str !== 'string') return false;
if (str.startsWith('@/assets/')) return true;
if (str.includes('/assets/')) return true;
// Not starting with . or / means it's a node_modules package — always valid
if (!str.startsWith('.') && !str.startsWith('/')) return true;
return false;
}
/**
* Returns true if the string is an external URL.
* Used to avoid false positives on CDN/API URLs with asset extensions.
*/
function isExternalUrl(str) {
if (typeof str !== 'string') return false;
return (
str.startsWith('http://') ||
str.startsWith('https://') ||
str.startsWith('//')
);
}
module.exports = {
ASSET_EXTENSIONS,
PUBLIC_DIR_SEGMENTS,
hasAssetExtension,
containsAssetExtension,
extractUrlPath,
isAbsolutePath,
isPublicRelative,
isRelativePublicDir,
isValidAssetImport,
isExternalUrl,
};

View File

@@ -6,3 +6,6 @@ VITE_APPCUES_APP_ID="appcess-app-id"
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
CI="1"
# Uncomment to test sub-path deployment locally (e.g. app served at /signoz/).
# VITE_BASE_PATH="/signoz/"

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="<%- BASE_PATH %>" />
<meta charset="utf-8" />
<meta
http-equiv="Cache-Control"
@@ -59,7 +60,7 @@
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<link data-react-helmet="true" rel="shortcut icon" href="./favicon.ico" />
</head>
<body data-theme="default">
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -113,7 +114,7 @@
})(document, 'script');
}
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
<link rel="stylesheet" href="./css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

View File

@@ -11,12 +11,16 @@ const config: Config.InitialOptions = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
modulePathIgnorePatterns: ['dist'],
moduleNameMapper: {
'\\.(png|jpg|jpeg|gif|svg|webp|avif|ico|bmp|tiff)$':
'<rootDir>/__mocks__/fileMock.ts',
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
'^@signozhq/resizable$': '<rootDir>/__mocks__/resizableMock.tsx',
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^hooks/useSafeNavigate\\.impl$': '<rootDir>/src/hooks/useSafeNavigate.ts',
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^constants/env$': '<rootDir>/__mocks__/env.ts',
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',

View File

@@ -10,9 +10,10 @@
"preview": "vite preview",
"prettify": "prettier --write .",
"fmt": "prettier --check .",
"lint": "eslint ./src",
"lint": "eslint ./src && stylelint \"src/**/*.scss\"",
"lint:generated": "eslint ./src/api/generated --fix",
"lint:fix": "eslint ./src --fix",
"lint:styles": "stylelint \"src/**/*.scss\"",
"jest": "jest",
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
@@ -65,14 +66,12 @@
"@signozhq/sonner": "0.1.0",
"@signozhq/switch": "0.0.2",
"@signozhq/table": "0.3.7",
"@signozhq/tabs": "0.0.11",
"@signozhq/toggle-group": "0.0.1",
"@signozhq/tooltip": "0.0.2",
"@signozhq/ui": "0.0.5",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",
"@uiw/codemirror-theme-dracula": "4.25.9",
"@uiw/codemirror-theme-github": "4.24.1",
"@uiw/react-codemirror": "4.23.10",
"@uiw/react-md-editor": "3.23.5",
@@ -231,6 +230,7 @@
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-rulesdir": "0.2.2",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sonarjs": "^0.12.0",
"husky": "^7.0.4",
@@ -246,6 +246,7 @@
"orval": "7.18.0",
"portfinder-sync": "^0.0.2",
"postcss": "8.5.6",
"postcss-scss": "4.0.9",
"prettier": "2.2.1",
"prop-types": "15.8.1",
"react-hooks-testing-library": "0.6.0",
@@ -253,6 +254,8 @@
"redux-mock-store": "1.5.4",
"sass": "1.97.3",
"sharp": "0.34.5",
"stylelint": "17.7.0",
"stylelint-scss": "7.0.0",
"svgo": "4.0.0",
"ts-api-utils": "2.4.0",
"ts-jest": "29.4.6",

View File

@@ -1,312 +0,0 @@
<svg width="929" height="8" viewBox="0 0 929 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="2" height="2" rx="1" fill="#242834"/>
<rect x="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="12" width="2" height="2" rx="1" fill="#242834"/>
<rect x="18" width="2" height="2" rx="1" fill="#242834"/>
<rect x="24" width="2" height="2" rx="1" fill="#242834"/>
<rect x="30" width="2" height="2" rx="1" fill="#242834"/>
<rect x="36" width="2" height="2" rx="1" fill="#242834"/>
<rect x="42" width="2" height="2" rx="1" fill="#242834"/>
<rect x="48" width="2" height="2" rx="1" fill="#242834"/>
<rect x="54" width="2" height="2" rx="1" fill="#242834"/>
<rect x="60" width="2" height="2" rx="1" fill="#242834"/>
<rect x="66" width="2" height="2" rx="1" fill="#242834"/>
<rect x="72" width="2" height="2" rx="1" fill="#242834"/>
<rect x="78" width="2" height="2" rx="1" fill="#242834"/>
<rect x="84" width="2" height="2" rx="1" fill="#242834"/>
<rect x="90" width="2" height="2" rx="1" fill="#242834"/>
<rect x="96" width="2" height="2" rx="1" fill="#242834"/>
<rect x="102" width="2" height="2" rx="1" fill="#242834"/>
<rect x="108" width="2" height="2" rx="1" fill="#242834"/>
<rect x="114" width="2" height="2" rx="1" fill="#242834"/>
<rect x="120" width="2" height="2" rx="1" fill="#242834"/>
<rect x="126" width="2" height="2" rx="1" fill="#242834"/>
<rect x="132" width="2" height="2" rx="1" fill="#242834"/>
<rect x="138" width="2" height="2" rx="1" fill="#242834"/>
<rect x="144" width="2" height="2" rx="1" fill="#242834"/>
<rect x="150" width="2" height="2" rx="1" fill="#242834"/>
<rect x="156" width="2" height="2" rx="1" fill="#242834"/>
<rect x="162" width="2" height="2" rx="1" fill="#242834"/>
<rect x="168" width="2" height="2" rx="1" fill="#242834"/>
<rect x="174" width="2" height="2" rx="1" fill="#242834"/>
<rect x="180" width="2" height="2" rx="1" fill="#242834"/>
<rect x="186" width="2" height="2" rx="1" fill="#242834"/>
<rect x="192" width="2" height="2" rx="1" fill="#242834"/>
<rect x="198" width="2" height="2" rx="1" fill="#242834"/>
<rect x="204" width="2" height="2" rx="1" fill="#242834"/>
<rect x="210" width="2" height="2" rx="1" fill="#242834"/>
<rect x="216" width="2" height="2" rx="1" fill="#242834"/>
<rect x="222" width="2" height="2" rx="1" fill="#242834"/>
<rect x="228" width="2" height="2" rx="1" fill="#242834"/>
<rect x="234" width="2" height="2" rx="1" fill="#242834"/>
<rect x="240" width="2" height="2" rx="1" fill="#242834"/>
<rect x="246" width="2" height="2" rx="1" fill="#242834"/>
<rect x="252" width="2" height="2" rx="1" fill="#242834"/>
<rect x="258" width="2" height="2" rx="1" fill="#242834"/>
<rect x="264" width="2" height="2" rx="1" fill="#242834"/>
<rect x="270" width="2" height="2" rx="1" fill="#242834"/>
<rect x="276" width="2" height="2" rx="1" fill="#242834"/>
<rect x="282" width="2" height="2" rx="1" fill="#242834"/>
<rect x="288" width="2" height="2" rx="1" fill="#242834"/>
<rect x="294" width="2" height="2" rx="1" fill="#242834"/>
<rect x="300" width="2" height="2" rx="1" fill="#242834"/>
<rect x="306" width="2" height="2" rx="1" fill="#242834"/>
<rect x="312" width="2" height="2" rx="1" fill="#242834"/>
<rect x="318" width="2" height="2" rx="1" fill="#242834"/>
<rect x="324" width="2" height="2" rx="1" fill="#242834"/>
<rect x="330" width="2" height="2" rx="1" fill="#242834"/>
<rect x="336" width="2" height="2" rx="1" fill="#242834"/>
<rect x="342" width="2" height="2" rx="1" fill="#242834"/>
<rect x="348" width="2" height="2" rx="1" fill="#242834"/>
<rect x="354" width="2" height="2" rx="1" fill="#242834"/>
<rect x="360" width="2" height="2" rx="1" fill="#242834"/>
<rect x="366" width="2" height="2" rx="1" fill="#242834"/>
<rect x="372" width="2" height="2" rx="1" fill="#242834"/>
<rect x="378" width="2" height="2" rx="1" fill="#242834"/>
<rect x="384" width="2" height="2" rx="1" fill="#242834"/>
<rect x="390" width="2" height="2" rx="1" fill="#242834"/>
<rect x="396" width="2" height="2" rx="1" fill="#242834"/>
<rect x="402" width="2" height="2" rx="1" fill="#242834"/>
<rect x="408" width="2" height="2" rx="1" fill="#242834"/>
<rect x="414" width="2" height="2" rx="1" fill="#242834"/>
<rect x="420" width="2" height="2" rx="1" fill="#242834"/>
<rect x="426" width="2" height="2" rx="1" fill="#242834"/>
<rect x="432" width="2" height="2" rx="1" fill="#242834"/>
<rect x="438" width="2" height="2" rx="1" fill="#242834"/>
<rect x="444" width="2" height="2" rx="1" fill="#242834"/>
<rect x="450" width="2" height="2" rx="1" fill="#242834"/>
<rect x="456" width="2" height="2" rx="1" fill="#242834"/>
<rect x="462" width="2" height="2" rx="1" fill="#242834"/>
<rect x="468" width="2" height="2" rx="1" fill="#242834"/>
<rect x="474" width="2" height="2" rx="1" fill="#242834"/>
<rect x="480" width="2" height="2" rx="1" fill="#242834"/>
<rect x="486" width="2" height="2" rx="1" fill="#242834"/>
<rect x="492" width="2" height="2" rx="1" fill="#242834"/>
<rect x="498" width="2" height="2" rx="1" fill="#242834"/>
<rect x="504" width="2" height="2" rx="1" fill="#242834"/>
<rect x="510" width="2" height="2" rx="1" fill="#242834"/>
<rect x="516" width="2" height="2" rx="1" fill="#242834"/>
<rect x="522" width="2" height="2" rx="1" fill="#242834"/>
<rect x="528" width="2" height="2" rx="1" fill="#242834"/>
<rect x="534" width="2" height="2" rx="1" fill="#242834"/>
<rect x="540" width="2" height="2" rx="1" fill="#242834"/>
<rect x="546" width="2" height="2" rx="1" fill="#242834"/>
<rect x="552" width="2" height="2" rx="1" fill="#242834"/>
<rect x="558" width="2" height="2" rx="1" fill="#242834"/>
<rect x="564" width="2" height="2" rx="1" fill="#242834"/>
<rect x="570" width="2" height="2" rx="1" fill="#242834"/>
<rect x="576" width="2" height="2" rx="1" fill="#242834"/>
<rect x="582" width="2" height="2" rx="1" fill="#242834"/>
<rect x="588" width="2" height="2" rx="1" fill="#242834"/>
<rect x="594" width="2" height="2" rx="1" fill="#242834"/>
<rect x="600" width="2" height="2" rx="1" fill="#242834"/>
<rect x="606" width="2" height="2" rx="1" fill="#242834"/>
<rect x="612" width="2" height="2" rx="1" fill="#242834"/>
<rect x="618" width="2" height="2" rx="1" fill="#242834"/>
<rect x="624" width="2" height="2" rx="1" fill="#242834"/>
<rect x="630" width="2" height="2" rx="1" fill="#242834"/>
<rect x="636" width="2" height="2" rx="1" fill="#242834"/>
<rect x="642" width="2" height="2" rx="1" fill="#242834"/>
<rect x="648" width="2" height="2" rx="1" fill="#242834"/>
<rect x="654" width="2" height="2" rx="1" fill="#242834"/>
<rect x="660" width="2" height="2" rx="1" fill="#242834"/>
<rect x="666" width="2" height="2" rx="1" fill="#242834"/>
<rect x="672" width="2" height="2" rx="1" fill="#242834"/>
<rect x="678" width="2" height="2" rx="1" fill="#242834"/>
<rect x="684" width="2" height="2" rx="1" fill="#242834"/>
<rect x="690" width="2" height="2" rx="1" fill="#242834"/>
<rect x="696" width="2" height="2" rx="1" fill="#242834"/>
<rect x="702" width="2" height="2" rx="1" fill="#242834"/>
<rect x="708" width="2" height="2" rx="1" fill="#242834"/>
<rect x="714" width="2" height="2" rx="1" fill="#242834"/>
<rect x="720" width="2" height="2" rx="1" fill="#242834"/>
<rect x="726" width="2" height="2" rx="1" fill="#242834"/>
<rect x="732" width="2" height="2" rx="1" fill="#242834"/>
<rect x="738" width="2" height="2" rx="1" fill="#242834"/>
<rect x="744" width="2" height="2" rx="1" fill="#242834"/>
<rect x="750" width="2" height="2" rx="1" fill="#242834"/>
<rect x="756" width="2" height="2" rx="1" fill="#242834"/>
<rect x="762" width="2" height="2" rx="1" fill="#242834"/>
<rect x="768" width="2" height="2" rx="1" fill="#242834"/>
<rect x="774" width="2" height="2" rx="1" fill="#242834"/>
<rect x="780" width="2" height="2" rx="1" fill="#242834"/>
<rect x="786" width="2" height="2" rx="1" fill="#242834"/>
<rect x="792" width="2" height="2" rx="1" fill="#242834"/>
<rect x="798" width="2" height="2" rx="1" fill="#242834"/>
<rect x="804" width="2" height="2" rx="1" fill="#242834"/>
<rect x="810" width="2" height="2" rx="1" fill="#242834"/>
<rect x="816" width="2" height="2" rx="1" fill="#242834"/>
<rect x="822" width="2" height="2" rx="1" fill="#242834"/>
<rect x="828" width="2" height="2" rx="1" fill="#242834"/>
<rect x="834" width="2" height="2" rx="1" fill="#242834"/>
<rect x="840" width="2" height="2" rx="1" fill="#242834"/>
<rect x="846" width="2" height="2" rx="1" fill="#242834"/>
<rect x="852" width="2" height="2" rx="1" fill="#242834"/>
<rect x="858" width="2" height="2" rx="1" fill="#242834"/>
<rect x="864" width="2" height="2" rx="1" fill="#242834"/>
<rect x="870" width="2" height="2" rx="1" fill="#242834"/>
<rect x="876" width="2" height="2" rx="1" fill="#242834"/>
<rect x="882" width="2" height="2" rx="1" fill="#242834"/>
<rect x="888" width="2" height="2" rx="1" fill="#242834"/>
<rect x="894" width="2" height="2" rx="1" fill="#242834"/>
<rect x="900" width="2" height="2" rx="1" fill="#242834"/>
<rect x="906" width="2" height="2" rx="1" fill="#242834"/>
<rect x="912" width="2" height="2" rx="1" fill="#242834"/>
<rect x="918" width="2" height="2" rx="1" fill="#242834"/>
<rect x="924" width="2" height="2" rx="1" fill="#242834"/>
<rect y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="6" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="12" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="18" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="24" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="30" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="36" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="42" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="48" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="54" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="60" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="66" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="72" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="78" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="84" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="90" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="96" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="102" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="108" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="114" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="120" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="126" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="132" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="138" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="144" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="150" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="156" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="162" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="168" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="174" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="180" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="186" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="192" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="198" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="204" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="210" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="216" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="222" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="228" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="234" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="240" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="246" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="252" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="258" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="264" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="270" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="276" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="282" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="288" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="294" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="300" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="306" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="312" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="318" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="324" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="330" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="336" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="342" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="348" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="354" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="360" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="366" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="372" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="378" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="384" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="390" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="396" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="402" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="408" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="414" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="420" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="426" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="432" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="438" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="444" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="450" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="456" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="462" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="468" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="474" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="480" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="486" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="492" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="498" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="504" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="510" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="516" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="522" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="528" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="534" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="540" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="546" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="552" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="558" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="564" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="570" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="576" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="582" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="588" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="594" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="600" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="606" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="612" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="618" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="624" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="630" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="636" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="642" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="648" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="654" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="660" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="666" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="672" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="678" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="684" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="690" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="696" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="702" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="708" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="714" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="720" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="726" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="732" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="738" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="744" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="750" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="756" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="762" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="768" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="774" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="780" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="786" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="792" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="798" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="804" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="810" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="816" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="822" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="828" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="834" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="840" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="846" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="852" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="858" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="864" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="870" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="876" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="882" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="888" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="894" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="900" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="906" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="912" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="918" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="924" y="6" width="2" height="2" rx="1" fill="#242834"/>
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -244,18 +244,12 @@ export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
);
export const Integrations = Loadable(
export const InstalledIntegrations = Loadable(
() =>
import(
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
),
);
export const IntegrationsDetailsPage = Loadable(
() =>
import(
/* webpackChunkName: "IntegrationsDetailsPage" */ 'pages/IntegrationsDetailsPage'
),
);
export const MessagingQueuesMainPage = Loadable(
() =>

View File

@@ -18,8 +18,7 @@ import {
ForgotPassword,
Home,
InfrastructureMonitoring,
Integrations,
IntegrationsDetailsPage,
InstalledIntegrations,
LicensePage,
ListAllALertsPage,
LiveLogs,
@@ -390,17 +389,10 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.INTEGRATIONS_DETAIL,
exact: true,
component: IntegrationsDetailsPage,
isPrivate: true,
key: 'INTEGRATIONS_DETAIL',
},
{
path: ROUTES.INTEGRATIONS,
exact: true,
component: Integrations,
component: InstalledIntegrations,
isPrivate: true,
key: 'INTEGRATIONS',
},

View File

@@ -5,10 +5,11 @@ import {
ServiceData,
UpdateServiceConfigPayload,
UpdateServiceConfigResponse,
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
} from 'container/CloudIntegrationPage/ServicesSection/types';
import {
AccountConfigPayload,
AccountConfigResponse,
AWSAccountConfigPayload,
ConnectionParams,
ConnectionUrlResponse,
} from 'types/api/integrations/aws';
@@ -59,7 +60,7 @@ export const generateConnectionUrl = async (params: {
export const updateAccountConfig = async (
accountId: string,
payload: AWSAccountConfigPayload,
payload: AccountConfigPayload,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/aws/accounts/${accountId}/config`,
@@ -78,3 +79,10 @@ export const updateServiceConfig = async (
);
return response.data;
};
export const getConnectionParams = async (): Promise<ConnectionParams> => {
const response = await axios.get(
'/cloud-integrations/aws/accounts/generate-connection-params',
);
return response.data.data;
};

View File

@@ -1,113 +0,0 @@
import axios from 'api';
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
import {
CloudAccount,
ServiceData,
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
import {
AzureCloudAccountConfig,
AzureService,
AzureServiceConfigPayload,
} from 'container/Integrations/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AccountConfigResponse,
AWSAccountConfigPayload,
} from 'types/api/integrations/aws';
import {
AzureAccountConfig,
IAzureDeploymentCommands,
} from 'types/api/integrations/types';
export const getCloudIntegrationAccounts = async (
cloudServiceId: string,
): Promise<CloudAccount[]> => {
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/accounts`,
);
return response.data.data.accounts;
};
export const getCloudIntegrationServices = async (
cloudServiceId: string,
cloudAccountId?: string,
): Promise<AzureService[]> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/services`,
{
params,
},
);
return response.data.data.services;
};
export const getCloudIntegrationServiceDetails = async (
cloudServiceId: string,
serviceId: string,
cloudAccountId?: string,
): Promise<ServiceData> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/services/${serviceId}`,
{ params },
);
return response.data.data;
};
export const updateAccountConfig = async (
cloudServiceId: string,
accountId: string,
payload: AWSAccountConfigPayload | AzureAccountConfig,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/config`,
payload,
);
return response.data;
};
export const updateServiceConfig = async (
cloudServiceId: string,
serviceId: string,
payload: AzureServiceConfigPayload,
): Promise<AzureServiceConfigPayload> => {
const response = await axios.post<AzureServiceConfigPayload>(
`/cloud-integrations/${cloudServiceId}/services/${serviceId}/config`,
payload,
);
return response.data;
};
export const getAzureDeploymentCommands = async (params: {
agent_config: CloudintegrationtypesCredentialsDTO;
account_config: AzureCloudAccountConfig;
}): Promise<IAzureDeploymentCommands> => {
const response = await axios.post(
`/cloud-integrations/azure/accounts/generate-connection-url`,
params,
);
return response.data.data;
};
export const removeIntegrationAccount = async ({
cloudServiceId,
accountId,
}: {
cloudServiceId: string;
accountId: string;
}): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
const response = await axios.post(
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/disconnect`,
);
return response.data;
};

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 507 B

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 644 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 418 B

View File

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 604 B

View File

Before

Width:  |  Height:  |  Size: 878 B

After

Width:  |  Height:  |  Size: 878 B

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 303 B

After

Width:  |  Height:  |  Size: 303 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 629 B

View File

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View File

Before

Width:  |  Height:  |  Size: 467 B

After

Width:  |  Height:  |  Size: 467 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 300 B

After

Width:  |  Height:  |  Size: 300 B

View File

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 910 B

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

View File

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 204 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 408 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 695 B

View File

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 872 B

After

Width:  |  Height:  |  Size: 872 B

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

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