mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-03 19:00:27 +01:00
Compare commits
74 Commits
refactor/c
...
fix/array-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b30bfa6371 | ||
|
|
e7f4a04b36 | ||
|
|
0687634da3 | ||
|
|
7e7732243e | ||
|
|
bad80399a6 | ||
|
|
2f952e402f | ||
|
|
a12febca4a | ||
|
|
e2cd203c8f | ||
|
|
a4c6394542 | ||
|
|
71a13b4818 | ||
|
|
a8e2155bb6 | ||
|
|
a9cbf9a4df | ||
|
|
cb71c9c3f7 | ||
|
|
1cd4ce6509 | ||
|
|
2163e1ce41 | ||
|
|
b9eecacab7 | ||
|
|
13982033dc | ||
|
|
b198cfc11a | ||
|
|
7b6f77bd52 | ||
|
|
e588c57e44 | ||
|
|
98f53423dc | ||
|
|
e41d400aa0 | ||
|
|
d19592ce7b | ||
|
|
1f43feaf3c | ||
|
|
37d202de92 | ||
|
|
87e5ef2f0a | ||
|
|
b151bcd697 | ||
|
|
bb4e7df68b | ||
|
|
9299c8ab18 | ||
|
|
24749de269 | ||
|
|
39098ec3f4 | ||
|
|
fe554f5c94 | ||
|
|
8a60a041a6 | ||
|
|
198b54252d | ||
|
|
541f19c34a | ||
|
|
010db03d6e | ||
|
|
5408acbd8c | ||
|
|
e4f0d026c1 | ||
|
|
0de6c85f81 | ||
|
|
754dbc7f38 | ||
|
|
07dbf1e69f | ||
|
|
abe5454d11 | ||
|
|
749884ce70 | ||
|
|
70fdc88112 | ||
|
|
234787f2c1 | ||
|
|
e7d0dd8850 | ||
|
|
c23c6c8c27 | ||
|
|
58dabf9a6c | ||
|
|
91edc2a895 | ||
|
|
69ec24fa05 | ||
|
|
2e70857554 | ||
|
|
539d732b65 | ||
|
|
843d5fb199 | ||
|
|
fabdfb8cc1 | ||
|
|
f3e6892d5b | ||
|
|
23a4960e74 | ||
|
|
5d0c55d682 | ||
|
|
15704e0433 | ||
|
|
5db0501c02 | ||
|
|
73da474563 | ||
|
|
028c134ea9 | ||
|
|
31b61a89fd | ||
|
|
8609f43fe0 | ||
|
|
658f794842 | ||
|
|
e9abd5ddfc | ||
|
|
ea2663b145 | ||
|
|
234716df53 | ||
|
|
531979543c | ||
|
|
4b09f057b9 | ||
|
|
dde7c79b4d | ||
|
|
c95523c747 | ||
|
|
63cb54c5b0 | ||
|
|
19e8196472 | ||
|
|
c360e4498d |
@@ -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,13 +47,16 @@ 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:
|
||||||
@@ -74,12 +77,19 @@ 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
|
||||||
|
|||||||
@@ -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,4 +34,11 @@ 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
|
||||||
|
|||||||
@@ -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,7 +29,26 @@ processors:
|
|||||||
signozspanmetrics/delta:
|
signozspanmetrics/delta:
|
||||||
metrics_exporter: signozclickhousemetrics
|
metrics_exporter: signozclickhousemetrics
|
||||||
metrics_flush_interval: 60s
|
metrics_flush_interval: 60s
|
||||||
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
latency_histogram_buckets:
|
||||||
|
[
|
||||||
|
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
|
||||||
@@ -60,13 +79,13 @@ extensions:
|
|||||||
|
|
||||||
exporters:
|
exporters:
|
||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://host.docker.internal:9000/signoz_traces
|
datasource: tcp://clickhouse: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://host.docker.internal:9000/signoz_metrics
|
dsn: tcp://clickhouse:9000/signoz_metrics
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://host.docker.internal:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/signoz_logs
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
use_new_schema: true
|
use_new_schema: true
|
||||||
|
|
||||||
@@ -93,4 +112,4 @@ service:
|
|||||||
logs:
|
logs:
|
||||||
receivers: [otlp]
|
receivers: [otlp]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [clickhouselogsexporter]
|
exporters: [clickhouselogsexporter]
|
||||||
|
|||||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -86,6 +86,8 @@ 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
|
||||||
|
|
||||||
@@ -105,7 +107,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
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/integrationci.yaml
vendored
1
.github/workflows/integrationci.yaml
vendored
@@ -51,6 +51,7 @@ jobs:
|
|||||||
- alerts
|
- alerts
|
||||||
- ingestionkeys
|
- ingestionkeys
|
||||||
- rootuser
|
- rootuser
|
||||||
|
- serviceaccount
|
||||||
sqlstore-provider:
|
sqlstore-provider:
|
||||||
- postgres
|
- postgres
|
||||||
- sqlite
|
- sqlite
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ 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
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -17,5 +17,7 @@
|
|||||||
},
|
},
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "vscode.html-language-features"
|
"editor.defaultFormatter": "vscode.html-language-features"
|
||||||
}
|
},
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:system",
|
||||||
|
"python-envs.pythonProjects": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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"
|
||||||
@@ -78,8 +79,13 @@ 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] {
|
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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"
|
||||||
@@ -118,8 +119,13 @@ 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] {
|
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), licensing, dashboardModule)
|
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -85,10 +85,12 @@ sqlstore:
|
|||||||
sqlite:
|
sqlite:
|
||||||
# The path to the SQLite database file.
|
# The path to the SQLite database file.
|
||||||
path: /var/lib/signoz/signoz.db
|
path: /var/lib/signoz/signoz.db
|
||||||
# Mode is the mode to use for the sqlite database.
|
# The journal mode for the sqlite database. Supported values: delete, wal.
|
||||||
mode: delete
|
mode: delete
|
||||||
# BusyTimeout is the timeout for the sqlite database to wait for a lock.
|
# The timeout for the sqlite database to wait for a lock.
|
||||||
busy_timeout: 10s
|
busy_timeout: 10s
|
||||||
|
# The default transaction locking behavior. Supported values: deferred, immediate, exclusive.
|
||||||
|
transaction_mode: deferred
|
||||||
|
|
||||||
##################### APIServer #####################
|
##################### APIServer #####################
|
||||||
apiserver:
|
apiserver:
|
||||||
@@ -144,6 +146,8 @@ 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
|
||||||
@@ -350,3 +354,13 @@ identn:
|
|||||||
impersonation:
|
impersonation:
|
||||||
# toggle impersonation identN, when enabled, all requests will impersonate the root user
|
# toggle impersonation identN, when enabled, all requests will impersonate the root user
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
##################### Service Account #####################
|
||||||
|
serviceaccount:
|
||||||
|
email:
|
||||||
|
# email domain for the service account principal
|
||||||
|
domain: signozserviceaccount.com
|
||||||
|
|
||||||
|
analytics:
|
||||||
|
# toggle service account analytics
|
||||||
|
enabled: true
|
||||||
|
|||||||
@@ -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.116.1
|
image: signoz/signoz:v0.117.1
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # signoz port
|
- "8080:8080" # signoz port
|
||||||
# - "6060:6060" # pprof port
|
# - "6060:6060" # pprof port
|
||||||
|
|||||||
@@ -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.116.1
|
image: signoz/signoz:v0.117.1
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # signoz port
|
- "8080:8080" # signoz port
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -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.116.1}
|
image: signoz/signoz:${VERSION:-v0.117.1}
|
||||||
container_name: signoz
|
container_name: signoz
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # signoz port
|
- "8080:8080" # signoz port
|
||||||
|
|||||||
@@ -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.116.1}
|
image: signoz/signoz:${VERSION:-v0.117.1}
|
||||||
container_name: signoz
|
container_name: signoz
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # signoz port
|
- "8080:8080" # signoz port
|
||||||
|
|||||||
3061
docs/api/openapi.yml
3061
docs/api/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -123,6 +123,7 @@ 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,
|
||||||
@@ -155,6 +156,8 @@ 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.
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ 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
|
||||||
|
|||||||
@@ -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{},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,13 +155,14 @@ 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 {
|
||||||
sum += math.Pow(smpl.Value-avg, 2)
|
d := smpl.Value - avg
|
||||||
|
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
|
||||||
@@ -236,7 +237,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,
|
||||||
@@ -269,7 +270,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 {
|
||||||
@@ -283,7 +284,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 {
|
||||||
@@ -296,7 +297,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 {
|
||||||
|
|||||||
143
ee/auditor/otlphttpauditor/export.go
Normal file
143
ee/auditor/otlphttpauditor/export.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package otlphttpauditor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/auditor"
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||||
|
collogspb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxHTTPResponseReadBytes int64 = 64 * 1024
|
||||||
|
protobufContentType string = "application/x-protobuf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (provider *provider) export(ctx context.Context, events []audittypes.AuditEvent) error {
|
||||||
|
logs := audittypes.NewPLogsFromAuditEvents(events, "signoz", provider.build.Version(), "signoz.audit")
|
||||||
|
|
||||||
|
request, err := provider.marshaler.MarshalLogs(logs)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "failed to marshal audit logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := provider.send(ctx, request); err != nil {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "audit export failed", errors.Attr(err), slog.Int("dropped_log_records", len(events)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts a protobuf-encoded OTLP request to the configured endpoint.
|
||||||
|
// Retries are handled by the underlying heimdall HTTP client.
|
||||||
|
// Ref: https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/otlphttpexporter/otlp.go
|
||||||
|
func (provider *provider) send(ctx context.Context, body []byte) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, provider.config.OTLPHTTP.Endpoint.String(), bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", protobufContentType)
|
||||||
|
|
||||||
|
res, err := provider.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_, _ = io.CopyN(io.Discard, res.Body, maxHTTPResponseReadBytes)
|
||||||
|
res.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if res.StatusCode >= 200 && res.StatusCode <= 299 {
|
||||||
|
provider.onSuccess(ctx, res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.onErr(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref: https://github.com/open-telemetry/opentelemetry-collector/blob/01b07fcbb7a253bd996c290dcae6166e71d13732/exporter/otlphttpexporter/otlp.go#L403.
|
||||||
|
func (provider *provider) onSuccess(ctx context.Context, res *http.Response) {
|
||||||
|
resBytes, err := readResponseBody(res)
|
||||||
|
if err != nil || resBytes == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exportResponse := &collogspb.ExportLogsServiceResponse{}
|
||||||
|
if err := proto.Unmarshal(resBytes, exportResponse); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ps := exportResponse.GetPartialSuccess()
|
||||||
|
if ps == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ps.GetErrorMessage() != "" || ps.GetRejectedLogRecords() != 0 {
|
||||||
|
provider.settings.Logger().WarnContext(ctx, "partial success response", slog.String("message", ps.GetErrorMessage()), slog.Int64("dropped_log_records", ps.GetRejectedLogRecords()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) onErr(res *http.Response) error {
|
||||||
|
status := readResponseStatus(res)
|
||||||
|
|
||||||
|
if status != nil {
|
||||||
|
return errors.Newf(errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "request to %s responded with status code %d, Message=%s, Details=%v", provider.config.OTLPHTTP.Endpoint.String(), res.StatusCode, status.Message, status.Details)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Newf(errors.TypeInternal, auditor.ErrCodeAuditExportFailed, "request to %s responded with status code %d", provider.config.OTLPHTTP.Endpoint.String(), res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads at most maxHTTPResponseReadBytes from the response body.
|
||||||
|
// Ref: https://github.com/open-telemetry/opentelemetry-collector/blob/01b07fcbb7a253bd996c290dcae6166e71d13732/exporter/otlphttpexporter/otlp.go#L275.
|
||||||
|
func readResponseBody(resp *http.Response) ([]byte, error) {
|
||||||
|
if resp.ContentLength == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
maxRead := resp.ContentLength
|
||||||
|
if maxRead == -1 || maxRead > maxHTTPResponseReadBytes {
|
||||||
|
maxRead = maxHTTPResponseReadBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
protoBytes := make([]byte, maxRead)
|
||||||
|
n, err := io.ReadFull(resp.Body, protoBytes)
|
||||||
|
if n == 0 && (err == nil || errors.Is(err, io.EOF)) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return protoBytes[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes a protobuf-encoded Status from 4xx/5xx response bodies. Returns nil if the response is empty or cannot be decoded.
|
||||||
|
// Ref: https://github.com/open-telemetry/opentelemetry-collector/blob/01b07fcbb7a253bd996c290dcae6166e71d13732/exporter/otlphttpexporter/otlp.go#L310.
|
||||||
|
func readResponseStatus(resp *http.Response) *spb.Status {
|
||||||
|
if resp.StatusCode < 400 || resp.StatusCode > 599 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := readResponseBody(resp)
|
||||||
|
if err != nil || respBytes == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
respStatus := &spb.Status{}
|
||||||
|
if err := proto.Unmarshal(respBytes, respStatus); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return respStatus
|
||||||
|
}
|
||||||
97
ee/auditor/otlphttpauditor/provider.go
Normal file
97
ee/auditor/otlphttpauditor/provider.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package otlphttpauditor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/auditor"
|
||||||
|
"github.com/SigNoz/signoz/pkg/auditor/auditorserver"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
client "github.com/SigNoz/signoz/pkg/http/client"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/audittypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/version"
|
||||||
|
"go.opentelemetry.io/collector/pdata/plog"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ auditor.Auditor = (*provider)(nil)
|
||||||
|
|
||||||
|
type provider struct {
|
||||||
|
settings factory.ScopedProviderSettings
|
||||||
|
config auditor.Config
|
||||||
|
licensing licensing.Licensing
|
||||||
|
build version.Build
|
||||||
|
server *auditorserver.Server
|
||||||
|
marshaler plog.ProtoMarshaler
|
||||||
|
httpClient *client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory(licensing licensing.Licensing, build version.Build) factory.ProviderFactory[auditor.Auditor, auditor.Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("otlphttp"), func(ctx context.Context, providerSettings factory.ProviderSettings, config auditor.Config) (auditor.Auditor, error) {
|
||||||
|
return newProvider(ctx, providerSettings, config, licensing, build)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProvider(_ context.Context, providerSettings factory.ProviderSettings, config auditor.Config, licensing licensing.Licensing, build version.Build) (auditor.Auditor, error) {
|
||||||
|
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/auditor/otlphttpauditor")
|
||||||
|
|
||||||
|
httpClient, err := client.New(
|
||||||
|
settings.Logger(),
|
||||||
|
providerSettings.TracerProvider,
|
||||||
|
providerSettings.MeterProvider,
|
||||||
|
client.WithTimeout(config.OTLPHTTP.Timeout),
|
||||||
|
client.WithRetryCount(retryCountFromConfig(config.OTLPHTTP.Retry)),
|
||||||
|
retrierOption(config.OTLPHTTP.Retry),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &provider{
|
||||||
|
settings: settings,
|
||||||
|
config: config,
|
||||||
|
licensing: licensing,
|
||||||
|
build: build,
|
||||||
|
marshaler: plog.ProtoMarshaler{},
|
||||||
|
httpClient: httpClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := auditorserver.New(settings,
|
||||||
|
auditorserver.Config{
|
||||||
|
BufferSize: config.BufferSize,
|
||||||
|
BatchSize: config.BatchSize,
|
||||||
|
FlushInterval: config.FlushInterval,
|
||||||
|
},
|
||||||
|
provider.export,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.server = server
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Start(ctx context.Context) error {
|
||||||
|
return provider.server.Start(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Audit(ctx context.Context, event audittypes.AuditEvent) {
|
||||||
|
if event.PrincipalOrgID.IsZero() {
|
||||||
|
provider.settings.Logger().WarnContext(ctx, "audit event dropped as org_id is zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := provider.licensing.GetActive(ctx, event.PrincipalOrgID); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.server.Add(ctx, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Healthy() <-chan struct{} {
|
||||||
|
return provider.server.Healthy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Stop(ctx context.Context) error {
|
||||||
|
return provider.server.Stop(ctx)
|
||||||
|
}
|
||||||
52
ee/auditor/otlphttpauditor/retrier.go
Normal file
52
ee/auditor/otlphttpauditor/retrier.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package otlphttpauditor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/auditor"
|
||||||
|
client "github.com/SigNoz/signoz/pkg/http/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// retrier implements client.Retriable with exponential backoff
|
||||||
|
// derived from auditor.RetryConfig.
|
||||||
|
type retrier struct {
|
||||||
|
initialInterval time.Duration
|
||||||
|
maxInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRetrier(cfg auditor.RetryConfig) *retrier {
|
||||||
|
return &retrier{
|
||||||
|
initialInterval: cfg.InitialInterval,
|
||||||
|
maxInterval: cfg.MaxInterval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextInterval returns the backoff duration for the given retry attempt.
|
||||||
|
// Uses exponential backoff: initialInterval * 2^retry, capped at maxInterval.
|
||||||
|
func (r *retrier) NextInterval(retry int) time.Duration {
|
||||||
|
interval := r.initialInterval
|
||||||
|
for range retry {
|
||||||
|
interval *= 2
|
||||||
|
}
|
||||||
|
return min(interval, r.maxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrierOption(cfg auditor.RetryConfig) client.Option {
|
||||||
|
return client.WithRetriable(newRetrier(cfg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func retryCountFromConfig(cfg auditor.RetryConfig) int {
|
||||||
|
if !cfg.Enabled || cfg.MaxElapsedTime <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
elapsed := time.Duration(0)
|
||||||
|
interval := cfg.InitialInterval
|
||||||
|
for elapsed < cfg.MaxElapsedTime {
|
||||||
|
elapsed += interval
|
||||||
|
interval = min(interval*2, cfg.MaxInterval)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ 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 {
|
||||||
@@ -26,14 +27,14 @@ type provider struct {
|
|||||||
registry []authz.RegisterTypeable
|
registry []authz.RegisterTypeable
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, 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, licensing, registry)
|
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, registry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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) {
|
||||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema)
|
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore)
|
||||||
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
|
||||||
@@ -57,6 +58,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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
|
||||||
@@ -26,14 +25,31 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||||
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
subject := ""
|
||||||
if err != nil {
|
switch claims.Principal {
|
||||||
return err
|
case authtypes.PrincipalUser:
|
||||||
|
user, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = user
|
||||||
|
case authtypes.PrincipalServiceAccount:
|
||||||
|
serviceAccount, err := authtypes.NewSubject(authtypes.TypeableServiceAccount, claims.ServiceAccountID, orgID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = serviceAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||||
|
|||||||
32
ee/authz/openfgaserver/sqlstore.go
Normal file
32
ee/authz/openfgaserver/sqlstore.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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())
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ var (
|
|||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// initializes the licensing configuration
|
// Config 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}
|
||||||
|
|||||||
@@ -213,8 +213,8 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
|
|||||||
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
|
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
|
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
|
||||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
|
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||||
|
|||||||
@@ -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.Duration.Seconds())
|
stepIntervals[anomalyQuery.Name] = uint64(anomalyQuery.StepInterval.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
finalResp := &qbtypes.QueryRangeResponse{
|
finalResp := &qbtypes.QueryRangeResponse{
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -50,7 +49,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
|
apiKey, apiErr := ah.getOrCreateCloudIntegrationFactorAPIKey(r.Context(), valuer.MustNewUUID(claims.OrgID), cloudProvider)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
RespondError(w, basemodel.WrapApiError(
|
RespondError(w, basemodel.WrapApiError(
|
||||||
apiErr, "couldn't provision PAT for cloud integration:",
|
apiErr, "couldn't provision PAT for cloud integration:",
|
||||||
@@ -110,84 +109,44 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
ah.Respond(w, result)
|
ah.Respond(w, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
|
func (ah *APIHandler) getOrCreateCloudIntegrationFactorAPIKey(ctx context.Context, orgID valuer.UUID, cloudProvider string) (
|
||||||
string, *basemodel.ApiError,
|
string, *basemodel.ApiError,
|
||||||
) {
|
) {
|
||||||
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
|
integrationPATName := fmt.Sprintf("%s", cloudProvider)
|
||||||
|
serviceAccount, apiErr := ah.getOrCreateCloudIntegrationServiceAccount(ctx, orgID)
|
||||||
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
|
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
return "", apiErr
|
return "", apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
orgIdUUID, err := valuer.NewUUID(orgId)
|
factorAPIKey, err := serviceAccount.NewFactorAPIKey(integrationPATName, 0)
|
||||||
if err != nil {
|
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
|
||||||
"couldn't parse orgId: %w", err,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
allPats, err := ah.Signoz.Modules.UserSetter.ListAPIKeys(ctx, orgIdUUID)
|
|
||||||
if err != nil {
|
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
|
||||||
"couldn't list PATs: %w", err,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
for _, p := range allPats {
|
|
||||||
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
|
|
||||||
return p.Token, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.InfoContext(ctx, "no PAT found for cloud integration, creating a new one",
|
|
||||||
"cloud_provider", cloudProvider,
|
|
||||||
)
|
|
||||||
|
|
||||||
newPAT, err := types.NewStorableAPIKey(
|
|
||||||
integrationPATName,
|
|
||||||
integrationUser.ID,
|
|
||||||
types.RoleViewer,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", basemodel.InternalError(fmt.Errorf(
|
||||||
"couldn't create cloud integration PAT: %w", err,
|
"couldn't create cloud integration PAT: %w", err,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ah.Signoz.Modules.UserSetter.CreateAPIKey(ctx, newPAT)
|
factorAPIKey, err = ah.Signoz.Modules.ServiceAccount.GetOrCreateFactorAPIKey(ctx, factorAPIKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", basemodel.InternalError(fmt.Errorf(
|
return "", basemodel.InternalError(fmt.Errorf(
|
||||||
"couldn't create cloud integration PAT: %w", err,
|
"couldn't create cloud integration PAT: %w", err,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return newPAT.Token, nil
|
return factorAPIKey.Key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
func (ah *APIHandler) getOrCreateCloudIntegrationServiceAccount(ctx context.Context, orgId valuer.UUID) (*serviceaccounttypes.ServiceAccount, *basemodel.ApiError) {
|
||||||
ctx context.Context, orgId string, cloudProvider string,
|
domain := ah.Signoz.Modules.ServiceAccount.Config().Email.Domain
|
||||||
) (*types.User, *basemodel.ApiError) {
|
cloudIntegrationServiceAccount := serviceaccounttypes.NewServiceAccount("integration", domain, serviceaccounttypes.ServiceAccountStatusActive, orgId)
|
||||||
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
|
cloudIntegrationServiceAccount, err := ah.Signoz.Modules.ServiceAccount.GetOrCreate(ctx, orgId, cloudIntegrationServiceAccount)
|
||||||
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
|
|
||||||
|
|
||||||
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, valuer.MustNewUUID(orgId), types.UserStatusActive)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
|
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||||
|
}
|
||||||
|
err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
|
return cloudIntegrationServiceAccount, nil
|
||||||
|
|
||||||
cloudIntegrationUser, err = ah.Signoz.Modules.UserSetter.GetOrCreateUser(
|
|
||||||
ctx,
|
|
||||||
cloudIntegrationUser,
|
|
||||||
user.WithFactorPassword(password),
|
|
||||||
user.WithRoleNames([]string{authtypes.SigNozViewerRoleName}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't look for integration user: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloudIntegrationUser, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
|
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ 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"
|
||||||
@@ -106,6 +107,7 @@ 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,
|
||||||
@@ -136,6 +138,7 @@ 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
|
||||||
@@ -239,7 +242,6 @@ 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)
|
||||||
@@ -343,28 +345,29 @@ 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, 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, ruleStateHistoryModule rulestatehistory.Module, 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
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ 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(
|
||||||
@@ -41,6 +42,7 @@ 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 {
|
||||||
@@ -65,6 +67,7 @@ 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 {
|
||||||
@@ -90,6 +93,7 @@ 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
|
||||||
|
|||||||
@@ -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{}, store)
|
promProvider = prometheustest.New(context.Background(), instrumentationtest.New().ToProviderSettings(), prometheus.Config{Timeout: 2 * time.Minute}, store)
|
||||||
},
|
},
|
||||||
ManagerOptionsHook: func(opts *rules.ManagerOptions) {
|
ManagerOptionsHook: func(opts *rules.ManagerOptions) {
|
||||||
// Set Prometheus provider for PromQL queries
|
// Set Prometheus provider for PromQL queries
|
||||||
|
|||||||
@@ -336,9 +336,10 @@ func (dialect *dialect) UpdatePrimaryKey(ctx context.Context, bun bun.IDB, oldMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
fkReference := ""
|
fkReference := ""
|
||||||
if reference == Org {
|
switch reference {
|
||||||
|
case Org:
|
||||||
fkReference = OrgReference
|
fkReference = OrgReference
|
||||||
} else if reference == User {
|
case User:
|
||||||
fkReference = UserReference
|
fkReference = UserReference
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,9 +393,10 @@ func (dialect *dialect) AddPrimaryKey(ctx context.Context, bun bun.IDB, oldModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
fkReference := ""
|
fkReference := ""
|
||||||
if reference == Org {
|
switch reference {
|
||||||
|
case Org:
|
||||||
fkReference = OrgReference
|
fkReference = OrgReference
|
||||||
} else if reference == User {
|
case User:
|
||||||
fkReference = UserReference
|
fkReference = UserReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,14 +14,21 @@ 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))
|
||||||
@@ -62,6 +69,7 @@ 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
|
||||||
@@ -75,6 +83,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -205,6 +205,25 @@ 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}',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
or
|
or
|
||||||
`docker build . -t tagname`
|
`docker build . -t tagname`
|
||||||
|
|
||||||
**Tag to remote url- Introduce versinoing later on**
|
**Tag to remote url- Introduce versioning later on**
|
||||||
|
|
||||||
```
|
```
|
||||||
docker tag signoz/frontend:latest 7296823551/signoz:latest
|
docker tag signoz/frontend:latest 7296823551/signoz:latest
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
interface SafeNavigateOptions {
|
interface SafeNavigateOptions {
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
state?: unknown;
|
state?: unknown;
|
||||||
|
newTab?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SafeNavigateTo {
|
interface SafeNavigateTo {
|
||||||
@@ -20,9 +21,7 @@ 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
|
||||||
>,
|
>,
|
||||||
|
|||||||
@@ -164,6 +164,7 @@
|
|||||||
"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": {
|
||||||
@@ -286,4 +287,4 @@
|
|||||||
"tmp": "0.2.4",
|
"tmp": "0.2.4",
|
||||||
"vite": "npm:rolldown-vite@7.3.1"
|
"vite": "npm:rolldown-vite@7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend/public/Logos/mistral.svg
Normal file
1
frontend/public/Logos/mistral.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 294 B |
1
frontend/public/Logos/openclaw.svg
Normal file
1
frontend/public/Logos/openclaw.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 809 B |
1
frontend/public/Logos/render.svg
Normal file
1
frontend/public/Logos/render.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 362 B |
@@ -101,6 +101,22 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
preference.name === ORG_PREFERENCES.ORG_ONBOARDING,
|
preference.name === ORG_PREFERENCES.ORG_ONBOARDING,
|
||||||
)?.value;
|
)?.value;
|
||||||
|
|
||||||
|
// Don't redirect to onboarding if workspace has issues (blocked, suspended, or restricted)
|
||||||
|
// User needs access to settings/billing to fix payment issues
|
||||||
|
const isWorkspaceBlocked = trialInfo?.workSpaceBlock;
|
||||||
|
const isWorkspaceSuspended = activeLicense?.state === LicenseState.DEFAULTED;
|
||||||
|
const isWorkspaceAccessRestricted =
|
||||||
|
activeLicense?.state === LicenseState.TERMINATED ||
|
||||||
|
activeLicense?.state === LicenseState.EXPIRED ||
|
||||||
|
activeLicense?.state === LicenseState.CANCELLED;
|
||||||
|
|
||||||
|
const hasWorkspaceIssue =
|
||||||
|
isWorkspaceBlocked || isWorkspaceSuspended || isWorkspaceAccessRestricted;
|
||||||
|
|
||||||
|
if (hasWorkspaceIssue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isFirstUser = checkFirstTimeUser();
|
const isFirstUser = checkFirstTimeUser();
|
||||||
if (
|
if (
|
||||||
isFirstUser &&
|
isFirstUser &&
|
||||||
@@ -119,40 +135,36 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
orgPreferences,
|
orgPreferences,
|
||||||
usersData,
|
usersData,
|
||||||
pathname,
|
pathname,
|
||||||
|
trialInfo?.workSpaceBlock,
|
||||||
|
activeLicense?.state,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const navigateToWorkSpaceBlocked = (route: any): void => {
|
const navigateToWorkSpaceBlocked = useCallback((): void => {
|
||||||
const { path } = route;
|
|
||||||
|
|
||||||
const isRouteEnabledForWorkspaceBlockedState =
|
const isRouteEnabledForWorkspaceBlockedState =
|
||||||
isAdmin &&
|
isAdmin &&
|
||||||
(path === ROUTES.SETTINGS ||
|
(pathname === ROUTES.SETTINGS ||
|
||||||
path === ROUTES.ORG_SETTINGS ||
|
pathname === ROUTES.ORG_SETTINGS ||
|
||||||
path === ROUTES.MEMBERS_SETTINGS ||
|
pathname === ROUTES.MEMBERS_SETTINGS ||
|
||||||
path === ROUTES.BILLING ||
|
pathname === ROUTES.BILLING ||
|
||||||
path === ROUTES.MY_SETTINGS);
|
pathname === ROUTES.MY_SETTINGS);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
path &&
|
pathname &&
|
||||||
path !== ROUTES.WORKSPACE_LOCKED &&
|
pathname !== ROUTES.WORKSPACE_LOCKED &&
|
||||||
!isRouteEnabledForWorkspaceBlockedState
|
!isRouteEnabledForWorkspaceBlockedState
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.WORKSPACE_LOCKED);
|
history.push(ROUTES.WORKSPACE_LOCKED);
|
||||||
}
|
}
|
||||||
};
|
}, [isAdmin, pathname]);
|
||||||
|
|
||||||
const navigateToWorkSpaceAccessRestricted = (route: any): void => {
|
const navigateToWorkSpaceAccessRestricted = useCallback((): void => {
|
||||||
const { path } = route;
|
if (pathname && pathname !== ROUTES.WORKSPACE_ACCESS_RESTRICTED) {
|
||||||
|
|
||||||
if (path && path !== ROUTES.WORKSPACE_ACCESS_RESTRICTED) {
|
|
||||||
history.push(ROUTES.WORKSPACE_ACCESS_RESTRICTED);
|
history.push(ROUTES.WORKSPACE_ACCESS_RESTRICTED);
|
||||||
}
|
}
|
||||||
};
|
}, [pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicense && activeLicense) {
|
if (!isFetchingActiveLicense && activeLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
|
||||||
|
|
||||||
const isTerminated = activeLicense.state === LicenseState.TERMINATED;
|
const isTerminated = activeLicense.state === LicenseState.TERMINATED;
|
||||||
const isExpired = activeLicense.state === LicenseState.EXPIRED;
|
const isExpired = activeLicense.state === LicenseState.EXPIRED;
|
||||||
const isCancelled = activeLicense.state === LicenseState.CANCELLED;
|
const isCancelled = activeLicense.state === LicenseState.CANCELLED;
|
||||||
@@ -161,61 +173,53 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
const { platform } = activeLicense;
|
const { platform } = activeLicense;
|
||||||
|
|
||||||
if (
|
if (isWorkspaceAccessRestricted && platform === LicensePlatform.CLOUD) {
|
||||||
isWorkspaceAccessRestricted &&
|
navigateToWorkSpaceAccessRestricted();
|
||||||
platform === LicensePlatform.CLOUD &&
|
|
||||||
currentRoute
|
|
||||||
) {
|
|
||||||
navigateToWorkSpaceAccessRestricted(currentRoute);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicense, activeLicense, mapRoutes, pathname]);
|
}, [
|
||||||
|
isFetchingActiveLicense,
|
||||||
|
activeLicense,
|
||||||
|
navigateToWorkSpaceAccessRestricted,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicense) {
|
if (!isFetchingActiveLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
|
||||||
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shouldBlockWorkspace &&
|
shouldBlockWorkspace &&
|
||||||
currentRoute &&
|
|
||||||
activeLicense?.platform === LicensePlatform.CLOUD
|
activeLicense?.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
navigateToWorkSpaceBlocked(currentRoute);
|
navigateToWorkSpaceBlocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [
|
}, [
|
||||||
isFetchingActiveLicense,
|
isFetchingActiveLicense,
|
||||||
trialInfo?.workSpaceBlock,
|
trialInfo?.workSpaceBlock,
|
||||||
activeLicense?.platform,
|
activeLicense?.platform,
|
||||||
mapRoutes,
|
navigateToWorkSpaceBlocked,
|
||||||
pathname,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const navigateToWorkSpaceSuspended = (route: any): void => {
|
const navigateToWorkSpaceSuspended = useCallback((): void => {
|
||||||
const { path } = route;
|
if (pathname && pathname !== ROUTES.WORKSPACE_SUSPENDED) {
|
||||||
|
|
||||||
if (path && path !== ROUTES.WORKSPACE_SUSPENDED) {
|
|
||||||
history.push(ROUTES.WORKSPACE_SUSPENDED);
|
history.push(ROUTES.WORKSPACE_SUSPENDED);
|
||||||
}
|
}
|
||||||
};
|
}, [pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicense && activeLicense) {
|
if (!isFetchingActiveLicense && activeLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
|
||||||
const shouldSuspendWorkspace =
|
const shouldSuspendWorkspace =
|
||||||
activeLicense.state === LicenseState.DEFAULTED;
|
activeLicense.state === LicenseState.DEFAULTED;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shouldSuspendWorkspace &&
|
shouldSuspendWorkspace &&
|
||||||
currentRoute &&
|
|
||||||
activeLicense.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
navigateToWorkSpaceSuspended(currentRoute);
|
navigateToWorkSpaceSuspended();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicense, activeLicense, mapRoutes, pathname]);
|
}, [isFetchingActiveLicense, activeLicense, navigateToWorkSpaceSuspended]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (org && org.length > 0 && org[0].id !== undefined) {
|
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||||
|
|||||||
1539
frontend/src/AppRoutes/__tests__/Private.test.tsx
Normal file
1539
frontend/src/AppRoutes/__tests__/Private.test.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -157,10 +157,6 @@ export const IngestionSettings = Loadable(
|
|||||||
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const APIKeys = Loadable(
|
|
||||||
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const MySettings = Loadable(
|
export const MySettings = Loadable(
|
||||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/Settings'),
|
() => import(/* webpackChunkName: "All MySettings" */ 'pages/Settings'),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -513,6 +513,7 @@ export const oldRoutes = [
|
|||||||
'/logs-save-views',
|
'/logs-save-views',
|
||||||
'/traces-save-views',
|
'/traces-save-views',
|
||||||
'/settings/access-tokens',
|
'/settings/access-tokens',
|
||||||
|
'/settings/api-keys',
|
||||||
'/messaging-queues',
|
'/messaging-queues',
|
||||||
'/alerts/edit',
|
'/alerts/edit',
|
||||||
];
|
];
|
||||||
@@ -523,7 +524,8 @@ export const oldNewRoutesMapping: Record<string, string> = {
|
|||||||
'/logs-explorer/live': '/logs/logs-explorer/live',
|
'/logs-explorer/live': '/logs/logs-explorer/live',
|
||||||
'/logs-save-views': '/logs/saved-views',
|
'/logs-save-views': '/logs/saved-views',
|
||||||
'/traces-save-views': '/traces/saved-views',
|
'/traces-save-views': '/traces/saved-views',
|
||||||
'/settings/access-tokens': '/settings/api-keys',
|
'/settings/access-tokens': '/settings/service-accounts',
|
||||||
|
'/settings/api-keys': '/settings/service-accounts',
|
||||||
'/messaging-queues': '/messaging-queues/overview',
|
'/messaging-queues': '/messaging-queues/overview',
|
||||||
'/alerts/edit': '/alerts/overview',
|
'/alerts/edit': '/alerts/overview',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ const createPublicDashboard = async (
|
|||||||
props: CreatePublicDashboardProps,
|
props: CreatePublicDashboardProps,
|
||||||
): Promise<SuccessResponseV2<CreatePublicDashboardProps>> => {
|
): Promise<SuccessResponseV2<CreatePublicDashboardProps>> => {
|
||||||
|
|
||||||
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = '30m' } = props;
|
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = DEFAULT_TIME_RANGE } = props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ const updatePublicDashboard = async (
|
|||||||
props: UpdatePublicDashboardProps,
|
props: UpdatePublicDashboardProps,
|
||||||
): Promise<SuccessResponseV2<UpdatePublicDashboardProps>> => {
|
): Promise<SuccessResponseV2<UpdatePublicDashboardProps>> => {
|
||||||
|
|
||||||
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = '30m' } = props;
|
const { dashboardId, timeRangeEnabled = false, defaultTimeRange = DEFAULT_TIME_RANGE } = props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(
|
const response = await axios.put(
|
||||||
|
|||||||
1041
frontend/src/api/generated/services/cloudintegration/index.ts
Normal file
1041
frontend/src/api/generated/services/cloudintegration/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
250
frontend/src/api/generated/services/health/index.ts
Normal file
250
frontend/src/api/generated/services/health/index.ts
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* ! 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;
|
||||||
|
};
|
||||||
@@ -20,11 +20,113 @@ 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
|
||||||
|
|||||||
@@ -31,10 +31,13 @@ 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,
|
||||||
@@ -778,6 +781,176 @@ 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
|
||||||
|
|||||||
744
frontend/src/api/generated/services/rules/index.ts
Normal file
744
frontend/src/api/generated/services/rules/index.ts
Normal file
@@ -0,0 +1,744 @@
|
|||||||
|
/**
|
||||||
|
* ! 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;
|
||||||
|
};
|
||||||
@@ -23,9 +23,15 @@ import type {
|
|||||||
CreateServiceAccount201,
|
CreateServiceAccount201,
|
||||||
CreateServiceAccountKey201,
|
CreateServiceAccountKey201,
|
||||||
CreateServiceAccountKeyPathParameters,
|
CreateServiceAccountKeyPathParameters,
|
||||||
|
CreateServiceAccountRole201,
|
||||||
|
CreateServiceAccountRolePathParameters,
|
||||||
DeleteServiceAccountPathParameters,
|
DeleteServiceAccountPathParameters,
|
||||||
|
DeleteServiceAccountRolePathParameters,
|
||||||
|
GetMyServiceAccount200,
|
||||||
GetServiceAccount200,
|
GetServiceAccount200,
|
||||||
GetServiceAccountPathParameters,
|
GetServiceAccountPathParameters,
|
||||||
|
GetServiceAccountRoles200,
|
||||||
|
GetServiceAccountRolesPathParameters,
|
||||||
ListServiceAccountKeys200,
|
ListServiceAccountKeys200,
|
||||||
ListServiceAccountKeysPathParameters,
|
ListServiceAccountKeysPathParameters,
|
||||||
ListServiceAccounts200,
|
ListServiceAccounts200,
|
||||||
@@ -33,12 +39,10 @@ import type {
|
|||||||
RevokeServiceAccountKeyPathParameters,
|
RevokeServiceAccountKeyPathParameters,
|
||||||
ServiceaccounttypesPostableFactorAPIKeyDTO,
|
ServiceaccounttypesPostableFactorAPIKeyDTO,
|
||||||
ServiceaccounttypesPostableServiceAccountDTO,
|
ServiceaccounttypesPostableServiceAccountDTO,
|
||||||
|
ServiceaccounttypesPostableServiceAccountRoleDTO,
|
||||||
ServiceaccounttypesUpdatableFactorAPIKeyDTO,
|
ServiceaccounttypesUpdatableFactorAPIKeyDTO,
|
||||||
ServiceaccounttypesUpdatableServiceAccountDTO,
|
|
||||||
ServiceaccounttypesUpdatableServiceAccountStatusDTO,
|
|
||||||
UpdateServiceAccountKeyPathParameters,
|
UpdateServiceAccountKeyPathParameters,
|
||||||
UpdateServiceAccountPathParameters,
|
UpdateServiceAccountPathParameters,
|
||||||
UpdateServiceAccountStatusPathParameters,
|
|
||||||
} from '../sigNoz.schemas';
|
} from '../sigNoz.schemas';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -399,13 +403,13 @@ export const invalidateGetServiceAccount = async (
|
|||||||
*/
|
*/
|
||||||
export const updateServiceAccount = (
|
export const updateServiceAccount = (
|
||||||
{ id }: UpdateServiceAccountPathParameters,
|
{ id }: UpdateServiceAccountPathParameters,
|
||||||
serviceaccounttypesUpdatableServiceAccountDTO: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>,
|
serviceaccounttypesPostableServiceAccountDTO: BodyType<ServiceaccounttypesPostableServiceAccountDTO>,
|
||||||
) => {
|
) => {
|
||||||
return GeneratedAPIInstance<string>({
|
return GeneratedAPIInstance<string>({
|
||||||
url: `/api/v1/service_accounts/${id}`,
|
url: `/api/v1/service_accounts/${id}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
data: serviceaccounttypesUpdatableServiceAccountDTO,
|
data: serviceaccounttypesPostableServiceAccountDTO,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -418,7 +422,7 @@ export const getUpdateServiceAccountMutationOptions = <
|
|||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountPathParameters;
|
pathParams: UpdateServiceAccountPathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
@@ -427,7 +431,7 @@ export const getUpdateServiceAccountMutationOptions = <
|
|||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountPathParameters;
|
pathParams: UpdateServiceAccountPathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
@@ -444,7 +448,7 @@ export const getUpdateServiceAccountMutationOptions = <
|
|||||||
Awaited<ReturnType<typeof updateServiceAccount>>,
|
Awaited<ReturnType<typeof updateServiceAccount>>,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountPathParameters;
|
pathParams: UpdateServiceAccountPathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
}
|
}
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { pathParams, data } = props ?? {};
|
const { pathParams, data } = props ?? {};
|
||||||
@@ -458,7 +462,7 @@ export const getUpdateServiceAccountMutationOptions = <
|
|||||||
export type UpdateServiceAccountMutationResult = NonNullable<
|
export type UpdateServiceAccountMutationResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof updateServiceAccount>>
|
Awaited<ReturnType<typeof updateServiceAccount>>
|
||||||
>;
|
>;
|
||||||
export type UpdateServiceAccountMutationBody = BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
export type UpdateServiceAccountMutationBody = BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
export type UpdateServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
|
export type UpdateServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -473,7 +477,7 @@ export const useUpdateServiceAccount = <
|
|||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountPathParameters;
|
pathParams: UpdateServiceAccountPathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
@@ -482,7 +486,7 @@ export const useUpdateServiceAccount = <
|
|||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountPathParameters;
|
pathParams: UpdateServiceAccountPathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
@@ -871,44 +875,150 @@ export const useUpdateServiceAccountKey = <
|
|||||||
return useMutation(mutationOptions);
|
return useMutation(mutationOptions);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* This endpoint updates an existing service account status
|
* This endpoint gets all the roles for the existing service account
|
||||||
* @summary Updates a service account status
|
* @summary Gets service account roles
|
||||||
*/
|
*/
|
||||||
export const updateServiceAccountStatus = (
|
export const getServiceAccountRoles = (
|
||||||
{ id }: UpdateServiceAccountStatusPathParameters,
|
{ id }: GetServiceAccountRolesPathParameters,
|
||||||
serviceaccounttypesUpdatableServiceAccountStatusDTO: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>,
|
signal?: AbortSignal,
|
||||||
) => {
|
) => {
|
||||||
return GeneratedAPIInstance<string>({
|
return GeneratedAPIInstance<GetServiceAccountRoles200>({
|
||||||
url: `/api/v1/service_accounts/${id}/status`,
|
url: `/api/v1/service_accounts/${id}/roles`,
|
||||||
method: 'PUT',
|
method: 'GET',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
signal,
|
||||||
data: serviceaccounttypesUpdatableServiceAccountStatusDTO,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUpdateServiceAccountStatusMutationOptions = <
|
export const getGetServiceAccountRolesQueryKey = ({
|
||||||
|
id,
|
||||||
|
}: GetServiceAccountRolesPathParameters) => {
|
||||||
|
return [`/api/v1/service_accounts/${id}/roles`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetServiceAccountRolesQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof getServiceAccountRoles>>,
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>
|
||||||
|
>(
|
||||||
|
{ id }: GetServiceAccountRolesPathParameters,
|
||||||
|
options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getServiceAccountRoles>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey =
|
||||||
|
queryOptions?.queryKey ?? getGetServiceAccountRolesQueryKey({ id });
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<
|
||||||
|
Awaited<ReturnType<typeof getServiceAccountRoles>>
|
||||||
|
> = ({ signal }) => getServiceAccountRoles({ id }, signal);
|
||||||
|
|
||||||
|
return {
|
||||||
|
queryKey,
|
||||||
|
queryFn,
|
||||||
|
enabled: !!id,
|
||||||
|
...queryOptions,
|
||||||
|
} as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getServiceAccountRoles>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: QueryKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetServiceAccountRolesQueryResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof getServiceAccountRoles>>
|
||||||
|
>;
|
||||||
|
export type GetServiceAccountRolesQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Gets service account roles
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetServiceAccountRoles<
|
||||||
|
TData = Awaited<ReturnType<typeof getServiceAccountRoles>>,
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>
|
||||||
|
>(
|
||||||
|
{ id }: GetServiceAccountRolesPathParameters,
|
||||||
|
options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getServiceAccountRoles>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||||
|
const queryOptions = getGetServiceAccountRolesQueryOptions({ id }, options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: QueryKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Gets service account roles
|
||||||
|
*/
|
||||||
|
export const invalidateGetServiceAccountRoles = async (
|
||||||
|
queryClient: QueryClient,
|
||||||
|
{ id }: GetServiceAccountRolesPathParameters,
|
||||||
|
options?: InvalidateOptions,
|
||||||
|
): Promise<QueryClient> => {
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
{ queryKey: getGetServiceAccountRolesQueryKey({ id }) },
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This endpoint assigns a role to a service account
|
||||||
|
* @summary Create service account role
|
||||||
|
*/
|
||||||
|
export const createServiceAccountRole = (
|
||||||
|
{ id }: CreateServiceAccountRolePathParameters,
|
||||||
|
serviceaccounttypesPostableServiceAccountRoleDTO: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
|
return GeneratedAPIInstance<CreateServiceAccountRole201>({
|
||||||
|
url: `/api/v1/service_accounts/${id}/roles`,
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: serviceaccounttypesPostableServiceAccountRoleDTO,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCreateServiceAccountRoleMutationOptions = <
|
||||||
TError = ErrorType<RenderErrorResponseDTO>,
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
TContext = unknown
|
TContext = unknown
|
||||||
>(options?: {
|
>(options?: {
|
||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
|
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountStatusPathParameters;
|
pathParams: CreateServiceAccountRolePathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationOptions<
|
}): UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
|
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountStatusPathParameters;
|
pathParams: CreateServiceAccountRolePathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationKey = ['updateServiceAccountStatus'];
|
const mutationKey = ['createServiceAccountRole'];
|
||||||
const { mutation: mutationOptions } = options
|
const { mutation: mutationOptions } = options
|
||||||
? options.mutation &&
|
? options.mutation &&
|
||||||
'mutationKey' in options.mutation &&
|
'mutationKey' in options.mutation &&
|
||||||
@@ -918,52 +1028,299 @@ export const getUpdateServiceAccountStatusMutationOptions = <
|
|||||||
: { mutation: { mutationKey } };
|
: { mutation: { mutationKey } };
|
||||||
|
|
||||||
const mutationFn: MutationFunction<
|
const mutationFn: MutationFunction<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
|
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountStatusPathParameters;
|
pathParams: CreateServiceAccountRolePathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
}
|
}
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { pathParams, data } = props ?? {};
|
const { pathParams, data } = props ?? {};
|
||||||
|
|
||||||
return updateServiceAccountStatus(pathParams, data);
|
return createServiceAccountRole(pathParams, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { mutationFn, ...mutationOptions };
|
return { mutationFn, ...mutationOptions };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateServiceAccountStatusMutationResult = NonNullable<
|
export type CreateServiceAccountRoleMutationResult = NonNullable<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>
|
Awaited<ReturnType<typeof createServiceAccountRole>>
|
||||||
>;
|
>;
|
||||||
export type UpdateServiceAccountStatusMutationBody = BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
export type CreateServiceAccountRoleMutationBody = BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
export type UpdateServiceAccountStatusMutationError = ErrorType<RenderErrorResponseDTO>;
|
export type CreateServiceAccountRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Updates a service account status
|
* @summary Create service account role
|
||||||
*/
|
*/
|
||||||
export const useUpdateServiceAccountStatus = <
|
export const useCreateServiceAccountRole = <
|
||||||
TError = ErrorType<RenderErrorResponseDTO>,
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
TContext = unknown
|
TContext = unknown
|
||||||
>(options?: {
|
>(options?: {
|
||||||
mutation?: UseMutationOptions<
|
mutation?: UseMutationOptions<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
|
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountStatusPathParameters;
|
pathParams: CreateServiceAccountRolePathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
>;
|
>;
|
||||||
}): UseMutationResult<
|
}): UseMutationResult<
|
||||||
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
|
Awaited<ReturnType<typeof createServiceAccountRole>>,
|
||||||
TError,
|
TError,
|
||||||
{
|
{
|
||||||
pathParams: UpdateServiceAccountStatusPathParameters;
|
pathParams: CreateServiceAccountRolePathParameters;
|
||||||
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
|
data: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
|
||||||
},
|
},
|
||||||
TContext
|
TContext
|
||||||
> => {
|
> => {
|
||||||
const mutationOptions = getUpdateServiceAccountStatusMutationOptions(options);
|
const mutationOptions = getCreateServiceAccountRoleMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* This endpoint revokes a role from service account
|
||||||
|
* @summary Delete service account role
|
||||||
|
*/
|
||||||
|
export const deleteServiceAccountRole = ({
|
||||||
|
id,
|
||||||
|
rid,
|
||||||
|
}: DeleteServiceAccountRolePathParameters) => {
|
||||||
|
return GeneratedAPIInstance<string>({
|
||||||
|
url: `/api/v1/service_accounts/${id}/roles/${rid}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDeleteServiceAccountRoleMutationOptions = <
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ['deleteServiceAccountRole'];
|
||||||
|
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 deleteServiceAccountRole>>,
|
||||||
|
{ pathParams: DeleteServiceAccountRolePathParameters }
|
||||||
|
> = (props) => {
|
||||||
|
const { pathParams } = props ?? {};
|
||||||
|
|
||||||
|
return deleteServiceAccountRole(pathParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteServiceAccountRoleMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof deleteServiceAccountRole>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type DeleteServiceAccountRoleMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Delete service account role
|
||||||
|
*/
|
||||||
|
export const useDeleteServiceAccountRole = <
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
|
||||||
|
TError,
|
||||||
|
{ pathParams: DeleteServiceAccountRolePathParameters },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationOptions = getDeleteServiceAccountRoleMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* This endpoint gets my service account
|
||||||
|
* @summary Gets my service account
|
||||||
|
*/
|
||||||
|
export const getMyServiceAccount = (signal?: AbortSignal) => {
|
||||||
|
return GeneratedAPIInstance<GetMyServiceAccount200>({
|
||||||
|
url: `/api/v1/service_accounts/me`,
|
||||||
|
method: 'GET',
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetMyServiceAccountQueryKey = () => {
|
||||||
|
return [`/api/v1/service_accounts/me`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGetMyServiceAccountQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof getMyServiceAccount>>,
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>
|
||||||
|
>(options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
}) => {
|
||||||
|
const { query: queryOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getGetMyServiceAccountQueryKey();
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<
|
||||||
|
Awaited<ReturnType<typeof getMyServiceAccount>>
|
||||||
|
> = ({ signal }) => getMyServiceAccount(signal);
|
||||||
|
|
||||||
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: QueryKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetMyServiceAccountQueryResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof getMyServiceAccount>>
|
||||||
|
>;
|
||||||
|
export type GetMyServiceAccountQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Gets my service account
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetMyServiceAccount<
|
||||||
|
TData = Awaited<ReturnType<typeof getMyServiceAccount>>,
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>
|
||||||
|
>(options?: {
|
||||||
|
query?: UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof getMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>;
|
||||||
|
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||||
|
const queryOptions = getGetMyServiceAccountQueryOptions(options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: QueryKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Gets my service account
|
||||||
|
*/
|
||||||
|
export const invalidateGetMyServiceAccount = async (
|
||||||
|
queryClient: QueryClient,
|
||||||
|
options?: InvalidateOptions,
|
||||||
|
): Promise<QueryClient> => {
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
{ queryKey: getGetMyServiceAccountQueryKey() },
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This endpoint gets my service account
|
||||||
|
* @summary Updates my service account
|
||||||
|
*/
|
||||||
|
export const updateMyServiceAccount = (
|
||||||
|
serviceaccounttypesPostableServiceAccountDTO: BodyType<ServiceaccounttypesPostableServiceAccountDTO>,
|
||||||
|
) => {
|
||||||
|
return GeneratedAPIInstance<string>({
|
||||||
|
url: `/api/v1/service_accounts/me`,
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: serviceaccounttypesPostableServiceAccountDTO,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUpdateMyServiceAccountMutationOptions = <
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof updateMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof updateMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ['updateMyServiceAccount'];
|
||||||
|
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 updateMyServiceAccount>>,
|
||||||
|
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> }
|
||||||
|
> = (props) => {
|
||||||
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return updateMyServiceAccount(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateMyServiceAccountMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof updateMyServiceAccount>>
|
||||||
|
>;
|
||||||
|
export type UpdateMyServiceAccountMutationBody = BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
|
||||||
|
export type UpdateMyServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Updates my service account
|
||||||
|
*/
|
||||||
|
export const useUpdateMyServiceAccount = <
|
||||||
|
TError = ErrorType<RenderErrorResponseDTO>,
|
||||||
|
TContext = unknown
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof updateMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof updateMyServiceAccount>>,
|
||||||
|
TError,
|
||||||
|
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationOptions = getUpdateMyServiceAccountMutationOptions(options);
|
||||||
|
|
||||||
return useMutation(mutationOptions);
|
return useMutation(mutationOptions);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
|
||||||
import {
|
|
||||||
APIKeyProps,
|
|
||||||
CreateAPIKeyProps,
|
|
||||||
CreatePayloadProps,
|
|
||||||
} from 'types/api/pat/types';
|
|
||||||
|
|
||||||
const create = async (
|
|
||||||
props: CreateAPIKeyProps,
|
|
||||||
): Promise<SuccessResponseV2<APIKeyProps>> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post<CreatePayloadProps>('/pats', {
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
httpStatusCode: response.status,
|
|
||||||
data: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default create;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
|
||||||
|
|
||||||
const deleteAPIKey = async (id: string): Promise<SuccessResponseV2<null>> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.delete(`/pats/${id}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
httpStatusCode: response.status,
|
|
||||||
data: null,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default deleteAPIKey;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
|
||||||
import { AllAPIKeyProps, APIKeyProps } from 'types/api/pat/types';
|
|
||||||
|
|
||||||
const list = async (): Promise<SuccessResponseV2<APIKeyProps[]>> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get<AllAPIKeyProps>('/pats');
|
|
||||||
|
|
||||||
return {
|
|
||||||
httpStatusCode: response.status,
|
|
||||||
data: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default list;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
|
||||||
import { UpdateAPIKeyProps } from 'types/api/pat/types';
|
|
||||||
|
|
||||||
const updateAPIKey = async (
|
|
||||||
props: UpdateAPIKeyProps,
|
|
||||||
): Promise<SuccessResponseV2<null>> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.put(`/pats/${props.id}`, {
|
|
||||||
...props.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
httpStatusCode: response.status,
|
|
||||||
data: null,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateAPIKey;
|
|
||||||
@@ -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 { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||||
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 } = useDashboard();
|
const { selectedDashboard } = useDashboardStore();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
|
|||||||
@@ -12,17 +12,13 @@ import {
|
|||||||
} from 'api/generated/services/serviceaccount';
|
} from 'api/generated/services/serviceaccount';
|
||||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import RolesSelect, { useRoles } from 'components/RolesSelect';
|
|
||||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||||
import { parseAsBoolean, useQueryState } from 'nuqs';
|
import { parseAsBoolean, useQueryState } from 'nuqs';
|
||||||
import { EMAIL_REGEX } from 'utils/app';
|
|
||||||
|
|
||||||
import './CreateServiceAccountModal.styles.scss';
|
import './CreateServiceAccountModal.styles.scss';
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
|
||||||
roles: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateServiceAccountModal(): JSX.Element {
|
function CreateServiceAccountModal(): JSX.Element {
|
||||||
@@ -41,8 +37,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
|||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
|
||||||
roles: [],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,13 +64,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const {
|
|
||||||
roles,
|
|
||||||
isLoading: rolesLoading,
|
|
||||||
isError: rolesError,
|
|
||||||
error: rolesErrorObj,
|
|
||||||
refetch: refetchRoles,
|
|
||||||
} = useRoles();
|
|
||||||
|
|
||||||
function handleClose(): void {
|
function handleClose(): void {
|
||||||
reset();
|
reset();
|
||||||
@@ -87,8 +74,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
|||||||
createServiceAccount({
|
createServiceAccount({
|
||||||
data: {
|
data: {
|
||||||
name: values.name.trim(),
|
name: values.name.trim(),
|
||||||
email: values.email.trim(),
|
|
||||||
roles: values.roles,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,68 +119,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
|||||||
<p className="create-sa-form__error">{errors.name.message}</p>
|
<p className="create-sa-form__error">{errors.name.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="create-sa-form__item">
|
|
||||||
<label htmlFor="sa-email">Email Address</label>
|
|
||||||
<Controller
|
|
||||||
name="email"
|
|
||||||
control={control}
|
|
||||||
rules={{
|
|
||||||
required: 'Email Address is required',
|
|
||||||
pattern: {
|
|
||||||
value: EMAIL_REGEX,
|
|
||||||
message: 'Please enter a valid email address',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
render={({ field }): JSX.Element => (
|
|
||||||
<Input
|
|
||||||
id="sa-email"
|
|
||||||
type="email"
|
|
||||||
placeholder="email@example.com"
|
|
||||||
className="create-sa-form__input"
|
|
||||||
value={field.value}
|
|
||||||
onChange={field.onChange}
|
|
||||||
onBlur={field.onBlur}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{errors.email && (
|
|
||||||
<p className="create-sa-form__error">{errors.email.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="create-sa-form__helper">
|
|
||||||
Used only for notifications about this service account. It is not used for
|
|
||||||
authentication.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="create-sa-form__item">
|
|
||||||
<label htmlFor="sa-roles">Roles</label>
|
|
||||||
<Controller
|
|
||||||
name="roles"
|
|
||||||
control={control}
|
|
||||||
rules={{
|
|
||||||
validate: (value): string | true =>
|
|
||||||
value.length > 0 || 'At least one role is required',
|
|
||||||
}}
|
|
||||||
render={({ field }): JSX.Element => (
|
|
||||||
<RolesSelect
|
|
||||||
id="sa-roles"
|
|
||||||
mode="multiple"
|
|
||||||
roles={roles}
|
|
||||||
loading={rolesLoading}
|
|
||||||
isError={rolesError}
|
|
||||||
error={rolesErrorObj}
|
|
||||||
onRefetch={refetchRoles}
|
|
||||||
placeholder="Select roles"
|
|
||||||
value={field.value}
|
|
||||||
onChange={field.onChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{errors.roles && (
|
|
||||||
<p className="create-sa-form__error">{errors.roles.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { toast } from '@signozhq/sonner';
|
import { toast } from '@signozhq/sonner';
|
||||||
import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
|
||||||
import { rest, server } from 'mocks-server/server';
|
import { rest, server } from 'mocks-server/server';
|
||||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
@@ -12,7 +11,6 @@ jest.mock('@signozhq/sonner', () => ({
|
|||||||
|
|
||||||
const mockToast = jest.mocked(toast);
|
const mockToast = jest.mocked(toast);
|
||||||
|
|
||||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
|
||||||
const SERVICE_ACCOUNTS_ENDPOINT = '*/api/v1/service_accounts';
|
const SERVICE_ACCOUNTS_ENDPOINT = '*/api/v1/service_accounts';
|
||||||
|
|
||||||
function renderModal(): ReturnType<typeof render> {
|
function renderModal(): ReturnType<typeof render> {
|
||||||
@@ -27,9 +25,6 @@ describe('CreateServiceAccountModal', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
server.use(
|
server.use(
|
||||||
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
|
||||||
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
|
||||||
),
|
|
||||||
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) =>
|
rest.post(SERVICE_ACCOUNTS_ENDPOINT, (_, res, ctx) =>
|
||||||
res(ctx.status(201), ctx.json({ status: 'success', data: {} })),
|
res(ctx.status(201), ctx.json({ status: 'success', data: {} })),
|
||||||
),
|
),
|
||||||
@@ -48,38 +43,11 @@ describe('CreateServiceAccountModal', () => {
|
|||||||
).toBeDisabled();
|
).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submit button remains disabled when email is invalid', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderModal();
|
|
||||||
|
|
||||||
await user.type(screen.getByPlaceholderText('Enter a name'), 'My Bot');
|
|
||||||
await user.type(
|
|
||||||
screen.getByPlaceholderText('email@example.com'),
|
|
||||||
'not-an-email',
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByText('Select roles'));
|
|
||||||
await user.click(await screen.findByTitle('signoz-admin'));
|
|
||||||
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(
|
|
||||||
screen.getByRole('button', { name: /Create Service Account/i }),
|
|
||||||
).toBeDisabled(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('successful submit shows toast.success and closes modal', async () => {
|
it('successful submit shows toast.success and closes modal', async () => {
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
renderModal();
|
renderModal();
|
||||||
|
|
||||||
await user.type(screen.getByPlaceholderText('Enter a name'), 'Deploy Bot');
|
await user.type(screen.getByPlaceholderText('Enter a name'), 'Deploy Bot');
|
||||||
await user.type(
|
|
||||||
screen.getByPlaceholderText('email@example.com'),
|
|
||||||
'deploy@acme.io',
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByText('Select roles'));
|
|
||||||
await user.click(await screen.findByTitle('signoz-admin'));
|
|
||||||
|
|
||||||
const submitBtn = screen.getByRole('button', {
|
const submitBtn = screen.getByRole('button', {
|
||||||
name: /Create Service Account/i,
|
name: /Create Service Account/i,
|
||||||
@@ -116,13 +84,6 @@ describe('CreateServiceAccountModal', () => {
|
|||||||
renderModal();
|
renderModal();
|
||||||
|
|
||||||
await user.type(screen.getByPlaceholderText('Enter a name'), 'Dupe Bot');
|
await user.type(screen.getByPlaceholderText('Enter a name'), 'Dupe Bot');
|
||||||
await user.type(
|
|
||||||
screen.getByPlaceholderText('email@example.com'),
|
|
||||||
'dupe@acme.io',
|
|
||||||
);
|
|
||||||
|
|
||||||
await user.click(screen.getByText('Select roles'));
|
|
||||||
await user.click(await screen.findByTitle('signoz-admin'));
|
|
||||||
|
|
||||||
const submitBtn = screen.getByRole('button', {
|
const submitBtn = screen.getByRole('button', {
|
||||||
name: /Create Service Account/i,
|
name: /Create Service Account/i,
|
||||||
@@ -164,16 +125,4 @@ describe('CreateServiceAccountModal', () => {
|
|||||||
|
|
||||||
await screen.findByText('Name is required');
|
await screen.findByText('Name is required');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows "Please enter a valid email address" for a malformed email', async () => {
|
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
||||||
renderModal();
|
|
||||||
|
|
||||||
await user.type(
|
|
||||||
screen.getByPlaceholderText('email@example.com'),
|
|
||||||
'not-an-email',
|
|
||||||
);
|
|
||||||
|
|
||||||
await screen.findByText('Please enter a valid email address');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,7 +116,12 @@ 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', () => {
|
||||||
@@ -146,6 +151,17 @@ 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));
|
||||||
|
|
||||||
@@ -210,7 +226,12 @@ 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(() => {
|
||||||
@@ -227,6 +248,11 @@ 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' }));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
@@ -14,10 +15,12 @@ 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);
|
||||||
@@ -35,9 +38,19 @@ export default function DownloadOptionsMenu({
|
|||||||
await handleExportRawData({
|
await handleExportRawData({
|
||||||
format: exportFormat,
|
format: exportFormat,
|
||||||
rowLimit,
|
rowLimit,
|
||||||
clearSelectColumns: columnsScope === DownloadColumnsScopes.ALL,
|
clearSelectColumns:
|
||||||
|
dataSource !== DataSource.TRACES &&
|
||||||
|
columnsScope === DownloadColumnsScopes.ALL,
|
||||||
|
selectedColumns,
|
||||||
});
|
});
|
||||||
}, [exportFormat, rowLimit, columnsScope, handleExportRawData]);
|
}, [
|
||||||
|
exportFormat,
|
||||||
|
rowLimit,
|
||||||
|
columnsScope,
|
||||||
|
selectedColumns,
|
||||||
|
handleExportRawData,
|
||||||
|
dataSource,
|
||||||
|
]);
|
||||||
|
|
||||||
const popoverContent = useMemo(
|
const popoverContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@@ -72,18 +85,22 @@ export default function DownloadOptionsMenu({
|
|||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="horizontal-line" />
|
{dataSource !== DataSource.TRACES && (
|
||||||
|
<>
|
||||||
|
<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"
|
||||||
@@ -97,7 +114,14 @@ export default function DownloadOptionsMenu({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[exportFormat, rowLimit, columnsScope, isDownloading, handleExport],
|
[
|
||||||
|
exportFormat,
|
||||||
|
rowLimit,
|
||||||
|
columnsScope,
|
||||||
|
isDownloading,
|
||||||
|
handleExport,
|
||||||
|
dataSource,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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';
|
||||||
@@ -20,7 +21,7 @@ import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
|||||||
import {
|
import {
|
||||||
getResetPasswordToken,
|
getResetPasswordToken,
|
||||||
useDeleteUser,
|
useDeleteUser,
|
||||||
useUpdateUser,
|
useUpdateUserDeprecated,
|
||||||
} 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';
|
||||||
@@ -60,7 +61,7 @@ function EditMemberDrawer({
|
|||||||
|
|
||||||
const isInvited = member?.status === MemberStatus.Invited;
|
const isInvited = member?.status === MemberStatus.Invited;
|
||||||
|
|
||||||
const { mutate: updateUser, isLoading: isSaving } = useUpdateUser({
|
const { mutate: updateUser, isLoading: isSaving } = useUpdateUserDeprecated({
|
||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: (): void => {
|
onSuccess: (): void => {
|
||||||
toast.success('Member details updated successfully', { richColors: true });
|
toast.success('Member details updated successfully', { richColors: true });
|
||||||
@@ -177,26 +178,30 @@ 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;
|
||||||
}
|
}
|
||||||
try {
|
copyToClipboard(resetLink);
|
||||||
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 },
|
||||||
);
|
);
|
||||||
} catch {
|
}, [resetLink, copyToClipboard, linkType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (copyState.error) {
|
||||||
toast.error('Failed to copy link', {
|
toast.error('Failed to copy link', {
|
||||||
richColors: true,
|
richColors: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [resetLink, linkType]);
|
}, [copyState.error]);
|
||||||
|
|
||||||
const handleClose = useCallback((): void => {
|
const handleClose = useCallback((): void => {
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
|
|||||||
@@ -4,16 +4,10 @@ import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
|||||||
import {
|
import {
|
||||||
getResetPasswordToken,
|
getResetPasswordToken,
|
||||||
useDeleteUser,
|
useDeleteUser,
|
||||||
useUpdateUser,
|
useUpdateUserDeprecated,
|
||||||
} from 'api/generated/services/users';
|
} from 'api/generated/services/users';
|
||||||
import { MemberStatus } from 'container/MembersSettings/utils';
|
import { MemberStatus } from 'container/MembersSettings/utils';
|
||||||
import {
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
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';
|
||||||
@@ -50,7 +44,7 @@ jest.mock('@signozhq/dialog', () => ({
|
|||||||
|
|
||||||
jest.mock('api/generated/services/users', () => ({
|
jest.mock('api/generated/services/users', () => ({
|
||||||
useDeleteUser: jest.fn(),
|
useDeleteUser: jest.fn(),
|
||||||
useUpdateUser: jest.fn(),
|
useUpdateUserDeprecated: jest.fn(),
|
||||||
getResetPasswordToken: jest.fn(),
|
getResetPasswordToken: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -65,6 +59,16 @@ 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);
|
||||||
@@ -105,7 +109,7 @@ function renderDrawer(
|
|||||||
describe('EditMemberDrawer', () => {
|
describe('EditMemberDrawer', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
(useUpdateUser as jest.Mock).mockReturnValue({
|
(useUpdateUserDeprecated as jest.Mock).mockReturnValue({
|
||||||
mutate: mockUpdateMutate,
|
mutate: mockUpdateMutate,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
@@ -130,7 +134,7 @@ describe('EditMemberDrawer', () => {
|
|||||||
const onComplete = jest.fn();
|
const onComplete = jest.fn();
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
(useUpdateUser as jest.Mock).mockImplementation((options) => ({
|
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({
|
||||||
mutate: mockUpdateMutate.mockImplementation(() => {
|
mutate: mockUpdateMutate.mockImplementation(() => {
|
||||||
options?.mutation?.onSuccess?.();
|
options?.mutation?.onSuccess?.();
|
||||||
}),
|
}),
|
||||||
@@ -239,7 +243,7 @@ describe('EditMemberDrawer', () => {
|
|||||||
const onComplete = jest.fn();
|
const onComplete = jest.fn();
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
(useUpdateUser as jest.Mock).mockImplementation((options) => ({
|
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({
|
||||||
mutate: mockUpdateMutate.mockImplementation(() => {
|
mutate: mockUpdateMutate.mockImplementation(() => {
|
||||||
options?.mutation?.onSuccess?.();
|
options?.mutation?.onSuccess?.();
|
||||||
}),
|
}),
|
||||||
@@ -280,7 +284,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);
|
||||||
|
|
||||||
(useUpdateUser as jest.Mock).mockImplementation((options) => ({
|
(useUpdateUserDeprecated as jest.Mock).mockImplementation((options) => ({
|
||||||
mutate: mockUpdateMutate.mockImplementation(() => {
|
mutate: mockUpdateMutate.mockImplementation(() => {
|
||||||
options?.mutation?.onError?.({});
|
options?.mutation?.onError?.({});
|
||||||
}),
|
}),
|
||||||
@@ -361,32 +365,14 @@ 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(() => {
|
||||||
mockWriteText.mockClear();
|
mockCopyToClipboard.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 });
|
||||||
|
|
||||||
@@ -421,7 +407,7 @@ describe('EditMemberDrawer', () => {
|
|||||||
});
|
});
|
||||||
expect(dialog).toHaveTextContent('reset-tok-abc');
|
expect(dialog).toHaveTextContent('reset-tok-abc');
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: /^copy$/i }));
|
await user.click(screen.getByRole('button', { name: /^copy$/i }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockToast.success).toHaveBeenCalledWith(
|
expect(mockToast.success).toHaveBeenCalledWith(
|
||||||
@@ -430,7 +416,7 @@ describe('EditMemberDrawer', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockWriteText).toHaveBeenCalledWith(
|
expect(mockCopyToClipboard).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();
|
||||||
|
|||||||
@@ -202,19 +202,8 @@ function InviteMembersModal({
|
|||||||
onComplete?.();
|
onComplete?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const apiErr = err as APIError;
|
const apiErr = err as APIError;
|
||||||
if (apiErr?.getHttpStatusCode() === 409) {
|
const errorMessage = apiErr?.getErrorMessage?.() ?? 'An error occurred';
|
||||||
toast.error(
|
toast.error(errorMessage, { richColors: true });
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
|
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', () => ({
|
||||||
@@ -142,6 +151,90 @@ 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();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { 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';
|
import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
|
||||||
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,6 +49,7 @@ 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';
|
||||||
@@ -221,7 +222,7 @@ function LogDetailInner({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Go to logs explorer page with the log data
|
// Go to logs explorer page with the log data
|
||||||
const handleOpenInExplorer = (): void => {
|
const handleOpenInExplorer = (e?: React.MouseEvent): void => {
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
[QueryParams.activeLogId]: `"${log?.id}"`,
|
[QueryParams.activeLogId]: `"${log?.id}"`,
|
||||||
[QueryParams.startTime]: minTime?.toString() || '',
|
[QueryParams.startTime]: minTime?.toString() || '',
|
||||||
@@ -234,7 +235,9 @@ function LogDetailInner({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
|
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`, {
|
||||||
|
newTab: !!e && isModifierKeyPressed(e),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQueryExpressionChange = useCallback(
|
const handleQueryExpressionChange = useCallback(
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ 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(() => {
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ 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]);
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ jest.mock('hooks/useDarkMode', () => ({
|
|||||||
useIsDarkMode: (): boolean => false,
|
useIsDarkMode: (): boolean => false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('providers/Dashboard/Dashboard', () => ({
|
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
|
||||||
useDashboard: (): { selectedDashboard: undefined } => ({
|
useDashboardStore: (): { selectedDashboard: undefined } => ({
|
||||||
selectedDashboard: undefined,
|
selectedDashboard: undefined,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function useRoles(): {
|
|||||||
export function getRoleOptions(roles: AuthtypesRoleDTO[]): RoleOption[] {
|
export function getRoleOptions(roles: AuthtypesRoleDTO[]): RoleOption[] {
|
||||||
return roles.map((role) => ({
|
return roles.map((role) => ({
|
||||||
label: role.name ?? '',
|
label: role.name ?? '',
|
||||||
value: role.name ?? '',
|
value: role.id ?? '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
@@ -105,19 +106,23 @@ 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 {
|
|
||||||
await navigator.clipboard.writeText(createdKey.key);
|
copyToClipboard(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 });
|
||||||
} catch {
|
}, [copyToClipboard, createdKey?.key]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (copyState.error) {
|
||||||
toast.error('Failed to copy key', { richColors: true });
|
toast.error('Failed to copy key', { richColors: true });
|
||||||
}
|
}
|
||||||
}, [createdKey]);
|
}, [copyState.error]);
|
||||||
|
|
||||||
const handleClose = useCallback((): void => {
|
const handleClose = useCallback((): void => {
|
||||||
setIsAddKeyOpen(null);
|
setIsAddKeyOpen(null);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useQueryClient } from 'react-query';
|
import { useQueryClient } from 'react-query';
|
||||||
import { Button } from '@signozhq/button';
|
import { Button } from '@signozhq/button';
|
||||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||||
import { PowerOff, X } from '@signozhq/icons';
|
import { Trash2, X } from '@signozhq/icons';
|
||||||
import { toast } from '@signozhq/sonner';
|
import { toast } from '@signozhq/sonner';
|
||||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||||
import {
|
import {
|
||||||
getGetServiceAccountQueryKey,
|
getGetServiceAccountQueryKey,
|
||||||
invalidateListServiceAccounts,
|
invalidateListServiceAccounts,
|
||||||
useUpdateServiceAccountStatus,
|
useDeleteServiceAccount,
|
||||||
} from 'api/generated/services/serviceaccount';
|
} from 'api/generated/services/serviceaccount';
|
||||||
import type {
|
import type {
|
||||||
RenderErrorResponseDTO,
|
RenderErrorResponseDTO,
|
||||||
@@ -17,14 +17,14 @@ import { AxiosError } from 'axios';
|
|||||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||||
import { parseAsBoolean, useQueryState } from 'nuqs';
|
import { parseAsBoolean, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
function DisableAccountModal(): JSX.Element {
|
function DeleteAccountModal(): JSX.Element {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [accountId, setAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
|
const [accountId, setAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
|
||||||
const [isDisableOpen, setIsDisableOpen] = useQueryState(
|
const [isDeleteOpen, setIsDeleteOpen] = useQueryState(
|
||||||
SA_QUERY_PARAMS.DISABLE_SA,
|
SA_QUERY_PARAMS.DELETE_SA,
|
||||||
parseAsBoolean.withDefault(false),
|
parseAsBoolean.withDefault(false),
|
||||||
);
|
);
|
||||||
const open = !!isDisableOpen && !!accountId;
|
const open = !!isDeleteOpen && !!accountId;
|
||||||
|
|
||||||
const cachedAccount = accountId
|
const cachedAccount = accountId
|
||||||
? queryClient.getQueryData<{
|
? queryClient.getQueryData<{
|
||||||
@@ -34,13 +34,13 @@ function DisableAccountModal(): JSX.Element {
|
|||||||
const accountName = cachedAccount?.data?.name;
|
const accountName = cachedAccount?.data?.name;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate: updateStatus,
|
mutate: deleteAccount,
|
||||||
isLoading: isDisabling,
|
isLoading: isDeleting,
|
||||||
} = useUpdateServiceAccountStatus({
|
} = useDeleteServiceAccount({
|
||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
toast.success('Service account disabled', { richColors: true });
|
toast.success('Service account deleted', { richColors: true });
|
||||||
await setIsDisableOpen(null);
|
await setIsDeleteOpen(null);
|
||||||
await setAccountId(null);
|
await setAccountId(null);
|
||||||
await invalidateListServiceAccounts(queryClient);
|
await invalidateListServiceAccounts(queryClient);
|
||||||
},
|
},
|
||||||
@@ -48,7 +48,7 @@ function DisableAccountModal(): JSX.Element {
|
|||||||
const errMessage =
|
const errMessage =
|
||||||
convertToApiError(
|
convertToApiError(
|
||||||
error as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
error as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
||||||
)?.getErrorMessage() || 'Failed to disable service account';
|
)?.getErrorMessage() || 'Failed to delete service account';
|
||||||
toast.error(errMessage, { richColors: true });
|
toast.error(errMessage, { richColors: true });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -58,14 +58,13 @@ function DisableAccountModal(): JSX.Element {
|
|||||||
if (!accountId) {
|
if (!accountId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStatus({
|
deleteAccount({
|
||||||
pathParams: { id: accountId },
|
pathParams: { id: accountId },
|
||||||
data: { status: 'DISABLED' },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel(): void {
|
function handleCancel(): void {
|
||||||
setIsDisableOpen(null);
|
setIsDeleteOpen(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -76,17 +75,18 @@ function DisableAccountModal(): JSX.Element {
|
|||||||
handleCancel();
|
handleCancel();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={`Disable service account ${accountName ?? ''}?`}
|
title={`Delete service account ${accountName ?? ''}?`}
|
||||||
width="narrow"
|
width="narrow"
|
||||||
className="alert-dialog sa-disable-dialog"
|
className="alert-dialog sa-delete-dialog"
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
disableOutsideClick={false}
|
disableOutsideClick={false}
|
||||||
>
|
>
|
||||||
<p className="sa-disable-dialog__body">
|
<p className="sa-delete-dialog__body">
|
||||||
Disabling this service account will revoke access for all its keys. Any
|
Are you sure you want to delete <strong>{accountName}</strong>? This action
|
||||||
systems using this account will lose access immediately.
|
cannot be undone. All keys associated with this service account will be
|
||||||
|
permanently removed.
|
||||||
</p>
|
</p>
|
||||||
<DialogFooter className="sa-disable-dialog__footer">
|
<DialogFooter className="sa-delete-dialog__footer">
|
||||||
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
|
<Button variant="solid" color="secondary" size="sm" onClick={handleCancel}>
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
Cancel
|
Cancel
|
||||||
@@ -95,15 +95,15 @@ function DisableAccountModal(): JSX.Element {
|
|||||||
variant="solid"
|
variant="solid"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
loading={isDisabling}
|
loading={isDeleting}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
>
|
>
|
||||||
<PowerOff size={12} />
|
<Trash2 size={12} />
|
||||||
Disable
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogWrapper>
|
</DialogWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DisableAccountModal;
|
export default DeleteAccountModal;
|
||||||
@@ -6,7 +6,7 @@ import { LockKeyhole, Trash2, X } from '@signozhq/icons';
|
|||||||
import { Input } from '@signozhq/input';
|
import { Input } from '@signozhq/input';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||||
import { DatePicker } from 'antd';
|
import { DatePicker } from 'antd';
|
||||||
import type { ServiceaccounttypesFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
|
|
||||||
import { disabledDate, formatLastObservedAt } from '../utils';
|
import { disabledDate, formatLastObservedAt } from '../utils';
|
||||||
@@ -17,7 +17,7 @@ export interface EditKeyFormProps {
|
|||||||
register: UseFormRegister<FormValues>;
|
register: UseFormRegister<FormValues>;
|
||||||
control: Control<FormValues>;
|
control: Control<FormValues>;
|
||||||
expiryMode: ExpiryMode;
|
expiryMode: ExpiryMode;
|
||||||
keyItem: ServiceaccounttypesFactorAPIKeyDTO | null;
|
keyItem: ServiceaccounttypesGettableFactorAPIKeyDTO | null;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from 'api/generated/services/serviceaccount';
|
} from 'api/generated/services/serviceaccount';
|
||||||
import type {
|
import type {
|
||||||
RenderErrorResponseDTO,
|
RenderErrorResponseDTO,
|
||||||
ServiceaccounttypesFactorAPIKeyDTO,
|
ServiceaccounttypesGettableFactorAPIKeyDTO,
|
||||||
} from 'api/generated/services/sigNoz.schemas';
|
} from 'api/generated/services/sigNoz.schemas';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||||
@@ -27,7 +27,7 @@ import { DEFAULT_FORM_VALUES, ExpiryMode } from './types';
|
|||||||
import './EditKeyModal.styles.scss';
|
import './EditKeyModal.styles.scss';
|
||||||
|
|
||||||
export interface EditKeyModalProps {
|
export interface EditKeyModalProps {
|
||||||
keyItem: ServiceaccounttypesFactorAPIKeyDTO | null;
|
keyItem: ServiceaccounttypesGettableFactorAPIKeyDTO | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
function EditKeyModal({ keyItem }: EditKeyModalProps): JSX.Element {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Button } from '@signozhq/button';
|
|||||||
import { KeyRound, X } from '@signozhq/icons';
|
import { KeyRound, X } from '@signozhq/icons';
|
||||||
import { Skeleton, Table, Tooltip } from 'antd';
|
import { Skeleton, Table, Tooltip } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table/interface';
|
import type { ColumnsType } from 'antd/es/table/interface';
|
||||||
import type { ServiceaccounttypesFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs';
|
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs';
|
||||||
@@ -14,7 +14,7 @@ import RevokeKeyModal from './RevokeKeyModal';
|
|||||||
import { formatLastObservedAt } from './utils';
|
import { formatLastObservedAt } from './utils';
|
||||||
|
|
||||||
interface KeysTabProps {
|
interface KeysTabProps {
|
||||||
keys: ServiceaccounttypesFactorAPIKeyDTO[];
|
keys: ServiceaccounttypesGettableFactorAPIKeyDTO[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
@@ -44,7 +44,7 @@ function buildColumns({
|
|||||||
isDisabled,
|
isDisabled,
|
||||||
onRevokeClick,
|
onRevokeClick,
|
||||||
handleformatLastObservedAt,
|
handleformatLastObservedAt,
|
||||||
}: BuildColumnsParams): ColumnsType<ServiceaccounttypesFactorAPIKeyDTO> {
|
}: BuildColumnsParams): ColumnsType<ServiceaccounttypesGettableFactorAPIKeyDTO> {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: 'Name',
|
title: 'Name',
|
||||||
@@ -183,7 +183,7 @@ function KeysTab({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Todo: use new table component from periscope when ready */}
|
{/* Todo: use new table component from periscope when ready */}
|
||||||
<Table<ServiceaccounttypesFactorAPIKeyDTO>
|
<Table<ServiceaccounttypesGettableFactorAPIKeyDTO>
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={keys}
|
dataSource={keys}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
|
|||||||
import { useTimezone } from 'providers/Timezone';
|
import { useTimezone } from 'providers/Timezone';
|
||||||
import APIError from 'types/api/error';
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
|
import SaveErrorItem from './SaveErrorItem';
|
||||||
|
import type { SaveError } from './utils';
|
||||||
|
|
||||||
interface OverviewTabProps {
|
interface OverviewTabProps {
|
||||||
account: ServiceAccountRow;
|
account: ServiceAccountRow;
|
||||||
localName: string;
|
localName: string;
|
||||||
@@ -21,6 +24,7 @@ interface OverviewTabProps {
|
|||||||
rolesError?: boolean;
|
rolesError?: boolean;
|
||||||
rolesErrorObj?: APIError | undefined;
|
rolesErrorObj?: APIError | undefined;
|
||||||
onRefetchRoles?: () => void;
|
onRefetchRoles?: () => void;
|
||||||
|
saveErrors?: SaveError[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function OverviewTab({
|
function OverviewTab({
|
||||||
@@ -35,6 +39,7 @@ function OverviewTab({
|
|||||||
rolesError,
|
rolesError,
|
||||||
rolesErrorObj,
|
rolesErrorObj,
|
||||||
onRefetchRoles,
|
onRefetchRoles,
|
||||||
|
saveErrors = [],
|
||||||
}: OverviewTabProps): JSX.Element {
|
}: OverviewTabProps): JSX.Element {
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||||
|
|
||||||
@@ -92,11 +97,14 @@ function OverviewTab({
|
|||||||
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
|
<div className="sa-drawer__input-wrapper sa-drawer__input-wrapper--disabled">
|
||||||
<div className="sa-drawer__disabled-roles">
|
<div className="sa-drawer__disabled-roles">
|
||||||
{localRoles.length > 0 ? (
|
{localRoles.length > 0 ? (
|
||||||
localRoles.map((r) => (
|
localRoles.map((roleId) => {
|
||||||
<Badge key={r} color="vanilla">
|
const role = availableRoles.find((r) => r.id === roleId);
|
||||||
{r}
|
return (
|
||||||
</Badge>
|
<Badge key={roleId} color="vanilla">
|
||||||
))
|
{role?.name ?? roleId}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})
|
||||||
) : (
|
) : (
|
||||||
<span className="sa-drawer__input-text">—</span>
|
<span className="sa-drawer__input-text">—</span>
|
||||||
)}
|
)}
|
||||||
@@ -126,9 +134,13 @@ function OverviewTab({
|
|||||||
<Badge color="forest" variant="outline">
|
<Badge color="forest" variant="outline">
|
||||||
ACTIVE
|
ACTIVE
|
||||||
</Badge>
|
</Badge>
|
||||||
|
) : account.status?.toUpperCase() === 'DELETED' ? (
|
||||||
|
<Badge color="cherry" variant="outline">
|
||||||
|
DELETED
|
||||||
|
</Badge>
|
||||||
) : (
|
) : (
|
||||||
<Badge color="vanilla" variant="outline" className="sa-status-badge">
|
<Badge color="vanilla" variant="outline" className="sa-status-badge">
|
||||||
DISABLED
|
{account.status ? account.status.toUpperCase() : 'UNKNOWN'}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -143,6 +155,19 @@ function OverviewTab({
|
|||||||
<Badge color="vanilla">{formatTimestamp(account.updatedAt)}</Badge>
|
<Badge color="vanilla">{formatTimestamp(account.updatedAt)}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{saveErrors.length > 0 && (
|
||||||
|
<div className="sa-drawer__save-errors">
|
||||||
|
{saveErrors.map(({ context, apiError, onRetry }) => (
|
||||||
|
<SaveErrorItem
|
||||||
|
key={context}
|
||||||
|
context={context}
|
||||||
|
apiError={apiError}
|
||||||
|
onRetry={onRetry}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from 'api/generated/services/serviceaccount';
|
} from 'api/generated/services/serviceaccount';
|
||||||
import type {
|
import type {
|
||||||
RenderErrorResponseDTO,
|
RenderErrorResponseDTO,
|
||||||
ServiceaccounttypesFactorAPIKeyDTO,
|
ServiceaccounttypesGettableFactorAPIKeyDTO,
|
||||||
} from 'api/generated/services/sigNoz.schemas';
|
} from 'api/generated/services/sigNoz.schemas';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||||
@@ -64,9 +64,9 @@ function RevokeKeyModal(): JSX.Element {
|
|||||||
const open = !!revokeKeyId && !!accountId;
|
const open = !!revokeKeyId && !!accountId;
|
||||||
|
|
||||||
const cachedKeys = accountId
|
const cachedKeys = accountId
|
||||||
? queryClient.getQueryData<{ data: ServiceaccounttypesFactorAPIKeyDTO[] }>(
|
? queryClient.getQueryData<{
|
||||||
getListServiceAccountKeysQueryKey({ id: accountId }),
|
data: ServiceaccounttypesGettableFactorAPIKeyDTO[];
|
||||||
)
|
}>(getListServiceAccountKeysQueryKey({ id: accountId }))
|
||||||
: null;
|
: null;
|
||||||
const keyName = cachedKeys?.data?.find((k) => k.id === revokeKeyId)?.name;
|
const keyName = cachedKeys?.data?.find((k) => k.id === revokeKeyId)?.name;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@signozhq/button';
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { ChevronDown, ChevronUp, CircleAlert, RotateCw } from '@signozhq/icons';
|
||||||
|
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
|
interface SaveErrorItemProps {
|
||||||
|
context: string;
|
||||||
|
apiError: APIError;
|
||||||
|
onRetry?: () => void | Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SaveErrorItem({
|
||||||
|
context,
|
||||||
|
apiError,
|
||||||
|
onRetry,
|
||||||
|
}: SaveErrorItemProps): JSX.Element {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const [isRetrying, setIsRetrying] = useState(false);
|
||||||
|
|
||||||
|
const ChevronIcon = expanded ? ChevronUp : ChevronDown;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sa-error-item">
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className="sa-error-item__header"
|
||||||
|
aria-disabled={isRetrying}
|
||||||
|
onClick={(): void => {
|
||||||
|
if (!isRetrying) {
|
||||||
|
setExpanded((prev) => !prev);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleAlert size={12} className="sa-error-item__icon" />
|
||||||
|
<span className="sa-error-item__title">
|
||||||
|
{isRetrying ? 'Retrying...' : `${context}: ${apiError.getErrorMessage()}`}
|
||||||
|
</span>
|
||||||
|
{onRetry && !isRetrying && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
aria-label="Retry"
|
||||||
|
size="xs"
|
||||||
|
onClick={async (e): Promise<void> => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsRetrying(true);
|
||||||
|
setExpanded(false);
|
||||||
|
try {
|
||||||
|
await onRetry();
|
||||||
|
} finally {
|
||||||
|
setIsRetrying(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RotateCw size={12} color={Color.BG_CHERRY_400} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!isRetrying && (
|
||||||
|
<ChevronIcon size={14} className="sa-error-item__chevron" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{expanded && !isRetrying && (
|
||||||
|
<div className="sa-error-item__body">
|
||||||
|
<ErrorContent error={apiError} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SaveErrorItem;
|
||||||
@@ -92,6 +92,23 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-8);
|
gap: var(--spacing-8);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(136, 136, 136, 0.4);
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(136, 136, 136, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
@@ -239,6 +256,113 @@
|
|||||||
letter-spacing: 0.48px;
|
letter-spacing: 0.48px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__save-errors {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-error-item {
|
||||||
|
border: 1px solid var(--l1-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-3);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--padding-2) var(--padding-4);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(229, 72, 77, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-disabled='true'] {
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--callout-error-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--bg-cherry-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--bg-cherry-500);
|
||||||
|
line-height: var(--line-height-18);
|
||||||
|
letter-spacing: -0.06px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__chevron {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--l2-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
border-top: 1px solid var(--l1-border);
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
&__summary {
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__summary-left {
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error-code {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error-message {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__docs-button {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message-badge {
|
||||||
|
padding: 0 12px 10px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message-item {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 12px 2px 22px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keys-tab {
|
.keys-tab {
|
||||||
@@ -429,7 +553,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-disable-dialog {
|
.sa-delete-dialog {
|
||||||
background: var(--l2-background);
|
background: var(--l2-background);
|
||||||
border: 1px solid var(--l2-border);
|
border: 1px solid var(--l2-border);
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useQueryClient } from 'react-query';
|
||||||
import { Button } from '@signozhq/button';
|
import { Button } from '@signozhq/button';
|
||||||
import { DrawerWrapper } from '@signozhq/drawer';
|
import { DrawerWrapper } from '@signozhq/drawer';
|
||||||
import { Key, LayoutGrid, Plus, PowerOff, X } from '@signozhq/icons';
|
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
|
||||||
import { toast } from '@signozhq/sonner';
|
import { toast } from '@signozhq/sonner';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||||
import { Pagination, Skeleton } from 'antd';
|
import { Pagination, Skeleton } from 'antd';
|
||||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||||
import {
|
import {
|
||||||
|
getListServiceAccountsQueryKey,
|
||||||
useGetServiceAccount,
|
useGetServiceAccount,
|
||||||
useListServiceAccountKeys,
|
useListServiceAccountKeys,
|
||||||
useUpdateServiceAccount,
|
useUpdateServiceAccount,
|
||||||
} from 'api/generated/services/serviceaccount';
|
} from 'api/generated/services/serviceaccount';
|
||||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||||
import { useRoles } from 'components/RolesSelect';
|
import { useRoles } from 'components/RolesSelect';
|
||||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||||
import {
|
import {
|
||||||
ServiceAccountRow,
|
ServiceAccountRow,
|
||||||
|
ServiceAccountStatus,
|
||||||
toServiceAccountRow,
|
toServiceAccountRow,
|
||||||
} from 'container/ServiceAccountsSettings/utils';
|
} from 'container/ServiceAccountsSettings/utils';
|
||||||
|
import { useServiceAccountRoleManager } from 'hooks/serviceAccount/useServiceAccountRoleManager';
|
||||||
import {
|
import {
|
||||||
parseAsBoolean,
|
parseAsBoolean,
|
||||||
parseAsInteger,
|
parseAsInteger,
|
||||||
@@ -27,12 +31,14 @@ import {
|
|||||||
parseAsStringEnum,
|
parseAsStringEnum,
|
||||||
useQueryState,
|
useQueryState,
|
||||||
} from 'nuqs';
|
} from 'nuqs';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { toAPIError } from 'utils/errorUtils';
|
import { toAPIError } from 'utils/errorUtils';
|
||||||
|
|
||||||
import AddKeyModal from './AddKeyModal';
|
import AddKeyModal from './AddKeyModal';
|
||||||
import DisableAccountModal from './DisableAccountModal';
|
import DeleteAccountModal from './DeleteAccountModal';
|
||||||
import KeysTab from './KeysTab';
|
import KeysTab from './KeysTab';
|
||||||
import OverviewTab from './OverviewTab';
|
import OverviewTab from './OverviewTab';
|
||||||
|
import type { SaveError } from './utils';
|
||||||
import { ServiceAccountDrawerTab } from './utils';
|
import { ServiceAccountDrawerTab } from './utils';
|
||||||
|
|
||||||
import './ServiceAccountDrawer.styles.scss';
|
import './ServiceAccountDrawer.styles.scss';
|
||||||
@@ -69,12 +75,16 @@ function ServiceAccountDrawer({
|
|||||||
SA_QUERY_PARAMS.ADD_KEY,
|
SA_QUERY_PARAMS.ADD_KEY,
|
||||||
parseAsBoolean.withDefault(false),
|
parseAsBoolean.withDefault(false),
|
||||||
);
|
);
|
||||||
const [, setIsDisableOpen] = useQueryState(
|
const [, setIsDeleteOpen] = useQueryState(
|
||||||
SA_QUERY_PARAMS.DISABLE_SA,
|
SA_QUERY_PARAMS.DELETE_SA,
|
||||||
parseAsBoolean.withDefault(false),
|
parseAsBoolean.withDefault(false),
|
||||||
);
|
);
|
||||||
const [localName, setLocalName] = useState('');
|
const [localName, setLocalName] = useState('');
|
||||||
const [localRoles, setLocalRoles] = useState<string[]>([]);
|
const [localRoles, setLocalRoles] = useState<string[]>([]);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [saveErrors, setSaveErrors] = useState<SaveError[]>([]);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: accountData,
|
data: accountData,
|
||||||
@@ -93,21 +103,30 @@ function ServiceAccountDrawer({
|
|||||||
[accountData],
|
[accountData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { currentRoles, applyDiff } = useServiceAccountRoleManager(
|
||||||
|
selectedAccountId ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (account) {
|
if (account?.id) {
|
||||||
setLocalName(account.name ?? '');
|
setLocalName(account?.name ?? '');
|
||||||
setLocalRoles(account.roles ?? []);
|
|
||||||
setKeysPage(1);
|
setKeysPage(1);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
setSaveErrors([]);
|
||||||
}, [account?.id]);
|
}, [account?.id, account?.name, setKeysPage]);
|
||||||
|
|
||||||
const isDisabled = account?.status?.toUpperCase() !== 'ACTIVE';
|
useEffect(() => {
|
||||||
|
setLocalRoles(currentRoles.map((r) => r.id).filter(Boolean) as string[]);
|
||||||
|
}, [currentRoles]);
|
||||||
|
|
||||||
|
const isDeleted =
|
||||||
|
account?.status?.toUpperCase() === ServiceAccountStatus.Deleted;
|
||||||
|
|
||||||
const isDirty =
|
const isDirty =
|
||||||
account !== null &&
|
account !== null &&
|
||||||
(localName !== (account.name ?? '') ||
|
(localName !== (account.name ?? '') ||
|
||||||
JSON.stringify(localRoles) !== JSON.stringify(account.roles ?? []));
|
JSON.stringify([...localRoles].sort()) !==
|
||||||
|
JSON.stringify([...currentRoles.map((r) => r.id).filter(Boolean)].sort()));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roles: availableRoles,
|
roles: availableRoles,
|
||||||
@@ -133,51 +152,189 @@ function ServiceAccountDrawer({
|
|||||||
}
|
}
|
||||||
}, [keysLoading, keys.length, keysPage, setKeysPage]);
|
}, [keysLoading, keys.length, keysPage, setKeysPage]);
|
||||||
|
|
||||||
const { mutate: updateAccount, isLoading: isSaving } = useUpdateServiceAccount(
|
// the retry for this mutation is safe due to the api being idempotent on backend
|
||||||
{
|
const { mutateAsync: updateMutateAsync } = useUpdateServiceAccount();
|
||||||
mutation: {
|
|
||||||
onSuccess: () => {
|
const toSaveApiError = useCallback(
|
||||||
toast.success('Service account updated successfully', {
|
(err: unknown): APIError =>
|
||||||
richColors: true,
|
convertToApiError(err as AxiosError<RenderErrorResponseDTO>) ??
|
||||||
});
|
toAPIError(err as AxiosError<RenderErrorResponseDTO>),
|
||||||
refetchAccount();
|
[],
|
||||||
onSuccess({ closeDrawer: false });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
const errMessage =
|
|
||||||
convertToApiError(
|
|
||||||
error as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
|
||||||
)?.getErrorMessage() || 'Failed to update service account';
|
|
||||||
toast.error(errMessage, { richColors: true });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleSave(): void {
|
const retryNameUpdate = useCallback(async (): Promise<void> => {
|
||||||
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateMutateAsync({
|
||||||
|
pathParams: { id: account.id },
|
||||||
|
data: { name: localName },
|
||||||
|
});
|
||||||
|
setSaveErrors((prev) => prev.filter((e) => e.context !== 'Name update'));
|
||||||
|
refetchAccount();
|
||||||
|
queryClient.invalidateQueries(getListServiceAccountsQueryKey());
|
||||||
|
} catch (err) {
|
||||||
|
setSaveErrors((prev) =>
|
||||||
|
prev.map((e) =>
|
||||||
|
e.context === 'Name update' ? { ...e, apiError: toSaveApiError(err) } : e,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
account,
|
||||||
|
localName,
|
||||||
|
updateMutateAsync,
|
||||||
|
refetchAccount,
|
||||||
|
queryClient,
|
||||||
|
toSaveApiError,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleNameChange = useCallback((name: string): void => {
|
||||||
|
setLocalName(name);
|
||||||
|
setSaveErrors((prev) => prev.filter((e) => e.context !== 'Name update'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const makeRoleRetry = useCallback(
|
||||||
|
(
|
||||||
|
context: string,
|
||||||
|
rawRetry: () => Promise<void>,
|
||||||
|
) => async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await rawRetry();
|
||||||
|
setSaveErrors((prev) => prev.filter((e) => e.context !== context));
|
||||||
|
} catch (err) {
|
||||||
|
setSaveErrors((prev) =>
|
||||||
|
prev.map((e) =>
|
||||||
|
e.context === context ? { ...e, apiError: toSaveApiError(err) } : e,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[toSaveApiError],
|
||||||
|
);
|
||||||
|
|
||||||
|
const retryRolesUpdate = useCallback(async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const failures = await applyDiff(localRoles, availableRoles);
|
||||||
|
if (failures.length === 0) {
|
||||||
|
setSaveErrors((prev) => prev.filter((e) => e.context !== 'Roles update'));
|
||||||
|
} else {
|
||||||
|
setSaveErrors((prev) => {
|
||||||
|
const rest = prev.filter((e) => e.context !== 'Roles update');
|
||||||
|
const roleErrors = failures.map((f) => {
|
||||||
|
const ctx = `Role '${f.roleName}'`;
|
||||||
|
return {
|
||||||
|
context: ctx,
|
||||||
|
apiError: toSaveApiError(f.error),
|
||||||
|
onRetry: makeRoleRetry(ctx, f.onRetry),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return [...rest, ...roleErrors];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setSaveErrors((prev) =>
|
||||||
|
prev.map((e) =>
|
||||||
|
e.context === 'Roles update' ? { ...e, apiError: toSaveApiError(err) } : e,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [localRoles, availableRoles, applyDiff, toSaveApiError, makeRoleRetry]);
|
||||||
|
|
||||||
|
const handleSave = useCallback(async (): Promise<void> => {
|
||||||
if (!account || !isDirty) {
|
if (!account || !isDirty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateAccount({
|
setSaveErrors([]);
|
||||||
pathParams: { id: account.id },
|
setIsSaving(true);
|
||||||
data: { name: localName, email: account.email, roles: localRoles },
|
try {
|
||||||
});
|
const namePromise =
|
||||||
}
|
localName !== (account.name ?? '')
|
||||||
|
? updateMutateAsync({
|
||||||
|
pathParams: { id: account.id },
|
||||||
|
data: { name: localName },
|
||||||
|
})
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
|
const [nameResult, rolesResult] = await Promise.allSettled([
|
||||||
|
namePromise,
|
||||||
|
applyDiff(localRoles, availableRoles),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const errors: SaveError[] = [];
|
||||||
|
|
||||||
|
if (nameResult.status === 'rejected') {
|
||||||
|
errors.push({
|
||||||
|
context: 'Name update',
|
||||||
|
apiError: toSaveApiError(nameResult.reason),
|
||||||
|
onRetry: retryNameUpdate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rolesResult.status === 'rejected') {
|
||||||
|
errors.push({
|
||||||
|
context: 'Roles update',
|
||||||
|
apiError: toSaveApiError(rolesResult.reason),
|
||||||
|
onRetry: retryRolesUpdate,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (const failure of rolesResult.value) {
|
||||||
|
const context = `Role '${failure.roleName}'`;
|
||||||
|
errors.push({
|
||||||
|
context,
|
||||||
|
apiError: toSaveApiError(failure.error),
|
||||||
|
onRetry: makeRoleRetry(context, failure.onRetry),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
setSaveErrors(errors);
|
||||||
|
} else {
|
||||||
|
toast.success('Service account updated successfully', {
|
||||||
|
richColors: true,
|
||||||
|
});
|
||||||
|
onSuccess({ closeDrawer: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
refetchAccount();
|
||||||
|
queryClient.invalidateQueries(getListServiceAccountsQueryKey());
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
account,
|
||||||
|
isDirty,
|
||||||
|
localName,
|
||||||
|
localRoles,
|
||||||
|
availableRoles,
|
||||||
|
updateMutateAsync,
|
||||||
|
applyDiff,
|
||||||
|
refetchAccount,
|
||||||
|
onSuccess,
|
||||||
|
queryClient,
|
||||||
|
toSaveApiError,
|
||||||
|
retryNameUpdate,
|
||||||
|
makeRoleRetry,
|
||||||
|
retryRolesUpdate,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleClose = useCallback((): void => {
|
const handleClose = useCallback((): void => {
|
||||||
setIsDisableOpen(null);
|
setIsDeleteOpen(null);
|
||||||
setIsAddKeyOpen(null);
|
setIsAddKeyOpen(null);
|
||||||
setSelectedAccountId(null);
|
setSelectedAccountId(null);
|
||||||
setActiveTab(null);
|
setActiveTab(null);
|
||||||
setKeysPage(null);
|
setKeysPage(null);
|
||||||
setEditKeyId(null);
|
setEditKeyId(null);
|
||||||
|
setSaveErrors([]);
|
||||||
}, [
|
}, [
|
||||||
setSelectedAccountId,
|
setSelectedAccountId,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
setKeysPage,
|
setKeysPage,
|
||||||
setEditKeyId,
|
setEditKeyId,
|
||||||
setIsAddKeyOpen,
|
setIsAddKeyOpen,
|
||||||
setIsDisableOpen,
|
setIsDeleteOpen,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const drawerContent = (
|
const drawerContent = (
|
||||||
@@ -220,7 +377,7 @@ function ServiceAccountDrawer({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
disabled={isDisabled}
|
disabled={isDeleted}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setIsAddKeyOpen(true);
|
setIsAddKeyOpen(true);
|
||||||
}}
|
}}
|
||||||
@@ -251,22 +408,23 @@ function ServiceAccountDrawer({
|
|||||||
<OverviewTab
|
<OverviewTab
|
||||||
account={account}
|
account={account}
|
||||||
localName={localName}
|
localName={localName}
|
||||||
onNameChange={setLocalName}
|
onNameChange={handleNameChange}
|
||||||
localRoles={localRoles}
|
localRoles={localRoles}
|
||||||
onRolesChange={setLocalRoles}
|
onRolesChange={setLocalRoles}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDeleted}
|
||||||
availableRoles={availableRoles}
|
availableRoles={availableRoles}
|
||||||
rolesLoading={rolesLoading}
|
rolesLoading={rolesLoading}
|
||||||
rolesError={rolesError}
|
rolesError={rolesError}
|
||||||
rolesErrorObj={rolesErrorObj}
|
rolesErrorObj={rolesErrorObj}
|
||||||
onRefetchRoles={refetchRoles}
|
onRefetchRoles={refetchRoles}
|
||||||
|
saveErrors={saveErrors}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === ServiceAccountDrawerTab.Keys && (
|
{activeTab === ServiceAccountDrawerTab.Keys && (
|
||||||
<KeysTab
|
<KeysTab
|
||||||
keys={keys}
|
keys={keys}
|
||||||
isLoading={keysLoading}
|
isLoading={keysLoading}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDeleted}
|
||||||
currentPage={keysPage}
|
currentPage={keysPage}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
/>
|
/>
|
||||||
@@ -298,20 +456,20 @@ function ServiceAccountDrawer({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!isDisabled && (
|
{!isDeleted && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
className="sa-drawer__footer-btn"
|
className="sa-drawer__footer-btn"
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setIsDisableOpen(true);
|
setIsDeleteOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PowerOff size={12} />
|
<Trash2 size={12} />
|
||||||
Disable Service Account
|
Delete Service Account
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isDisabled && (
|
{!isDeleted && (
|
||||||
<div className="sa-drawer__footer-right">
|
<div className="sa-drawer__footer-right">
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -359,7 +517,7 @@ function ServiceAccountDrawer({
|
|||||||
className="sa-drawer"
|
className="sa-drawer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DisableAccountModal />
|
<DeleteAccountModal />
|
||||||
|
|
||||||
<AddKeyModal />
|
<AddKeyModal />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,6 +9,16 @@ 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';
|
||||||
@@ -35,16 +45,9 @@ 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)),
|
||||||
@@ -90,9 +93,6 @@ 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,14 +115,12 @@ describe('AddKeyModal', () => {
|
|||||||
await user.click(copyBtn);
|
await user.click(copyBtn);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(writeTextSpy).toHaveBeenCalledWith('snz_abc123xyz456secret');
|
expect(mockCopyToClipboard).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 () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { toast } from '@signozhq/sonner';
|
import { toast } from '@signozhq/sonner';
|
||||||
import type { ServiceaccounttypesFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { rest, server } from 'mocks-server/server';
|
import { rest, server } from 'mocks-server/server';
|
||||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
@@ -14,17 +14,16 @@ const mockToast = jest.mocked(toast);
|
|||||||
|
|
||||||
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/key-1';
|
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/key-1';
|
||||||
|
|
||||||
const mockKey: ServiceaccounttypesFactorAPIKeyDTO = {
|
const mockKey: ServiceaccounttypesGettableFactorAPIKeyDTO = {
|
||||||
id: 'key-1',
|
id: 'key-1',
|
||||||
name: 'Original Key Name',
|
name: 'Original Key Name',
|
||||||
expiresAt: 0,
|
expiresAt: 0,
|
||||||
lastObservedAt: null as any,
|
lastObservedAt: null as any,
|
||||||
key: 'snz_abc123',
|
|
||||||
serviceAccountId: 'sa-1',
|
serviceAccountId: 'sa-1',
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderModal(
|
function renderModal(
|
||||||
keyItem: ServiceaccounttypesFactorAPIKeyDTO | null = mockKey,
|
keyItem: ServiceaccounttypesGettableFactorAPIKeyDTO | null = mockKey,
|
||||||
searchParams: Record<string, string> = {
|
searchParams: Record<string, string> = {
|
||||||
account: 'sa-1',
|
account: 'sa-1',
|
||||||
'edit-key': 'key-1',
|
'edit-key': 'key-1',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { toast } from '@signozhq/sonner';
|
import { toast } from '@signozhq/sonner';
|
||||||
import { ServiceaccounttypesFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
import { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||||
import { rest, server } from 'mocks-server/server';
|
import { rest, server } from 'mocks-server/server';
|
||||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
@@ -14,13 +14,12 @@ const mockToast = jest.mocked(toast);
|
|||||||
|
|
||||||
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/:fid';
|
const SA_KEY_ENDPOINT = '*/api/v1/service_accounts/sa-1/keys/:fid';
|
||||||
|
|
||||||
const keys: ServiceaccounttypesFactorAPIKeyDTO[] = [
|
const keys: ServiceaccounttypesGettableFactorAPIKeyDTO[] = [
|
||||||
{
|
{
|
||||||
id: 'key-1',
|
id: 'key-1',
|
||||||
name: 'Production Key',
|
name: 'Production Key',
|
||||||
expiresAt: 0,
|
expiresAt: 0,
|
||||||
lastObservedAt: null as any,
|
lastObservedAt: null as any,
|
||||||
key: 'snz_prod_123',
|
|
||||||
serviceAccountId: 'sa-1',
|
serviceAccountId: 'sa-1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -28,7 +27,6 @@ const keys: ServiceaccounttypesFactorAPIKeyDTO[] = [
|
|||||||
name: 'Staging Key',
|
name: 'Staging Key',
|
||||||
expiresAt: 1924905600, // 2030-12-31
|
expiresAt: 1924905600, // 2030-12-31
|
||||||
lastObservedAt: new Date('2026-03-10T10:00:00Z'),
|
lastObservedAt: new Date('2026-03-10T10:00:00Z'),
|
||||||
key: 'snz_stag_456',
|
|
||||||
serviceAccountId: 'sa-1',
|
serviceAccountId: 'sa-1',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ jest.mock('@signozhq/sonner', () => ({
|
|||||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||||
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/:id/keys';
|
const SA_KEYS_ENDPOINT = '*/api/v1/service_accounts/:id/keys';
|
||||||
const SA_ENDPOINT = '*/api/v1/service_accounts/sa-1';
|
const SA_ENDPOINT = '*/api/v1/service_accounts/sa-1';
|
||||||
const SA_STATUS_ENDPOINT = '*/api/v1/service_accounts/sa-1/status';
|
const SA_DELETE_ENDPOINT = '*/api/v1/service_accounts/sa-1';
|
||||||
|
const SA_ROLES_ENDPOINT = '*/api/v1/service_accounts/:id/roles';
|
||||||
|
const SA_ROLE_DELETE_ENDPOINT = '*/api/v1/service_accounts/:id/roles/:rid';
|
||||||
|
|
||||||
const activeAccountResponse = {
|
const activeAccountResponse = {
|
||||||
id: 'sa-1',
|
id: 'sa-1',
|
||||||
@@ -35,10 +37,10 @@ const activeAccountResponse = {
|
|||||||
updatedAt: '2026-01-02T00:00:00Z',
|
updatedAt: '2026-01-02T00:00:00Z',
|
||||||
};
|
};
|
||||||
|
|
||||||
const disabledAccountResponse = {
|
const deletedAccountResponse = {
|
||||||
...activeAccountResponse,
|
...activeAccountResponse,
|
||||||
id: 'sa-2',
|
id: 'sa-2',
|
||||||
status: 'DISABLED',
|
status: 'DELETED',
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderDrawer(
|
function renderDrawer(
|
||||||
@@ -67,7 +69,23 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
),
|
),
|
||||||
rest.put(SA_STATUS_ENDPOINT, (_, res, ctx) =>
|
rest.delete(SA_DELETE_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
rest.get(SA_ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(200),
|
||||||
|
ctx.json({
|
||||||
|
data: listRolesSuccessResponse.data.filter(
|
||||||
|
(r) => r.name === 'signoz-admin',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rest.post(SA_ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
rest.delete(SA_ROLE_DELETE_ENDPOINT, (_, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -115,8 +133,6 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
expect(updateSpy).toHaveBeenCalledWith(
|
expect(updateSpy).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'CI Bot Updated',
|
name: 'CI Bot Updated',
|
||||||
email: 'ci-bot@signoz.io',
|
|
||||||
roles: ['signoz-admin'],
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(onSuccess).toHaveBeenCalledWith({ closeDrawer: false });
|
expect(onSuccess).toHaveBeenCalledWith({ closeDrawer: false });
|
||||||
@@ -125,6 +141,7 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
|
|
||||||
it('changing roles enables Save; clicking Save sends updated roles in payload', async () => {
|
it('changing roles enables Save; clicking Save sends updated roles in payload', async () => {
|
||||||
const updateSpy = jest.fn();
|
const updateSpy = jest.fn();
|
||||||
|
const roleSpy = jest.fn();
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
@@ -132,6 +149,10 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
updateSpy(await req.json());
|
updateSpy(await req.json());
|
||||||
return res(ctx.status(200), ctx.json({ status: 'success', data: {} }));
|
return res(ctx.status(200), ctx.json({ status: 'success', data: {} }));
|
||||||
}),
|
}),
|
||||||
|
rest.post(SA_ROLES_ENDPOINT, async (req, res, ctx) => {
|
||||||
|
roleSpy(await req.json());
|
||||||
|
return res(ctx.status(200), ctx.json({ status: 'success', data: {} }));
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
renderDrawer();
|
renderDrawer();
|
||||||
@@ -146,21 +167,22 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
await user.click(saveBtn);
|
await user.click(saveBtn);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(updateSpy).toHaveBeenCalledWith(
|
expect(updateSpy).not.toHaveBeenCalled();
|
||||||
|
expect(roleSpy).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
roles: expect.arrayContaining(['signoz-admin', 'signoz-viewer']),
|
id: '019c24aa-2248-7585-a129-4188b3473c27',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('"Disable Service Account" opens confirm dialog; confirming sends correct status payload', async () => {
|
it('"Delete Service Account" opens confirm dialog; confirming sends delete request', async () => {
|
||||||
const statusSpy = jest.fn();
|
const deleteSpy = jest.fn();
|
||||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
rest.put(SA_STATUS_ENDPOINT, async (req, res, ctx) => {
|
rest.delete(SA_DELETE_ENDPOINT, (_, res, ctx) => {
|
||||||
statusSpy(await req.json());
|
deleteSpy();
|
||||||
return res(ctx.status(200), ctx.json({ status: 'success', data: {} }));
|
return res(ctx.status(200), ctx.json({ status: 'success', data: {} }));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -170,19 +192,19 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
await screen.findByDisplayValue('CI Bot');
|
await screen.findByDisplayValue('CI Bot');
|
||||||
|
|
||||||
await user.click(
|
await user.click(
|
||||||
screen.getByRole('button', { name: /Disable Service Account/i }),
|
screen.getByRole('button', { name: /Delete Service Account/i }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const dialog = await screen.findByRole('dialog', {
|
const dialog = await screen.findByRole('dialog', {
|
||||||
name: /Disable service account CI Bot/i,
|
name: /Delete service account CI Bot/i,
|
||||||
});
|
});
|
||||||
expect(dialog).toBeInTheDocument();
|
expect(dialog).toBeInTheDocument();
|
||||||
|
|
||||||
const confirmBtns = screen.getAllByRole('button', { name: /^Disable$/i });
|
const confirmBtns = screen.getAllByRole('button', { name: /^Delete$/i });
|
||||||
await user.click(confirmBtns[confirmBtns.length - 1]);
|
await user.click(confirmBtns[confirmBtns.length - 1]);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(statusSpy).toHaveBeenCalledWith({ status: 'DISABLED' });
|
expect(deleteSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -190,14 +212,17 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('disabled account shows read-only name, no Save button, no Disable button', async () => {
|
it('deleted account shows read-only name, no Save button, no Delete button', async () => {
|
||||||
server.use(
|
server.use(
|
||||||
rest.get('*/api/v1/service_accounts/sa-2', (_, res, ctx) =>
|
rest.get('*/api/v1/service_accounts/sa-2', (_, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json({ data: disabledAccountResponse })),
|
res(ctx.status(200), ctx.json({ data: deletedAccountResponse })),
|
||||||
),
|
),
|
||||||
rest.get('*/api/v1/service_accounts/sa-2/keys', (_, res, ctx) =>
|
rest.get('*/api/v1/service_accounts/sa-2/keys', (_, res, ctx) =>
|
||||||
res(ctx.status(200), ctx.json({ data: [] })),
|
res(ctx.status(200), ctx.json({ data: [] })),
|
||||||
),
|
),
|
||||||
|
rest.get('*/api/v1/service_accounts/sa-2/roles', (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ data: [] })),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
renderDrawer({ account: 'sa-2' });
|
renderDrawer({ account: 'sa-2' });
|
||||||
@@ -208,7 +233,7 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
screen.queryByRole('button', { name: /Save Changes/i }),
|
screen.queryByRole('button', { name: /Save Changes/i }),
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.queryByRole('button', { name: /Disable Service Account/i }),
|
screen.queryByRole('button', { name: /Delete Service Account/i }),
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(screen.queryByDisplayValue('CI Bot')).not.toBeInTheDocument();
|
expect(screen.queryByDisplayValue('CI Bot')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -248,3 +273,169 @@ describe('ServiceAccountDrawer', () => {
|
|||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ServiceAccountDrawer – save-error UX', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
server.use(
|
||||||
|
rest.get(ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json(listRolesSuccessResponse)),
|
||||||
|
),
|
||||||
|
rest.get(SA_KEYS_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ data: [] })),
|
||||||
|
),
|
||||||
|
rest.get(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ data: activeAccountResponse })),
|
||||||
|
),
|
||||||
|
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
rest.delete(SA_DELETE_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
rest.get(SA_ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(200),
|
||||||
|
ctx.json({
|
||||||
|
data: listRolesSuccessResponse.data.filter(
|
||||||
|
(r) => r.name === 'signoz-admin',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rest.post(SA_ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
rest.delete(SA_ROLE_DELETE_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
server.resetHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('name update failure shows SaveErrorItem with "Name update" context', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(500),
|
||||||
|
ctx.json({
|
||||||
|
error: {
|
||||||
|
code: 'INTERNAL_ERROR',
|
||||||
|
message: 'name update failed',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
renderDrawer();
|
||||||
|
|
||||||
|
const nameInput = await screen.findByDisplayValue('CI Bot');
|
||||||
|
await user.clear(nameInput);
|
||||||
|
await user.type(nameInput, 'New Name');
|
||||||
|
|
||||||
|
const saveBtn = screen.getByRole('button', { name: /Save Changes/i });
|
||||||
|
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||||
|
await user.click(saveBtn);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText(/Name update.*name update failed/i, undefined, {
|
||||||
|
timeout: 5000,
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('role update failure shows SaveErrorItem with the role name context', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.post(SA_ROLES_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(500),
|
||||||
|
ctx.json({
|
||||||
|
error: {
|
||||||
|
code: 'INTERNAL_ERROR',
|
||||||
|
message: 'role assign failed',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
renderDrawer();
|
||||||
|
|
||||||
|
await screen.findByDisplayValue('CI Bot');
|
||||||
|
|
||||||
|
// Add the signoz-viewer role (which is not currently assigned)
|
||||||
|
await user.click(screen.getByLabelText('Roles'));
|
||||||
|
await user.click(await screen.findByTitle('signoz-viewer'));
|
||||||
|
|
||||||
|
const saveBtn = screen.getByRole('button', { name: /Save Changes/i });
|
||||||
|
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||||
|
await user.click(saveBtn);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
/Role 'signoz-viewer'.*role assign failed/i,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clicking Retry on a name-update error re-triggers the request; on success the error item is removed', async () => {
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
// First: PUT always fails so the error appears
|
||||||
|
server.use(
|
||||||
|
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(
|
||||||
|
ctx.status(500),
|
||||||
|
ctx.json({
|
||||||
|
error: {
|
||||||
|
code: 'INTERNAL_ERROR',
|
||||||
|
message: 'name update failed',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
renderDrawer();
|
||||||
|
|
||||||
|
const nameInput = await screen.findByDisplayValue('CI Bot');
|
||||||
|
await user.clear(nameInput);
|
||||||
|
await user.type(nameInput, 'Retry Test');
|
||||||
|
|
||||||
|
const saveBtn = screen.getByRole('button', { name: /Save Changes/i });
|
||||||
|
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||||
|
await user.click(saveBtn);
|
||||||
|
|
||||||
|
await screen.findByText(/Name update.*name update failed/i, undefined, {
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
rest.put(SA_ENDPOINT, (_, res, ctx) =>
|
||||||
|
res(ctx.status(200), ctx.json({ status: 'success', data: {} })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const retryBtn = screen.getByRole('button', { name: /Retry/i });
|
||||||
|
await user.click(retryBtn);
|
||||||
|
|
||||||
|
// Error item should be removed after successful retry
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen.queryByText(/Name update.*name update failed/i),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||||
import type { Dayjs } from 'dayjs';
|
import type { Dayjs } from 'dayjs';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
|
export interface SaveError {
|
||||||
|
context: string;
|
||||||
|
apiError: APIError;
|
||||||
|
onRetry: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export enum ServiceAccountDrawerTab {
|
export enum ServiceAccountDrawerTab {
|
||||||
Overview = 'overview',
|
Overview = 'overview',
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ const mockActiveAccount: ServiceAccountRow = {
|
|||||||
id: 'sa-1',
|
id: 'sa-1',
|
||||||
name: 'CI Bot',
|
name: 'CI Bot',
|
||||||
email: 'ci-bot@signoz.io',
|
email: 'ci-bot@signoz.io',
|
||||||
roles: ['signoz-admin'],
|
|
||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
createdAt: '2026-01-01T00:00:00Z',
|
createdAt: '2026-01-01T00:00:00Z',
|
||||||
updatedAt: '2026-01-02T00:00:00Z',
|
updatedAt: '2026-01-02T00:00:00Z',
|
||||||
@@ -18,7 +17,6 @@ const mockDisabledAccount: ServiceAccountRow = {
|
|||||||
id: 'sa-2',
|
id: 'sa-2',
|
||||||
name: 'Legacy Bot',
|
name: 'Legacy Bot',
|
||||||
email: 'legacy@signoz.io',
|
email: 'legacy@signoz.io',
|
||||||
roles: ['signoz-viewer', 'signoz-editor', 'billing-manager'],
|
|
||||||
status: 'DISABLED',
|
status: 'DISABLED',
|
||||||
createdAt: '2025-06-01T00:00:00Z',
|
createdAt: '2025-06-01T00:00:00Z',
|
||||||
updatedAt: '2025-12-01T00:00:00Z',
|
updatedAt: '2025-12-01T00:00:00Z',
|
||||||
@@ -39,7 +37,6 @@ describe('ServiceAccountsTable', () => {
|
|||||||
|
|
||||||
expect(screen.getByText('CI Bot')).toBeInTheDocument();
|
expect(screen.getByText('CI Bot')).toBeInTheDocument();
|
||||||
expect(screen.getByText('ci-bot@signoz.io')).toBeInTheDocument();
|
expect(screen.getByText('ci-bot@signoz.io')).toBeInTheDocument();
|
||||||
expect(screen.getByText('signoz-admin')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('ACTIVE')).toBeInTheDocument();
|
expect(screen.getByText('ACTIVE')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,8 +46,6 @@ describe('ServiceAccountsTable', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText('DISABLED')).toBeInTheDocument();
|
expect(screen.getByText('DISABLED')).toBeInTheDocument();
|
||||||
expect(screen.getByText('signoz-viewer')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('+2')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onRowClick with the correct account when a row is clicked', async () => {
|
it('calls onRowClick with the correct account when a row is clicked', async () => {
|
||||||
|
|||||||
@@ -25,32 +25,6 @@ export function NameEmailCell({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RolesCell({ roles }: { roles: string[] }): JSX.Element {
|
|
||||||
if (!roles || roles.length === 0) {
|
|
||||||
return <span className="sa-dash">—</span>;
|
|
||||||
}
|
|
||||||
const first = roles[0];
|
|
||||||
const overflow = roles.length - 1;
|
|
||||||
const tooltipContent = roles.slice(1).join(', ');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="sa-roles-cell">
|
|
||||||
<Badge color="vanilla">{first}</Badge>
|
|
||||||
{overflow > 0 && (
|
|
||||||
<Tooltip
|
|
||||||
title={tooltipContent}
|
|
||||||
overlayClassName="sa-tooltip"
|
|
||||||
overlayStyle={{ maxWidth: '600px' }}
|
|
||||||
>
|
|
||||||
<Badge color="vanilla" variant="outline" className="sa-status-badge">
|
|
||||||
+{overflow}
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StatusBadge({ status }: { status: string }): JSX.Element {
|
export function StatusBadge({ status }: { status: string }): JSX.Element {
|
||||||
if (status?.toUpperCase() === 'ACTIVE') {
|
if (status?.toUpperCase() === 'ACTIVE') {
|
||||||
return (
|
return (
|
||||||
@@ -59,9 +33,16 @@ export function StatusBadge({ status }: { status: string }): JSX.Element {
|
|||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (status?.toUpperCase() === 'DELETED') {
|
||||||
|
return (
|
||||||
|
<Badge color="cherry" variant="outline">
|
||||||
|
DELETED
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Badge color="vanilla" variant="outline" className="sa-status-badge">
|
<Badge color="vanilla" variant="outline" className="sa-status-badge">
|
||||||
DISABLED
|
{status ? status.toUpperCase() : 'UNKNOWN'}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -98,13 +79,6 @@ export const columns: ColumnsType<ServiceAccountRow> = [
|
|||||||
<NameEmailCell name={record.name} email={record.email} />
|
<NameEmailCell name={record.name} email={record.email} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Roles',
|
|
||||||
dataIndex: 'roles',
|
|
||||||
key: 'roles',
|
|
||||||
width: 420,
|
|
||||||
render: (roles: string[]): JSX.Element => <RolesCell roles={roles} />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Status',
|
title: 'Status',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ const ROUTES = {
|
|||||||
SETTINGS: '/settings',
|
SETTINGS: '/settings',
|
||||||
MY_SETTINGS: '/settings/my-settings',
|
MY_SETTINGS: '/settings/my-settings',
|
||||||
ORG_SETTINGS: '/settings/org-settings',
|
ORG_SETTINGS: '/settings/org-settings',
|
||||||
API_KEYS: '/settings/api-keys',
|
|
||||||
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
INGESTION_SETTINGS: '/settings/ingestion-settings',
|
||||||
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
SOMETHING_WENT_WRONG: '/something-went-wrong',
|
||||||
UN_AUTHORIZED: '/un-authorized',
|
UN_AUTHORIZED: '/un-authorized',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user