Compare commits

..

8 Commits

Author SHA1 Message Date
nityanandagohain
a9d33e83a2 chore: module ref 2026-02-26 18:29:28 +05:30
Nityananda Gohain
afcf2c6053 Merge branch 'main' into feat/cloud-integrations-apis 2026-02-25 19:53:25 +05:30
swapnil-signoz
ec59fecdda Feat/azure integration (#10289)
* refactor: updating azure cloud integrations api

* feat: extending cloud integration apis

* refactor: updating cloud integration apis

* refactor: updating cloud-integration controller

* refactor: updating cloud provider type

* refactor: updating service details api

* refactor: code beautification

* refactor: sorting services list for consistency

* refactor: cloud integration API cleanup

* refactor: reverting

* ci: fixing lint ci issues

* feat: adding logs

* fix: aws connection url generation

* refactor: removing comment

* chore: wip

* refactor: wip

* refactor:  updating response for conn status

* refactor: removing wrong deps

* feat: adding comments

* feat: adding default values for azure service config

* feat: updating service definitions

* refactor: updating azure telemetry collections strat

* refactor: aws cloud integration provider impl

* refactor: improving aws cloud integration apis structure

* feat: adding logger in opts

* feat: updating cloud integration aws api

* feat: adding azure cloud integration apis

* test: testing generics

* feat: adding more generic struct

* refactor: removing dummy dashboards

* feat: implement service connection status retrieval for Azure integration

* feat: add ingestion status check for metrics and logs in Azure integration

* chore: merge back

* feat: adding base provider

* feat: moving methods to generic base provider

* refactor: updating agent version

* refactor: update generic type parameters in cloud provider and integration types

* refactor: cloud integration type comments and cleanup

* chore: adding more comments

---------

Co-authored-by: Piyush Singariya <piyushsingariya@gmail.com>
2026-02-25 12:56:34 +05:30
swapnil-signoz
5a762c678e Merge branch 'main' into feat/cloud-integrations-apis 2026-02-23 20:11:36 +05:30
swapnil-signoz
55b1311f78 Merge branch 'main' into feat/cloud-integrations-apis 2026-02-23 10:45:09 +05:30
swapnil-signoz
59668698a2 refactor: using QBv5 for connection status (#10288)
* refactor: updating azure cloud integrations api

* feat: extending cloud integration apis

* refactor: updating cloud integration apis

* refactor: updating cloud-integration controller

* refactor: updating cloud provider type

* refactor: updating service details api

* refactor: code beautification

* refactor: sorting services list for consistency

* refactor: cloud integration API cleanup

* refactor: reverting

* ci: fixing lint ci issues

* feat: adding logs

* fix: aws connection url generation

* refactor: removing comment

* chore: wip

* refactor: wip

* refactor:  updating response for conn status

* refactor: removing wrong deps

* feat: adding comments

* feat: adding default values for azure service config

* feat: updating service definitions

* refactor: updating azure telemetry collections strat

* refactor: aws cloud integration provider impl

* refactor: improving aws cloud integration apis structure

* feat: adding logger in opts

* feat: updating cloud integration aws api

* refactor: using QBv5 for connection status

* refactor: status check

* fix: handle unexpected query response types for service metrics and logs

* refactor: implement centralized panic recovery

* refactor: simplify AWS cloud provider initialization by removing unused reader parameter
2026-02-19 16:19:38 +05:30
swapnil-signoz
22ed687d44 Refactor/aws cloudintegration (#10339)
* refactor: updating azure cloud integrations api

* feat: extending cloud integration apis

* refactor: updating cloud integration apis

* refactor: updating cloud-integration controller

* refactor: updating cloud provider type

* refactor: updating service details api

* refactor: code beautification

* refactor: sorting services list for consistency

* refactor: cloud integration API cleanup

* refactor: reverting

* ci: fixing lint ci issues

* feat: adding logs

* fix: aws connection url generation

* refactor: removing comment

* chore: wip

* refactor: wip

* refactor:  updating response for conn status

* refactor: removing wrong deps

* feat: adding comments

* feat: adding default values for azure service config

* feat: updating service definitions

* refactor: updating azure telemetry collections strat

* refactor: aws cloud integration provider impl

* refactor: improving aws cloud integration apis structure

* feat: adding logger in opts

* feat: updating cloud integration aws api

* feat: using generics

* fix: update error handling in cloud integration for deployment info response

* refactor: optimize dashboard retrieval logic in AWS provider

* refactor: update GetDashboardsFromAssets to include orgID parameter
2026-02-18 12:09:35 +05:30
swapnil-signoz
1da016cf1a Feat: improving cloud integrations APIs (#10280)
* refactor: updating azure cloud integrations api

* feat: extending cloud integration apis

* refactor: updating cloud integration apis

* refactor: updating cloud-integration controller

* refactor: updating cloud provider type

* refactor: updating service details api

* refactor: code beautification

* refactor: sorting services list for consistency

* refactor: cloud integration API cleanup

* refactor: reverting

* ci: fixing lint ci issues

* feat: adding logs

* fix: aws connection url generation

* refactor: removing comment

* chore: wip

* refactor: wip

* refactor:  updating response for conn status

* refactor: removing wrong deps

* feat: adding comments

* feat: adding default values for azure service config

* feat: updating service definitions

* refactor: updating azure telemetry collections strat

* refactor: aws cloud integration provider impl

* refactor: improving aws cloud integration apis structure

* feat: adding logger in opts

* feat: updating cloud integration aws api

* feat: using generics
2026-02-18 11:43:26 +05:30
840 changed files with 12269 additions and 14352 deletions

View File

@@ -320,4 +320,3 @@ user:
# The name of the organization to create or look up for the root user.
org:
name: default
id: 00000000-0000-0000-0000-000000000000

View File

@@ -842,17 +842,6 @@ components:
- temporality
- isMonotonic
type: object
MetrictypesComparisonSpaceAggregationParam:
properties:
operator:
type: string
threshold:
format: double
type: number
required:
- operator
- threshold
type: object
MetrictypesSpaceAggregation:
enum:
- sum
@@ -1149,8 +1138,6 @@ components:
type: object
Querybuildertypesv5MetricAggregation:
properties:
comparisonSpaceAggregationParam:
$ref: '#/components/schemas/MetrictypesComparisonSpaceAggregationParam'
metricName:
type: string
reduceTo:

View File

@@ -0,0 +1,164 @@
package implawsprovider
import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/modules/cloudintegrations"
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
)
var _ cloudintegrations.CloudProvider = (*AWSProvider)(nil)
type AWSProvider struct {
store integrationtypes.Store
}
func NewAWSProvider(store integrationtypes.Store) *AWSProvider {
return &AWSProvider{store: store}
}
func (a *AWSProvider) AgentCheckIn(ctx context.Context, req *cloudintegrations.PostableAgentCheckInPayload) (any, error) {
// if apiErr := validateCloudProviderName(cloudProvider); apiErr != nil {
// return nil, apiErr
// }
// existingAccount, apiErr := c.accountsRepo.get(ctx, orgId, cloudProvider, req.ID)
// if existingAccount != nil && existingAccount.AccountID != nil && *existingAccount.AccountID != req.AccountID {
// return nil, model.BadRequest(fmt.Errorf(
// "can't check in with new %s account id %s for account %s with existing %s id %s",
// cloudProvider, req.AccountID, existingAccount.ID.StringValue(), cloudProvider, *existingAccount.AccountID,
// ))
// }
// existingAccount, apiErr = c.accountsRepo.getConnectedCloudAccount(ctx, orgId, cloudProvider, req.AccountID)
// if existingAccount != nil && existingAccount.ID.StringValue() != req.ID {
// return nil, model.BadRequest(fmt.Errorf(
// "can't check in to %s account %s with id %s. already connected with id %s",
// cloudProvider, req.AccountID, req.ID, existingAccount.ID.StringValue(),
// ))
// }
// agentReport := types.AgentReport{
// TimestampMillis: time.Now().UnixMilli(),
// Data: req.Data,
// }
// account, apiErr := c.accountsRepo.upsert(
// ctx, orgId, cloudProvider, &req.ID, nil, &req.AccountID, &agentReport, nil,
// )
// if apiErr != nil {
// return nil, model.WrapApiError(apiErr, "couldn't upsert cloud account")
// }
// // prepare and return integration config to be consumed by agent
// compiledStrategy, err := NewCompiledCollectionStrategy(cloudProvider)
// if err != nil {
// return nil, model.InternalError(fmt.Errorf(
// "couldn't init telemetry collection strategy: %w", err,
// ))
// }
// agentConfig := IntegrationConfigForAgent{
// EnabledRegions: []string{},
// TelemetryCollectionStrategy: compiledStrategy,
// }
// if account.Config != nil && account.Config.EnabledRegions != nil {
// agentConfig.EnabledRegions = account.Config.EnabledRegions
// }
// services, err := services.Map(cloudProvider)
// if err != nil {
// return nil, err
// }
// svcConfigs, apiErr := c.serviceConfigRepo.getAllForAccount(
// ctx, orgId, account.ID.StringValue(),
// )
// if apiErr != nil {
// return nil, model.WrapApiError(
// apiErr, "couldn't get service configs for cloud account",
// )
// }
// // accumulate config in a fixed order to ensure same config generated across runs
// configuredServices := maps.Keys(svcConfigs)
// slices.Sort(configuredServices)
// for _, svcType := range configuredServices {
// definition, ok := services[svcType]
// if !ok {
// continue
// }
// config := svcConfigs[svcType]
// err := AddServiceStrategy(svcType, compiledStrategy, definition.Strategy, config)
// if err != nil {
// return nil, err
// }
// }
// return &AgentCheckInResponse{
// AccountId: account.ID.StringValue(),
// CloudAccountId: *account.AccountID,
// RemovedAt: account.RemovedAt,
// IntegrationConfig: agentConfig,
// }, nil
}
func (a *AWSProvider) ListServices(ctx context.Context, orgID string, cloudAccountID *string) (any, error) {
svcConfigs := make(map[string]*integrationtypes.AWSServiceConfig)
if cloudAccountID != nil {
activeAccount, err := a.store.GetConnectedCloudAccount(ctx, orgID, a.GetName().String(), *cloudAccountID)
if err != nil {
return nil, err
}
serviceConfigs, err := a.ServiceConfigRepo.GetAllForAccount(ctx, orgID, activeAccount.ID.String())
if err != nil {
return nil, err
}
for svcType, config := range serviceConfigs {
serviceConfig := new(integrationtypes.AWSServiceConfig)
err = integrationtypes.UnmarshalJSON(config, serviceConfig)
if err != nil {
return nil, err
}
svcConfigs[svcType] = serviceConfig
}
}
summaries := make([]integrationtypes.AWSServiceSummary, 0)
definitions, err := a.ServiceDefinitions.ListServiceDefinitions(ctx)
if err != nil {
return nil, err
}
for _, def := range definitions {
summary := integrationtypes.AWSServiceSummary{
DefinitionMetadata: def.DefinitionMetadata,
Config: nil,
}
summary.Config = svcConfigs[summary.Id]
summaries = append(summaries, summary)
}
slices.SortFunc(summaries, func(a, b integrationtypes.AWSServiceSummary) int {
if a.DefinitionMetadata.Title < b.DefinitionMetadata.Title {
return -1
}
if a.DefinitionMetadata.Title > b.DefinitionMetadata.Title {
return 1
}
return 0
})
return &integrationtypes.GettableAWSServices{
Services: summaries,
}, nil
}

View File

@@ -0,0 +1,36 @@
package implcloudintegrations
import (
"context"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/modules/cloudintegrations"
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
)
type module struct {
store integrationtypes.Store
providers map[integrationtypes.CloudProviderType]cloudintegrations.CloudProvider
}
func NewModule(store integrationtypes.Store, providers map[integrationtypes.CloudProviderType]cloudintegrations.CloudProvider) cloudintegrations.Module {
return &module{store: store}
}
func (m *module) ListServices(ctx context.Context, orgID string, cloudProvider string, cloudAccountId *string) (any, error) {
provider, err := m.getProvider(cloudProvider)
if err != nil {
return nil, err
}
return provider.ListServices(ctx, orgID, cloudAccountId)
}
func (m *module) getProvider(cloudProvider integrationtypes.CloudProviderType) (cloudintegrations.CloudProvider, error) {
provider, ok := m.providers[cloudProvider]
if !ok {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid cloud provider: %s", cloudProvider)
}
return provider, nil
}

View File

@@ -1,6 +1,7 @@
package api
import (
"log/slog"
"net/http"
"time"
@@ -10,7 +11,6 @@ import (
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/http/middleware"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
@@ -27,12 +27,12 @@ type APIHandlerOptions struct {
RulesManager *rules.Manager
UsageManager *usage.Manager
IntegrationsController *integrations.Controller
CloudIntegrationsController *cloudintegrations.Controller
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
GatewayUrl string
// Querier Influx Interval
FluxInterval time.Duration
GlobalConfig global.Config
Logger *slog.Logger // this is present in Signoz.Instrumentation but adding for quick access
}
type APIHandler struct {
@@ -46,13 +46,13 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
Reader: opts.DataConnector,
RuleManager: opts.RulesManager,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
LogsParsingPipelineController: opts.LogsParsingPipelineController,
FluxInterval: opts.FluxInterval,
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
Signoz: signoz,
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
Logger: opts.Logger,
}, config)
if err != nil {
@@ -101,14 +101,12 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
}
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
router.HandleFunc(
"/api/v1/cloud-integrations/{cloudProvider}/accounts/generate-connection-params",
am.EditAccess(ah.CloudIntegrationsGenerateConnectionParams),
).Methods(http.MethodGet)
}
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"time"
@@ -13,20 +14,14 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/user"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/integrationtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
"go.uber.org/zap"
)
type CloudIntegrationConnectionParamsResponse struct {
IngestionUrl string `json:"ingestion_url,omitempty"`
IngestionKey string `json:"ingestion_key,omitempty"`
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
}
// TODO: move this file with other cloud integration related code
func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
claims, err := authtypes.ClaimsFromContext(r.Context())
@@ -41,23 +36,21 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}
cloudProvider := mux.Vars(r)["cloudProvider"]
if cloudProvider != "aws" {
RespondError(w, basemodel.BadRequest(fmt.Errorf(
"cloud provider not supported: %s", cloudProvider,
)), nil)
cloudProviderString := mux.Vars(r)["cloudProvider"]
cloudProvider, err := integrationtypes.NewCloudProvider(cloudProviderString)
if err != nil {
render.Error(w, err)
return
}
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
), nil)
apiKey, err := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
if err != nil {
render.Error(w, err)
return
}
result := CloudIntegrationConnectionParamsResponse{
result := integrationtypes.GettableCloudIntegrationConnectionParams{
SigNozAPIKey: apiKey,
}
@@ -71,16 +64,17 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
// Return the API Key (PAT) even if the rest of the params can not be deduced.
// Params not returned from here will be requested from the user via form inputs.
// This enables gracefully degraded but working experience even for non-cloud deployments.
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
ah.Respond(w, result)
ah.opts.Logger.InfoContext(
r.Context(),
"ingestion params and signoz api url can not be deduced since no license was found",
)
render.Success(w, http.StatusOK, result)
return
}
signozApiUrl, apiErr := ah.getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't deduce ingestion url and signoz api url",
), nil)
signozApiUrl, err := ah.getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
if err != nil {
render.Error(w, err)
return
}
@@ -89,48 +83,41 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
gatewayUrl := ah.opts.GatewayUrl
if len(gatewayUrl) > 0 {
ingestionKey, apiErr := getOrCreateCloudProviderIngestionKey(
ingestionKeyString, err := ah.getOrCreateCloudProviderIngestionKey(
r.Context(), gatewayUrl, license.Key, cloudProvider,
)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't get or create ingestion key",
), nil)
if err != nil {
render.Error(w, err)
return
}
result.IngestionKey = ingestionKey
result.IngestionKey = ingestionKeyString
} else {
zap.L().Info("ingestion key can't be deduced since no gateway url has been configured")
ah.opts.Logger.InfoContext(
r.Context(),
"ingestion key can't be deduced since no gateway url has been configured",
)
}
ah.Respond(w, result)
render.Success(w, http.StatusOK, result)
}
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
string, *basemodel.ApiError,
) {
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider valuer.String) (string, error) {
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
if apiErr != nil {
return "", apiErr
integrationUser, err := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
if err != nil {
return "", err
}
orgIdUUID, err := valuer.NewUUID(orgId)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't parse orgId: %w", err,
))
return "", err
}
allPats, err := ah.Signoz.Modules.User.ListAPIKeys(ctx, orgIdUUID)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't list PATs: %w", err,
))
return "", err
}
for _, p := range allPats {
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
@@ -138,9 +125,10 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
}
}
zap.L().Info(
ah.opts.Logger.InfoContext(
ctx,
"no PAT found for cloud integration, creating a new one",
zap.String("cloudProvider", cloudProvider),
slog.String("cloudProvider", cloudProvider.String()),
)
newPAT, err := types.NewStorableAPIKey(
@@ -150,68 +138,48 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
0,
)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err,
))
return "", err
}
err = ah.Signoz.Modules.User.CreateAPIKey(ctx, newPAT)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err,
))
return "", err
}
return newPAT.Token, nil
}
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*types.User, *basemodel.ApiError) {
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
// TODO: move this function out of handler and use proper module structure
func (ah *APIHandler) getOrCreateCloudIntegrationUser(ctx context.Context, orgId string, cloudProvider valuer.String) (*types.User, error) {
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider.String())
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId))
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
return nil, err
}
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't look for integration user: %w", err))
return nil, err
}
return cloudIntegrationUser, nil
}
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
string, *basemodel.ApiError,
) {
// TODO: remove this struct from here
type deploymentResponse struct {
Name string `json:"name"`
ClusterInfo struct {
Region struct {
DNS string `json:"dns"`
} `json:"region"`
} `json:"cluster"`
}
// TODO: move this function out of handler and use proper module structure
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (string, error) {
respBytes, err := ah.Signoz.Zeus.GetDeployment(ctx, licenseKey)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't query for deployment info: error: %w", err,
))
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't query for deployment info: error")
}
resp := new(deploymentResponse)
resp := new(integrationtypes.GettableDeployment)
err = json.Unmarshal(respBytes, resp)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't unmarshal deployment info response: error: %w", err,
))
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't unmarshal deployment info response")
}
regionDns := resp.ClusterInfo.Region.DNS
@@ -219,9 +187,10 @@ func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licens
if len(regionDns) < 1 || len(deploymentName) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", basemodel.InternalError(fmt.Errorf(
return "", errors.NewInternalf(
errors.CodeInternal,
"deployment info response not in expected shape. couldn't determine region dns and deployment name",
))
)
}
signozApiUrl := fmt.Sprintf("https://%s.%s", deploymentName, regionDns)
@@ -229,102 +198,85 @@ func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licens
return signozApiUrl, nil
}
type ingestionKey struct {
Name string `json:"name"`
Value string `json:"value"`
// other attributes from gateway response not included here since they are not being used.
}
type ingestionKeysSearchResponse struct {
Status string `json:"status"`
Data []ingestionKey `json:"data"`
Error string `json:"error"`
}
type createIngestionKeyResponse struct {
Status string `json:"status"`
Data ingestionKey `json:"data"`
Error string `json:"error"`
}
func getOrCreateCloudProviderIngestionKey(
ctx context.Context, gatewayUrl string, licenseKey string, cloudProvider string,
) (string, *basemodel.ApiError) {
func (ah *APIHandler) getOrCreateCloudProviderIngestionKey(
ctx context.Context, gatewayUrl string, licenseKey string, cloudProvider valuer.String,
) (string, error) {
cloudProviderKeyName := fmt.Sprintf("%s-integration", cloudProvider)
// see if the key already exists
searchResult, apiErr := requestGateway[ingestionKeysSearchResponse](
searchResult, err := requestGateway[integrationtypes.GettableIngestionKeysSearch](
ctx,
gatewayUrl,
licenseKey,
fmt.Sprintf("/v1/workspaces/me/keys/search?name=%s", cloudProviderKeyName),
nil,
ah.opts.Logger,
)
if apiErr != nil {
return "", basemodel.WrapApiError(
apiErr, "couldn't search for cloudprovider ingestion key",
)
if err != nil {
return "", err
}
if searchResult.Status != "success" {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't search for cloudprovider ingestion key: status: %s, error: %s",
return "", errors.NewInternalf(
errors.CodeInternal,
"couldn't search for cloud provider ingestion key: status: %s, error: %s",
searchResult.Status, searchResult.Error,
))
}
for _, k := range searchResult.Data {
if k.Name == cloudProviderKeyName {
if len(k.Value) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", basemodel.InternalError(fmt.Errorf(
"ingestion keys search response not as expected",
))
}
return k.Value, nil
}
}
zap.L().Info(
"no existing ingestion key found for cloud integration, creating a new one",
zap.String("cloudProvider", cloudProvider),
)
createKeyResult, apiErr := requestGateway[createIngestionKeyResponse](
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
map[string]any{
"name": cloudProviderKeyName,
"tags": []string{"integration", cloudProvider},
},
)
if apiErr != nil {
return "", basemodel.WrapApiError(
apiErr, "couldn't create cloudprovider ingestion key",
)
}
for _, k := range searchResult.Data {
if k.Name != cloudProviderKeyName {
continue
}
if len(k.Value) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", errors.NewInternalf(errors.CodeInternal, "ingestion keys search response not as expected")
}
return k.Value, nil
}
ah.opts.Logger.InfoContext(
ctx,
"no existing ingestion key found for cloud integration, creating a new one",
slog.String("cloudProvider", cloudProvider.String()),
)
createKeyResult, err := requestGateway[integrationtypes.GettableCreateIngestionKey](
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
map[string]any{
"name": cloudProviderKeyName,
"tags": []string{"integration", cloudProvider.String()},
},
ah.opts.Logger,
)
if err != nil {
return "", err
}
if createKeyResult.Status != "success" {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloudprovider ingestion key: status: %s, error: %s",
return "", errors.NewInternalf(
errors.CodeInternal,
"couldn't create cloud provider ingestion key: status: %s, error: %s",
createKeyResult.Status, createKeyResult.Error,
))
)
}
ingestionKey := createKeyResult.Data.Value
if len(ingestionKey) < 1 {
ingestionKeyString := createKeyResult.Data.Value
if len(ingestionKeyString) < 1 {
// Fail early if actual response structure and expectation here ever diverge
return "", basemodel.InternalError(fmt.Errorf(
return "", errors.NewInternalf(errors.CodeInternal,
"ingestion key creation response not as expected",
))
)
}
return ingestionKey, nil
return ingestionKeyString, nil
}
func requestGateway[ResponseType any](
ctx context.Context, gatewayUrl string, licenseKey string, path string, payload any,
) (*ResponseType, *basemodel.ApiError) {
ctx context.Context, gatewayUrl, licenseKey, path string, payload any, logger *slog.Logger,
) (*ResponseType, error) {
baseUrl := strings.TrimSuffix(gatewayUrl, "/")
reqUrl := fmt.Sprintf("%s%s", baseUrl, path)
@@ -335,13 +287,12 @@ func requestGateway[ResponseType any](
"X-Consumer-Groups": "ns:default",
}
return requestAndParseResponse[ResponseType](ctx, reqUrl, headers, payload)
return requestAndParseResponse[ResponseType](ctx, reqUrl, headers, payload, logger)
}
func requestAndParseResponse[ResponseType any](
ctx context.Context, url string, headers map[string]string, payload any,
) (*ResponseType, *basemodel.ApiError) {
ctx context.Context, url string, headers map[string]string, payload any, logger *slog.Logger,
) (*ResponseType, error) {
reqMethod := http.MethodGet
var reqBody io.Reader
if payload != nil {
@@ -349,18 +300,14 @@ func requestAndParseResponse[ResponseType any](
bodyJson, err := json.Marshal(payload)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't serialize request payload to JSON: %w", err,
))
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't marshal payload")
}
reqBody = bytes.NewBuffer([]byte(bodyJson))
reqBody = bytes.NewBuffer(bodyJson)
}
req, err := http.NewRequestWithContext(ctx, reqMethod, url, reqBody)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't prepare request: %w", err,
))
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't create req")
}
for k, v := range headers {
@@ -373,23 +320,26 @@ func requestAndParseResponse[ResponseType any](
response, err := client.Do(req)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't make request: %w", err))
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't make req")
}
defer response.Body.Close()
defer func() {
err = response.Body.Close()
if err != nil {
logger.ErrorContext(ctx, "couldn't close response body", "error", err)
}
}()
respBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't read response: %w", err))
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't read response body")
}
var resp ResponseType
err = json.Unmarshal(respBody, &resp)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't unmarshal gateway response into %T", resp,
))
return nil, errors.WrapInternalf(err, errors.CodeInternal, "couldn't unmarshal response body")
}
return &resp, nil

