Compare commits

..

22 Commits

Author SHA1 Message Date
SagarRajput-7
35443b3bd3 feat: added components and styles 2026-02-27 22:03:52 +05:30
SagarRajput-7
b797464995 feat: added members page and listing and edit view 2026-02-27 21:58:29 +05:30
Ashwin Bhatkal
ed812b7c16 chore(frontend): add state governance lint rules (#10441)
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
2026-02-27 14:29:02 +05:30
Ashwin Bhatkal
14cfc31c88 chore(frontend): remove stale eslint-disable comments (#10440)
* chore(frontend): remove stale eslint-disable comments

* chore(frontend): remove stale eslint-disable comments
2026-02-27 12:50:49 +05:30
Ashwin Bhatkal
376d650a8c refactor: dashboard list to not use dashboard provider (#10410)
* refactor: dashboard list to not use dashboard provider

* chore: fix tests
2026-02-27 11:38:11 +05:30
Ishan
0ab179b5df feat: css hover updates in log explorer (#10401)
* feat: css hover updates in log explorer

* feat: test css commit

* Revert "feat: test css commit"

This reverts commit f0a34dd460.
2026-02-27 11:20:30 +05:30
Amlan Kumar Nandy
a9b1dd8510 chore: replace search bar in inspect page (#10342) 2026-02-27 05:23:51 +00:00
Amlan Kumar Nandy
6e28f4dd84 chore: metrics explorer v2 api migration in summary page (#10337)
* chore: metrics explorer summary page api migration

* chore: search bar replacement

* chore: additional fixes

* chore: fix CI

* chore: additional performance fix

* chore: address comments

* chore: additional fixes

* chore: address comments

* chore: corresponding fix for generated api changes

* chore: additional changes

* chore: additional fixes

* chore: address comments

* chore: address comments
2026-02-27 09:27:56 +05:30
Srikanth Chekuri
43933f3a33 chore: move converter/formatter to pkg/units/... (#10408)
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
2026-02-26 23:52:58 +05:30
Naman Verma
d2e1a24b20 chore: choose latest seen unit for metrics instead of any unit (#10431) 2026-02-26 16:48:22 +00:00
Nikhil Mantri
887b3c7b3a chore: improve error messaging and UI edge cases in infra hosts monitoring (#10304) 2026-02-26 13:38:57 +00:00
Vinicius Lourenço
476fe7a29a perf(service-map): use react-force-graph-2d dep to reduce bundle size (#10191)
Co-authored-by: Yunus M <myounis.ar@live.com>
2026-02-26 10:10:19 -03:00
Ashwin Bhatkal
c1d38d86f1 chore: add nuqs and zustand to the repo (#10434) 2026-02-26 12:21:53 +00:00
Vinicius Lourenço
4f2594c31d perf(tooltip-value): cache intl number object (#9965) 2026-02-26 11:45:19 +00:00
Karan Balani
c9985b56bc feat: add org id support in root user config (#10418)
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 org id support in root user config

* chore: address review comments

* fix: use zero value uuid for org id in example.conf
2026-02-26 13:44:14 +05:30
Abhi kumar
f9868e2221 fix: thresholds working correctly with number panel (#10394)
* fix: fixed unit converstion support across thresholds and yaxisunit

* fix: thresholds working correctly with number panel

* fix: fixed tsc

* chore: fixed unit tests

* chore: reverted the extractnumberfromstring change

* chore: replaced select component with yaxisunitselector in threshold

* fix: fixed test

* chore: pr review fix

* chore: added test for yaxisunitselector
2026-02-26 07:42:29 +00:00
Amlan Kumar Nandy
72b0398eaf chore: metrics explorer v2 api migration in explorer section (#10111) 2026-02-26 06:57:41 +00:00
Abhi kumar
5b75a39777 chore: removed sentry instrumentation for querysearch (#10426) 2026-02-26 12:04:58 +05:30
Abhi kumar
6948b69012 fix: throttled legend color picker in dashboard + memory leak fix due to tooltip persistance (#10421)
* fix: creating tooltip plugin container only once

* chore: throttled legendcolor change

* fix: fixed tooltip plugin test
2026-02-26 11:28:20 +05:30
Amlan Kumar Nandy
bc9701397e chore: migrate metric details side drawer in metrics explorer to v2 APIs (#9995) 2026-02-26 04:18:33 +00:00
Naman Verma
396cf3194e feat: add support for count based aggregation in histogram metrics (#10355)
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
2026-02-25 17:03:06 +00:00
Abhi kumar
8be96a0ded fix: fetch the current version changelog instead of latest version (#10422) 2026-02-25 21:03:25 +05:30
840 changed files with 14372 additions and 12289 deletions

View File

@@ -320,3 +320,4 @@ 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,6 +842,17 @@ components:
- temporality
- isMonotonic
type: object
MetrictypesComparisonSpaceAggregationParam:
properties:
operator:
type: string
threshold:
format: double
type: number
required:
- operator
- threshold
type: object
MetrictypesSpaceAggregation:
enum:
- sum
@@ -1138,6 +1149,8 @@ components:
type: object
Querybuildertypesv5MetricAggregation:
properties:
comparisonSpaceAggregationParam:
$ref: '#/components/schemas/MetrictypesComparisonSpaceAggregationParam'
metricName:
type: string
reduceTo:

View File

@@ -1,164 +0,0 @@
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

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

View File

@@ -37,6 +37,7 @@ 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"
@@ -120,6 +121,13 @@ 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,
@@ -153,11 +161,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/query-service/formatter"
"github.com/SigNoz/signoz/pkg/units"
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 := formatter.FromUnit(r.Unit())
valueFormatter := units.FormatterFromUnit(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': 'off', // Allows @ts-ignore comments (sometimes needed for third-party libs)
'@typescript-eslint/ban-ts-comment': 'warn', // Allows @ts-ignore comments (sometimes needed for third-party libs)
'no-empty-function': 'off', // Disabled in favor of TypeScript version above
// React rules
@@ -146,6 +146,49 @@ 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,6 +117,7 @@
"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",
@@ -130,7 +131,7 @@
"react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-error-boundary": "4.0.11",
"react-force-graph": "^1.43.0",
"react-force-graph-2d": "^1.29.1",
"react-full-screen": "1.1.1",
"react-grid-layout": "^1.3.4",
"react-helmet-async": "1.3.0",
@@ -162,7 +163,8 @@
"webpack": "5.94.0",
"webpack-dev-server": "^5.2.1",
"webpack-retry-chunk-load-plugin": "3.1.1",
"xstate": "^4.31.0"
"xstate": "^4.31.0",
"zustand": "5.0.11"
},
"browserslist": {
"production": [
@@ -286,6 +288,5 @@
"brace-expansion": "^2.0.2",
"on-headers": "^1.1.0",
"tmp": "0.2.4"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
}

View File

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

View File

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

View File

@@ -297,7 +297,6 @@ 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,12 +218,8 @@ 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,7 +44,6 @@ 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,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import axios from 'api';
import { getFieldKeys } from '../getFieldKeys';

View File

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

View File

@@ -1006,6 +1006,18 @@ 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',
@@ -1367,6 +1379,7 @@ export interface Querybuildertypesv5LogAggregationDTO {
}
export interface Querybuildertypesv5MetricAggregationDTO {
comparisonSpaceAggregationParam?: MetrictypesComparisonSpaceAggregationParamDTO;
/**
* @type string
*/

View File

@@ -1,6 +1,4 @@
/* 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,6 +50,7 @@ export interface HostListResponse {
total: number;
sentAnyHostMetricsData: boolean;
isSendingK8SAgentMetrics: boolean;
endTimeBeforeRetention: boolean;
};
}

View File

@@ -1,26 +0,0 @@
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,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { SuccessResponse } from 'types/api';
import {
MetricRangePayloadV5,

View File

@@ -274,7 +274,6 @@ 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,
@@ -415,7 +414,6 @@ 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,7 +17,6 @@ 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>, // eslint-disable-line @typescript-eslint/no-explicit-any
builderQueries: Record<string, 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>, // eslint-disable-line @typescript-eslint/no-explicit-any
promQueries: Record<string, 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>, // eslint-disable-line @typescript-eslint/no-explicit-any
chQueries: Record<string, any>,
): QueryEnvelope[] {
return Object.entries(chQueries).map(
([queryName, queryData]): QueryEnvelope => ({
@@ -508,9 +508,8 @@ export function convertClickHouseQueriesToV5(
* Helper function to reduce query arrays to objects
*/
function reduceQueriesToObject(
queryArray: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
queryArray: 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) {
@@ -519,7 +518,7 @@ function reduceQueriesToObject(
acc[queryItem.name] = queryItem;
legends[queryItem.name] = queryItem.legend;
return acc;
}, {} as Record<string, any>); // eslint-disable-line @typescript-eslint/no-explicit-any
}, {} as Record<string, any>);
return { queries, legends };
}
@@ -589,7 +588,6 @@ 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,7 +10,6 @@ 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,7 +96,6 @@ 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,5 +1,6 @@
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';
@@ -90,7 +91,6 @@ 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,4 +1,5 @@
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,4 +1,5 @@
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,4 +1,5 @@
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,4 +1,3 @@
/* 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,4 +1,5 @@
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,4 +1,3 @@
/* 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,5 +1,6 @@
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,4 +1,5 @@
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,5 +1,3 @@
/* 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,5 +1,3 @@
/* 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,6 +1,3 @@
/* 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,7 +92,6 @@ 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,7 +45,6 @@ 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,7 +124,6 @@ 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,13 +37,7 @@ function DraggableTableRow({
drop(drag(ref));
return (
<tr
ref={ref}
className={className}
style={{ ...style }}
// eslint-disable-next-line react/jsx-props-no-spreading
{...restProps}
/>
<tr ref={ref} className={className} style={{ ...style }} {...restProps} />
);
}

View File

@@ -81,7 +81,6 @@ 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,11 +19,8 @@ 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,4 +1,3 @@
/* eslint-disable react/jsx-props-no-spreading */
import { ReactNode } from 'react';
import { Popover, PopoverProps } from 'antd';

View File

@@ -9,7 +9,6 @@ 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,7 +37,6 @@ 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,7 +310,6 @@ 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,7 +142,6 @@ 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);
@@ -151,7 +150,6 @@ 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,7 +55,6 @@ 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';
@@ -65,7 +64,6 @@ 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,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { PrecisionOptionsEnum } from '../types';
import { getYAxisFormattedValue } from '../yAxisConfig';

View File

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

View File

@@ -1,6 +1,5 @@
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';
@@ -208,7 +207,6 @@ 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;
@@ -235,7 +233,6 @@ export const getGraphOptions = (
);
if (interactions[0]) {
// eslint-disable-next-line no-param-reassign
nearestDatasetIndex.current = interactions[0].datasetIndex;
}
}
@@ -248,6 +245,11 @@ 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.
@@ -270,10 +272,7 @@ export const formatDecimalWithLeadingZeros = (
}
// Use toLocaleString to get a full decimal representation without scientific notation.
const numStr = value.toLocaleString('en-US', {
useGrouping: false,
maximumFractionDigits: 20,
});
const numStr = intlNumberFormatter.format(value);
const [integerPart, decimalPart = ''] = numStr.split('.');

View File

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

View File

@@ -1,6 +1,3 @@
/* 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,4 +1,5 @@
// 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,4 +1,5 @@
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';
@@ -143,7 +144,6 @@ function HostMetricsDetails({
page: InfraMonitoringEvents.DetailedPage,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [host]);
useEffect(() => {
@@ -207,7 +207,6 @@ function HostMetricsDetails({
page: InfraMonitoringEvents.DetailedPage,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -490,7 +489,6 @@ 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,4 +1,3 @@
/* eslint-disable no-nested-ternary */
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useQuery } from 'react-query';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
@@ -122,7 +121,6 @@ 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,7 +34,6 @@ function InputComponent({
addonBefore={addonBefore}
onBlur={onBlurHandler}
onPressEnter={onPressEnterHandler}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
</Form.Item>

View File

@@ -112,8 +112,6 @@ 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,7 +48,6 @@ 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,6 +55,7 @@ import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
import './LogDetails.styles.scss';
/* eslint-disable-next-line sonarjs/cognitive-complexity */
function LogDetailInner({
log,
onClose,
@@ -109,6 +110,7 @@ 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 ||
@@ -424,7 +426,6 @@ 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}
@@ -573,11 +574,9 @@ 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,7 +25,6 @@ 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,9 +70,6 @@
padding-left: 0;
}
transition: background-color 0.2s ease-in;
&:hover {
background-color: rgba(171, 189, 255, 0.04) !important;
}
}
.log-selected-fields {
@@ -183,11 +180,6 @@
.log-value {
color: var(--text-slate-400);
}
.log-line {
&:hover {
background-color: var(--text-vanilla-200) !important;
}
}
}
.dark {

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-nested-ternary */
import { Card } from 'antd';
import { FontSize } from 'container/OptionsMenu/types';
import styled from 'styled-components';
@@ -49,6 +48,12 @@ 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,7 +78,6 @@ 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',
@@ -90,11 +89,9 @@ 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,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ILog } from 'types/api/logs/log';
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';

View File

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

View File

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

View File

@@ -84,7 +84,6 @@ 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,6 +1,3 @@
/* 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';
@@ -80,7 +77,6 @@ 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,4 +1,3 @@
/* eslint-disable prefer-destructuring */
import React, { useState } from 'react';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import cx from 'classnames';

View File

@@ -1,5 +1,3 @@
/* 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';
@@ -53,7 +51,6 @@ 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]}
@@ -116,7 +113,6 @@ 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

@@ -0,0 +1,259 @@
// ─── 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

@@ -0,0 +1,228 @@
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,5 +1,3 @@
/* 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,4 +1,3 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { useEffect, useMemo, useState } from 'react';
import { Button } from 'antd';
import cx from 'classnames';

View File

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

View File

@@ -1,9 +1,4 @@
/* 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,7 +1,4 @@
/* 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,5 +1,3 @@
/* 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,5 +1,3 @@
/* 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,7 +65,6 @@ 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,4 +1,3 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { uniqueOptions } from 'container/DashboardContainer/DashboardVariablesSelection/util';
import { OptionData } from './types';

View File

@@ -1,3 +1,4 @@
// 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,6 +1,3 @@
/* 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,7 +1,9 @@
import {
// eslint-disable-next-line no-restricted-imports
createContext,
ReactNode,
useCallback,
// eslint-disable-next-line no-restricted-imports
useContext,
useMemo,
useState,

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
/* 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';
@@ -248,8 +247,6 @@ 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,7 +26,6 @@ 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,8 +1,4 @@
/* 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';
@@ -149,7 +145,6 @@ const stopEventsExtension = EditorView.domEventHandlers({
},
});
// eslint-disable-next-line react/no-this-in-sfc
function QueryAggregationSelect({
onChange,
queryData,

View File

@@ -1,5 +1,4 @@
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,4 +1,3 @@
/* 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';
@@ -11,7 +10,6 @@ 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';
@@ -86,6 +84,7 @@ interface QuerySearchProps {
signalSource?: string;
hardcodedAttributeKeys?: QueryKeyDataSuggestionsProps[];
onRun?: (query: string) => void;
showFilterSuggestionsWithoutMetric?: boolean;
}
function QuerySearch({
@@ -96,6 +95,7 @@ function QuerySearch({
onRun,
signalSource,
hardcodedAttributeKeys,
showFilterSuggestionsWithoutMetric,
}: QuerySearchProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
@@ -252,7 +252,8 @@ function QuerySearch({
async (searchText?: string): Promise<void> => {
if (
dataSource === DataSource.METRICS &&
!queryData.aggregateAttribute?.key
!queryData.aggregateAttribute?.key &&
!showFilterSuggestionsWithoutMetric
) {
setKeySuggestions([]);
return;
@@ -301,6 +302,7 @@ function QuerySearch({
queryData.aggregateAttribute?.key,
signalSource,
hardcodedAttributeKeys,
showFilterSuggestionsWithoutMetric,
],
);
@@ -389,7 +391,6 @@ function QuerySearch({
// Use callback to prevent dependency changes on each render
const fetchValueSuggestions = useCallback(
// eslint-disable-next-line sonarjs/cognitive-complexity
async ({
key,
searchText,
@@ -564,15 +565,7 @@ function QuerySearch({
const lastPos = lastPosRef.current;
if (newPos.line !== lastPos.line || newPos.ch !== lastPos.ch) {
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;
});
setCursorPos(newPos);
lastPosRef.current = newPos;
if (doc) {
@@ -676,7 +669,6 @@ 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
@@ -1094,7 +1086,6 @@ function QuerySearch({
!(isLoadingSuggestions && lastKeyRef.current === keyName);
if (shouldFetch) {
// eslint-disable-next-line sonarjs/no-identical-functions
debouncedFetchValueSuggestions({
key: keyName,
searchText,
@@ -1565,6 +1556,7 @@ 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,6 +1,3 @@
/* 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,6 +1,3 @@
/* 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