Compare commits

..

61 Commits

Author SHA1 Message Date
swapnil-signoz
42300bd0a0 Merge branch 'main' into feat/cloudintegration-module 2026-03-23 20:57:18 +05:30
swapnil-signoz
6e52f2c8f0 Merge branch 'refactor/cloud-integration-impl-store' into refactor/cloud-integration-handlers 2026-03-22 17:13:53 +05:30
swapnil-signoz
d9f8a4ae5a Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-22 17:13:40 +05:30
swapnil-signoz
eefe3edffd Merge branch 'main' into refactor/cloud-integration-handlers 2026-03-22 17:13:02 +05:30
swapnil-signoz
2051861a03 feat: adding handler skeleton 2026-03-22 17:12:35 +05:30
swapnil-signoz
4b01a40fb9 Merge branch 'refactor/cloud-integration-impl-store' into refactor/cloud-integration-handlers 2026-03-20 20:53:54 +05:30
swapnil-signoz
2d8a00bf18 fix: update error code for service not found 2026-03-20 20:53:33 +05:30
swapnil-signoz
f1b26b310f Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-20 20:51:44 +05:30
swapnil-signoz
2c438b6c32 Merge branch 'refactor/cloud-integration-impl-store' into refactor/cloud-integration-handlers 2026-03-20 20:48:34 +05:30
swapnil-signoz
1814c2d13c Merge branch 'main' into refactor/cloud-integration-handlers 2026-03-20 17:52:31 +05:30
swapnil-signoz
e6cd771f11 Merge origin/main into refactor/cloud-integration-handlers 2026-03-20 16:46:36 +05:30
swapnil-signoz
6b94f87ca0 Merge branch 'main' into refactor/cloud-integration-handlers 2026-03-19 11:43:21 +05:30
swapnil-signoz
bf315253ae fix: lint issues 2026-03-19 11:43:09 +05:30
swapnil-signoz
668ff7bc39 fix: lint and ci issues 2026-03-19 11:34:27 +05:30
swapnil-signoz
07f2aa52fd feat: adding handlers 2026-03-19 01:35:01 +05:30
swapnil-signoz
3416b3ad55 Merge branch 'main' into refactor/cloud-integration-handlers 2026-03-18 21:50:40 +05:30
swapnil-signoz
d6caa4f2c7 Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-18 14:08:14 +05:30
swapnil-signoz
f86371566d refactor: clean up 2026-03-18 13:45:31 +05:30
swapnil-signoz
9115803084 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-18 13:42:43 +05:30
swapnil-signoz
0c14d8f966 refactor: review comments 2026-03-18 13:40:17 +05:30
swapnil-signoz
7afb461af8 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-18 11:14:33 +05:30
swapnil-signoz
a21fbb4ee0 refactor: clean up 2026-03-18 11:14:05 +05:30
swapnil-signoz
0369842f3d refactor: clean up 2026-03-17 23:40:14 +05:30
swapnil-signoz
59cd96562a Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-17 23:10:54 +05:30
swapnil-signoz
cc4475cab7 refactor: updating store methods 2026-03-17 23:10:15 +05:30
swapnil-signoz
ac8c648420 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-17 21:09:47 +05:30
swapnil-signoz
bede6be4b8 feat: adding method for service id creation 2026-03-17 21:09:26 +05:30
swapnil-signoz
dd3d60e6df Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-17 20:49:31 +05:30
swapnil-signoz
538ab686d2 refactor: using serviceID type 2026-03-17 20:49:17 +05:30
swapnil-signoz
936a325cb9 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-17 17:25:58 +05:30
swapnil-signoz
c6cdcd0143 refactor: renaming service type to service id 2026-03-17 17:25:29 +05:30
swapnil-signoz
cd9211d718 refactor: clean up types 2026-03-17 17:04:27 +05:30
swapnil-signoz
0601c28782 feat: adding integration test 2026-03-17 11:02:46 +05:30
swapnil-signoz
580610dbfa Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-16 23:02:19 +05:30
swapnil-signoz
2d2aa02a81 refactor: split upsert store method 2026-03-16 18:27:42 +05:30
swapnil-signoz
dd9723ad13 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-16 17:42:03 +05:30
swapnil-signoz
3651469416 Merge branch 'main' of https://github.com/SigNoz/signoz into refactor/cloud-integration-types 2026-03-16 17:41:52 +05:30
swapnil-signoz
febce75734 refactor: update Dashboard struct comments and remove unused fields 2026-03-16 17:41:28 +05:30
swapnil-signoz
e1616f3487 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-16 17:36:15 +05:30
swapnil-signoz
4b94287ac7 refactor: add comments for backward compatibility in PostableAgentCheckInRequest 2026-03-16 15:48:20 +05:30
swapnil-signoz
1575c7c54c refactor: streamlining types 2026-03-16 15:39:32 +05:30
swapnil-signoz
8def3f835b refactor: adding comments and removed wrong code 2026-03-16 11:10:53 +05:30
swapnil-signoz
11ed15f4c5 feat: implement cloud integration store 2026-03-14 17:05:02 +05:30
swapnil-signoz
f47877cca9 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-14 17:01:51 +05:30
swapnil-signoz
bb2b9215ba fix: correct GetService signature and remove shadowed Data field 2026-03-14 16:59:07 +05:30
swapnil-signoz
3111904223 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-14 16:36:35 +05:30
swapnil-signoz
003e2c30d8 Merge branch 'main' into refactor/cloud-integration-types 2026-03-14 16:25:35 +05:30
swapnil-signoz
00fe516d10 refactor: update cloud integration types and module interface 2026-03-14 16:25:16 +05:30
swapnil-signoz
0305f4f7db refactor: using struct for map 2026-03-13 16:09:26 +05:30
swapnil-signoz
c60019a6dc Merge branch 'main' into refactor/cloud-integration-types 2026-03-12 23:41:22 +05:30
swapnil-signoz
acde2a37fa feat: adding updated types for cloud integration 2026-03-12 23:40:44 +05:30
swapnil-signoz
945241a52a Merge branch 'main' into refactor/cloud-integration-types 2026-03-12 19:45:50 +05:30
swapnil-signoz
e967f80c86 Merge branch 'main' into refactor/cloud-integration-types 2026-03-02 16:39:42 +05:30
swapnil-signoz
a09dc325de Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-02 16:39:20 +05:30
swapnil-signoz
379b4f7fc4 refactor: removing interface check 2026-03-02 14:50:37 +05:30
swapnil-signoz
5e536ae077 Merge branch 'refactor/cloud-integration-types' into refactor/cloud-integration-impl-store 2026-03-02 14:49:35 +05:30
swapnil-signoz
234585e642 Merge branch 'main' into refactor/cloud-integration-types 2026-03-02 14:49:19 +05:30
swapnil-signoz
2cc14f1ad4 Merge branch 'main' into refactor/cloud-integration-impl-store 2026-03-02 14:49:00 +05:30
swapnil-signoz
dc4ed4d239 feat: adding sql store implementation 2026-03-02 14:44:56 +05:30
swapnil-signoz
7281c36873 refactor: store interfaces to use local types and error 2026-03-02 13:27:46 +05:30
swapnil-signoz
40288776e8 feat: adding cloud integration type for refactor 2026-02-28 16:59:14 +05:30
584 changed files with 10945 additions and 35964 deletions

View File

@@ -27,8 +27,8 @@ services:
- ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/ - ${PWD}/fs/tmp/var/lib/clickhouse/user_scripts/:/var/lib/clickhouse/user_scripts/
- ${PWD}/../../../deploy/common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml - ${PWD}/../../../deploy/common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
ports: ports:
- "127.0.0.1:8123:8123" - '127.0.0.1:8123:8123'
- "127.0.0.1:9000:9000" - '127.0.0.1:9000:9000'
tty: true tty: true
healthcheck: healthcheck:
test: test:
@@ -47,16 +47,13 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
- CLICKHOUSE_SKIP_USER_SETUP=1 - CLICKHOUSE_SKIP_USER_SETUP=1
networks:
- default
- signoz-devenv
zookeeper: zookeeper:
image: signoz/zookeeper:3.7.1 image: signoz/zookeeper:3.7.1
container_name: zookeeper container_name: zookeeper
volumes: volumes:
- ${PWD}/fs/tmp/zookeeper:/bitnami/zookeeper - ${PWD}/fs/tmp/zookeeper:/bitnami/zookeeper
ports: ports:
- "127.0.0.1:2181:2181" - '127.0.0.1:2181:2181'
environment: environment:
- ALLOW_ANONYMOUS_LOGIN=yes - ALLOW_ANONYMOUS_LOGIN=yes
healthcheck: healthcheck:
@@ -77,19 +74,12 @@ services:
entrypoint: entrypoint:
- /bin/sh - /bin/sh
command: command:
- -c - -c
- | - |
/signoz-otel-collector migrate bootstrap && /signoz-otel-collector migrate bootstrap &&
/signoz-otel-collector migrate sync up && /signoz-otel-collector migrate sync up &&
/signoz-otel-collector migrate async up /signoz-otel-collector migrate async up
depends_on: depends_on:
clickhouse: clickhouse:
condition: service_healthy condition: service_healthy
restart: on-failure restart: on-failure
networks:
- default
- signoz-devenv
networks:
signoz-devenv:
name: signoz-devenv

View File

@@ -3,7 +3,7 @@ services:
image: signoz/signoz-otel-collector:v0.142.0 image: signoz/signoz-otel-collector:v0.142.0
container_name: signoz-otel-collector-dev container_name: signoz-otel-collector-dev
entrypoint: entrypoint:
- /bin/sh - /bin/sh
command: command:
- -c - -c
- | - |
@@ -34,11 +34,4 @@ services:
retries: 3 retries: 3
restart: unless-stopped restart: unless-stopped
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
networks:
- default
- signoz-devenv
networks:
signoz-devenv:
name: signoz-devenv

View File

@@ -12,10 +12,10 @@ receivers:
scrape_configs: scrape_configs:
- job_name: otel-collector - job_name: otel-collector
static_configs: static_configs:
- targets: - targets:
- localhost:8888 - localhost:8888
labels: labels:
job_name: otel-collector job_name: otel-collector
processors: processors:
batch: batch:
@@ -29,26 +29,7 @@ processors:
signozspanmetrics/delta: signozspanmetrics/delta:
metrics_exporter: signozclickhousemetrics metrics_exporter: signozclickhousemetrics
metrics_flush_interval: 60s metrics_flush_interval: 60s
latency_histogram_buckets: latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
[
100us,
1ms,
2ms,
6ms,
10ms,
50ms,
100ms,
250ms,
500ms,
1000ms,
1400ms,
2000ms,
5s,
10s,
20s,
40s,
60s,
]
dimensions_cache_size: 100000 dimensions_cache_size: 100000
aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA
enable_exp_histogram: true enable_exp_histogram: true
@@ -79,13 +60,13 @@ extensions:
exporters: exporters:
clickhousetraces: clickhousetraces:
datasource: tcp://clickhouse:9000/signoz_traces datasource: tcp://host.docker.internal:9000/signoz_traces
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING} low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
use_new_schema: true use_new_schema: true
signozclickhousemetrics: signozclickhousemetrics:
dsn: tcp://clickhouse:9000/signoz_metrics dsn: tcp://host.docker.internal:9000/signoz_metrics
clickhouselogsexporter: clickhouselogsexporter:
dsn: tcp://clickhouse:9000/signoz_logs dsn: tcp://host.docker.internal:9000/signoz_logs
timeout: 10s timeout: 10s
use_new_schema: true use_new_schema: true
@@ -112,4 +93,4 @@ service:
logs: logs:
receivers: [otlp] receivers: [otlp]
processors: [batch] processors: [batch]
exporters: [clickhouselogsexporter] exporters: [clickhouselogsexporter]

4
.github/CODEOWNERS vendored
View File

@@ -86,8 +86,6 @@ go.mod @therealpandey
/pkg/types/alertmanagertypes @srikanthccv /pkg/types/alertmanagertypes @srikanthccv
/pkg/alertmanager/ @srikanthccv /pkg/alertmanager/ @srikanthccv
/pkg/ruler/ @srikanthccv /pkg/ruler/ @srikanthccv
/pkg/modules/rulestatehistory/ @srikanthccv
/pkg/types/rulestatehistorytypes/ @srikanthccv
# Correlation-adjacent # Correlation-adjacent
@@ -107,7 +105,7 @@ go.mod @therealpandey
/pkg/modules/authdomain/ @vikrantgupta25 /pkg/modules/authdomain/ @vikrantgupta25
/pkg/modules/role/ @vikrantgupta25 /pkg/modules/role/ @vikrantgupta25
# IdentN Owners # IdentN Owners
/pkg/identn/ @vikrantgupta25 /pkg/identn/ @vikrantgupta25
/pkg/http/middleware/identn.go @vikrantgupta25 /pkg/http/middleware/identn.go @vikrantgupta25

View File

@@ -6,14 +6,12 @@ linters:
- depguard - depguard
- errcheck - errcheck
- forbidigo - forbidigo
- godot
- govet - govet
- iface - iface
- ineffassign - ineffassign
- misspell - misspell
- nilnil - nilnil
- sloglint - sloglint
- staticcheck
- wastedassign - wastedassign
- unparam - unparam
- unused - unused

View File

@@ -17,7 +17,5 @@
}, },
"[html]": { "[html]": {
"editor.defaultFormatter": "vscode.html-language-features" "editor.defaultFormatter": "vscode.html-language-features"
}, }
"python-envs.defaultEnvManager": "ms-python.python:system",
"python-envs.pythonProjects": []
} }

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/authz" "github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz" "github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
"github.com/SigNoz/signoz/pkg/authz/openfgaschema" "github.com/SigNoz/signoz/pkg/authz/openfgaschema"
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway" "github.com/SigNoz/signoz/pkg/gateway"
@@ -79,13 +78,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) { func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing) return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
}, },
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) { func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore) return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
if err != nil {
return nil, err
}
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore), nil
}, },
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module { func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser) return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)

View File

@@ -12,7 +12,6 @@ import (
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn" "github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
"github.com/SigNoz/signoz/ee/authz/openfgaauthz" "github.com/SigNoz/signoz/ee/authz/openfgaauthz"
"github.com/SigNoz/signoz/ee/authz/openfgaschema" "github.com/SigNoz/signoz/ee/authz/openfgaschema"
"github.com/SigNoz/signoz/ee/authz/openfgaserver"
"github.com/SigNoz/signoz/ee/gateway/httpgateway" "github.com/SigNoz/signoz/ee/gateway/httpgateway"
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing" enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing" "github.com/SigNoz/signoz/ee/licensing/httplicensing"
@@ -119,13 +118,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return authNs, nil return authNs, nil
}, },
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) { func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore) return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), licensing, dashboardModule)
if err != nil {
return nil, err
}
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil
}, },
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module { func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing) return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)

View File

@@ -144,8 +144,6 @@ telemetrystore:
##################### Prometheus ##################### ##################### Prometheus #####################
prometheus: prometheus:
# The maximum time a PromQL query is allowed to run before being aborted.
timeout: 2m
active_query_tracker: active_query_tracker:
# Whether to enable the active query tracker. # Whether to enable the active query tracker.
enabled: true enabled: true

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.117.1 image: signoz/signoz:v0.116.1
ports: ports:
- "8080:8080" # signoz port - "8080:8080" # signoz port
# - "6060:6060" # pprof port # - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.117.1 image: signoz/signoz:v0.116.1
ports: ports:
- "8080:8080" # signoz port - "8080:8080" # signoz port
volumes: volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.117.1} image: signoz/signoz:${VERSION:-v0.116.1}
container_name: signoz container_name: signoz
ports: ports:
- "8080:8080" # signoz port - "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.117.1} image: signoz/signoz:${VERSION:-v0.116.1}
container_name: signoz container_name: signoz
ports: ports:
- "8080:8080" # signoz port - "8080:8080" # signoz port

File diff suppressed because it is too large Load Diff

View File