View File

@@ -37,7 +37,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/app/opamp"
@@ -121,13 +120,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
)
}
cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore)
if err != nil {
return nil, fmt.Errorf(
"couldn't create cloud provider integrations controller: %w", err,
)
}
// ingestion pipelines manager
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
signoz.SQLStore,
@@ -161,11 +153,11 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
RulesManager: rm,
UsageManager: usageManager,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
LogsParsingPipelineController: logParsingPipelineController,
FluxInterval: config.Querier.FluxInterval,
GatewayUrl: config.Gateway.URL.String(),
GlobalConfig: config.Global,
Logger: signoz.Instrumentation.Logger(),
}
apiHandler, err := api.NewAPIHandler(apiOpts, signoz, config)

View File

@@ -26,7 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/utils/times"
"github.com/SigNoz/signoz/pkg/query-service/utils/timestamp"
"github.com/SigNoz/signoz/pkg/units"
"github.com/SigNoz/signoz/pkg/query-service/formatter"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
@@ -335,7 +335,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
prevState := r.State()
valueFormatter := units.FormatterFromUnit(r.Unit())
valueFormatter := formatter.FromUnit(r.Unit())
var res ruletypes.Vector
var err error

View File

@@ -78,7 +78,7 @@ module.exports = {
// TODO: Change to 'error' after fixing ~80 empty function placeholders in providers/contexts
'@typescript-eslint/no-empty-function': 'off', // Disallows empty function bodies
'@typescript-eslint/no-var-requires': 'error', // Disallows require() in TypeScript (use import instead)
'@typescript-eslint/ban-ts-comment': 'warn', // Allows @ts-ignore comments (sometimes needed for third-party libs)
'@typescript-eslint/ban-ts-comment': 'off', // Allows @ts-ignore comments (sometimes needed for third-party libs)
'no-empty-function': 'off', // Disabled in favor of TypeScript version above
// React rules
@@ -146,49 +146,6 @@ module.exports = {
// SonarJS - code quality and complexity
'sonarjs/no-duplicate-string': 'off', // Disabled - can be noisy (enable periodically to check)
// State management governance
// Approved patterns: Zustand, nuqs (URL state), react-query (server state), useState/useRef/useReducer, localStorage/sessionStorage for simple cases
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'redux',
message:
'[State mgmt] redux is deprecated. Migrate to Zustand, nuqs, or react-query.',
},
{
name: 'react-redux',
message:
'[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query.',
},
{
name: 'xstate',
message:
'[State mgmt] xstate is deprecated. Migrate to Zustand or react-query.',
},
{
name: '@xstate/react',
message:
'[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query.',
},
{
// Restrict React Context — useState/useRef/useReducer remain allowed
name: 'react',
importNames: ['createContext', 'useContext'],
message:
'[State mgmt] React Context is deprecated. Migrate shared state to Zustand.',
},
{
// immer used standalone as a store pattern is deprecated; Zustand bundles it internally
name: 'immer',
message:
'[State mgmt] Direct immer usage is deprecated. Use Zustand (which integrates immer via the immer middleware) instead.',
},
],
},
],
},
overrides: [
{

View File

@@ -117,7 +117,6 @@
"lucide-react": "0.498.0",
"mini-css-extract-plugin": "2.4.5",
"motion": "12.4.13",
"nuqs": "2.8.8",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
@@ -131,7 +130,7 @@
"react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-error-boundary": "4.0.11",
"react-force-graph-2d": "^1.29.1",
"react-force-graph": "^1.43.0",
"react-full-screen": "1.1.1",
"react-grid-layout": "^1.3.4",
"react-helmet-async": "1.3.0",
@@ -163,8 +162,7 @@
"webpack": "5.94.0",
"webpack-dev-server": "^5.2.1",
"webpack-retry-chunk-load-plugin": "3.1.1",
"xstate": "^4.31.0",
"zustand": "5.0.11"
"xstate": "^4.31.0"
},
"browserslist": {
"production": [
@@ -288,5 +286,6 @@
"brace-expansion": "^2.0.2",
"on-headers": "^1.1.0",
"tmp": "0.2.4"
}
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@@ -13,6 +13,5 @@
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics",
"roles": "Roles",
"members": "Members"
"roles": "Roles"
}

View File

@@ -13,6 +13,5 @@
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics",
"roles": "Roles",
"members": "Members"
"roles": "Roles"
}

View File

@@ -297,6 +297,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}, [isLoggedInState, pathname, user, isOldRoute, currentRoute, location]);
// NOTE: disabling this rule as there is no need to have div
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}

View File

@@ -218,8 +218,12 @@ function App(): JSX.Element {
pathname === ROUTES.ONBOARDING ||
pathname.startsWith('/public/dashboard/')
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Pylon('hideChatBubble');
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Pylon('showChatBubble');
}
}, [pathname]);

View File

@@ -44,6 +44,7 @@ const dashboardVariablesQuery = async (
} catch (error) {
const formattedError = ErrorResponseHandler(error as AxiosError);
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw { message: 'Error fetching data', details: formattedError };
}
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import axios from 'api';
import { getFieldKeys } from '../getFieldKeys';

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import axios from 'api';
import { getFieldValues } from '../getFieldValues';

View File

@@ -1006,18 +1006,6 @@ export interface MetricsexplorertypesUpdateMetricMetadataRequestDTO {
unit: string;
}
export interface MetrictypesComparisonSpaceAggregationParamDTO {
/**
* @type string
*/
operator: string;
/**
* @type number
* @format double
*/
threshold: number;
}
export enum MetrictypesSpaceAggregationDTO {
sum = 'sum',
avg = 'avg',
@@ -1379,7 +1367,6 @@ export interface Querybuildertypesv5LogAggregationDTO {
}
export interface Querybuildertypesv5MetricAggregationDTO {
comparisonSpaceAggregationParam?: MetrictypesComparisonSpaceAggregationParamDTO;
/**
* @type string
*/

View File

@@ -1,4 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { QueryClient } from 'react-query';
import getLocalStorageApi from 'api/browser/localstorage/get';
import post from 'api/v2/sessions/rotate/post';

View File

@@ -50,7 +50,6 @@ export interface HostListResponse {
total: number;
sentAnyHostMetricsData: boolean;
isSendingK8SAgentMetrics: boolean;
endTimeBeforeRetention: boolean;
};
}

View File

@@ -0,0 +1,26 @@
import { ApiV2Instance as axios } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponseV2, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { MetricMetadataResponse } from 'types/api/metricsExplorer/v2/getMetricMetadata';
export const getMetricMetadata = async (
metricName: string,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponseV2<MetricMetadataResponse> | ErrorResponseV2> => {
try {
const encodedMetricName = encodeURIComponent(metricName);
const response = await axios.get(`/metrics/${encodedMetricName}/metadata`, {
signal,
headers,
});
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { SuccessResponse } from 'types/api';
import {
MetricRangePayloadV5,

View File

@@ -274,6 +274,7 @@ function convertDistributionData(
distributionData: DistributionData,
legendMap: Record<string, string>,
): any {
// eslint-disable-line @typescript-eslint/no-explicit-any
// Convert V5 distribution format to legacy histogram format
return {
...distributionData,
@@ -414,6 +415,7 @@ export function convertV5ResponseToLegacy(
if (legacyResponse.payload?.data?.result) {
legacyResponse.payload.data.result = legacyResponse.payload.data.result.map(
(queryData: any) => {
// eslint-disable-line @typescript-eslint/no-explicit-any
const newQueryData = cloneDeep(queryData);
newQueryData.legend = legendMap[queryData.queryName];

View File

@@ -1,10 +1,10 @@
/* eslint-disable sonarjs/no-duplicate-string, simple-import-sort/imports, @typescript-eslint/indent, no-mixed-spaces-and-tabs */
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import {
ClickHouseQuery,
LogAggregation,
@@ -17,6 +17,7 @@ import {
} from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { prepareQueryRangePayloadV5 } from './prepareQueryRangePayloadV5';

View File

@@ -308,7 +308,7 @@ export function createAggregation(
* Converts query builder data to V5 builder queries
*/
export function convertBuilderQueriesToV5(
builderQueries: Record<string, any>,
builderQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
requestType: RequestType,
panelType?: PANEL_TYPES,
): QueryEnvelope[] {
@@ -467,7 +467,7 @@ export function convertTraceOperatorToV5(
* Converts PromQL queries to V5 format
*/
export function convertPromQueriesToV5(
promQueries: Record<string, any>,
promQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
): QueryEnvelope[] {
return Object.entries(promQueries).map(
([queryName, queryData]): QueryEnvelope => ({
@@ -488,7 +488,7 @@ export function convertPromQueriesToV5(
* Converts ClickHouse queries to V5 format
*/
export function convertClickHouseQueriesToV5(
chQueries: Record<string, any>,
chQueries: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
): QueryEnvelope[] {
return Object.entries(chQueries).map(
([queryName, queryData]): QueryEnvelope => ({
@@ -508,8 +508,9 @@ export function convertClickHouseQueriesToV5(
* Helper function to reduce query arrays to objects
*/
function reduceQueriesToObject(
queryArray: any[],
queryArray: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
): { queries: Record<string, any>; legends: Record<string, string> } {
// eslint-disable-line @typescript-eslint/no-explicit-any
const legends: Record<string, string> = {};
const queries = queryArray.reduce((acc, queryItem) => {
if (!queryItem.query) {
@@ -518,7 +519,7 @@ function reduceQueriesToObject(
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
return acc;
}, {} as Record<string, any>);
}, {} as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
return { queries, legends };
}
@@ -588,6 +589,7 @@ export const prepareQueryRangePayloadV5 = ({
limit: formulaData.limit ?? undefined,
legend: isEmpty(formulaData.legend) ? undefined : formulaData.legend,
order: formulaData.orderBy?.map(
// eslint-disable-next-line sonarjs/no-identical-functions
(order: any): OrderBy => ({
key: {
name: order.columnName,

View File

@@ -10,6 +10,7 @@ function ErrorIcon({ ...props }: ErrorIconProps): JSX.Element {
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
>
<path

View File

@@ -96,6 +96,7 @@ export function FilterSelect({
key={filterType.toString()}
placeholder={placeholder}
showSearch
// eslint-disable-next-line react/jsx-props-no-spreading
{...(isMultiple ? { mode: 'multiple' } : {})}
options={mergedOptions}
loading={isFetching}

View File

@@ -1,6 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useMutation } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
@@ -91,6 +90,7 @@ const getColumnSearchProps = (
clearFilters,
close,
}): JSX.Element => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div style={{ padding: 8 }} onKeyDown={(e): void => e.stopPropagation()}>
<Input
ref={searchInput}

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useQuery } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { DefaultOptionType } from 'antd/es/select';
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';

View File

@@ -1,5 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { Color } from '@signozhq/design-tokens';

View File

@@ -1,5 +1,4 @@
import { useCallback, useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { ENTITY_VERSION_V4 } from 'constants/app';

View File

@@ -1,5 +1,4 @@
import { useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Card, Typography } from 'antd';
import logEvent from 'api/common/logEvent';

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { PANEL_TYPES } from 'constants/queryBuilder';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';

View File

@@ -1,5 +1,4 @@
import { useCallback, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { Col, Row } from 'antd';

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { Dispatch, SetStateAction, useMemo } from 'react';
import { Col, Row } from 'antd';
import logEvent from 'api/common/logEvent';

View File

@@ -1,6 +1,5 @@
import { useCallback } from 'react';
import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';

View File

@@ -1,5 +1,4 @@
import { useCallback } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';

View File

@@ -1,3 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { fireEvent, render, screen } from '@testing-library/react';

View File

@@ -1,3 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { render, screen } from '@testing-library/react';

View File

@@ -1,3 +1,6 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import {
ChangeEvent,
Dispatch,

View File

@@ -92,6 +92,7 @@ const getDateRange = (
return { from, to };
};
// eslint-disable-next-line sonarjs/cognitive-complexity
function CustomTimePickerPopoverContent({
isLiveLogsEnabled,
minTime,

View File

@@ -1,5 +1,5 @@
/* eslint-disable react/jsx-props-no-spreading */
import { Dispatch, SetStateAction, useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { DatePicker } from 'antd';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -45,6 +45,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
// Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
const disabledDate = (current: any): boolean => {
const currentDay = dayjs(current);
return currentDay.isAfter(dayjs());

View File

@@ -124,6 +124,7 @@ const filterAndSortTimezones = (
export const generateTimezoneData = (
includeEtcTimezones = false,
): Timezone[] => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const allTimezones = (Intl as any).supportedValuesOf('timeZone');
const timezones: Timezone[] = [];

View File

@@ -37,7 +37,13 @@ function DraggableTableRow({
drop(drag(ref));
return (
<tr ref={ref} className={className} style={{ ...style }} {...restProps} />
<tr
ref={ref}
className={className}
style={{ ...style }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...restProps}
/>
);
}

View File

@@ -81,6 +81,7 @@ function withErrorBoundary<P extends Record<string, unknown>>(
}}
onError={onError}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<WrappedComponent {...props} />
</Sentry.ErrorBoundary>
);

View File

@@ -19,8 +19,11 @@ jest.mock('react-query', () => ({
const mockError: APIError = new APIError({
httpStatusCode: 400,
error: {
// eslint-disable-next-line sonarjs/no-duplicate-string
message: 'Something went wrong while processing your request.',
// eslint-disable-next-line sonarjs/no-duplicate-string
code: 'An error occurred',
// eslint-disable-next-line sonarjs/no-duplicate-string
url: 'https://example.com/docs',
errors: [
{ message: 'First error detail' },

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-props-no-spreading */
import { ReactNode } from 'react';
import { Popover, PopoverProps } from 'antd';

View File

@@ -9,6 +9,7 @@ import ExplorerCard from '../ExplorerCard';
const historyReplace = jest.fn();
// eslint-disable-next-line sonarjs/no-duplicate-string
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({

View File

@@ -37,6 +37,7 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
return undefined;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const omitIdFromQuery = (query: Query | null): any => ({
...query,
builder: {

View File

@@ -310,6 +310,7 @@ export const createDragSelectPlugin = (): Plugin<
const top = chart.chartArea.top - 5;
const bottom = chart.chartArea.bottom + 5;
/* eslint-disable-next-line no-param-reassign */
chart.ctx.fillStyle = pluginOptions.color;
chart.ctx.fillRect(left, top, right - left, bottom - top);
}

View File

@@ -142,6 +142,7 @@ export const createIntersectionCursorPlugin = (): Plugin<
const { top, bottom, left, right } = chart.chartArea;
chart.ctx.beginPath();
/* eslint-disable-next-line no-param-reassign */
chart.ctx.strokeStyle = pluginOptions.color;
chart.ctx.setLineDash(lineDashData);
chart.ctx.moveTo(left, positionY);
@@ -150,6 +151,7 @@ export const createIntersectionCursorPlugin = (): Plugin<
chart.ctx.beginPath();
chart.ctx.setLineDash(lineDashData);
/* eslint-disable-next-line no-param-reassign */
chart.ctx.strokeStyle = pluginOptions.color;
chart.ctx.moveTo(positionX, top);
chart.ctx.lineTo(positionX, bottom);

View File

@@ -55,6 +55,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => ({
)
: null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
items?.forEach((item: Record<any, any>, index: number) => {
const li = document.createElement('li');
li.style.alignItems = 'center';
@@ -64,6 +65,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => ({
// li.style.marginTop = '5px';
li.onclick = (): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { type } = chart.config;
if (type === 'pie' || type === 'doughnut') {

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { PrecisionOptionsEnum } from '../types';
import { getYAxisFormattedValue } from '../yAxisConfig';

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-syntax */
import { ChartData } from 'chart.js';
export const hasData = (data: ChartData): boolean => {

View File

@@ -1,5 +1,6 @@
import { MutableRefObject } from 'react';
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
// eslint-disable-next-line import/namespace -- side-effect import that registers Chart.js date adapter
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -207,6 +208,7 @@ export const getGraphOptions = (
cubicInterpolationMode: 'monotone',
},
point: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hoverBackgroundColor: (ctx: any): string => {
if (ctx?.element?.options?.borderColor) {
return ctx.element.options.borderColor;
@@ -233,6 +235,7 @@ export const getGraphOptions = (
);
if (interactions[0]) {
// eslint-disable-next-line no-param-reassign
nearestDatasetIndex.current = interactions[0].datasetIndex;
}
}
@@ -245,11 +248,6 @@ declare module 'chart.js' {
}
}
const intlNumberFormatter = new Intl.NumberFormat('en-US', {
useGrouping: false,
maximumFractionDigits: 20,
});
/**
* Formats a number for display, preserving leading zeros after the decimal point
* and showing up to DEFAULT_SIGNIFICANT_DIGITS digits after the first non-zero decimal digit.
@@ -272,7 +270,10 @@ export const formatDecimalWithLeadingZeros = (
}
// Use toLocaleString to get a full decimal representation without scientific notation.
const numStr = intlNumberFormatter.format(value);
const numStr = value.toLocaleString('en-US', {
useGrouping: false,
maximumFractionDigits: 20,
});
const [integerPart, decimalPart = ''] = numStr.split('.');

View File

@@ -1,5 +1,4 @@
import { useMemo } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Chart, TimeUnit } from 'chart.js';
import { AppState } from 'store/reducers';

View File

@@ -1,5 +1,4 @@
import { useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
// Mock dependencies before imports
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/sonner';

View File

@@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable react/jsx-props-no-spreading */
// Mock dependencies before imports
import { useLocation } from 'react-router-dom';
import { render, screen } from '@testing-library/react';

View File

@@ -1,5 +1,4 @@
// Mock dependencies before imports
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { Color, Spacing } from '@signozhq/design-tokens';
@@ -144,6 +143,7 @@ function HostMetricsDetails({
page: InfraMonitoringEvents.DetailedPage,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [host]);
useEffect(() => {
@@ -207,6 +207,7 @@ function HostMetricsDetails({
page: InfraMonitoringEvents.DetailedPage,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -489,6 +490,7 @@ function HostMetricsDetails({
>
<Radio.Button
className={
// eslint-disable-next-line sonarjs/no-duplicate-string
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.METRICS}

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useQuery } from 'react-query';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
@@ -121,6 +122,7 @@ function HostMetricsLogs({ timeRange, filters }: Props): JSX.Element {
const renderFooter = useCallback(
(): JSX.Element | null => (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{isFetching ? (
<div className="logs-loading-skeleton"> Loading more logs ... </div>

View File

@@ -34,6 +34,7 @@ function InputComponent({
addonBefore={addonBefore}
onBlur={onBlurHandler}
onPressEnter={onPressEnterHandler}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
</Form.Item>

View File

@@ -112,6 +112,8 @@ function LaunchChatSupport({
} else {
logEvent(eventName, attributes);
if (window.pylon && !chatMessageDisabled) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Pylon('showNewMessage', defaultTo(message, ''));
}
}

View File

@@ -48,6 +48,7 @@ function QueryBuilderSearchWrapper({
setContextQuery({ ...nextQuery });
};
// eslint-disable-next-line react/jsx-no-useless-fragment
if (!contextQuery || !isEdit) {
return <></>;
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens';
@@ -55,7 +55,6 @@ import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
import './LogDetails.styles.scss';
/* eslint-disable-next-line sonarjs/cognitive-complexity */
function LogDetailInner({
log,
onClose,
@@ -110,7 +109,6 @@ function LogDetailInner({
// Keyboard navigation - handle up/down arrow keys
// Only listen when in OVERVIEW tab
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
if (
!logs ||
@@ -426,6 +424,7 @@ function LogDetailInner({
>
<Radio.Button
className={
// eslint-disable-next-line sonarjs/no-duplicate-string
selectedView === VIEW_TYPES.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.OVERVIEW}
@@ -574,9 +573,11 @@ function LogDetailInner({
function LogDetail(props: LogDetailProps): JSX.Element {
const { log } = props;
if (!log) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
// eslint-disable-next-line react/jsx-props-no-spreading
return <LogDetailInner {...(props as LogDetailInnerProps)} />;
}

View File

@@ -25,6 +25,7 @@ function AddToQueryHOC({
]);
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div className={cx('addToQueryContainer', fontSize)} onClick={handleQueryAdd}>
<Popover
overlayClassName="drawer-popover"

View File

@@ -70,6 +70,9 @@
padding-left: 0;
}
transition: background-color 0.2s ease-in;
&:hover {
background-color: rgba(171, 189, 255, 0.04) !important;
}
}
.log-selected-fields {
@@ -180,6 +183,11 @@
.log-value {
color: var(--text-slate-400);
}
.log-line {
&:hover {
background-color: var(--text-vanilla-200) !important;
}
}
}
.dark {

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { Card } from 'antd';
import { FontSize } from 'container/OptionsMenu/types';
import styled from 'styled-components';
@@ -48,12 +49,6 @@ export const Container = styled(Card)<{
${({ $isActiveLog, $isDarkMode, $logType }): string =>
getActiveLogBackground($isActiveLog, $isDarkMode, $logType)}
}
&:hover .ant-card-body {
${({ $isDarkMode, $logType }): string =>
getActiveLogBackground(true, $isDarkMode, $logType)}
}
`;
export const LogContainer = styled.div<LogContainerProps>`

View File

@@ -78,6 +78,7 @@ const SEVERITY_VARIANT_CLASSES: Record<string, string> = {
Wrn: 'severity-warn-4',
// Error variants - cherry-600 to cherry-200
// eslint-disable-next-line sonarjs/no-duplicate-string
ERROR: 'severity-error-0',
Error: 'severity-error-1',
error: 'severity-error-2',
@@ -89,9 +90,11 @@ const SEVERITY_VARIANT_CLASSES: Record<string, string> = {
FAIL: 'severity-error-0',
// Fatal variants - sakura-600 to sakura-200
// eslint-disable-next-line sonarjs/no-duplicate-string
FATAL: 'severity-fatal-0',
Fatal: 'severity-fatal-1',
fatal: 'severity-fatal-2',
// eslint-disable-next-line sonarjs/no-duplicate-string
critical: 'severity-fatal-3',
Critical: 'severity-fatal-4',
CRITICAL: 'severity-fatal-0',

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ILog } from 'types/api/logs/log';
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { blue } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Col, Row, Space } from 'antd';
@@ -7,6 +8,7 @@ import styled from 'styled-components';
import {
getActiveLogBackground,
getCustomHighlightBackground,
getDefaultLogBackground,
} from 'utils/logs';
import { RawLogContentProps } from './types';
@@ -46,9 +48,7 @@ export const RawLogViewContainer = styled(Row)<{
${({ $isReadOnly, $isActiveLog, $isDarkMode, $logType }): string =>
$isActiveLog
? getActiveLogBackground($isActiveLog, $isDarkMode, $logType)
: !$isReadOnly
? `&:hover { ${getActiveLogBackground(true, $isDarkMode, $logType)} }`
: ''}
: getDefaultLogBackground($isReadOnly, $isDarkMode)}
${({ $isHightlightedLog, $isDarkMode }): string =>
$isHightlightedLog

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { FontSize } from 'container/OptionsMenu/types';
import styled from 'styled-components';

View File

@@ -84,6 +84,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
// We do not need any title and data index for the log state indicator
title: '',
dataIndex: '',
// eslint-disable-next-line sonarjs/no-duplicate-string
key: 'state-indicator',
accessorKey: 'state-indicator',
id: 'state-indicator',

View File

@@ -1,3 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Input, InputNumber, Popover, Tooltip, Typography } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
@@ -77,6 +80,7 @@ function OptionsMenu({
};
const handleSearchValueChange = useDebouncedFn((event): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const value = event?.target?.value || '';

View File

@@ -1,3 +1,4 @@
/* eslint-disable prefer-destructuring */
import React, { useState } from 'react';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import cx from 'classnames';

View File

@@ -1,3 +1,5 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import ReactMarkdown from 'react-markdown';
@@ -51,6 +53,7 @@ function Code({
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
style={a11yDark}
language={match[1]}
@@ -113,6 +116,7 @@ function MarkdownRenderer({
<ReactMarkdown
rehypePlugins={[rehypeRaw as any]}
components={{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
a: Link,
pre: ({ children }) =>

View File

@@ -1,259 +0,0 @@
// ─── Table wrapper ────────────────────────────────────────────────────────────
.members-table-wrapper {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
overflow: hidden;
border-radius: 4px;
}
// ─── Ant Design Table overrides ───────────────────────────────────────────────
.members-table {
// Flatten Ant Design chrome
.ant-table {
background: transparent;
font-size: 14px;
}
.ant-table-container {
border-radius: 0 !important;
border: none !important;
}
// ── Header ──────────────────────────────────────────────────────────────
.ant-table-thead {
> tr > th,
> tr > td {
background: var(--bg-ink-500, #0b0c0e);
font-family: Inter, sans-serif;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.44px;
text-transform: uppercase;
color: var(--vanilla-400, #c0c1c3);
line-height: 18px;
padding: 8px 16px;
border-bottom: none !important;
border-top: none !important;
// Remove the column divider pseudo-element
&::before {
display: none !important;
}
// Sorter icon color
.ant-table-column-sorter {
color: var(--vanilla-400, #c0c1c3);
opacity: 0.6;
}
.ant-table-column-sorter-up.active,
.ant-table-column-sorter-down.active {
color: var(--vanilla-100, #ffffff);
opacity: 1;
}
}
}
// ── Body rows ────────────────────────────────────────────────────────────
.ant-table-tbody {
> tr > td {
border-bottom: none !important;
padding: 8px 16px;
background: transparent;
transition: none;
}
// Even-indexed rows (0, 2, 4 …) get a subtle tint — matches Figma
> tr.members-table-row--tinted > td {
background: rgba(171, 189, 255, 0.02);
}
// Hover — slightly brighter tint
> tr:hover > td {
background: rgba(171, 189, 255, 0.04) !important;
}
}
// Remove outer table border
.ant-table-wrapper,
.ant-table-container,
.ant-spin-nested-loading,
.ant-spin-container {
border: none !important;
box-shadow: none !important;
}
// ── Status cell — right-align badge and add 4px right padding ─────────
.member-status-cell {
padding-right: 4px !important;
}
}
// ─── Name / Email cell ────────────────────────────────────────────────────────
.member-name-email-cell {
display: flex;
align-items: center;
gap: 4px;
height: 22px;
overflow: hidden;
.member-name {
font-size: 14px;
font-weight: 500;
color: var(--vanilla-400, #c0c1c3);
line-height: 20px;
letter-spacing: -0.07px;
white-space: nowrap;
flex-shrink: 0;
}
.member-email {
font-size: 14px;
font-weight: 400;
color: var(--slate-50, #62687c);
line-height: 20px;
letter-spacing: -0.07px;
flex: 1 0 0;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
// ─── Permissions dash ─────────────────────────────────────────────────────────
.member-dash {
font-size: 14px;
color: var(--slate-50, #62687c);
line-height: 18px;
letter-spacing: -0.07px;
}
// ─── Joined On ────────────────────────────────────────────────────────────────
.member-joined-date {
font-size: 14px;
font-weight: 400;
color: var(--vanilla-400, #c0c1c3);
line-height: 18px;
letter-spacing: -0.07px;
white-space: nowrap;
}
.member-joined-dash {
font-size: 14px;
color: var(--slate-50, #62687c);
line-height: 18px;
letter-spacing: -0.07px;
}
// ─── Empty state ─────────────────────────────────────────────────────────────
.members-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 16px;
gap: 8px;
&__emoji {
font-size: 24px;
line-height: 1;
}
&__text {
font-size: 14px;
font-weight: 400;
color: var(--vanilla-400, #c0c1c3);
margin: 0;
line-height: 20px;
strong {
font-weight: 500;
color: var(--vanilla-100, #ffffff);
}
}
}
// ─── Pagination ───────────────────────────────────────────────────────────────
.members-table-pagination {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 8px 16px;
.ant-pagination-total-text {
margin-right: auto;
}
.members-pagination-range {
font-family: Inter, sans-serif;
font-size: 12px;
color: var(--vanilla-400, #c0c1c3);
}
.members-pagination-total {
font-family: Inter, sans-serif;
font-size: 12px;
color: var(--vanilla-400, #c0c1c3);
opacity: 0.5;
}
}
// ─── Light mode overrides ─────────────────────────────────────────────────────
.lightMode {
.members-table {
.ant-table-thead {
> tr > th,
> tr > td {
background: #f5f5f7;
color: #5a5f6e;
}
}
.ant-table-tbody {
> tr.members-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
.member-name-email-cell {
.member-name {
color: #1d1f29;
}
.member-email {
color: #5a5f6e;
}
}
.member-dash,
.member-joined-dash {
color: #5a5f6e;
}
.member-joined-date {
color: #1d1f29;
}
.members-empty-state {
&__text {
color: #5a5f6e;
strong {
color: #1d1f29;
}
}
}
.members-pagination-range,
.members-pagination-total {
color: #5a5f6e;
}
}

View File

@@ -1,228 +0,0 @@
import { Badge } from '@signozhq/badge';
import { Pagination, Table, Tooltip } from 'antd';
import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { useTimezone } from 'providers/Timezone';
import { ROLES } from 'types/roles';
import './MembersTable.styles.scss';
export interface MemberRow {
id: string;
name: string;
email: string;
role: ROLES;
status: 'Active' | 'Invited';
joinedOn: string | null;
}
interface MembersTableProps {
data: MemberRow[];
loading: boolean;
total: number;
currentPage: number;
pageSize: number;
searchQuery: string;
onPageChange: (page: number) => void;
onSortChange?: (
sorter: SorterResult<MemberRow> | SorterResult<MemberRow>[],
) => void;
}
function formatRoleLabel(role: string): string {
return role.charAt(0).toUpperCase() + role.slice(1).toLowerCase();
}
function NameEmailCell({
name,
email,
}: {
name: string;
email: string;
}): JSX.Element {
return (
<div className="member-name-email-cell">
{name && (
<span className="member-name" title={name}>
{name}
</span>
)}
<Tooltip title={email} overlayClassName="member-tooltip">
<span className="member-email">{email}</span>
</Tooltip>
</div>
);
}
function StatusBadge({ status }: { status: MemberRow['status'] }): JSX.Element {
if (status === 'Active') {
return (
<Badge color="forest" variant="outline">
ACTIVE
</Badge>
);
}
return (
<Badge color="amber" variant="outline">
INVITED
</Badge>
);
}
function MembersEmptyState({
searchQuery,
}: {
searchQuery: string;
}): JSX.Element {
return (
<div className="members-empty-state">
<span className="members-empty-state__emoji">🧐</span>
{searchQuery ? (
<p className="members-empty-state__text">
No results for <strong>{searchQuery}</strong>
</p>
) : (
<p className="members-empty-state__text">No members found</p>
)}
</div>
);
}
function MembersTable({
data,
loading,
total,
currentPage,
pageSize,
searchQuery,
onPageChange,
onSortChange,
}: MembersTableProps): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const formatJoinedOn = (date: string | null): string => {
if (!date) {
return '—';
}
const d = new Date(date);
if (Number.isNaN(d.getTime())) {
return '—';
}
return formatTimezoneAdjustedTimestamp(date, DATE_TIME_FORMATS.DASH_DATETIME);
};
const columns: ColumnsType<MemberRow> = [
{
title: 'Name / Email',
dataIndex: 'name',
key: 'name',
render: (_, record): JSX.Element => (
<NameEmailCell name={record.name} email={record.email} />
),
},
{
title: 'Roles',
dataIndex: 'role',
key: 'role',
width: 180,
render: (role: ROLES): JSX.Element => (
<Badge color="vanilla">{formatRoleLabel(role)}</Badge>
),
},
{
title: 'Permissions',
key: 'permissions',
width: 250,
render: (): JSX.Element => <span className="member-dash">&#8213;</span>,
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
width: 100,
align: 'right' as const,
className: 'member-status-cell',
sorter: (a, b): number => a.status.localeCompare(b.status),
render: (status: MemberRow['status']): JSX.Element => (
<StatusBadge status={status} />
),
},
{
title: 'Joined On',
dataIndex: 'joinedOn',
key: 'joinedOn',
width: 220,
align: 'right' as const,
sorter: (a, b): number => {
if (!a.joinedOn && !b.joinedOn) {
return 0;
}
if (!a.joinedOn) {
return 1;
}
if (!b.joinedOn) {
return -1;
}
return new Date(a.joinedOn).getTime() - new Date(b.joinedOn).getTime();
},
render: (joinedOn: string | null): JSX.Element => {
const formatted = formatJoinedOn(joinedOn);
const isDash = formatted === '—';
return (
<span className={isDash ? 'member-joined-dash' : 'member-joined-date'}>
{formatted}
</span>
);
},
},
];
const showPaginationTotal = (_total: number, range: number[]): JSX.Element => (
<>
<span className="members-pagination-range">
{range[0]} &#8212; {range[1]}
</span>
<span className="members-pagination-total"> of {_total}</span>
</>
);
return (
<div className="members-table-wrapper">
<Table<MemberRow>
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
pagination={false}
rowClassName={(_, index): string =>
index % 2 === 0 ? 'members-table-row--tinted' : ''
}
onChange={(_, __, sorter): void => {
if (onSortChange) {
onSortChange(
sorter as SorterResult<MemberRow> | SorterResult<MemberRow>[],
);
}
}}
showSorterTooltip={false}
locale={{
emptyText: <MembersEmptyState searchQuery={searchQuery} />,
}}
className="members-table"
/>
{total > pageSize && (
<Pagination
current={currentPage}
pageSize={pageSize}
total={total}
showTotal={showPaginationTotal}
showSizeChanger={false}
onChange={onPageChange}
className="members-table-pagination"
/>
)}
</div>
);
}
export default MembersTable;

View File

@@ -1,3 +1,5 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { ReactNode, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { CaretDownOutlined, LoadingOutlined } from '@ant-design/icons';

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { useEffect, useMemo, useState } from 'react';
import { Button } from 'antd';
import cx from 'classnames';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/destructuring-assignment */
import { Color } from '@signozhq/design-tokens';
import { Tooltip } from 'antd';
import { DefaultOptionType } from 'antd/es/select';

View File

@@ -1,4 +1,9 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable no-nested-ternary */
/* eslint-disable react/function-component-definition */
import React, {
useCallback,
useEffect,

View File

@@ -1,4 +1,7 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable react/function-component-definition */
import React, {
useCallback,
useEffect,

View File

@@ -1,3 +1,5 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/no-duplicate-string */
import { VirtuosoMockContext } from 'react-virtuoso';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

View File

@@ -1,3 +1,5 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/no-duplicate-string */
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

View File

@@ -1,5 +1,5 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { QueryClient, QueryClientProvider } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { Provider } from 'react-redux';
import { VirtuosoMockContext } from 'react-virtuoso';
import { render, screen, waitFor } from '@testing-library/react';
@@ -65,6 +65,7 @@ function TestWrapper({ children }: { children: React.ReactNode }): JSX.Element {
<Provider store={mockStore}>
<QueryClientProvider client={queryClient}>
<VirtuosoMockContext.Provider
// eslint-disable-next-line react/jsx-no-constructed-context-values
value={{ viewportHeight: 300, itemHeight: 40 }}
>
{children}

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { uniqueOptions } from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { OptionData } from './types';

View File

@@ -1,4 +1,3 @@
// eslint-disable-next-line no-restricted-imports
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';

View File

@@ -1,3 +1,6 @@
/* eslint-disable react/require-default-props */
/* eslint-disable react/jsx-props-no-spreading */
import { useEffect, useRef, useState } from 'react';
import { Input, InputProps, InputRef, Tooltip } from 'antd';
import cx from 'classnames';

View File

@@ -1,9 +1,7 @@
import {
// eslint-disable-next-line no-restricted-imports
createContext,
ReactNode,
useCallback,
// eslint-disable-next-line no-restricted-imports
useContext,
useMemo,
useState,

View File

@@ -78,10 +78,14 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
[handleChangeQueryData],
);
const showAggregationInterval = useMemo(
() => panelType !== PANEL_TYPES.VALUE,
[panelType],
);
const showAggregationInterval = useMemo(() => {
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
if (panelType === PANEL_TYPES.VALUE) {
return false;
}
return true;
}, [panelType]);
const disableOperatorSelector =
!queryAggregation.metricName || queryAggregation.metricName === '';

View File

@@ -1,3 +1,4 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable sonarjs/cognitive-complexity */
import { useEffect, useMemo, useRef, useState } from 'react';
import {

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/require-default-props */
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
@@ -247,6 +248,8 @@ function QueryAddOns({
filteredAddOns.some((addOn) => addOn.key === view.key),
),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [panelType, isListViewPanel, query, showReduceTo]);
const handleOptionClick = (e: RadioChangeEvent): void => {

View File

@@ -26,6 +26,7 @@ function QueryAggregationOptions({
queryData: IBuilderQuery | IBuilderTraceOperator;
}): JSX.Element {
const showAggregationInterval = useMemo(() => {
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
if (panelType === PANEL_TYPES.VALUE) {
return false;
}

View File

@@ -1,4 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-cond-assign */
/* eslint-disable no-restricted-syntax */
/* eslint-disable class-methods-use-this */
/* eslint-disable react/no-this-in-sfc */
/* eslint-disable sonarjs/cognitive-complexity */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query';
@@ -145,6 +149,7 @@ const stopEventsExtension = EditorView.domEventHandlers({
},
});
// eslint-disable-next-line react/no-this-in-sfc
function QueryAggregationSelect({
onChange,
queryData,

View File

@@ -1,4 +1,5 @@
import { useMemo } from 'react';
/* eslint-disable react/require-default-props */
import { Button, Tooltip, Typography } from 'antd';
import WarningPopover from 'components/WarningPopover/WarningPopover';
import { PANEL_TYPES } from 'constants/queryBuilder';

View File

@@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable sonarjs/cognitive-complexity */
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CheckCircleFilled } from '@ant-design/icons';
@@ -10,6 +11,7 @@ import {
startCompletion,
} from '@codemirror/autocomplete';
import { javascript } from '@codemirror/lang-javascript';
import * as Sentry from '@sentry/react';
import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
@@ -84,7 +86,6 @@ interface QuerySearchProps {
signalSource?: string;
hardcodedAttributeKeys?: QueryKeyDataSuggestionsProps[];
onRun?: (query: string) => void;
showFilterSuggestionsWithoutMetric?: boolean;
}
function QuerySearch({
@@ -95,7 +96,6 @@ function QuerySearch({
onRun,
signalSource,
hardcodedAttributeKeys,
showFilterSuggestionsWithoutMetric,
}: QuerySearchProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
@@ -252,8 +252,7 @@ function QuerySearch({
async (searchText?: string): Promise<void> => {
if (
dataSource === DataSource.METRICS &&
!queryData.aggregateAttribute?.key &&
!showFilterSuggestionsWithoutMetric
!queryData.aggregateAttribute?.key
) {
setKeySuggestions([]);
return;
@@ -302,7 +301,6 @@ function QuerySearch({
queryData.aggregateAttribute?.key,
signalSource,
hardcodedAttributeKeys,
showFilterSuggestionsWithoutMetric,
],
);
@@ -391,6 +389,7 @@ function QuerySearch({
// Use callback to prevent dependency changes on each render
const fetchValueSuggestions = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
async ({
key,
searchText,
@@ -565,7 +564,15 @@ function QuerySearch({
const lastPos = lastPosRef.current;
if (newPos.line !== lastPos.line || newPos.ch !== lastPos.ch) {
setCursorPos(newPos);
setCursorPos((lastPos) => {
if (newPos.ch !== lastPos.ch && newPos.ch === 0) {
Sentry.captureEvent({
message: `Cursor jumped to start of line from ${lastPos.ch} to ${newPos.ch}`,
level: 'warning',
});
}
return newPos;
});
lastPosRef.current = newPos;
if (doc) {
@@ -669,6 +676,7 @@ function QuerySearch({
};
// Enhanced myCompletions function to better use context including query pairs
// eslint-disable-next-line sonarjs/cognitive-complexity
function autoSuggestions(context: CompletionContext): CompletionResult | null {
// This matches words before the cursor position
// eslint-disable-next-line no-useless-escape
@@ -1086,6 +1094,7 @@ function QuerySearch({
!(isLoadingSuggestions && lastKeyRef.current === keyName);
if (shouldFetch) {
// eslint-disable-next-line sonarjs/no-identical-functions
debouncedFetchValueSuggestions({
key: keyName,
searchText,
@@ -1556,7 +1565,6 @@ QuerySearch.defaultProps = {
hardcodedAttributeKeys: undefined,
placeholder:
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')",
showFilterSuggestionsWithoutMetric: false,
};
export default QuerySearch;

View File

@@ -1,3 +1,6 @@
/* eslint-disable react/require-default-props */
/* eslint-disable sonarjs/no-duplicate-string */
import { useCallback } from 'react';
import { Button, Tooltip, Typography } from 'antd';
import cx from 'classnames';

View File

@@ -1,3 +1,6 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/cognitive-complexity */
import { Token } from 'antlr4';
import TraceOperatorGrammarLexer from 'parser/TraceOperatorParser/TraceOperatorGrammarLexer';

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