@@ -123,7 +123,6 @@ if err := router.Handle("/api/v1/things", handler.New(
Description: "This endpoint creates a thing", Description: "This endpoint creates a thing",
Request: new(types.PostableThing), Request: new(types.PostableThing),
RequestContentType: "application/json", RequestContentType: "application/json",
RequestQuery: new(types.QueryableThing),
Response: new(types.GettableThing), Response: new(types.GettableThing),
ResponseContentType: "application/json", ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated, SuccessStatusCode: http.StatusCreated,
@@ -156,8 +155,6 @@ The `handler.New` function ties the HTTP handler to OpenAPI metadata via `OpenAP
- **Request / RequestContentType**: - **Request / RequestContentType**:
- `Request` is a Go type that describes the request body or form. - `Request` is a Go type that describes the request body or form.
- `RequestContentType` is usually `"application/json"` or `"application/x-www-form-urlencoded"` (for callbacks like SAML). - `RequestContentType` is usually `"application/json"` or `"application/x-www-form-urlencoded"` (for callbacks like SAML).
- **RequestQuery**:
- `RequestQuery` is a Go type that descirbes query url params.
- **RequestExamples**: An array of `handler.OpenAPIExample` that provide concrete request payloads in the generated spec. See [Adding request examples](#adding-request-examples) below. - **RequestExamples**: An array of `handler.OpenAPIExample` that provide concrete request payloads in the generated spec. See [Adding request examples](#adding-request-examples) below.
- **Response / ResponseContentType**: - **Response / ResponseContentType**:
- `Response` is the Go type for the successful response payload. - `Response` is the Go type for the successful response payload.

View File

@@ -273,7 +273,6 @@ Options can be simple (direct link) or nested (with another question):
- Place logo files in `public/Logos/` - Place logo files in `public/Logos/`
- Use SVG format - Use SVG format
- Reference as `"/Logos/your-logo.svg"` - Reference as `"/Logos/your-logo.svg"`
- **Fetching Icons**: New icons can be easily fetched from [OpenBrand](https://openbrand.sh/). Use the pattern `https://openbrand.sh/?url=<TARGET_URL>`, where `<TARGET_URL>` is the URL-encoded link to the service's website. For example, to get Render's logo, use [https://openbrand.sh/?url=https%3A%2F%2Frender.com](https://openbrand.sh/?url=https%3A%2F%2Frender.com).
- **Optimize new SVGs**: Run any newly downloaded SVGs through an optimizer like [SVGOMG (svgo)](https://svgomg.net/) or use `npx svgo public/Logos/your-logo.svg` to minimise their size before committing. - **Optimize new SVGs**: Run any newly downloaded SVGs through an optimizer like [SVGOMG (svgo)](https://svgomg.net/) or use `npx svgo public/Logos/your-logo.svg` to minimise their size before committing.
### 4. Links ### 4. Links

View File

@@ -16,7 +16,7 @@ func (hp *HourlyProvider) GetBaseSeasonalProvider() *BaseSeasonalProvider {
return &hp.BaseSeasonalProvider return &hp.BaseSeasonalProvider
} }
// NewHourlyProvider now uses the generic option type. // NewHourlyProvider now uses the generic option type
func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyProvider { func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyProvider {
hp := &HourlyProvider{ hp := &HourlyProvider{
BaseSeasonalProvider: BaseSeasonalProvider{}, BaseSeasonalProvider: BaseSeasonalProvider{},

View File

@@ -47,7 +47,7 @@ type AnomaliesResponse struct {
// | | // | |
// (rounded value for past peiod) + (seasonal growth) // (rounded value for past peiod) + (seasonal growth)
// //
// score = abs(value - prediction) / stddev (current_season_query). // score = abs(value - prediction) / stddev (current_season_query)
type anomalyQueryParams struct { type anomalyQueryParams struct {
// CurrentPeriodQuery is the query range params for period user is looking at or eval window // CurrentPeriodQuery is the query range params for period user is looking at or eval window
// Example: (now-5m, now), (now-30m, now), (now-1h, now) // Example: (now-5m, now), (now-30m, now), (now-1h, now)

View File

@@ -18,12 +18,12 @@ var (
movingAvgWindowSize = 7 movingAvgWindowSize = 7
) )
// BaseProvider is an interface that includes common methods for all provider types. // BaseProvider is an interface that includes common methods for all provider types
type BaseProvider interface { type BaseProvider interface {
GetBaseSeasonalProvider() *BaseSeasonalProvider GetBaseSeasonalProvider() *BaseSeasonalProvider
} }
// GenericProviderOption is a generic type for provider options. // GenericProviderOption is a generic type for provider options
type GenericProviderOption[T BaseProvider] func(T) type GenericProviderOption[T BaseProvider] func(T)
func WithQuerier[T BaseProvider](querier querier.Querier) GenericProviderOption[T] { func WithQuerier[T BaseProvider](querier querier.Querier) GenericProviderOption[T] {
@@ -121,7 +121,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
} }
// getMatchingSeries gets the matching series from the query result // getMatchingSeries gets the matching series from the query result
// for the given series. // for the given series
func (p *BaseSeasonalProvider) getMatchingSeries(_ context.Context, queryResult *qbtypes.TimeSeriesData, series *qbtypes.TimeSeries) *qbtypes.TimeSeries { func (p *BaseSeasonalProvider) getMatchingSeries(_ context.Context, queryResult *qbtypes.TimeSeriesData, series *qbtypes.TimeSeries) *qbtypes.TimeSeries {
if queryResult == nil || len(queryResult.Aggregations) == 0 || len(queryResult.Aggregations[0].Series) == 0 { if queryResult == nil || len(queryResult.Aggregations) == 0 || len(queryResult.Aggregations[0].Series) == 0 {
return nil return nil
@@ -155,14 +155,13 @@ func (p *BaseSeasonalProvider) getStdDev(series *qbtypes.TimeSeries) float64 {
avg := p.getAvg(series) avg := p.getAvg(series)
var sum float64 var sum float64
for _, smpl := range series.Values { for _, smpl := range series.Values {
d := smpl.Value - avg sum += math.Pow(smpl.Value-avg, 2)
sum += d * d
} }
return math.Sqrt(sum / float64(len(series.Values))) return math.Sqrt(sum / float64(len(series.Values)))
} }
// getMovingAvg gets the moving average for the given series // getMovingAvg gets the moving average for the given series
// for the given window size and start index. // for the given window size and start index
func (p *BaseSeasonalProvider) getMovingAvg(series *qbtypes.TimeSeries, movingAvgWindowSize, startIdx int) float64 { func (p *BaseSeasonalProvider) getMovingAvg(series *qbtypes.TimeSeries, movingAvgWindowSize, startIdx int) float64 {
if series == nil || len(series.Values) == 0 { if series == nil || len(series.Values) == 0 {
return 0 return 0
@@ -237,7 +236,7 @@ func (p *BaseSeasonalProvider) getPredictedSeries(
// getBounds gets the upper and lower bounds for the given series // getBounds gets the upper and lower bounds for the given series
// for the given z score threshold // for the given z score threshold
// moving avg of the previous period series + z score threshold * std dev of the series // moving avg of the previous period series + z score threshold * std dev of the series
// moving avg of the previous period series - z score threshold * std dev of the series. // moving avg of the previous period series - z score threshold * std dev of the series
func (p *BaseSeasonalProvider) getBounds( func (p *BaseSeasonalProvider) getBounds(
series, predictedSeries, weekSeries *qbtypes.TimeSeries, series, predictedSeries, weekSeries *qbtypes.TimeSeries,
zScoreThreshold float64, zScoreThreshold float64,
@@ -270,7 +269,7 @@ func (p *BaseSeasonalProvider) getBounds(
// getExpectedValue gets the expected value for the given series // getExpectedValue gets the expected value for the given series
// for the given index // for the given index
// prevSeriesAvg + currentSeasonSeriesAvg - mean of past season series, past2 season series and past3 season series. // prevSeriesAvg + currentSeasonSeriesAvg - mean of past season series, past2 season series and past3 season series
func (p *BaseSeasonalProvider) getExpectedValue( func (p *BaseSeasonalProvider) getExpectedValue(
_, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries, idx int, _, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries, idx int,
) float64 { ) float64 {
@@ -284,7 +283,7 @@ func (p *BaseSeasonalProvider) getExpectedValue(
// getScore gets the anomaly score for the given series // getScore gets the anomaly score for the given series
// for the given index // for the given index
// (value - expectedValue) / std dev of the series. // (value - expectedValue) / std dev of the series
func (p *BaseSeasonalProvider) getScore( func (p *BaseSeasonalProvider) getScore(
series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries, value float64, idx int, series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries, value float64, idx int,
) float64 { ) float64 {
@@ -297,7 +296,7 @@ func (p *BaseSeasonalProvider) getScore(
// getAnomalyScores gets the anomaly scores for the given series // getAnomalyScores gets the anomaly scores for the given series
// for the given index // for the given index
// (value - expectedValue) / std dev of the series. // (value - expectedValue) / std dev of the series
func (p *BaseSeasonalProvider) getAnomalyScores( func (p *BaseSeasonalProvider) getAnomalyScores(
series, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries, series, prevSeries, currentSeasonSeries, pastSeasonSeries, past2SeasonSeries, past3SeasonSeries *qbtypes.TimeSeries,
) *qbtypes.TimeSeries { ) *qbtypes.TimeSeries {

View File

@@ -16,7 +16,6 @@ import (
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
openfgav1 "github.com/openfga/api/proto/openfga/v1" openfgav1 "github.com/openfga/api/proto/openfga/v1"
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer" openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
"github.com/openfga/openfga/pkg/storage"
) )
type provider struct { type provider struct {
@@ -27,14 +26,14 @@ type provider struct {
registry []authz.RegisterTypeable registry []authz.RegisterTypeable
} }
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] { func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) { return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, registry) return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, licensing, registry)
}) })
} }
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry []authz.RegisterTypeable) (authz.AuthZ, error) { func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, licensing licensing.Licensing, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore) pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema)
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config) pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -58,10 +57,6 @@ func (provider *provider) Start(ctx context.Context) error {
return provider.openfgaServer.Start(ctx) return provider.openfgaServer.Start(ctx)
} }
func (provider *provider) Healthy() <-chan struct{} {
return provider.openfgaServer.Healthy()
}
func (provider *provider) Stop(ctx context.Context) error { func (provider *provider) Stop(ctx context.Context) error {
return provider.openfgaServer.Stop(ctx) return provider.openfgaServer.Stop(ctx)
} }

View File

@@ -16,6 +16,7 @@ type Server struct {
} }
func NewOpenfgaServer(ctx context.Context, pkgAuthzService authz.AuthZ) (*Server, error) { func NewOpenfgaServer(ctx context.Context, pkgAuthzService authz.AuthZ) (*Server, error) {
return &Server{ return &Server{
pkgAuthzService: pkgAuthzService, pkgAuthzService: pkgAuthzService,
}, nil }, nil
@@ -25,10 +26,6 @@ func (server *Server) Start(ctx context.Context) error {
return server.pkgAuthzService.Start(ctx) return server.pkgAuthzService.Start(ctx)
} }
func (server *Server) Healthy() <-chan struct{} {
return server.pkgAuthzService.Healthy()
}
func (server *Server) Stop(ctx context.Context) error { func (server *Server) Stop(ctx context.Context) error {
return server.pkgAuthzService.Stop(ctx) return server.pkgAuthzService.Stop(ctx)
} }

View File

@@ -1,32 +0,0 @@
package openfgaserver
import (
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/openfga/openfga/pkg/storage"
"github.com/openfga/openfga/pkg/storage/postgres"
"github.com/openfga/openfga/pkg/storage/sqlcommon"
"github.com/openfga/openfga/pkg/storage/sqlite"
)
func NewSQLStore(store sqlstore.SQLStore) (storage.OpenFGADatastore, error) {
switch store.BunDB().Dialect().Name().String() {
case "sqlite":
return sqlite.NewWithDB(store.SQLDB(), &sqlcommon.Config{
MaxTuplesPerWriteField: 100,
MaxTypesPerModelField: 100,
})
case "pg":
pgStore, ok := store.(postgressqlstore.Pooler)
if !ok {
panic(errors.New(errors.TypeInternal, errors.CodeInternal, "postgressqlstore should implement Pooler"))
}
return postgres.NewWithDB(pgStore.Pool(), nil, &sqlcommon.Config{
MaxTuplesPerWriteField: 100,
MaxTypesPerModelField: 100,
})
}
return nil, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid store type: %s", store.BunDB().Dialect().Name().String())
}

View File

@@ -13,7 +13,7 @@ var (
once sync.Once once sync.Once
) )
// Config initializes the licensing configuration. // initializes the licensing configuration
func Config(pollInterval time.Duration, failureThreshold int) licensing.Config { func Config(pollInterval time.Duration, failureThreshold int) licensing.Config {
once.Do(func() { once.Do(func() {
config = licensing.Config{PollInterval: pollInterval, FailureThreshold: failureThreshold} config = licensing.Config{PollInterval: pollInterval, FailureThreshold: failureThreshold}

View File

@@ -79,7 +79,7 @@ func (h *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
// Build step intervals from the anomaly query // Build step intervals from the anomaly query
stepIntervals := make(map[string]uint64) stepIntervals := make(map[string]uint64)
if anomalyQuery.StepInterval.Duration > 0 { if anomalyQuery.StepInterval.Duration > 0 {
stepIntervals[anomalyQuery.Name] = uint64(anomalyQuery.StepInterval.Seconds()) stepIntervals[anomalyQuery.Name] = uint64(anomalyQuery.StepInterval.Duration.Seconds())
} }
finalResp := &qbtypes.QueryRangeResponse{ finalResp := &qbtypes.QueryRangeResponse{

View File

@@ -29,7 +29,6 @@ import (
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier" "github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
@@ -107,7 +106,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
signoz.TelemetryMetadataStore, signoz.TelemetryMetadataStore,
signoz.Prometheus, signoz.Prometheus,
signoz.Modules.OrgGetter, signoz.Modules.OrgGetter,
signoz.Modules.RuleStateHistory,
signoz.Querier, signoz.Querier,
signoz.Instrumentation.ToProviderSettings(), signoz.Instrumentation.ToProviderSettings(),
signoz.QueryParser, signoz.QueryParser,
@@ -138,7 +136,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController( logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
signoz.SQLStore, signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations, integrationsController.GetPipelinesForInstalledIntegrations,
reader,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -242,6 +239,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
apiHandler.RegisterWebSocketPaths(r, am) apiHandler.RegisterWebSocketPaths(r, am)
apiHandler.RegisterMessagingQueuesRoutes(r, am) apiHandler.RegisterMessagingQueuesRoutes(r, am)
apiHandler.RegisterThirdPartyApiRoutes(r, am) apiHandler.RegisterThirdPartyApiRoutes(r, am)
apiHandler.MetricExplorerRoutes(r, am)
apiHandler.RegisterTraceFunnelsRoutes(r, am) apiHandler.RegisterTraceFunnelsRoutes(r, am)
err := s.signoz.APIServer.AddToRouter(r) err := s.signoz.APIServer.AddToRouter(r)
@@ -345,29 +343,28 @@ func (s *Server) Stop(ctx context.Context) error {
return nil return nil
} }
func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, metadataStore telemetrytypes.MetadataStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, ruleStateHistoryModule rulestatehistory.Module, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) { func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, metadataStore telemetrytypes.MetadataStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings) ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore) maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts // create manager opts
managerOpts := &baserules.ManagerOptions{ managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore, TelemetryStore: telemetryStore,
MetadataStore: metadataStore, MetadataStore: metadataStore,
Prometheus: prometheus, Prometheus: prometheus,
Context: context.Background(), Context: context.Background(),
Reader: ch, Reader: ch,
Querier: querier, Querier: querier,
Logger: providerSettings.Logger, Logger: providerSettings.Logger,
Cache: cache, Cache: cache,
EvalDelay: baseconst.GetEvalDelay(), EvalDelay: baseconst.GetEvalDelay(),
PrepareTaskFunc: rules.PrepareTaskFunc, PrepareTaskFunc: rules.PrepareTaskFunc,
PrepareTestRuleFunc: rules.TestNotification, PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager, Alertmanager: alertmanager,
OrgGetter: orgGetter, OrgGetter: orgGetter,
RuleStore: ruleStore, RuleStore: ruleStore,
MaintenanceStore: maintenanceStore, MaintenanceStore: maintenanceStore,
SqlStore: sqlstore, SqlStore: sqlstore,
QueryParser: queryParser, QueryParser: queryParser,
RuleStateHistoryModule: ruleStateHistoryModule,
} }
// create Manager // create Manager

View File

@@ -28,7 +28,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
if err != nil { if err != nil {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "evaluation is invalid: %v", err) return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "evaluation is invalid: %v", err)
} }
if opts.Rule.RuleType == ruletypes.RuleTypeThreshold { if opts.Rule.RuleType == ruletypes.RuleTypeThreshold {
// create a threshold rule // create a threshold rule
tr, err := baserules.NewThresholdRule( tr, err := baserules.NewThresholdRule(
@@ -42,7 +41,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithSQLStore(opts.SQLStore), baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser), baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore), baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
) )
if err != nil { if err != nil {
@@ -67,7 +65,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithSQLStore(opts.SQLStore), baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser), baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore), baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
) )
if err != nil { if err != nil {
@@ -93,7 +90,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithSQLStore(opts.SQLStore), baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser), baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore), baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
) )
if err != nil { if err != nil {
return task, err return task, err

View File

@@ -257,7 +257,7 @@ func TestManager_TestNotification_SendUnmatched_PromRule(t *testing.T) {
WillReturnRows(samplesRows) WillReturnRows(samplesRows)
// Create Prometheus provider for this test // Create Prometheus provider for this test
promProvider = prometheustest.New(context.Background(), instrumentationtest.New().ToProviderSettings(), prometheus.Config{Timeout: 2 * time.Minute}, store) promProvider = prometheustest.New(context.Background(), instrumentationtest.New().ToProviderSettings(), prometheus.Config{}, store)
}, },
ManagerOptionsHook: func(opts *rules.ManagerOptions) { ManagerOptionsHook: func(opts *rules.ManagerOptions) {
// Set Prometheus provider for PromQL queries // Set Prometheus provider for PromQL queries

View File

@@ -336,10 +336,9 @@ func (dialect *dialect) UpdatePrimaryKey(ctx context.Context, bun bun.IDB, oldMo
} }
fkReference := "" fkReference := ""
switch reference { if reference == Org {
case Org:
fkReference = OrgReference fkReference = OrgReference
case User: } else if reference == User {
fkReference = UserReference fkReference = UserReference
} }
@@ -393,10 +392,9 @@ func (dialect *dialect) AddPrimaryKey(ctx context.Context, bun bun.IDB, oldModel
} }
fkReference := "" fkReference := ""
switch reference { if reference == Org {
case Org:
fkReference = OrgReference fkReference = OrgReference
case User: } else if reference == User {
fkReference = UserReference fkReference = UserReference
} }

View File

@@ -14,21 +14,14 @@ import (
"github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/dialect/pgdialect"
) )
var _ Pooler = new(provider)
type provider struct { type provider struct {
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
sqldb *sql.DB sqldb *sql.DB
bundb *sqlstore.BunDB bundb *sqlstore.BunDB
pgxPool *pgxpool.Pool
dialect *dialect dialect *dialect
formatter sqlstore.SQLFormatter formatter sqlstore.SQLFormatter
} }
type Pooler interface {
Pool() *pgxpool.Pool
}
func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("postgres"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) { return factory.NewProviderFactory(factory.MustNewName("postgres"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) {
hooks := make([]sqlstore.SQLStoreHook, len(hookFactories)) hooks := make([]sqlstore.SQLStoreHook, len(hookFactories))
@@ -69,7 +62,6 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
settings: settings, settings: settings,
sqldb: sqldb, sqldb: sqldb,
bundb: bunDB, bundb: bunDB,
pgxPool: pool,
dialect: new(dialect), dialect: new(dialect),
formatter: newFormatter(bunDB.Dialect()), formatter: newFormatter(bunDB.Dialect()),
}, nil }, nil
@@ -83,10 +75,6 @@ func (provider *provider) SQLDB() *sql.DB {
return provider.sqldb return provider.sqldb
} }
func (provider *provider) Pool() *pgxpool.Pool {
return provider.pgxPool
}
func (provider *provider) Dialect() sqlstore.SQLDialect { func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect return provider.dialect
} }

View File

@@ -19,7 +19,7 @@ var (
once sync.Once once sync.Once
) )
// initializes the Zeus configuration. // initializes the Zeus configuration
func Config() zeus.Config { func Config() zeus.Config {
once.Do(func() { once.Do(func() {
parsedURL, err := neturl.Parse(url) parsedURL, err := neturl.Parse(url)

View File

@@ -189,7 +189,7 @@ func (provider *Provider) do(ctx context.Context, url *url.URL, method string, k
return nil, provider.errFromStatusCode(response.StatusCode, errorMessage) return nil, provider.errFromStatusCode(response.StatusCode, errorMessage)
} }
// This can be taken down to the client package. // This can be taken down to the client package
func (provider *Provider) errFromStatusCode(statusCode int, errorMessage string) error { func (provider *Provider) errFromStatusCode(statusCode int, errorMessage string) error {
switch statusCode { switch statusCode {
case http.StatusBadRequest: case http.StatusBadRequest:

View File

@@ -205,25 +205,6 @@ module.exports = {
], ],
}, },
overrides: [ overrides: [
{
files: ['src/**/*.{jsx,tsx,ts}'],
excludedFiles: [
'**/*.test.{js,jsx,ts,tsx}',
'**/*.spec.{js,jsx,ts,tsx}',
'**/__tests__/**/*.{js,jsx,ts,tsx}',
],
rules: {
'no-restricted-properties': [
'error',
{
object: 'navigator',
property: 'clipboard',
message:
'Do not use navigator.clipboard directly since it does not work well with specific browsers. Use hook useCopyToClipboard from react-use library. https://streamich.github.io/react-use/?path=/story/side-effects-usecopytoclipboard--docs',
},
],
},
},
{ {
files: [ files: [
'**/*.test.{js,jsx,ts,tsx}', '**/*.test.{js,jsx,ts,tsx}',

View File

@@ -12,7 +12,7 @@
or or
`docker build . -t tagname` `docker build . -t tagname`
**Tag to remote url- Introduce versioning later on** **Tag to remote url- Introduce versinoing later on**
``` ```
docker tag signoz/frontend:latest 7296823551/signoz:latest docker tag signoz/frontend:latest 7296823551/signoz:latest

View File

@@ -2,7 +2,6 @@
interface SafeNavigateOptions { interface SafeNavigateOptions {
replace?: boolean; replace?: boolean;
state?: unknown; state?: unknown;
newTab?: boolean;
} }
interface SafeNavigateTo { interface SafeNavigateTo {
@@ -21,7 +20,9 @@ interface UseSafeNavigateReturn {
export const useSafeNavigate = (): UseSafeNavigateReturn => ({ export const useSafeNavigate = (): UseSafeNavigateReturn => ({
safeNavigate: jest.fn( safeNavigate: jest.fn(
(_to: SafeNavigateToType, _options?: SafeNavigateOptions) => {}, (to: SafeNavigateToType, options?: SafeNavigateOptions) => {
console.log(`Mock safeNavigate called with:`, to, options);
},
) as jest.MockedFunction< ) as jest.MockedFunction<
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void (to: SafeNavigateToType, options?: SafeNavigateOptions) => void
>, >,

View File

@@ -164,7 +164,6 @@
"vite-plugin-html": "3.2.2", "vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4", "web-vitals": "^0.2.4",
"xstate": "^4.31.0", "xstate": "^4.31.0",
"zod": "4.3.6",
"zustand": "5.0.11" "zustand": "5.0.11"
}, },
"browserslist": { "browserslist": {
@@ -287,4 +286,4 @@
"tmp": "0.2.4", "tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1" "vite": "npm:rolldown-vite@7.3.1"
} }
} }

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#fa520f" viewBox="0 0 24 24"><title>Mistral AI</title><path d="M17.143 3.429v3.428h-3.429v3.429h-3.428V6.857H6.857V3.43H3.43v13.714H0v3.428h10.286v-3.428H6.857v-3.429h3.429v3.429h3.429v-3.429h3.428v3.429h-3.428v3.428H24v-3.428h-3.43V3.429z"/></svg>

Before

Width:  |  Height:  |  Size: 294 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 120 120"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="100%"><stop offset="0%" stop-color="#ff4d4d"/><stop offset="100%" stop-color="#991b1b"/></linearGradient></defs><path fill="url(#a)" d="M60 10c-30 0-45 25-45 45s15 40 30 45v10h10v-10s5 2 10 0v10h10v-10c15-5 30-25 30-45S90 10 60 10"/><path fill="url(#a)" d="M20 45C5 40 0 50 5 60s15 5 20-5c3-7 0-10-5-10"/><path fill="url(#a)" d="M100 45c15-5 20 5 15 15s-15 5-20-5c-3-7 0-10 5-10"/><path stroke="#ff4d4d" stroke-linecap="round" stroke-width="3" d="M45 15Q35 5 30 8M75 15Q85 5 90 8"/><circle cx="45" cy="35" r="6" fill="#050810"/><circle cx="75" cy="35" r="6" fill="#050810"/><circle cx="46" cy="34" r="2.5" fill="#00e5cc"/><circle cx="76" cy="34" r="2.5" fill="#00e5cc"/></svg>

Before

Width:  |  Height:  |  Size: 809 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Render</title><path d="M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507a7.9 7.9 0 0 1-3.825-.979.202.202 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75"/></svg>

Before

Width:  |  Height:  |  Size: 362 B

View File

@@ -1,7 +1,6 @@
import axios from 'api'; import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2'; import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api'; import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { CreatePublicDashboardProps } from 'types/api/dashboard/public/create'; import { CreatePublicDashboardProps } from 'types/api/dashboard/public/create';
@@ -9,7 +8,7 @@ const createPublicDashboard = async (
props: CreatePublicDashboardProps, props: CreatePublicDashboardProps,
): Promise<SuccessResponseV2<CreatePublicDashboardProps>> => { ): Promise<SuccessResponseV2<CreatePublicDashboardProps>> => {
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = DEFAULT_TIME_RANGE } = props; const { dashboardId, timeRangeEnabled = false, defaultTimeRange = '30m' } = props;
try { try {
const response = await axios.post( const response = await axios.post(

View File

@@ -1,7 +1,6 @@
import axios from 'api'; import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2'; import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api'; import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { UpdatePublicDashboardProps } from 'types/api/dashboard/public/update'; import { UpdatePublicDashboardProps } from 'types/api/dashboard/public/update';
@@ -9,7 +8,7 @@ const updatePublicDashboard = async (
props: UpdatePublicDashboardProps, props: UpdatePublicDashboardProps,
): Promise<SuccessResponseV2<UpdatePublicDashboardProps>> => { ): Promise<SuccessResponseV2<UpdatePublicDashboardProps>> => {
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = DEFAULT_TIME_RANGE } = props; const { dashboardId, timeRangeEnabled = false, defaultTimeRange = '30m' } = props;
try { try {
const response = await axios.put( const response = await axios.put(

File diff suppressed because it is too large Load Diff

View File

@@ -1,250 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
InvalidateOptions,
QueryClient,
QueryFunction,
QueryKey,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useQuery } from 'react-query';
import type { ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
Healthz200,
Healthz503,
Livez200,
Readyz200,
Readyz503,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
/**
* @summary Health check
*/
export const healthz = (signal?: AbortSignal) => {
return GeneratedAPIInstance<Healthz200>({
url: `/api/v2/healthz`,
method: 'GET',
signal,
});
};
export const getHealthzQueryKey = () => {
return [`/api/v2/healthz`] as const;
};
export const getHealthzQueryOptions = <
TData = Awaited<ReturnType<typeof healthz>>,
TError = ErrorType<Healthz503>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof healthz>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getHealthzQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof healthz>>> = ({
signal,
}) => healthz(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof healthz>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type HealthzQueryResult = NonNullable<
Awaited<ReturnType<typeof healthz>>
>;
export type HealthzQueryError = ErrorType<Healthz503>;
/**
* @summary Health check
*/
export function useHealthz<
TData = Awaited<ReturnType<typeof healthz>>,
TError = ErrorType<Healthz503>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof healthz>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getHealthzQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Health check
*/
export const invalidateHealthz = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getHealthzQueryKey() },
options,
);
return queryClient;
};
/**
* @summary Liveness check
*/
export const livez = (signal?: AbortSignal) => {
return GeneratedAPIInstance<Livez200>({
url: `/api/v2/livez`,
method: 'GET',
signal,
});
};
export const getLivezQueryKey = () => {
return [`/api/v2/livez`] as const;
};
export const getLivezQueryOptions = <
TData = Awaited<ReturnType<typeof livez>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof livez>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getLivezQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof livez>>> = ({
signal,
}) => livez(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof livez>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type LivezQueryResult = NonNullable<Awaited<ReturnType<typeof livez>>>;
export type LivezQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Liveness check
*/
export function useLivez<
TData = Awaited<ReturnType<typeof livez>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof livez>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getLivezQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Liveness check
*/
export const invalidateLivez = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries({ queryKey: getLivezQueryKey() }, options);
return queryClient;
};
/**
* @summary Readiness check
*/
export const readyz = (signal?: AbortSignal) => {
return GeneratedAPIInstance<Readyz200>({
url: `/api/v2/readyz`,
method: 'GET',
signal,
});
};
export const getReadyzQueryKey = () => {
return [`/api/v2/readyz`] as const;
};
export const getReadyzQueryOptions = <
TData = Awaited<ReturnType<typeof readyz>>,
TError = ErrorType<Readyz503>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof readyz>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getReadyzQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof readyz>>> = ({
signal,
}) => readyz(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof readyz>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ReadyzQueryResult = NonNullable<Awaited<ReturnType<typeof readyz>>>;
export type ReadyzQueryError = ErrorType<Readyz503>;
/**
* @summary Readiness check
*/
export function useReadyz<
TData = Awaited<ReturnType<typeof readyz>>,
TError = ErrorType<Readyz503>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof readyz>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getReadyzQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Readiness check
*/
export const invalidateReadyz = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getReadyzQueryKey() },
options,
);
return queryClient;
};

View File

@@ -20,113 +20,11 @@ import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance'; import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance'; import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { import type {
HandleExportRawDataPOSTParams,
ListPromotedAndIndexedPaths200, ListPromotedAndIndexedPaths200,
PromotetypesPromotePathDTO, PromotetypesPromotePathDTO,
Querybuildertypesv5QueryRangeRequestDTO,
RenderErrorResponseDTO, RenderErrorResponseDTO,
} from '../sigNoz.schemas'; } from '../sigNoz.schemas';
/**
* This endpoints allows complex query exporting raw data for traces and logs
* @summary Export raw data
*/
export const handleExportRawDataPOST = (
querybuildertypesv5QueryRangeRequestDTO: BodyType<Querybuildertypesv5QueryRangeRequestDTO>,
params?: HandleExportRawDataPOSTParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/export_raw_data`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: querybuildertypesv5QueryRangeRequestDTO,
params,
signal,
});
};
export const getHandleExportRawDataPOSTMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof handleExportRawDataPOST>>,
TError,
{
data: BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
params?: HandleExportRawDataPOSTParams;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof handleExportRawDataPOST>>,
TError,
{
data: BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
params?: HandleExportRawDataPOSTParams;
},
TContext
> => {
const mutationKey = ['handleExportRawDataPOST'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof handleExportRawDataPOST>>,
{
data: BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
params?: HandleExportRawDataPOSTParams;
}
> = (props) => {
const { data, params } = props ?? {};
return handleExportRawDataPOST(data, params);
};
return { mutationFn, ...mutationOptions };
};
export type HandleExportRawDataPOSTMutationResult = NonNullable<
Awaited<ReturnType<typeof handleExportRawDataPOST>>
>;
export type HandleExportRawDataPOSTMutationBody = BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
export type HandleExportRawDataPOSTMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Export raw data
*/
export const useHandleExportRawDataPOST = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof handleExportRawDataPOST>>,
TError,
{
data: BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
params?: HandleExportRawDataPOSTParams;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof handleExportRawDataPOST>>,
TError,
{
data: BodyType<Querybuildertypesv5QueryRangeRequestDTO>;
params?: HandleExportRawDataPOSTParams;
},
TContext
> => {
const mutationOptions = getHandleExportRawDataPOSTMutationOptions(options);
return useMutation(mutationOptions);
};
/** /**
* This endpoints promotes and indexes paths * This endpoints promotes and indexes paths
* @summary Promote and index paths * @summary Promote and index paths

View File

@@ -31,13 +31,10 @@ import type {
GetMetricHighlightsPathParameters, GetMetricHighlightsPathParameters,
GetMetricMetadata200, GetMetricMetadata200,
GetMetricMetadataPathParameters, GetMetricMetadataPathParameters,
GetMetricsOnboardingStatus200,
GetMetricsStats200, GetMetricsStats200,
GetMetricsTreemap200, GetMetricsTreemap200,
InspectMetrics200,
ListMetrics200, ListMetrics200,
ListMetricsParams, ListMetricsParams,
MetricsexplorertypesInspectMetricsRequestDTO,
MetricsexplorertypesStatsRequestDTO, MetricsexplorertypesStatsRequestDTO,
MetricsexplorertypesTreemapRequestDTO, MetricsexplorertypesTreemapRequestDTO,
MetricsexplorertypesUpdateMetricMetadataRequestDTO, MetricsexplorertypesUpdateMetricMetadataRequestDTO,
@@ -781,176 +778,6 @@ export const useUpdateMetricMetadata = <
return useMutation(mutationOptions); return useMutation(mutationOptions);
}; };
/**
* Returns raw time series data points for a metric within a time range (max 30 minutes). Each series includes labels and timestamp/value pairs.
* @summary Inspect raw metric data points
*/
export const inspectMetrics = (
metricsexplorertypesInspectMetricsRequestDTO: BodyType<MetricsexplorertypesInspectMetricsRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<InspectMetrics200>({
url: `/api/v2/metrics/inspect`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: metricsexplorertypesInspectMetricsRequestDTO,
signal,
});
};
export const getInspectMetricsMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof inspectMetrics>>,
TError,
{ data: BodyType<MetricsexplorertypesInspectMetricsRequestDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof inspectMetrics>>,
TError,
{ data: BodyType<MetricsexplorertypesInspectMetricsRequestDTO> },
TContext
> => {
const mutationKey = ['inspectMetrics'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof inspectMetrics>>,
{ data: BodyType<MetricsexplorertypesInspectMetricsRequestDTO> }
> = (props) => {
const { data } = props ?? {};
return inspectMetrics(data);
};
return { mutationFn, ...mutationOptions };
};
export type InspectMetricsMutationResult = NonNullable<
Awaited<ReturnType<typeof inspectMetrics>>
>;
export type InspectMetricsMutationBody = BodyType<MetricsexplorertypesInspectMetricsRequestDTO>;
export type InspectMetricsMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Inspect raw metric data points
*/
export const useInspectMetrics = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof inspectMetrics>>,
TError,
{ data: BodyType<MetricsexplorertypesInspectMetricsRequestDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof inspectMetrics>>,
TError,
{ data: BodyType<MetricsexplorertypesInspectMetricsRequestDTO> },
TContext
> => {
const mutationOptions = getInspectMetricsMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Lightweight endpoint that checks if any non-SigNoz metrics have been ingested, used for onboarding status detection
* @summary Check if non-SigNoz metrics have been received
*/
export const getMetricsOnboardingStatus = (signal?: AbortSignal) => {
return GeneratedAPIInstance<GetMetricsOnboardingStatus200>({
url: `/api/v2/metrics/onboarding`,
method: 'GET',
signal,
});
};
export const getGetMetricsOnboardingStatusQueryKey = () => {
return [`/api/v2/metrics/onboarding`] as const;
};
export const getGetMetricsOnboardingStatusQueryOptions = <
TData = Awaited<ReturnType<typeof getMetricsOnboardingStatus>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getMetricsOnboardingStatus>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetMetricsOnboardingStatusQueryKey();
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getMetricsOnboardingStatus>>
> = ({ signal }) => getMetricsOnboardingStatus(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof getMetricsOnboardingStatus>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetMetricsOnboardingStatusQueryResult = NonNullable<
Awaited<ReturnType<typeof getMetricsOnboardingStatus>>
>;
export type GetMetricsOnboardingStatusQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Check if non-SigNoz metrics have been received
*/
export function useGetMetricsOnboardingStatus<
TData = Awaited<ReturnType<typeof getMetricsOnboardingStatus>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getMetricsOnboardingStatus>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetMetricsOnboardingStatusQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Check if non-SigNoz metrics have been received
*/
export const invalidateGetMetricsOnboardingStatus = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetMetricsOnboardingStatusQueryKey() },
options,
);
return queryClient;
};
/** /**
* This endpoint provides list of metrics with their number of samples and timeseries for the given time range * This endpoint provides list of metrics with their number of samples and timeseries for the given time range
* @summary Get metrics statistics * @summary Get metrics statistics

View File

@@ -1,744 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
InvalidateOptions,
QueryClient,
QueryFunction,
QueryKey,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useQuery } from 'react-query';
import type { ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
GetRuleHistoryFilterKeys200,
GetRuleHistoryFilterKeysParams,
GetRuleHistoryFilterKeysPathParameters,
GetRuleHistoryFilterValues200,
GetRuleHistoryFilterValuesParams,
GetRuleHistoryFilterValuesPathParameters,
GetRuleHistoryOverallStatus200,
GetRuleHistoryOverallStatusParams,
GetRuleHistoryOverallStatusPathParameters,
GetRuleHistoryStats200,
GetRuleHistoryStatsParams,
GetRuleHistoryStatsPathParameters,
GetRuleHistoryTimeline200,
GetRuleHistoryTimelineParams,
GetRuleHistoryTimelinePathParameters,
GetRuleHistoryTopContributors200,
GetRuleHistoryTopContributorsParams,
GetRuleHistoryTopContributorsPathParameters,
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
/**
* Returns distinct label keys from rule history entries for the selected range.
* @summary Get rule history filter keys
*/
export const getRuleHistoryFilterKeys = (
{ id }: GetRuleHistoryFilterKeysPathParameters,
params?: GetRuleHistoryFilterKeysParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryFilterKeys200>({
url: `/api/v2/rules/${id}/history/filter_keys`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryFilterKeysQueryKey = (
{ id }: GetRuleHistoryFilterKeysPathParameters,
params?: GetRuleHistoryFilterKeysParams,
) => {
return [
`/api/v2/rules/${id}/history/filter_keys`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryFilterKeysQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryFilterKeysPathParameters,
params?: GetRuleHistoryFilterKeysParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetRuleHistoryFilterKeysQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>
> = ({ signal }) => getRuleHistoryFilterKeys({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryFilterKeysQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>
>;
export type GetRuleHistoryFilterKeysQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get rule history filter keys
*/
export function useGetRuleHistoryFilterKeys<
TData = Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryFilterKeysPathParameters,
params?: GetRuleHistoryFilterKeysParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterKeys>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryFilterKeysQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get rule history filter keys
*/
export const invalidateGetRuleHistoryFilterKeys = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryFilterKeysPathParameters,
params?: GetRuleHistoryFilterKeysParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryFilterKeysQueryKey({ id }, params) },
options,
);
return queryClient;
};
/**
* Returns distinct label values for a given key from rule history entries.
* @summary Get rule history filter values
*/
export const getRuleHistoryFilterValues = (
{ id }: GetRuleHistoryFilterValuesPathParameters,
params?: GetRuleHistoryFilterValuesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryFilterValues200>({
url: `/api/v2/rules/${id}/history/filter_values`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryFilterValuesQueryKey = (
{ id }: GetRuleHistoryFilterValuesPathParameters,
params?: GetRuleHistoryFilterValuesParams,
) => {
return [
`/api/v2/rules/${id}/history/filter_values`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryFilterValuesQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryFilterValues>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryFilterValuesPathParameters,
params?: GetRuleHistoryFilterValuesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterValues>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getGetRuleHistoryFilterValuesQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryFilterValues>>
> = ({ signal }) => getRuleHistoryFilterValues({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterValues>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryFilterValuesQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryFilterValues>>
>;
export type GetRuleHistoryFilterValuesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get rule history filter values
*/
export function useGetRuleHistoryFilterValues<
TData = Awaited<ReturnType<typeof getRuleHistoryFilterValues>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryFilterValuesPathParameters,
params?: GetRuleHistoryFilterValuesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryFilterValues>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryFilterValuesQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get rule history filter values
*/
export const invalidateGetRuleHistoryFilterValues = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryFilterValuesPathParameters,
params?: GetRuleHistoryFilterValuesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryFilterValuesQueryKey({ id }, params) },
options,
);
return queryClient;
};
/**
* Returns overall firing/inactive intervals for a rule in the selected time range.
* @summary Get rule overall status timeline
*/
export const getRuleHistoryOverallStatus = (
{ id }: GetRuleHistoryOverallStatusPathParameters,
params: GetRuleHistoryOverallStatusParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryOverallStatus200>({
url: `/api/v2/rules/${id}/history/overall_status`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryOverallStatusQueryKey = (
{ id }: GetRuleHistoryOverallStatusPathParameters,
params?: GetRuleHistoryOverallStatusParams,
) => {
return [
`/api/v2/rules/${id}/history/overall_status`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryOverallStatusQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryOverallStatusPathParameters,
params: GetRuleHistoryOverallStatusParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getGetRuleHistoryOverallStatusQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>
> = ({ signal }) => getRuleHistoryOverallStatus({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryOverallStatusQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>
>;
export type GetRuleHistoryOverallStatusQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get rule overall status timeline
*/
export function useGetRuleHistoryOverallStatus<
TData = Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryOverallStatusPathParameters,
params: GetRuleHistoryOverallStatusParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryOverallStatus>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryOverallStatusQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get rule overall status timeline
*/
export const invalidateGetRuleHistoryOverallStatus = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryOverallStatusPathParameters,
params: GetRuleHistoryOverallStatusParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryOverallStatusQueryKey({ id }, params) },
options,
);
return queryClient;
};
/**
* Returns trigger and resolution statistics for a rule in the selected time range.
* @summary Get rule history stats
*/
export const getRuleHistoryStats = (
{ id }: GetRuleHistoryStatsPathParameters,
params: GetRuleHistoryStatsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryStats200>({
url: `/api/v2/rules/${id}/history/stats`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryStatsQueryKey = (
{ id }: GetRuleHistoryStatsPathParameters,
params?: GetRuleHistoryStatsParams,
) => {
return [
`/api/v2/rules/${id}/history/stats`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryStatsQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryStats>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryStatsPathParameters,
params: GetRuleHistoryStatsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryStats>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetRuleHistoryStatsQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryStats>>
> = ({ signal }) => getRuleHistoryStats({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryStats>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryStatsQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryStats>>
>;
export type GetRuleHistoryStatsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get rule history stats
*/
export function useGetRuleHistoryStats<
TData = Awaited<ReturnType<typeof getRuleHistoryStats>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryStatsPathParameters,
params: GetRuleHistoryStatsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryStats>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryStatsQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get rule history stats
*/
export const invalidateGetRuleHistoryStats = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryStatsPathParameters,
params: GetRuleHistoryStatsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryStatsQueryKey({ id }, params) },
options,
);
return queryClient;
};
/**
* Returns paginated timeline entries for rule state transitions.
* @summary Get rule history timeline
*/
export const getRuleHistoryTimeline = (
{ id }: GetRuleHistoryTimelinePathParameters,
params: GetRuleHistoryTimelineParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryTimeline200>({
url: `/api/v2/rules/${id}/history/timeline`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryTimelineQueryKey = (
{ id }: GetRuleHistoryTimelinePathParameters,
params?: GetRuleHistoryTimelineParams,
) => {
return [
`/api/v2/rules/${id}/history/timeline`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryTimelineQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryTimeline>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryTimelinePathParameters,
params: GetRuleHistoryTimelineParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTimeline>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetRuleHistoryTimelineQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryTimeline>>
> = ({ signal }) => getRuleHistoryTimeline({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTimeline>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryTimelineQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryTimeline>>
>;
export type GetRuleHistoryTimelineQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get rule history timeline
*/
export function useGetRuleHistoryTimeline<
TData = Awaited<ReturnType<typeof getRuleHistoryTimeline>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryTimelinePathParameters,
params: GetRuleHistoryTimelineParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTimeline>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryTimelineQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get rule history timeline
*/
export const invalidateGetRuleHistoryTimeline = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryTimelinePathParameters,
params: GetRuleHistoryTimelineParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryTimelineQueryKey({ id }, params) },
options,
);
return queryClient;
};
/**
* Returns top label combinations contributing to rule firing in the selected time range.
* @summary Get top contributors to rule firing
*/
export const getRuleHistoryTopContributors = (
{ id }: GetRuleHistoryTopContributorsPathParameters,
params: GetRuleHistoryTopContributorsParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleHistoryTopContributors200>({
url: `/api/v2/rules/${id}/history/top_contributors`,
method: 'GET',
params,
signal,
});
};
export const getGetRuleHistoryTopContributorsQueryKey = (
{ id }: GetRuleHistoryTopContributorsPathParameters,
params?: GetRuleHistoryTopContributorsParams,
) => {
return [
`/api/v2/rules/${id}/history/top_contributors`,
...(params ? [params] : []),
] as const;
};
export const getGetRuleHistoryTopContributorsQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleHistoryTopContributors>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryTopContributorsPathParameters,
params: GetRuleHistoryTopContributorsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTopContributors>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getGetRuleHistoryTopContributorsQueryKey({ id }, params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getRuleHistoryTopContributors>>
> = ({ signal }) => getRuleHistoryTopContributors({ id }, params, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTopContributors>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleHistoryTopContributorsQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleHistoryTopContributors>>
>;
export type GetRuleHistoryTopContributorsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get top contributors to rule firing
*/
export function useGetRuleHistoryTopContributors<
TData = Awaited<ReturnType<typeof getRuleHistoryTopContributors>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleHistoryTopContributorsPathParameters,
params: GetRuleHistoryTopContributorsParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleHistoryTopContributors>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleHistoryTopContributorsQueryOptions(
{ id },
params,
options,
);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get top contributors to rule firing
*/
export const invalidateGetRuleHistoryTopContributors = async (
queryClient: QueryClient,
{ id }: GetRuleHistoryTopContributorsPathParameters,
params: GetRuleHistoryTopContributorsParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleHistoryTopContributorsQueryKey({ id }, params) },
options,
);
return queryClient;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
export interface InspectMetricsRequest {
metricName: string;
start: number;
end: number;
filters: TagFilter;
}
export interface InspectMetricsResponse {
status: string;
data: {
series: InspectMetricsSeries[];
};
}
export interface InspectMetricsSeries {
title?: string;
strokeColor?: string;
labels: Record<string, string>;
labelsArray: Array<Record<string, string>>;
values: InspectMetricsTimestampValue[];
}
interface InspectMetricsTimestampValue {
timestamp: number;
value: string;
}
export const getInspectMetricsDetails = async (
request: InspectMetricsRequest,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<InspectMetricsResponse> | ErrorResponse> => {
try {
const response = await axios.post(`/metrics/inspect`, request, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,75 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { MetricType } from './getMetricsList';
export interface MetricDetails {
name: string;
description: string;
type: string;
unit: string;
timeseries: number;
samples: number;
timeSeriesTotal: number;
timeSeriesActive: number;
lastReceived: string;
attributes: MetricDetailsAttribute[] | null;
metadata?: {
metric_type: MetricType;
description: string;
unit: string;
temporality?: Temporality;
};
alerts: MetricDetailsAlert[] | null;
dashboards: MetricDetailsDashboard[] | null;
}
export enum Temporality {
CUMULATIVE = 'Cumulative',
DELTA = 'Delta',
}
export interface MetricDetailsAttribute {
key: string;
value: string[];
valueCount: number;
}
export interface MetricDetailsAlert {
alert_name: string;
alert_id: string;
}
export interface MetricDetailsDashboard {
dashboard_name: string;
dashboard_id: string;
}
export interface MetricDetailsResponse {
status: string;
data: MetricDetails;
}
export const getMetricDetails = async (
metricName: string,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricDetailsResponse> | ErrorResponse> => {
try {
const response = await axios.get(`/metrics/${metricName}/metadata`, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,67 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import {
OrderByPayload,
TreemapViewType,
} from 'container/MetricsExplorer/Summary/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
export interface MetricsListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: OrderByPayload;
}
export enum MetricType {
SUM = 'Sum',
GAUGE = 'Gauge',
HISTOGRAM = 'Histogram',
SUMMARY = 'Summary',
EXPONENTIAL_HISTOGRAM = 'ExponentialHistogram',
}
export interface MetricsListItemData {
metric_name: string;
description: string;
type: MetricType;
unit: string;
[TreemapViewType.TIMESERIES]: number;
[TreemapViewType.SAMPLES]: number;
lastReceived: string;
}
export interface MetricsListResponse {
status: string;
data: {
metrics: MetricsListItemData[];
total?: number;
};
}
export const getMetricsList = async (
props: MetricsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsListResponse> | ErrorResponse> => {
try {
const response = await axios.post('/metrics', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,44 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
export interface MetricsListFilterKeysResponse {
status: string;
data: {
metricColumns: string[];
attributeKeys: BaseAutocompleteData[];
};
}
export interface GetMetricsListFilterKeysParams {
searchText: string;
limit?: number;
}
export const getMetricsListFilterKeys = async (
params: GetMetricsListFilterKeysParams,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsListFilterKeysResponse> | ErrorResponse> => {
try {
const response = await axios.get('/metrics/filters/keys', {
params: {
searchText: params.searchText,
limit: params.limit,
},
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,43 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
export interface MetricsListFilterValuesPayload {
filterAttributeKeyDataType: string;
filterKey: string;
searchText: string;
limit: number;
}
export interface MetricsListFilterValuesResponse {
status: string;
data: {
filterValues: string[];
};
}
export const getMetricsListFilterValues = async (
props: MetricsListFilterValuesPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<
SuccessResponse<MetricsListFilterValuesResponse> | ErrorResponse
> => {
try {
const response = await axios.post('/metrics/filters/values', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -0,0 +1,60 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export interface RelatedMetricsPayload {
start: number;
end: number;
currentMetricName: string;
}
export interface RelatedMetricDashboard {
dashboard_name: string;
dashboard_id: string;
widget_id: string;
widget_name: string;
}
export interface RelatedMetricAlert {
alert_name: string;
alert_id: string;
}
export interface RelatedMetric {
name: string;
query: IBuilderQuery;
dashboards: RelatedMetricDashboard[];
alerts: RelatedMetricAlert[];
}
export interface RelatedMetricsResponse {
status: 'success';
data: {
related_metrics: RelatedMetric[];
};
}
export const getRelatedMetrics = async (
props: RelatedMetricsPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<RelatedMetricsResponse> | ErrorResponse> => {
try {
const response = await axios.post('/metrics/related', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import useUpdatedQuery from 'container/GridCardLayout/useResolveQuery'; import useUpdatedQuery from 'container/GridCardLayout/useResolveQuery';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
@@ -79,7 +79,7 @@ export function useNavigateToExplorer(): (
); );
const { getUpdatedQuery } = useUpdatedQuery(); const { getUpdatedQuery } = useUpdatedQuery();
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard } = useDashboard();
const { notifications } = useNotifications(); const { notifications } = useNotifications();
return useCallback( return useCallback(

View File

@@ -116,12 +116,7 @@ describe.each([
expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByText('FORMAT')).toBeInTheDocument(); expect(screen.getByText('FORMAT')).toBeInTheDocument();
expect(screen.getByText('Number of Rows')).toBeInTheDocument(); expect(screen.getByText('Number of Rows')).toBeInTheDocument();
expect(screen.getByText('Columns')).toBeInTheDocument();
if (dataSource === DataSource.TRACES) {
expect(screen.queryByText('Columns')).not.toBeInTheDocument();
} else {
expect(screen.getByText('Columns')).toBeInTheDocument();
}
}); });
it('allows changing export format', () => { it('allows changing export format', () => {
@@ -151,17 +146,6 @@ describe.each([
}); });
it('allows changing columns scope', () => { it('allows changing columns scope', () => {
if (dataSource === DataSource.TRACES) {
renderWithStore(dataSource);
fireEvent.click(screen.getByTestId(testId));
expect(screen.queryByRole('radio', { name: 'All' })).not.toBeInTheDocument();
expect(
screen.queryByRole('radio', { name: 'Selected' }),
).not.toBeInTheDocument();
return;
}
renderWithStore(dataSource); renderWithStore(dataSource);
fireEvent.click(screen.getByTestId(testId)); fireEvent.click(screen.getByTestId(testId));
@@ -226,12 +210,7 @@ describe.each([
mockUseQueryBuilder.mockReturnValue({ stagedQuery: mockQuery }); mockUseQueryBuilder.mockReturnValue({ stagedQuery: mockQuery });
renderWithStore(dataSource); renderWithStore(dataSource);
fireEvent.click(screen.getByTestId(testId)); fireEvent.click(screen.getByTestId(testId));
fireEvent.click(screen.getByRole('radio', { name: 'Selected' }));
// For traces, column scope is always Selected and the radio is hidden
if (dataSource !== DataSource.TRACES) {
fireEvent.click(screen.getByRole('radio', { name: 'Selected' }));
}
fireEvent.click(screen.getByText('Export')); fireEvent.click(screen.getByText('Export'));
await waitFor(() => { await waitFor(() => {
@@ -248,11 +227,6 @@ describe.each([
}); });
it('sends no selectFields when column scope is All', async () => { it('sends no selectFields when column scope is All', async () => {
// For traces, column scope is always Selected — this test only applies to other sources
if (dataSource === DataSource.TRACES) {
return;
}
renderWithStore(dataSource); renderWithStore(dataSource);
fireEvent.click(screen.getByTestId(testId)); fireEvent.click(screen.getByTestId(testId));
fireEvent.click(screen.getByRole('radio', { name: 'All' })); fireEvent.click(screen.getByRole('radio', { name: 'All' }));

View File

@@ -1,6 +1,5 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Radio, Tooltip, Typography } from 'antd'; import { Button, Popover, Radio, Tooltip, Typography } from 'antd';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu'; import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
import { Download, DownloadIcon, Loader2 } from 'lucide-react'; import { Download, DownloadIcon, Loader2 } from 'lucide-react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@@ -15,12 +14,10 @@ import './DownloadOptionsMenu.styles.scss';
interface DownloadOptionsMenuProps { interface DownloadOptionsMenuProps {
dataSource: DataSource; dataSource: DataSource;
selectedColumns?: TelemetryFieldKey[];
} }
export default function DownloadOptionsMenu({ export default function DownloadOptionsMenu({
dataSource, dataSource,
selectedColumns,
}: DownloadOptionsMenuProps): JSX.Element { }: DownloadOptionsMenuProps): JSX.Element {
const [exportFormat, setExportFormat] = useState<string>(DownloadFormats.CSV); const [exportFormat, setExportFormat] = useState<string>(DownloadFormats.CSV);
const [rowLimit, setRowLimit] = useState<number>(DownloadRowCounts.TEN_K); const [rowLimit, setRowLimit] = useState<number>(DownloadRowCounts.TEN_K);
@@ -38,19 +35,9 @@ export default function DownloadOptionsMenu({
await handleExportRawData({ await handleExportRawData({
format: exportFormat, format: exportFormat,
rowLimit, rowLimit,
clearSelectColumns: clearSelectColumns: columnsScope === DownloadColumnsScopes.ALL,
dataSource !== DataSource.TRACES &&
columnsScope === DownloadColumnsScopes.ALL,
selectedColumns,
}); });
}, [ }, [exportFormat, rowLimit, columnsScope, handleExportRawData]);
exportFormat,
rowLimit,
columnsScope,
selectedColumns,
handleExportRawData,
dataSource,
]);
const popoverContent = useMemo( const popoverContent = useMemo(
() => ( () => (
@@ -85,22 +72,18 @@ export default function DownloadOptionsMenu({
</Radio.Group> </Radio.Group>
</div> </div>
{dataSource !== DataSource.TRACES && ( <div className="horizontal-line" />
<>
<div className="horizontal-line" />
<div className="columns-scope"> <div className="columns-scope">
<Typography.Text className="title">Columns</Typography.Text> <Typography.Text className="title">Columns</Typography.Text>
<Radio.Group <Radio.Group
value={columnsScope} value={columnsScope}
onChange={(e): void => setColumnsScope(e.target.value)} onChange={(e): void => setColumnsScope(e.target.value)}
> >
<Radio value={DownloadColumnsScopes.ALL}>All</Radio> <Radio value={DownloadColumnsScopes.ALL}>All</Radio>
<Radio value={DownloadColumnsScopes.SELECTED}>Selected</Radio> <Radio value={DownloadColumnsScopes.SELECTED}>Selected</Radio>
</Radio.Group> </Radio.Group>
</div> </div>
</>
)}
<Button <Button
type="primary" type="primary"
@@ -114,14 +97,7 @@ export default function DownloadOptionsMenu({
</Button> </Button>
</div> </div>
), ),
[ [exportFormat, rowLimit, columnsScope, isDownloading, handleExport],
exportFormat,
rowLimit,
columnsScope,
isDownloading,
handleExport,
dataSource,
],
); );
return ( return (

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Badge } from '@signozhq/badge'; import { Badge } from '@signozhq/badge';
import { Button } from '@signozhq/button'; import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog'; import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
@@ -21,7 +20,7 @@ import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { import {
getResetPasswordToken, getResetPasswordToken,
useDeleteUser, useDeleteUser,
useUpdateUserDeprecated, useUpdateUser,
} from 'api/generated/services/users'; } from 'api/generated/services/users';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { MemberRow } from 'components/MembersTable/MembersTable'; import { MemberRow } from 'components/MembersTable/MembersTable';
@@ -61,7 +60,7 @@ function EditMemberDrawer({
const isInvited = member?.status === MemberStatus.Invited; const isInvited = member?.status === MemberStatus.Invited;
const { mutate: updateUser, isLoading: isSaving } = useUpdateUserDeprecated({ const { mutate: updateUser, isLoading: isSaving } = useUpdateUser({
mutation: { mutation: {
onSuccess: (): void => { onSuccess: (): void => {
toast.success('Member details updated successfully', { richColors: true }); toast.success('Member details updated successfully', { richColors: true });
@@ -178,30 +177,26 @@ function EditMemberDrawer({
} }
}, [member, isInvited, setLinkType, onClose]); }, [member, isInvited, setLinkType, onClose]);
const [copyState, copyToClipboard] = useCopyToClipboard();
const handleCopyResetLink = useCallback(async (): Promise<void> => { const handleCopyResetLink = useCallback(async (): Promise<void> => {
if (!resetLink) { if (!resetLink) {
return; return;
} }
copyToClipboard(resetLink); try {
await navigator.clipboard.writeText(resetLink);
setHasCopiedResetLink(true); setHasCopiedResetLink(true);
setTimeout(() => setHasCopiedResetLink(false), 2000); setTimeout(() => setHasCopiedResetLink(false), 2000);
toast.success( toast.success(
linkType === 'invite' linkType === 'invite'
? 'Invite link copied to clipboard' ? 'Invite link copied to clipboard'
: 'Reset link copied to clipboard', : 'Reset link copied to clipboard',
{ richColors: true }, { richColors: true },
); );
}, [resetLink, copyToClipboard, linkType]); } catch {
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link', { toast.error('Failed to copy link', {
richColors: true, richColors: true,
}); });
} }
}, [copyState.error]); }, [resetLink, linkType]);
const handleClose = useCallback((): void => { const handleClose = useCallback((): void => {
setShowDeleteConfirm(false); setShowDeleteConfirm(false);

View File

@@ -4,10 +4,16 @@ import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { import {
getResetPasswordToken, getResetPasswordToken,
useDeleteUser, useDeleteUser,
useUpdateUserDeprecated, useUpdateUser,
} from 'api/generated/services/users'; } from 'api/generated/services/users';
import { MemberStatus } from 'container/MembersSettings/utils'; import { MemberStatus } from 'container/MembersSettings/utils';
import { render, screen, userEvent, waitFor } from 'tests/test-utils'; import {
fireEvent,
render,
screen,
userEvent,
waitFor,
} from 'tests/test-utils';
import { ROLES } from 'types/roles'; import { ROLES } from 'types/roles';
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer'; import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
@@ -44,7 +50,7 @@ jest.mock('@signozhq/dialog', () => ({
jest.mock('api/generated/services/users', () => ({ jest.mock('api/generated/services/users', () => ({
useDeleteUser: jest.fn(), useDeleteUser: jest.fn(),
useUpdateUserDeprecated: jest.fn(), useUpdateUser: jest.fn(),
getResetPasswordToken: jest.fn(), getResetPasswordToken: jest.fn(),
})); }));
@@ -59,16 +65,6 @@ jest.mock('@signozhq/sonner', () => ({
}, },
})); }));
const mockCopyToClipboard = jest.fn();
const mockCopyState = { value: undefined, error: undefined };
jest.mock('react-use', () => ({
useCopyToClipboard: (): [typeof mockCopyState, typeof mockCopyToClipboard] => [
mockCopyState,
mockCopyToClipboard,
],
}));
const mockUpdateMutate = jest.fn(); const mockUpdateMutate = jest.fn();
const mockDeleteMutate = jest.fn(); const mockDeleteMutate = jest.fn();
const mockGetResetPasswordToken = jest.mocked(getResetPasswordToken); const mockGetResetPasswordToken = jest.mocked(getResetPasswordToken);
@@ -109,7 +105,7 @@ function renderDrawer(
describe('EditMemberDrawer', () => { describe('EditMemberDrawer', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
(useUpdateUserDeprecated as jest.Mock).mockReturnValue({ (useUpdateUser as jest.Mock).mockReturnValue({
mutate: mockUpdateMutate, mutate: mockUpdateMutate,
isLoading: false, isLoading: false,
}); });
@@ -134,7 +130,7 @@ describe('EditMemberDrawer', () => {
const onComplete = jest.fn(); const onComplete = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({ (useUpdateUser as jest.Mock).mockImplementation((options) => ({
mutate: mockUpdateMutate.mockImplementation(() => { mutate: mockUpdateMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.(); options?.mutation?.onSuccess?.();
}), }),
@@ -243,7 +239,7 @@ describe('EditMemberDrawer', () => {
const onComplete = jest.fn(); const onComplete = jest.fn();
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({ (useUpdateUser as jest.Mock).mockImplementation((options) => ({
mutate: mockUpdateMutate.mockImplementation(() => { mutate: mockUpdateMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.(); options?.mutation?.onSuccess?.();
}), }),
@@ -284,7 +280,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
const mockToast = jest.mocked(toast); const mockToast = jest.mocked(toast);
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({ (useUpdateUser as jest.Mock).mockImplementation((options) => ({
mutate: mockUpdateMutate.mockImplementation(() => { mutate: mockUpdateMutate.mockImplementation(() => {
options?.mutation?.onError?.({}); options?.mutation?.onError?.({});
}), }),
@@ -365,14 +361,32 @@ describe('EditMemberDrawer', () => {
}); });
describe('Generate Password Reset Link', () => { describe('Generate Password Reset Link', () => {
const mockWriteText = jest.fn().mockResolvedValue(undefined);
let clipboardSpy: jest.SpyInstance | undefined;
beforeAll(() => {
Object.defineProperty(navigator, 'clipboard', {
value: { writeText: (): Promise<void> => Promise.resolve() },
configurable: true,
writable: true,
});
});
beforeEach(() => { beforeEach(() => {
mockCopyToClipboard.mockClear(); mockWriteText.mockClear();
clipboardSpy = jest
.spyOn(navigator.clipboard, 'writeText')
.mockImplementation(mockWriteText);
mockGetResetPasswordToken.mockResolvedValue({ mockGetResetPasswordToken.mockResolvedValue({
status: 'success', status: 'success',
data: { token: 'reset-tok-abc', id: 'user-1' }, data: { token: 'reset-tok-abc', id: 'user-1' },
}); });
}); });
afterEach(() => {
clipboardSpy?.mockRestore();
});
it('calls getResetPasswordToken and opens the reset link dialog with the generated link', async () => { it('calls getResetPasswordToken and opens the reset link dialog with the generated link', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
@@ -407,7 +421,7 @@ describe('EditMemberDrawer', () => {
}); });
expect(dialog).toHaveTextContent('reset-tok-abc'); expect(dialog).toHaveTextContent('reset-tok-abc');
await user.click(screen.getByRole('button', { name: /^copy$/i })); fireEvent.click(screen.getByRole('button', { name: /^copy$/i }));
await waitFor(() => { await waitFor(() => {
expect(mockToast.success).toHaveBeenCalledWith( expect(mockToast.success).toHaveBeenCalledWith(
@@ -416,7 +430,7 @@ describe('EditMemberDrawer', () => {
); );
}); });
expect(mockCopyToClipboard).toHaveBeenCalledWith( expect(mockWriteText).toHaveBeenCalledWith(
expect.stringContaining('reset-tok-abc'), expect.stringContaining('reset-tok-abc'),
); );
expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /copied!/i })).toBeInTheDocument();

View File

@@ -202,8 +202,19 @@ function InviteMembersModal({
onComplete?.(); onComplete?.();
} catch (err) { } catch (err) {
const apiErr = err as APIError; const apiErr = err as APIError;
const errorMessage = apiErr?.getErrorMessage?.() ?? 'An error occurred'; if (apiErr?.getHttpStatusCode() === 409) {
toast.error(errorMessage, { richColors: true }); toast.error(
touchedRows.length === 1
? `${touchedRows[0].email} is already a member`
: 'Invite for one or more users already exists',
{ richColors: true },
);
} else {
const errorMessage = apiErr?.getErrorMessage?.() ?? 'An error occurred';
toast.error(`Failed to send invites: ${errorMessage}`, {
richColors: true,
});
}
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }

View File

@@ -1,18 +1,9 @@
import { toast } from '@signozhq/sonner';
import inviteUsers from 'api/v1/invite/bulk/create'; import inviteUsers from 'api/v1/invite/bulk/create';
import sendInvite from 'api/v1/invite/create'; import sendInvite from 'api/v1/invite/create';
import { StatusCodes } from 'http-status-codes';
import { render, screen, userEvent, waitFor } from 'tests/test-utils'; import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import APIError from 'types/api/error';
import InviteMembersModal from '../InviteMembersModal'; import InviteMembersModal from '../InviteMembersModal';
const makeApiError = (message: string, code = StatusCodes.CONFLICT): APIError =>
new APIError({
httpStatusCode: code,
error: { code: 'already_exists', message, url: '', errors: [] },
});
jest.mock('api/v1/invite/create'); jest.mock('api/v1/invite/create');
jest.mock('api/v1/invite/bulk/create'); jest.mock('api/v1/invite/bulk/create');
jest.mock('@signozhq/sonner', () => ({ jest.mock('@signozhq/sonner', () => ({
@@ -151,90 +142,6 @@ describe('InviteMembersModal', () => {
}); });
}); });
describe('error handling', () => {
it('shows BE message on single invite 409', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
mockSendInvite.mockRejectedValue(
makeApiError('An invite already exists for this email: single@signoz.io'),
);
render(<InviteMembersModal {...defaultProps} />);
await user.type(
screen.getAllByPlaceholderText('john@signoz.io')[0],
'single@signoz.io',
);
await user.click(screen.getAllByText('Select roles')[0]);
await user.click(await screen.findByText('Viewer'));
await user.click(
screen.getByRole('button', { name: /invite team members/i }),
);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith(
'An invite already exists for this email: single@signoz.io',
expect.anything(),
);
});
});
it('shows BE message on bulk invite 409', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
mockInviteUsers.mockRejectedValue(
makeApiError('An invite already exists for this email: alice@signoz.io'),
);
render(<InviteMembersModal {...defaultProps} />);
const emailInputs = screen.getAllByPlaceholderText('john@signoz.io');
await user.type(emailInputs[0], 'alice@signoz.io');
await user.click(screen.getAllByText('Select roles')[0]);
await user.click(await screen.findByText('Viewer'));
await user.type(emailInputs[1], 'bob@signoz.io');
await user.click(screen.getAllByText('Select roles')[0]);
const editorOptions = await screen.findAllByText('Editor');
await user.click(editorOptions[editorOptions.length - 1]);
await user.click(
screen.getByRole('button', { name: /invite team members/i }),
);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith(
'An invite already exists for this email: alice@signoz.io',
expect.anything(),
);
});
});
it('shows BE message on generic error', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
mockSendInvite.mockRejectedValue(
makeApiError('Internal server error', StatusCodes.INTERNAL_SERVER_ERROR),
);
render(<InviteMembersModal {...defaultProps} />);
await user.type(
screen.getAllByPlaceholderText('john@signoz.io')[0],
'single@signoz.io',
);
await user.click(screen.getAllByText('Select roles')[0]);
await user.click(await screen.findByText('Viewer'));
await user.click(
screen.getByRole('button', { name: /invite team members/i }),
);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith(
'Internal server error',
expect.anything(),
);
});
});
});
it('uses inviteUsers (bulk) when multiple rows are filled', async () => { it('uses inviteUsers (bulk) when multiple rows are filled', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
const onComplete = jest.fn(); const onComplete = jest.fn();

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly import { useSelector } from 'react-redux';
import { useCopyToClipboard, useLocation } from 'react-use'; import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens'; import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
@@ -49,7 +49,6 @@ import { AppState } from 'store/reducers';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder'; import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { isModifierKeyPressed } from 'utils/app';
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants'; import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces'; import { LogDetailInnerProps, LogDetailProps } from './LogDetail.interfaces';
@@ -222,7 +221,7 @@ function LogDetailInner({
}; };
// Go to logs explorer page with the log data // Go to logs explorer page with the log data
const handleOpenInExplorer = (e?: React.MouseEvent): void => { const handleOpenInExplorer = (): void => {
const queryParams = { const queryParams = {
[QueryParams.activeLogId]: `"${log?.id}"`, [QueryParams.activeLogId]: `"${log?.id}"`,
[QueryParams.startTime]: minTime?.toString() || '', [QueryParams.startTime]: minTime?.toString() || '',
@@ -235,9 +234,7 @@ function LogDetailInner({
), ),
), ),
}; };
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`, { safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
newTab: !!e && isModifierKeyPressed(e),
});
}; };
const handleQueryExpressionChange = useCallback( const handleQueryExpressionChange = useCallback(

View File

@@ -17,7 +17,6 @@ function CodeCopyBtn({
let copiedText = ''; let copiedText = '';
if (children && Array.isArray(children)) { if (children && Array.isArray(children)) {
setIsSnippetCopied(true); setIsSnippetCopied(true);
// eslint-disable-next-line no-restricted-properties
navigator.clipboard.writeText(children[0].props.children[0]).finally(() => { navigator.clipboard.writeText(children[0].props.children[0]).finally(() => {
copiedText = (children[0].props.children[0] as string).slice(0, 200); // slicing is done due to the limitation in accepted char length in attributes copiedText = (children[0].props.children[0] as string).slice(0, 200); // slicing is done due to the limitation in accepted char length in attributes
setTimeout(() => { setTimeout(() => {

View File

@@ -401,7 +401,6 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
const textToCopy = selectedTexts.join(', '); const textToCopy = selectedTexts.join(', ');
// eslint-disable-next-line no-restricted-properties
navigator.clipboard.writeText(textToCopy).catch(console.error); navigator.clipboard.writeText(textToCopy).catch(console.error);
}, [selectedChips, selectedValues]); }, [selectedChips, selectedValues]);

View File

@@ -86,8 +86,8 @@ jest.mock('hooks/useDarkMode', () => ({
useIsDarkMode: (): boolean => false, useIsDarkMode: (): boolean => false,
})); }));
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({ jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: (): { selectedDashboard: undefined } => ({ useDashboard: (): { selectedDashboard: undefined } => ({
selectedDashboard: undefined, selectedDashboard: undefined,
}), }),
})); }));

View File

@@ -1,7 +1,6 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { DialogWrapper } from '@signozhq/dialog'; import { DialogWrapper } from '@signozhq/dialog';
import { toast } from '@signozhq/sonner'; import { toast } from '@signozhq/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs'; import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
@@ -106,23 +105,19 @@ function AddKeyModal(): JSX.Element {
}); });
} }
const [copyState, copyToClipboard] = useCopyToClipboard();
const handleCopy = useCallback(async (): Promise<void> => { const handleCopy = useCallback(async (): Promise<void> => {
if (!createdKey?.key) { if (!createdKey?.key) {
return; return;
} }
try {
copyToClipboard(createdKey.key); await navigator.clipboard.writeText(createdKey.key);
setHasCopied(true); setHasCopied(true);
setTimeout(() => setHasCopied(false), 2000); setTimeout(() => setHasCopied(false), 2000);
toast.success('Key copied to clipboard', { richColors: true }); toast.success('Key copied to clipboard', { richColors: true });
}, [copyToClipboard, createdKey?.key]); } catch {
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy key', { richColors: true }); toast.error('Failed to copy key', { richColors: true });
} }
}, [copyState.error]); }, [createdKey]);
const handleClose = useCallback((): void => { const handleClose = useCallback((): void => {
setIsAddKeyOpen(null); setIsAddKeyOpen(null);

View File

@@ -9,16 +9,6 @@ jest.mock('@signozhq/sonner', () => ({
toast: { success: jest.fn(), error: jest.fn() }, toast: { success: jest.fn(), error: jest.fn() },
})); }));
const mockCopyToClipboard = jest.fn();
const mockCopyState = { value: undefined, error: undefined };
jest.mock('react-use', () => ({
useCopyToClipboard: (): [typeof mockCopyState, typeof mockCopyToClipboard] => [
mockCopyState,
mockCopyToClipboard,
],
}));
const mockToast = jest.mocked(toast); const mockToast = jest.mocked(toast);
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys'; const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys';
@@ -45,9 +35,16 @@ function renderModal(): ReturnType<typeof render> {
} }
describe('AddKeyModal', () => { describe('AddKeyModal', () => {
beforeAll(() => {
Object.defineProperty(navigator, 'clipboard', {
value: { writeText: jest.fn().mockResolvedValue(undefined) },
configurable: true,
writable: true,
});
});
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
mockCopyToClipboard.mockClear();
server.use( server.use(
rest.post(SA_KEYS_ENDPOINT, (_, res, ctx) => rest.post(SA_KEYS_ENDPOINT, (_, res, ctx) =>
res(ctx.status(201), ctx.json(createdKeyResponse)), res(ctx.status(201), ctx.json(createdKeyResponse)),
@@ -93,6 +90,9 @@ describe('AddKeyModal', () => {
it('copy button writes key to clipboard and shows toast.success', async () => { it('copy button writes key to clipboard and shows toast.success', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 }); const user = userEvent.setup({ pointerEventsCheck: 0 });
const writeTextSpy = jest
.spyOn(navigator.clipboard, 'writeText')
.mockResolvedValue(undefined);
renderModal(); renderModal();
@@ -115,12 +115,14 @@ describe('AddKeyModal', () => {
await user.click(copyBtn); await user.click(copyBtn);
await waitFor(() => { await waitFor(() => {
expect(mockCopyToClipboard).toHaveBeenCalledWith('snz_abc123xyz456secret'); expect(writeTextSpy).toHaveBeenCalledWith('snz_abc123xyz456secret');
expect(mockToast.success).toHaveBeenCalledWith( expect(mockToast.success).toHaveBeenCalledWith(
'Key copied to clipboard', 'Key copied to clipboard',
expect.anything(), expect.anything(),
); );
}); });
writeTextSpy.mockRestore();
}); });
it('Cancel button closes the modal', async () => { it('Cancel button closes the modal', async () => {

View File

@@ -16,9 +16,9 @@ function AverageResolutionCard({
}: TotalTriggeredCardProps): JSX.Element { }: TotalTriggeredCardProps): JSX.Element {
return ( return (
<StatsCard <StatsCard
displayValue={formatTime(+currentAvgResolutionTime)} displayValue={formatTime(currentAvgResolutionTime)}
totalCurrentCount={+currentAvgResolutionTime} totalCurrentCount={currentAvgResolutionTime}
totalPastCount={+pastAvgResolutionTime} totalPastCount={pastAvgResolutionTime}
title="Avg. Resolution Time" title="Avg. Resolution Time"
timeSeries={timeSeries} timeSeries={timeSeries}
/> />

View File

@@ -800,10 +800,14 @@
.ant-table-cell:has(.top-services-item-latency) { .ant-table-cell:has(.top-services-item-latency) {
text-align: center; text-align: center;
opacity: 0.8;
background: rgba(171, 189, 255, 0.04);
} }
.ant-table-cell:has(.top-services-item-latency-title) { .ant-table-cell:has(.top-services-item-latency-title) {
text-align: center; text-align: center;
opacity: 0.8;
background: rgba(171, 189, 255, 0.04);
} }
.ant-table-tbody > tr:hover > td { .ant-table-tbody > tr:hover > td {

View File

@@ -9,6 +9,7 @@
.api-quick-filters-header { .api-quick-filters-header {
padding: 12px; padding: 12px;
border-bottom: 1px solid var(--bg-slate-400); border-bottom: 1px solid var(--bg-slate-400);
border-right: 1px solid var(--bg-slate-400);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -25,7 +26,6 @@
width: 100%; width: 100%;
.toolbar { .toolbar {
border-top: 0px;
border-bottom: 1px solid var(--bg-slate-400); border-bottom: 1px solid var(--bg-slate-400);
} }
@@ -220,18 +220,6 @@
} }
.lightMode { .lightMode {
.api-quick-filter-left-section {
.api-quick-filters-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.api-module-right-section {
.toolbar {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.no-filtered-domains-message-container { .no-filtered-domains-message-container {
.no-filtered-domains-message-content { .no-filtered-domains-message-content {
.no-filtered-domains-message { .no-filtered-domains-message {

View File

@@ -6,7 +6,6 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertTypes } from 'types/api/alerts/alertTypes';
import { isModifierKeyPressed } from 'utils/app';
import { getOptionList } from './config'; import { getOptionList } from './config';
import { AlertTypeCard, SelectTypeContainer } from './styles'; import { AlertTypeCard, SelectTypeContainer } from './styles';
@@ -71,8 +70,8 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
</Tag> </Tag>
) : undefined ) : undefined
} }
onClick={(e): void => { onClick={(): void => {
onSelect(option.selection, isModifierKeyPressed(e)); onSelect(option.selection);
}} }}
data-testid={`alert-type-card-${option.selection}`} data-testid={`alert-type-card-${option.selection}`}
> >
@@ -109,7 +108,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
} }
interface SelectAlertTypeProps { interface SelectAlertTypeProps {
onSelect: (type: AlertTypes, newTab?: boolean) => void; onSelect: (typ: AlertTypes) => void;
} }
export default SelectAlertType; export default SelectAlertType;

View File

@@ -4,7 +4,6 @@ import { Button, Tooltip, Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate'; import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { Check, Loader, Send, X } from 'lucide-react'; import { Check, Loader, Send, X } from 'lucide-react';
import { isModifierKeyPressed } from 'utils/app';
import { useCreateAlertState } from '../context'; import { useCreateAlertState } from '../context';
import { import {
@@ -34,9 +33,9 @@ function Footer(): JSX.Element {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const handleDiscard = (e: React.MouseEvent): void => { const handleDiscard = (): void => {
discardAlertRule(); discardAlertRule();
safeNavigate('/alerts', { newTab: isModifierKeyPressed(e) }); safeNavigate('/alerts');
}; };
const alertValidationMessage = useMemo( const alertValidationMessage = useMemo(

View File

@@ -1,6 +1,4 @@
import { ReactNode } from 'react';
import { MemoryRouter, useLocation } from 'react-router-dom'; import { MemoryRouter, useLocation } from 'react-router-dom';
import { useDashboardBootstrap } from 'hooks/dashboard/useDashboardBootstrap';
import { import {
getDashboardById, getDashboardById,
getNonIntegrationDashboardById, getNonIntegrationDashboardById,
@@ -8,9 +6,10 @@ import {
import { server } from 'mocks-server/server'; import { server } from 'mocks-server/server';
import { rest } from 'msw'; import { rest } from 'msw';
import { import {
resetDashboard, DashboardContext,
useDashboardStore, DashboardProvider,
} from 'providers/Dashboard/store/useDashboardStore'; } from 'providers/Dashboard/Dashboard';
import { IDashboardContext } from 'providers/Dashboard/types';
import { import {
fireEvent, fireEvent,
render, render,
@@ -22,18 +21,6 @@ import { Dashboard } from 'types/api/dashboard/getAll';
import DashboardDescription from '..'; import DashboardDescription from '..';
function DashboardBootstrapWrapper({
dashboardId,
children,
}: {
dashboardId: string;
children: ReactNode;
}): JSX.Element {
useDashboardBootstrap(dashboardId);
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
interface MockSafeNavigateReturn { interface MockSafeNavigateReturn {
safeNavigate: jest.MockedFunction<(url: string) => void>; safeNavigate: jest.MockedFunction<(url: string) => void>;
} }
@@ -67,7 +54,6 @@ describe('Dashboard landing page actions header tests', () => {
beforeEach(() => { beforeEach(() => {
mockSafeNavigate.mockClear(); mockSafeNavigate.mockClear();
sessionStorage.clear(); sessionStorage.clear();
resetDashboard();
}); });
it('unlock dashboard should be disabled for integrations created dashboards', async () => { it('unlock dashboard should be disabled for integrations created dashboards', async () => {
@@ -78,7 +64,7 @@ describe('Dashboard landing page actions header tests', () => {
(useLocation as jest.Mock).mockReturnValue(mockLocation); (useLocation as jest.Mock).mockReturnValue(mockLocation);
const { getByTestId } = render( const { getByTestId } = render(
<MemoryRouter initialEntries={[DASHBOARD_PATH]}> <MemoryRouter initialEntries={[DASHBOARD_PATH]}>
<DashboardBootstrapWrapper dashboardId="4"> <DashboardProvider dashboardId="4">
<DashboardDescription <DashboardDescription
handle={{ handle={{
active: false, active: false,
@@ -87,7 +73,7 @@ describe('Dashboard landing page actions header tests', () => {
node: { current: null }, node: { current: null },
}} }}
/> />
</DashboardBootstrapWrapper> </DashboardProvider>
</MemoryRouter>, </MemoryRouter>,
); );
@@ -119,7 +105,7 @@ describe('Dashboard landing page actions header tests', () => {
); );
const { getByTestId } = render( const { getByTestId } = render(
<MemoryRouter initialEntries={[DASHBOARD_PATH]}> <MemoryRouter initialEntries={[DASHBOARD_PATH]}>
<DashboardBootstrapWrapper dashboardId="4"> <DashboardProvider dashboardId="4">
<DashboardDescription <DashboardDescription
handle={{ handle={{
active: false, active: false,
@@ -128,7 +114,7 @@ describe('Dashboard landing page actions header tests', () => {
node: { current: null }, node: { current: null },
}} }}
/> />
</DashboardBootstrapWrapper> </DashboardProvider>
</MemoryRouter>, </MemoryRouter>,
); );
@@ -158,7 +144,7 @@ describe('Dashboard landing page actions header tests', () => {
const { getByText } = render( const { getByText } = render(
<MemoryRouter initialEntries={[DASHBOARD_PATH]}> <MemoryRouter initialEntries={[DASHBOARD_PATH]}>
<DashboardBootstrapWrapper dashboardId="4"> <DashboardProvider dashboardId="4">
<DashboardDescription <DashboardDescription
handle={{ handle={{
active: false, active: false,
@@ -167,7 +153,7 @@ describe('Dashboard landing page actions header tests', () => {
node: { current: null }, node: { current: null },
}} }}
/> />
</DashboardBootstrapWrapper> </DashboardProvider>
</MemoryRouter>, </MemoryRouter>,
); );
@@ -195,26 +181,37 @@ describe('Dashboard landing page actions header tests', () => {
(useLocation as jest.Mock).mockReturnValue(mockLocation); (useLocation as jest.Mock).mockReturnValue(mockLocation);
useDashboardStore.setState({ const mockContextValue: IDashboardContext = {
isDashboardLocked: false,
handleDashboardLockToggle: jest.fn(),
dashboardResponse: {} as IDashboardContext['dashboardResponse'],
selectedDashboard: (getDashboardById.data as unknown) as Dashboard, selectedDashboard: (getDashboardById.data as unknown) as Dashboard,
layouts: [], layouts: [],
panelMap: {}, panelMap: {},
setPanelMap: jest.fn(), setPanelMap: jest.fn(),
setLayouts: jest.fn(), setLayouts: jest.fn(),
setSelectedDashboard: jest.fn(), setSelectedDashboard: jest.fn(),
updatedTimeRef: { current: null },
updateLocalStorageDashboardVariables: jest.fn(),
dashboardQueryRangeCalled: false,
setDashboardQueryRangeCalled: jest.fn(),
isDashboardFetching: false,
columnWidths: {}, columnWidths: {},
}); setColumnWidths: jest.fn(),
};
const { getByText } = render( const { getByText } = render(
<MemoryRouter initialEntries={[DASHBOARD_PATH]}> <MemoryRouter initialEntries={[DASHBOARD_PATH]}>
<DashboardDescription <DashboardContext.Provider value={mockContextValue}>
handle={{ <DashboardDescription
active: false, handle={{
enter: (): Promise<void> => Promise.resolve(), active: false,
exit: (): Promise<void> => Promise.resolve(), enter: (): Promise<void> => Promise.resolve(),
node: { current: null }, exit: (): Promise<void> => Promise.resolve(),
}} node: { current: null },
/> }}
/>
</DashboardContext.Provider>
</MemoryRouter>, </MemoryRouter>,
); );

View File

@@ -21,7 +21,6 @@ import { DeleteButton } from 'container/ListOfDashboard/TableComponents/DeleteBu
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables'; import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
import { useGetPublicDashboardMeta } from 'hooks/dashboard/useGetPublicDashboardMeta'; import { useGetPublicDashboardMeta } from 'hooks/dashboard/useGetPublicDashboardMeta';
import { useLockDashboard } from 'hooks/dashboard/useLockDashboard';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense'; import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
@@ -40,11 +39,8 @@ import {
X, X,
} from 'lucide-react'; } from 'lucide-react';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper'; import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
import {
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { sortLayout } from 'providers/Dashboard/util'; import { sortLayout } from 'providers/Dashboard/util';
import { DashboardData } from 'types/api/dashboard/getAll'; import { DashboardData } from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update'; import { Props } from 'types/api/dashboard/update';
@@ -83,11 +79,10 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
setPanelMap, setPanelMap,
layouts, layouts,
setLayouts, setLayouts,
isDashboardLocked,
setSelectedDashboard, setSelectedDashboard,
} = useDashboardStore(); handleDashboardLockToggle,
} = useDashboard();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked);
const handleDashboardLockToggle = useLockDashboard();
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null); const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>( const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>(

View File

@@ -30,7 +30,7 @@ import {
Pyramid, Pyramid,
X, X,
} from 'lucide-react'; } from 'lucide-react';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { import {
IDashboardVariable, IDashboardVariable,
@@ -239,7 +239,7 @@ function VariableItem({
const [selectedWidgets, setSelectedWidgets] = useState<string[]>([]); const [selectedWidgets, setSelectedWidgets] = useState<string[]>([]);
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard } = useDashboard();
const widgetsByDynamicVariableId = useWidgetsByDynamicVariableId(); const widgetsByDynamicVariableId = useWidgetsByDynamicVariableId();
useEffect(() => { useEffect(() => {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { CustomMultiSelect } from 'components/NewSelect'; import { CustomMultiSelect } from 'components/NewSelect';
import { PANEL_GROUP_TYPES } from 'constants/queryBuilder'; import { PANEL_GROUP_TYPES } from 'constants/queryBuilder';
import { generateGridTitle } from 'container/GridPanelSwitch/utils'; import { generateGridTitle } from 'container/GridPanelSwitch/utils';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { WidgetRow, Widgets } from 'types/api/dashboard/getAll'; import { WidgetRow, Widgets } from 'types/api/dashboard/getAll';
export function WidgetSelector({ export function WidgetSelector({
@@ -12,7 +12,7 @@ export function WidgetSelector({
selectedWidgets: string[]; selectedWidgets: string[];
setSelectedWidgets: (widgets: string[]) => void; setSelectedWidgets: (widgets: string[]) => void;
}): JSX.Element { }): JSX.Element {
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard } = useDashboard();
// Get layout IDs for cross-referencing // Get layout IDs for cross-referencing
const layoutIds = new Set( const layoutIds = new Set(

View File

@@ -19,8 +19,8 @@ import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { PenLine, Trash2 } from 'lucide-react'; import { PenLine, Trash2 } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes'; import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { TVariableMode } from './types'; import { TVariableMode } from './types';
@@ -87,7 +87,7 @@ function VariablesSettings({
const { t } = useTranslation(['dashboard']); const { t } = useTranslation(['dashboard']);
const { selectedDashboard, setSelectedDashboard } = useDashboardStore(); const { selectedDashboard, setSelectedDashboard } = useDashboard();
const { dashboardVariables } = useDashboardVariables(); const { dashboardVariables } = useDashboardVariables();
const { notifications } = useNotifications(); const { notifications } = useNotifications();

View File

@@ -5,7 +5,7 @@ import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddT
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { Check, X } from 'lucide-react'; import { Check, X } from 'lucide-react';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { Button } from './styles'; import { Button } from './styles';
import { Base64Icons } from './utils'; import { Base64Icons } from './utils';
@@ -15,7 +15,7 @@ import './GeneralSettings.styles.scss';
const { Option } = Select; const { Option } = Select;
function GeneralDashboardSettings(): JSX.Element { function GeneralDashboardSettings(): JSX.Element {
const { selectedDashboard, setSelectedDashboard } = useDashboardStore(); const { selectedDashboard, setSelectedDashboard } = useDashboard();
const updateDashboardMutation = useUpdateDashboard(); const updateDashboardMutation = useUpdateDashboard();

View File

@@ -1,21 +1,20 @@
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { toast } from '@signozhq/sonner'; import { toast } from '@signozhq/sonner';
import { fireEvent, within } from '@testing-library/react'; import { fireEvent, within } from '@testing-library/react';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import { import {
publishedPublicDashboardMeta, publishedPublicDashboardMeta,
unpublishedPublicDashboardMeta, unpublishedPublicDashboardMeta,
} from 'mocks-server/__mockdata__/publicDashboard'; } from 'mocks-server/__mockdata__/publicDashboard';
import { rest, server } from 'mocks-server/server'; import { rest, server } from 'mocks-server/server';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { render, screen, userEvent, waitFor } from 'tests/test-utils'; import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { USER_ROLES } from 'types/roles'; import { USER_ROLES } from 'types/roles';
import PublicDashboardSetting from '../index'; import PublicDashboardSetting from '../index';
// Mock dependencies // Mock dependencies
jest.mock('providers/Dashboard/store/useDashboardStore'); jest.mock('providers/Dashboard/Dashboard');
jest.mock('react-use', () => ({ jest.mock('react-use', () => ({
...jest.requireActual('react-use'), ...jest.requireActual('react-use'),
useCopyToClipboard: jest.fn(), useCopyToClipboard: jest.fn(),
@@ -27,13 +26,14 @@ jest.mock('@signozhq/sonner', () => ({
}, },
})); }));
const mockUseDashboard = jest.mocked(useDashboardStore); const mockUseDashboard = jest.mocked(useDashboard);
const mockUseCopyToClipboard = jest.mocked(useCopyToClipboard); const mockUseCopyToClipboard = jest.mocked(useCopyToClipboard);
const mockToast = jest.mocked(toast); const mockToast = jest.mocked(toast);
// Test constants // Test constants
const MOCK_DASHBOARD_ID = 'test-dashboard-id'; const MOCK_DASHBOARD_ID = 'test-dashboard-id';
const MOCK_PUBLIC_PATH = '/public/dashboard/test-dashboard-id'; const MOCK_PUBLIC_PATH = '/public/dashboard/test-dashboard-id';
const DEFAULT_TIME_RANGE = '30m';
const DASHBOARD_VARIABLES_WARNING = const DASHBOARD_VARIABLES_WARNING =
"Dashboard variables won't work in public dashboards"; "Dashboard variables won't work in public dashboards";
@@ -67,10 +67,10 @@ beforeEach(() => {
// Mock window.open // Mock window.open
window.open = jest.fn(); window.open = jest.fn();
// Mock useDashboardStore // Mock useDashboard
mockUseDashboard.mockReturnValue(({ mockUseDashboard.mockReturnValue(({
selectedDashboard: mockSelectedDashboard, selectedDashboard: mockSelectedDashboard,
} as unknown) as ReturnType<typeof useDashboardStore>); } as unknown) as ReturnType<typeof useDashboard>);
// Mock useCopyToClipboard // Mock useCopyToClipboard
mockUseCopyToClipboard.mockReturnValue(([ mockUseCopyToClipboard.mockReturnValue(([

View File

@@ -7,12 +7,11 @@ import { Button, Select, Typography } from 'antd';
import createPublicDashboardAPI from 'api/dashboard/public/createPublicDashboard'; import createPublicDashboardAPI from 'api/dashboard/public/createPublicDashboard';
import revokePublicDashboardAccessAPI from 'api/dashboard/public/revokePublicDashboardAccess'; import revokePublicDashboardAccessAPI from 'api/dashboard/public/revokePublicDashboardAccess';
import updatePublicDashboardAPI from 'api/dashboard/public/updatePublicDashboard'; import updatePublicDashboardAPI from 'api/dashboard/public/updatePublicDashboard';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import { useGetPublicDashboardMeta } from 'hooks/dashboard/useGetPublicDashboardMeta'; import { useGetPublicDashboardMeta } from 'hooks/dashboard/useGetPublicDashboardMeta';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense'; import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Copy, ExternalLink, Globe, Info, Loader2, Trash } from 'lucide-react'; import { Copy, ExternalLink, Globe, Info, Loader2, Trash } from 'lucide-react';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta'; import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
import APIError from 'types/api/error'; import APIError from 'types/api/error';
import { USER_ROLES } from 'types/roles'; import { USER_ROLES } from 'types/roles';
@@ -57,10 +56,10 @@ function PublicDashboardSetting(): JSX.Element {
PublicDashboardMetaProps | undefined PublicDashboardMetaProps | undefined
>(undefined); >(undefined);
const [timeRangeEnabled, setTimeRangeEnabled] = useState(true); const [timeRangeEnabled, setTimeRangeEnabled] = useState(true);
const [defaultTimeRange, setDefaultTimeRange] = useState(DEFAULT_TIME_RANGE); const [defaultTimeRange, setDefaultTimeRange] = useState('30m');
const [, setCopyPublicDashboardURL] = useCopyToClipboard(); const [, setCopyPublicDashboardURL] = useCopyToClipboard();
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard } = useDashboard();
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense(); const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
@@ -100,7 +99,7 @@ function PublicDashboardSetting(): JSX.Element {
console.error('Error getting public dashboard', errorPublicDashboard); console.error('Error getting public dashboard', errorPublicDashboard);
setPublicDashboardData(undefined); setPublicDashboardData(undefined);
setTimeRangeEnabled(true); setTimeRangeEnabled(true);
setDefaultTimeRange(DEFAULT_TIME_RANGE); setDefaultTimeRange('30m');
} }
}, [publicDashboardResponse, errorPublicDashboard]); }, [publicDashboardResponse, errorPublicDashboard]);
@@ -110,7 +109,7 @@ function PublicDashboardSetting(): JSX.Element {
publicDashboardResponse?.data?.timeRangeEnabled || false, publicDashboardResponse?.data?.timeRangeEnabled || false,
); );
setDefaultTimeRange( setDefaultTimeRange(
publicDashboardResponse?.data?.defaultTimeRange || DEFAULT_TIME_RANGE, publicDashboardResponse?.data?.defaultTimeRange || '30m',
); );
} }
}, [publicDashboardResponse]); }, [publicDashboardResponse]);

View File

@@ -3,14 +3,13 @@ import { memo, useCallback, useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Row } from 'antd'; import { Row } from 'antd';
import { ALL_SELECTED_VALUE } from 'components/NewSelect/utils'; import { ALL_SELECTED_VALUE } from 'components/NewSelect/utils';
import { updateLocalStorageDashboardVariable } from 'hooks/dashboard/useDashboardFromLocalStorage';
import { import {
useDashboardVariables, useDashboardVariables,
useDashboardVariablesSelector, useDashboardVariablesSelector,
} from 'hooks/dashboard/useDashboardVariables'; } from 'hooks/dashboard/useDashboardVariables';
import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl'; import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { updateDashboardVariablesStore } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore'; import { updateDashboardVariablesStore } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStore';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { import {
enqueueDescendantsOfVariable, enqueueDescendantsOfVariable,
enqueueFetchOfAllVariables, enqueueFetchOfAllVariables,
@@ -19,23 +18,23 @@ import {
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { useShallow } from 'zustand/react/shallow';
import VariableItem from './VariableItem'; import VariableItem from './VariableItem';
import './DashboardVariableSelection.styles.scss'; import './DashboardVariableSelection.styles.scss';
function DashboardVariableSelection(): JSX.Element | null { function DashboardVariableSelection(): JSX.Element | null {
const { dashboardId, setSelectedDashboard } = useDashboardStore( const {
useShallow((s) => ({ setSelectedDashboard,
dashboardId: s.selectedDashboard?.id ?? '', updateLocalStorageDashboardVariables,
setSelectedDashboard: s.setSelectedDashboard, } = useDashboard();
})),
);
const { updateUrlVariable } = useVariablesFromUrl(); const { updateUrlVariable } = useVariablesFromUrl();
const { dashboardVariables } = useDashboardVariables(); const { dashboardVariables } = useDashboardVariables();
const dashboardId = useDashboardVariablesSelector(
(state) => state.dashboardId,
);
const sortedVariablesArray = useDashboardVariablesSelector( const sortedVariablesArray = useDashboardVariablesSelector(
(state) => state.sortedVariablesArray, (state) => state.sortedVariablesArray,
); );
@@ -83,13 +82,7 @@ function DashboardVariableSelection(): JSX.Element | null {
// This makes localStorage much lighter by avoiding storing all individual values // This makes localStorage much lighter by avoiding storing all individual values
const variable = dashboardVariables[id] || dashboardVariables[name]; const variable = dashboardVariables[id] || dashboardVariables[name];
const isDynamic = variable.type === 'DYNAMIC'; const isDynamic = variable.type === 'DYNAMIC';
updateLocalStorageDashboardVariable( updateLocalStorageDashboardVariables(name, value, allSelected, isDynamic);
dashboardId,
name,
value,
allSelected,
isDynamic,
);
if (allSelected) { if (allSelected) {
updateUrlVariable(name || id, ALL_SELECTED_VALUE); updateUrlVariable(name || id, ALL_SELECTED_VALUE);
@@ -157,7 +150,13 @@ function DashboardVariableSelection(): JSX.Element | null {
// Safe to call synchronously now that the store already has the updated value. // Safe to call synchronously now that the store already has the updated value.
enqueueDescendantsOfVariable(name); enqueueDescendantsOfVariable(name);
}, },
[dashboardId, dashboardVariables, updateUrlVariable, setSelectedDashboard], [
dashboardId,
dashboardVariables,
updateLocalStorageDashboardVariables,
updateUrlVariable,
setSelectedDashboard,
],
); );
return ( return (

View File

@@ -32,22 +32,11 @@ const mockVariableItemCallbacks: {
// Mock providers/Dashboard/Dashboard // Mock providers/Dashboard/Dashboard
const mockSetSelectedDashboard = jest.fn(); const mockSetSelectedDashboard = jest.fn();
const mockUpdateLocalStorageDashboardVariables = jest.fn(); const mockUpdateLocalStorageDashboardVariables = jest.fn();
interface MockDashboardStoreState { jest.mock('providers/Dashboard/Dashboard', () => ({
selectedDashboard?: { id: string }; useDashboard: (): Record<string, unknown> => ({
setSelectedDashboard: typeof mockSetSelectedDashboard; setSelectedDashboard: mockSetSelectedDashboard,
updateLocalStorageDashboardVariables: typeof mockUpdateLocalStorageDashboardVariables; updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables,
} }),
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
useDashboardStore: (
selector?: (s: Record<string, unknown>) => MockDashboardStoreState,
): MockDashboardStoreState => {
const state = {
selectedDashboard: { id: 'dash-1' },
setSelectedDashboard: mockSetSelectedDashboard,
updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables,
};
return selector ? selector(state) : state;
},
})); }));
// Mock hooks/dashboard/useVariablesFromUrl // Mock hooks/dashboard/useVariablesFromUrl

View File

@@ -1,13 +1,11 @@
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels'; import { useAddDynamicVariableToPanels } from 'hooks/dashboard/useAddDynamicVariableToPanels';
import { updateLocalStorageDashboardVariable } from 'hooks/dashboard/useDashboardFromLocalStorage';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes'; import { IDashboardVariables } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useShallow } from 'zustand/react/shallow';
import { convertVariablesToDbFormat } from './util'; import { convertVariablesToDbFormat } from './util';
@@ -39,16 +37,11 @@ interface UseDashboardVariableUpdateReturn {
export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn => { export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn => {
const { const {
dashboardId,
selectedDashboard, selectedDashboard,
setSelectedDashboard, setSelectedDashboard,
} = useDashboardStore( updateLocalStorageDashboardVariables,
useShallow((s) => ({ } = useDashboard();
dashboardId: s.selectedDashboard?.id ?? '',
selectedDashboard: s.selectedDashboard,
setSelectedDashboard: s.setSelectedDashboard,
})),
);
const addDynamicVariableToPanels = useAddDynamicVariableToPanels(); const addDynamicVariableToPanels = useAddDynamicVariableToPanels();
const updateMutation = useUpdateDashboard(); const updateMutation = useUpdateDashboard();
@@ -66,13 +59,7 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
// This makes localStorage much lighter and more efficient. // This makes localStorage much lighter and more efficient.
// currently all the variables are dynamic // currently all the variables are dynamic
const isDynamic = true; const isDynamic = true;
updateLocalStorageDashboardVariable( updateLocalStorageDashboardVariables(name, value, allSelected, isDynamic);
dashboardId,
name,
value,
allSelected,
isDynamic,
);
if (selectedDashboard) { if (selectedDashboard) {
setSelectedDashboard((prev) => { setSelectedDashboard((prev) => {
@@ -110,7 +97,11 @@ export const useDashboardVariableUpdate = (): UseDashboardVariableUpdateReturn =
} }
} }
}, },
[dashboardId, selectedDashboard, setSelectedDashboard], [
selectedDashboard,
setSelectedDashboard,
updateLocalStorageDashboardVariables,
],
); );
const updateVariables = useCallback( const updateVariables = useCallback(

View File

@@ -49,8 +49,8 @@ const mockDashboard = {
// Mock the dashboard provider with stable functions to prevent infinite loops // Mock the dashboard provider with stable functions to prevent infinite loops
const mockSetSelectedDashboard = jest.fn(); const mockSetSelectedDashboard = jest.fn();
const mockUpdateLocalStorageDashboardVariables = jest.fn(); const mockUpdateLocalStorageDashboardVariables = jest.fn();
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({ jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: (): any => ({ useDashboard: (): any => ({
selectedDashboard: mockDashboard, selectedDashboard: mockDashboard,
setSelectedDashboard: mockSetSelectedDashboard, setSelectedDashboard: mockSetSelectedDashboard,
updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables, updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables,

View File

@@ -56,8 +56,8 @@ const mockDashboard = {
}, },
}; };
// Mock dependencies // Mock dependencies
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({ jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: (): any => ({ useDashboard: (): any => ({
selectedDashboard: mockDashboard, selectedDashboard: mockDashboard,
}), }),
})); }));
@@ -152,8 +152,8 @@ describe('Panel Management Tests', () => {
}; };
// Temporarily mock the dashboard // Temporarily mock the dashboard
jest.doMock('providers/Dashboard/store/useDashboardStore', () => ({ jest.doMock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: (): any => ({ useDashboard: (): any => ({
selectedDashboard: modifiedDashboard, selectedDashboard: modifiedDashboard,
}), }),
})); }));

View File

@@ -4,7 +4,7 @@ import ROUTES from 'constants/routes';
import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams'; import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams';
import { useSafeNavigate } from 'hooks/useSafeNavigate'; import { useSafeNavigate } from 'hooks/useSafeNavigate';
import { LayoutGrid } from 'lucide-react'; import { LayoutGrid } from 'lucide-react';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { DashboardData } from 'types/api/dashboard/getAll'; import { DashboardData } from 'types/api/dashboard/getAll';
import { Base64Icons } from '../../DashboardSettings/General/utils'; import { Base64Icons } from '../../DashboardSettings/General/utils';
@@ -13,7 +13,7 @@ import './DashboardBreadcrumbs.styles.scss';
function DashboardBreadcrumbs(): JSX.Element { function DashboardBreadcrumbs(): JSX.Element {
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard } = useDashboard();
const updatedAtRef = useRef(selectedDashboard?.updatedAt); const updatedAtRef = useRef(selectedDashboard?.updatedAt);
const selectedData = selectedDashboard const selectedData = selectedDashboard

View File

@@ -6,10 +6,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder'; import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { usePlotContext } from 'lib/uPlotV2/context/PlotContext'; import { usePlotContext } from 'lib/uPlotV2/context/PlotContext';
import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync'; import useLegendsSync from 'lib/uPlotV2/hooks/useLegendsSync';
import { import { useDashboard } from 'providers/Dashboard/Dashboard';
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { getChartManagerColumns } from './getChartMangerColumns'; import { getChartManagerColumns } from './getChartMangerColumns';
import { ExtendedChartDataset, getDefaultTableDataSet } from './utils'; import { ExtendedChartDataset, getDefaultTableDataSet } from './utils';
@@ -53,7 +50,7 @@ export default function ChartManager({
onToggleSeriesVisibility, onToggleSeriesVisibility,
syncSeriesVisibilityToLocalStorage, syncSeriesVisibilityToLocalStorage,
} = usePlotContext(); } = usePlotContext();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked); const { isDashboardLocked } = useDashboard();
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(() => const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(() =>
getDefaultTableDataSet( getDefaultTableDataSet(

View File

@@ -32,18 +32,10 @@ jest.mock('lib/uPlotV2/hooks/useLegendsSync', () => ({
}), }),
})); }));
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({ jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: ( useDashboard: (): { isDashboardLocked: boolean } => ({
selector?: (s: { isDashboardLocked: false,
selectedDashboard: { locked: boolean } | undefined; }),
}) => { selectedDashboard: { locked: boolean } },
): { selectedDashboard: { locked: boolean } } => {
const mockState = { selectedDashboard: { locked: false } };
return selector ? selector(mockState) : mockState;
},
selectIsDashboardLocked: (s: {
selectedDashboard: { locked: boolean } | undefined;
}): boolean => s.selectedDashboard?.locked ?? false,
})); }));
jest.mock('hooks/useNotifications', () => ({ jest.mock('hooks/useNotifications', () => ({

View File

@@ -16,8 +16,6 @@ import { isUndefined } from 'lodash-es';
import { urlKey } from 'pages/ErrorDetails/utils'; import { urlKey } from 'pages/ErrorDetails/utils';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService'; import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService';
import { isModifierKeyPressed } from 'utils/app';
import { openInNewTab } from 'utils/navigation';
import { keyToExclude } from './config'; import { keyToExclude } from './config';
import { DashedContainer, EditorContainer, EventContainer } from './styles'; import { DashedContainer, EditorContainer, EventContainer } from './styles';
@@ -113,19 +111,14 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
value: errorDetail[key as keyof GetByErrorTypeAndServicePayload], value: errorDetail[key as keyof GetByErrorTypeAndServicePayload],
})); }));
const onClickTraceHandler = (event: React.MouseEvent): void => { const onClickTraceHandler = (): void => {
logEvent('Exception: Navigate to trace detail page', { logEvent('Exception: Navigate to trace detail page', {
groupId: errorDetail?.groupID, groupId: errorDetail?.groupID,
spanId: errorDetail.spanID, spanId: errorDetail.spanID,
traceId: errorDetail.traceID, traceId: errorDetail.traceID,
exceptionId: errorDetail?.errorId, exceptionId: errorDetail?.errorId,
}); });
const path = `/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`; history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`);
if (isModifierKeyPressed(event)) {
openInNewTab(path);
} else {
history.push(path);
}
}; };
const logEventCalledRef = useRef(false); const logEventCalledRef = useRef(false);

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
@@ -44,7 +44,6 @@ import { QueryFunction } from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { isModifierKeyPressed } from 'utils/app';
import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope'; import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope';
import BasicInfo from './BasicInfo'; import BasicInfo from './BasicInfo';
@@ -331,18 +330,13 @@ function FormAlertRules({
} }
}, [alertDef, currentQuery?.queryType, queryOptions]); }, [alertDef, currentQuery?.queryType, queryOptions]);
const onCancelHandler = useCallback( const onCancelHandler = useCallback(() => {
(e?: React.MouseEvent) => { urlQuery.delete(QueryParams.compositeQuery);
urlQuery.delete(QueryParams.compositeQuery); urlQuery.delete(QueryParams.panelTypes);
urlQuery.delete(QueryParams.panelTypes); urlQuery.delete(QueryParams.ruleId);
urlQuery.delete(QueryParams.ruleId); urlQuery.delete(QueryParams.relativeTime);
urlQuery.delete(QueryParams.relativeTime); safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`, { }, [safeNavigate, urlQuery]);
newTab: !!e && isModifierKeyPressed(e),
});
},
[safeNavigate, urlQuery],
);
// onQueryCategoryChange handles changes to query category // onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults // in state as well as sets additional defaults

View File

@@ -464,10 +464,14 @@ function GeneralSettings({
onModalToggleHandler(type); onModalToggleHandler(type);
}; };
const { isCloudUser: isCloudUserVal } = useGetTenantLicense(); const {
isCloudUser: isCloudUserVal,
isEnterpriseSelfHostedUser,
} = useGetTenantLicense();
const isAdmin = user.role === USER_ROLES.ADMIN; const isAdmin = user.role === USER_ROLES.ADMIN;
const showCustomDomainSettings = isCloudUserVal && isAdmin; const showCustomDomainSettings =
(isCloudUserVal || isEnterpriseSelfHostedUser) && isAdmin;
const renderConfig = [ const renderConfig = [
{ {

View File

@@ -38,6 +38,7 @@ jest.mock('hooks/useComponentPermission', () => ({
jest.mock('hooks/useGetTenantLicense', () => ({ jest.mock('hooks/useGetTenantLicense', () => ({
useGetTenantLicense: jest.fn(() => ({ useGetTenantLicense: jest.fn(() => ({
isCloudUser: false, isCloudUser: false,
isEnterpriseSelfHostedUser: false,
})), })),
})); }));
@@ -388,6 +389,7 @@ describe('GeneralSettings - S3 Logs Retention', () => {
beforeEach(() => { beforeEach(() => {
(useGetTenantLicense as jest.Mock).mockReturnValue({ (useGetTenantLicense as jest.Mock).mockReturnValue({
isCloudUser: true, isCloudUser: true,
isEnterpriseSelfHostedUser: false,
}); });
}); });
@@ -409,14 +411,15 @@ describe('GeneralSettings - S3 Logs Retention', () => {
}); });
}); });
describe('Non-cloud user rendering', () => { describe('Enterprise Self-Hosted User Rendering', () => {
beforeEach(() => { beforeEach(() => {
(useGetTenantLicense as jest.Mock).mockReturnValue({ (useGetTenantLicense as jest.Mock).mockReturnValue({
isCloudUser: false, isCloudUser: false,
isEnterpriseSelfHostedUser: true,
}); });
}); });
it('should not render CustomDomainSettings or GeneralSettingsCloud', () => { it('should render CustomDomainSettings but not GeneralSettingsCloud', () => {
render( render(
<GeneralSettings <GeneralSettings
metricsTtlValuesPayload={mockMetricsRetention} metricsTtlValuesPayload={mockMetricsRetention}
@@ -429,14 +432,12 @@ describe('GeneralSettings - S3 Logs Retention', () => {
/>, />,
); );
expect( expect(screen.getByTestId('custom-domain-settings')).toBeInTheDocument();
screen.queryByTestId('custom-domain-settings'),
).not.toBeInTheDocument();
expect( expect(
screen.queryByTestId('general-settings-cloud'), screen.queryByTestId('general-settings-cloud'),
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
// Save buttons should be visible for non-cloud users (these are from retentions) // Save buttons should be visible for self-hosted
const saveButtons = screen.getAllByRole('button', { name: /save/i }); const saveButtons = screen.getAllByRole('button', { name: /save/i });
expect(saveButtons.length).toBeGreaterThan(0); expect(saveButtons.length).toBeGreaterThan(0);
}); });

View File

@@ -8,11 +8,8 @@ import { VariablesSettingsTab } from 'container/DashboardContainer/DashboardDesc
import DashboardSettings from 'container/DashboardContainer/DashboardSettings'; import DashboardSettings from 'container/DashboardContainer/DashboardSettings';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper'; import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
import {
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { ROLES, USER_ROLES } from 'types/roles'; import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission'; import { ComponentTypes } from 'utils/permission';
@@ -23,8 +20,7 @@ export default function DashboardEmptyState(): JSX.Element {
(s) => s.setIsPanelTypeSelectionModalOpen, (s) => s.setIsPanelTypeSelectionModalOpen,
); );
const { selectedDashboard } = useDashboardStore(); const { selectedDashboard, isDashboardLocked } = useDashboard();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked);
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null); const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>( const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>(

View File

@@ -3,10 +3,7 @@ import { Button, Input } from 'antd';
import type { CheckboxChangeEvent } from 'antd/es/checkbox'; import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { import { useDashboard } from 'providers/Dashboard/Dashboard';
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns'; import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
import { ExtendedChartDataset, GraphManagerProps } from './types'; import { ExtendedChartDataset, GraphManagerProps } from './types';
@@ -37,7 +34,7 @@ function GraphManager({
}, [data, options]); }, [data, options]);
const { notifications } = useNotifications(); const { notifications } = useNotifications();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked); const { isDashboardLocked } = useDashboard();
const checkBoxOnChangeHandler = useCallback( const checkBoxOnChangeHandler = useCallback(
(e: CheckboxChangeEvent, index: number): void => { (e: CheckboxChangeEvent, index: number): void => {

View File

@@ -1,13 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
import React, { import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly import { useSelector } from 'react-redux';
import { import {
LoadingOutlined, LoadingOutlined,
SearchOutlined, SearchOutlined,
@@ -45,14 +39,10 @@ import { getDashboardVariables } from 'lib/dashboardVariables/getDashboardVariab
import GetMinMax from 'lib/getMinMax'; import GetMinMax from 'lib/getMinMax';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { import { useDashboard } from 'providers/Dashboard/Dashboard';
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Warning } from 'types/api'; import { Warning } from 'types/api';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { isModifierKeyPressed } from 'utils/app';
import { getGraphType } from 'utils/getGraphType'; import { getGraphType } from 'utils/getGraphType';
import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getSortedSeriesData } from 'utils/getSortedSeriesData';
@@ -91,8 +81,11 @@ function FullView({
setCurrentGraphRef(fullViewRef); setCurrentGraphRef(fullViewRef);
}, [setCurrentGraphRef]); }, [setCurrentGraphRef]);
const { selectedDashboard, setColumnWidths } = useDashboardStore(); const {
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked); selectedDashboard,
isDashboardLocked,
setColumnWidths,
} = useDashboard();
const onColumnWidthsChange = useCallback( const onColumnWidthsChange = useCallback(
(widths: Record<string, number>) => { (widths: Record<string, number>) => {
@@ -297,11 +290,9 @@ function FullView({
<Button <Button
className="switch-edit-btn" className="switch-edit-btn"
disabled={response.isFetching || response.isLoading} disabled={response.isFetching || response.isLoading}
onClick={(e: React.MouseEvent): void => { onClick={(): void => {
if (dashboardEditView) { if (dashboardEditView) {
safeNavigate(dashboardEditView, { safeNavigate(dashboardEditView);
newTab: isModifierKeyPressed(e),
});
} }
}} }}
> >

View File

@@ -161,8 +161,8 @@ const mockProps: WidgetGraphComponentProps = {
}; };
// Mock useDashabord hook // Mock useDashabord hook
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({ jest.mock('providers/Dashboard/Dashboard', () => ({
useDashboardStore: (): any => ({ useDashboard: (): any => ({
selectedDashboard: { selectedDashboard: {
data: { data: {
variables: [], variables: [],

View File

@@ -28,7 +28,7 @@ import {
getCustomTimeRangeWindowSweepInMS, getCustomTimeRangeWindowSweepInMS,
getStartAndEndTimesInMilliseconds, getStartAndEndTimesInMilliseconds,
} from 'pages/MessagingQueues/MessagingQueuesUtils'; } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { Props } from 'types/api/dashboard/update'; import { Props } from 'types/api/dashboard/update';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
@@ -106,7 +106,7 @@ function WidgetGraphComponent({
selectedDashboard, selectedDashboard,
setSelectedDashboard, setSelectedDashboard,
setColumnWidths, setColumnWidths,
} = useDashboardStore(); } = useDashboard();
const onColumnWidthsChange = useCallback( const onColumnWidthsChange = useCallback(
(widths: Record<string, number>) => { (widths: Record<string, number>) => {

View File

@@ -1,65 +0,0 @@
import APIError from 'types/api/error';
import { errorDetails } from '../utils';
function makeAPIError(
message: string,
code = 'SOME_CODE',
errors: { message: string }[] = [],
): APIError {
return new APIError({
httpStatusCode: 500,
error: { code, message, url: '', errors },
});
}
describe('errorDetails', () => {
describe('when passed an APIError', () => {
it('returns the error message', () => {
const error = makeAPIError('something went wrong');
expect(errorDetails(error)).toBe('something went wrong');
});
it('appends details when errors array is non-empty', () => {
const error = makeAPIError('query failed', 'QUERY_ERROR', [
{ message: 'field X is invalid' },
{ message: 'field Y is missing' },
]);
const result = errorDetails(error);
expect(result).toContain('query failed');
expect(result).toContain('field X is invalid');
expect(result).toContain('field Y is missing');
});
it('does not append details when errors array is empty', () => {
const error = makeAPIError('simple error', 'CODE', []);
const result = errorDetails(error);
expect(result).toBe('simple error');
expect(result).not.toContain('Details');
});
});
describe('when passed a plain Error (not an APIError)', () => {
it('does not throw', () => {
const error = new Error('timeout exceeded');
expect(() => errorDetails(error)).not.toThrow();
});
it('returns the plain error message', () => {
const error = new Error('timeout exceeded');
expect(errorDetails(error)).toBe('timeout exceeded');
});
it('returns fallback when plain Error has no message', () => {
const error = new Error('');
expect(errorDetails(error)).toBe('Unknown error occurred');
});
});
describe('fallback behaviour', () => {
it('returns "Unknown error occurred" when message is undefined', () => {
const error = makeAPIError('');
expect(errorDetails(error)).toBe('Unknown error occurred');
});
});
});

View File

@@ -1,7 +1,6 @@
import { memo, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useEffect, useMemo, useRef, useState } from 'react';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import * as Sentry from '@sentry/react';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V5 } from 'constants/app'; import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
@@ -18,6 +17,7 @@ import { getVariableReferencesInQuery } from 'lib/dashboardVariables/variableRef
import getTimeString from 'lib/getTimeString'; import getTimeString from 'lib/getTimeString';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import isEmpty from 'lodash-es/isEmpty'; import isEmpty from 'lodash-es/isEmpty';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import APIError from 'types/api/error'; import APIError from 'types/api/error';
@@ -68,19 +68,7 @@ function GridCardGraph({
const [isInternalServerError, setIsInternalServerError] = useState<boolean>( const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
false, false,
); );
const queryRangeCalledRef = useRef(false); const { setDashboardQueryRangeCalled } = useDashboard();
useEffect(() => {
const timeoutId = setTimeout(() => {
if (!queryRangeCalledRef.current) {
Sentry.captureEvent({
message: `Dashboard query range not called within expected timeframe for widget ${widget?.id}`,
level: 'warning',
});
}
}, 120000);
return (): void => clearTimeout(timeoutId);
}, [widget?.id]);
const { const {
minTime, minTime,
@@ -272,14 +260,14 @@ function GridCardGraph({
}); });
} }
} }
queryRangeCalledRef.current = true; setDashboardQueryRangeCalled(true);
}, },
onSettled: (data) => { onSettled: (data) => {
dataAvailable?.( dataAvailable?.(
isDataAvailableByPanelType(data?.payload?.data, widget?.panelTypes), isDataAvailableByPanelType(data?.payload?.data, widget?.panelTypes),
); );
getGraphData?.(data?.payload?.data); getGraphData?.(data?.payload?.data);
queryRangeCalledRef.current = true; setDashboardQueryRangeCalled(true);
}, },
}, },
); );

View File

@@ -249,14 +249,13 @@ export const handleGraphClick = async ({
} }
}; };
export const errorDetails = (error: APIError | Error): string => { export const errorDetails = (error: APIError): string => {
const { message, errors } = const { message, errors } = error.getErrorDetails()?.error || {};
(error instanceof APIError ? error.getErrorDetails()?.error : null) || {};
const details = const details =
errors && errors.length > 0 errors?.length > 0
? `\n\nDetails: ${errors.map((e) => e.message).join('\n')}` ? `\n\nDetails: ${errors.map((e) => e.message).join('\n')}`
: ''; : '';
const errorDetails = `${message ?? error.message} ${details}`; const errorDetails = `${message} ${details}`;
return errorDetails.trim() || 'Unknown error occurred'; return errorDetails || 'Unknown error occurred';
}; };

View File

@@ -1,10 +1,10 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { FullScreen, FullScreenHandle } from 'react-full-screen'; import { FullScreen, FullScreenHandle } from 'react-full-screen';
import { ItemCallback, Layout } from 'react-grid-layout'; import { ItemCallback, Layout } from 'react-grid-layout';
import { useIsFetching } from 'react-query';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import * as Sentry from '@sentry/react';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Form, Input, Modal, Typography } from 'antd'; import { Button, Form, Input, Modal, Typography } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
@@ -12,7 +12,6 @@ import cx from 'classnames';
import { ENTITY_VERSION_V5 } from 'constants/app'; import { ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
import { DEFAULT_ROW_NAME } from 'container/DashboardContainer/DashboardDescription/utils'; import { DEFAULT_ROW_NAME } from 'container/DashboardContainer/DashboardDescription/utils';
import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables'; import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
@@ -32,10 +31,7 @@ import {
X, X,
} from 'lucide-react'; } from 'lucide-react';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { import { useDashboard } from 'providers/Dashboard/Dashboard';
selectIsDashboardLocked,
useDashboardStore,
} from 'providers/Dashboard/store/useDashboardStore';
import { sortLayout } from 'providers/Dashboard/util'; import { sortLayout } from 'providers/Dashboard/util';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
@@ -48,7 +44,6 @@ import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState';
import GridCard from './GridCard'; import GridCard from './GridCard';
import { Card, CardContainer, ReactGridLayout } from './styles'; import { Card, CardContainer, ReactGridLayout } from './styles';
import { import {
applyRowCollapse,
hasColumnWidthsChanged, hasColumnWidthsChanged,
removeUndefinedValuesFromLayout, removeUndefinedValuesFromLayout,
} from './utils'; } from './utils';
@@ -66,9 +61,6 @@ interface GraphLayoutProps {
function GraphLayout(props: GraphLayoutProps): JSX.Element { function GraphLayout(props: GraphLayoutProps): JSX.Element {
const { handle, enableDrillDown = false } = props; const { handle, enableDrillDown = false } = props;
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const isDashboardFetching =
useIsFetching([REACT_QUERY_KEY.DASHBOARD_BY_ID]) > 0;
const { const {
selectedDashboard, selectedDashboard,
layouts, layouts,
@@ -76,9 +68,12 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
panelMap, panelMap,
setPanelMap, setPanelMap,
setSelectedDashboard, setSelectedDashboard,
isDashboardLocked,
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
isDashboardFetching,
columnWidths, columnWidths,
} = useDashboardStore(); } = useDashboard();
const isDashboardLocked = useDashboardStore(selectIsDashboardLocked);
const { data } = selectedDashboard || {}; const { data } = selectedDashboard || {};
const { pathname } = useLocation(); const { pathname } = useLocation();
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -142,6 +137,25 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
setDashboardLayout(sortLayout(layouts)); setDashboardLayout(sortLayout(layouts));
}, [layouts]); }, [layouts]);
useEffect(() => {
setDashboardQueryRangeCalled(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const timeoutId = setTimeout(() => {
// Send Sentry event if query_range is not called within expected timeframe (2 mins) when there are widgets
if (!dashboardQueryRangeCalled && data?.widgets?.length) {
Sentry.captureEvent({
message: `Dashboard query range not called within expected timeframe even when there are ${data?.widgets?.length} widgets`,
level: 'warning',
});
}
}, 120000);
return (): void => clearTimeout(timeoutId);
}, [dashboardQueryRangeCalled, data?.widgets?.length]);
const logEventCalledRef = useRef(false); const logEventCalledRef = useRef(false);
useEffect(() => { useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data)) { if (!logEventCalledRef.current && !isUndefined(data)) {
@@ -269,10 +283,13 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
return; return;
} }
const updatedWidgets = selectedDashboard?.data?.widgets?.map((e) => currentWidget.title = newTitle;
e.id === currentSelectRowId ? { ...e, title: newTitle } : e, const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
(e) => e.id !== currentSelectRowId,
); );
updatedWidgets?.push(currentWidget);
const updatedSelectedDashboard: Props = { const updatedSelectedDashboard: Props = {
id: selectedDashboard.id, id: selectedDashboard.id,
data: { data: {
@@ -314,13 +331,88 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
if (!selectedDashboard) { if (!selectedDashboard) {
return; return;
} }
const { updatedLayout, updatedPanelMap } = applyRowCollapse( const rowProperties = { ...currentPanelMap[id] };
id, const updatedPanelMap = { ...currentPanelMap };
dashboardLayout,
currentPanelMap, let updatedDashboardLayout = [...dashboardLayout];
); if (rowProperties.collapsed === true) {
setCurrentPanelMap((prev) => ({ ...prev, ...updatedPanelMap })); rowProperties.collapsed = false;
setDashboardLayout(sortLayout(updatedLayout)); const widgetsInsideTheRow = rowProperties.widgets;
let maxY = 0;
widgetsInsideTheRow.forEach((w) => {
maxY = Math.max(maxY, w.y + w.h);
});
const currentRowWidget = dashboardLayout.find((w) => w.i === id);
if (currentRowWidget && widgetsInsideTheRow.length) {
maxY -= currentRowWidget.h + currentRowWidget.y;
}
const idxCurrentRow = dashboardLayout.findIndex((w) => w.i === id);
for (let j = idxCurrentRow + 1; j < dashboardLayout.length; j++) {
updatedDashboardLayout[j].y += maxY;
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
updatedDashboardLayout[j].i
].widgets.map((w) => ({
...w,
y: w.y + maxY,
}));
}
}
updatedDashboardLayout = [...updatedDashboardLayout, ...widgetsInsideTheRow];
} else {
rowProperties.collapsed = true;
const currentIdx = dashboardLayout.findIndex((w) => w.i === id);
let widgetsInsideTheRow: Layout[] = [];
let isPanelMapUpdated = false;
for (let j = currentIdx + 1; j < dashboardLayout.length; j++) {
if (currentPanelMap[dashboardLayout[j].i]) {
rowProperties.widgets = widgetsInsideTheRow;
widgetsInsideTheRow = [];
isPanelMapUpdated = true;
break;
} else {
widgetsInsideTheRow.push(dashboardLayout[j]);
}
}
if (!isPanelMapUpdated) {
rowProperties.widgets = widgetsInsideTheRow;
}
let maxY = 0;
widgetsInsideTheRow.forEach((w) => {
maxY = Math.max(maxY, w.y + w.h);
});
const currentRowWidget = dashboardLayout[currentIdx];
if (currentRowWidget && widgetsInsideTheRow.length) {
maxY -= currentRowWidget.h + currentRowWidget.y;
}
for (let j = currentIdx + 1; j < updatedDashboardLayout.length; j++) {
updatedDashboardLayout[j].y += maxY;
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
updatedDashboardLayout[j].i
].widgets.map((w) => ({
...w,
y: w.y + maxY,
}));
}
}
updatedDashboardLayout = updatedDashboardLayout.filter(
(widget) => !rowProperties.widgets.some((w: Layout) => w.i === widget.i),
);
}
setCurrentPanelMap((prev) => ({
...prev,
...updatedPanelMap,
[id]: {
...rowProperties,
},
}));
setDashboardLayout(sortLayout(updatedDashboardLayout));
}; };
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => { const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {

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