mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-14 21:32:04 +00:00
Compare commits
103 Commits
replace-pr
...
feat/azure
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048de52246 | ||
|
|
644480c4c3 | ||
|
|
08748dfe7f | ||
|
|
9dce854255 | ||
|
|
b2539b337e | ||
|
|
9d45e75d52 | ||
|
|
9c7a54b549 | ||
|
|
763e13df21 | ||
|
|
5cb81fe17a | ||
|
|
3ecd0a662c | ||
|
|
4bbe5ead07 | ||
|
|
b062a8a463 | ||
|
|
e36689ecba | ||
|
|
2c948ef9f6 | ||
|
|
3c30114642 | ||
|
|
d042fad1e3 | ||
|
|
235c606b44 | ||
|
|
a49d7e1662 | ||
|
|
b3e41b5520 | ||
|
|
83bb97cc58 | ||
|
|
68ea28cf6b | ||
|
|
3726c0aac1 | ||
|
|
dae2d3239b | ||
|
|
0c660f8618 | ||
|
|
97cffbc20a | ||
|
|
82a67b62e2 | ||
|
|
9a70da858f | ||
|
|
74a548e2a2 | ||
|
|
8317eb1735 | ||
|
|
b1789ea3f7 | ||
|
|
3b41d0a731 | ||
|
|
a171f7122f | ||
|
|
4a20e93b20 | ||
|
|
d4dc709aa5 | ||
|
|
cd014652a1 | ||
|
|
538351131f | ||
|
|
a3bd72ad86 | ||
|
|
786070d90b | ||
|
|
e699ad8122 | ||
|
|
a1cc05848c | ||
|
|
6d78df2275 | ||
|
|
be68b71bd8 | ||
|
|
5119a62a77 | ||
|
|
5203a9f177 | ||
|
|
df49484bea | ||
|
|
72b0f27494 | ||
|
|
09ac5abe33 | ||
|
|
e36b647bc7 | ||
|
|
b491772eaa | ||
|
|
128497f27a | ||
|
|
9e466b56b2 | ||
|
|
4ad0baa2a2 | ||
|
|
24b588bfba | ||
|
|
e5867cc2ad | ||
|
|
b420ca494e | ||
|
|
e4693ce64c | ||
|
|
ca9b3a910a | ||
|
|
9dc7d2389a | ||
|
|
da5860297f | ||
|
|
54fca5ba44 | ||
|
|
66f4c3d6ec | ||
|
|
a0f407a848 | ||
|
|
3562de8fbb | ||
|
|
ef80fb39fd | ||
|
|
594d4dc737 | ||
|
|
01415b58be | ||
|
|
f7728c9019 | ||
|
|
3fffe6e198 | ||
|
|
4594f4ffe3 | ||
|
|
b660dc8573 | ||
|
|
c7b9c0d36c | ||
|
|
4433d22c09 | ||
|
|
9443c42334 | ||
|
|
bea4f32fe9 | ||
|
|
9387c96023 | ||
|
|
ff2555d8dd | ||
|
|
85953c9a4c | ||
|
|
0155cc38cb | ||
|
|
c217cc96c3 | ||
|
|
580cf32eb5 | ||
|
|
6d3580cbfa | ||
|
|
6c5d36caa9 | ||
|
|
c4a6c7e277 | ||
|
|
c9cd974dca | ||
|
|
5b3f121431 | ||
|
|
c79373314a | ||
|
|
858cd287fa | ||
|
|
afdb674068 | ||
|
|
30a6721472 | ||
|
|
1e07714075 | ||
|
|
518dfcbe59 | ||
|
|
424127c27c | ||
|
|
2dcb817de1 | ||
|
|
f6f8c78aaf | ||
|
|
3c99dfdfa5 | ||
|
|
6ed72519b8 | ||
|
|
fe910aaa0f | ||
|
|
0d362b3ba8 | ||
|
|
51e9ffb847 | ||
|
|
f497a154a2 | ||
|
|
659fa361ef | ||
|
|
84e77182f6 | ||
|
|
32619869e7 |
@@ -42,7 +42,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
schema-migrator-sync:
|
||||
image: signoz/signoz-schema-migrator:v0.129.13
|
||||
image: signoz/signoz-schema-migrator:v0.142.0
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -55,7 +55,7 @@ services:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
schema-migrator-async:
|
||||
image: signoz/signoz-schema-migrator:v0.129.13
|
||||
image: signoz/signoz-schema-migrator:v0.142.0
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -4,7 +4,6 @@ services:
|
||||
container_name: signoz-otel-collector-dev
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
environment:
|
||||
|
||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -132,3 +132,9 @@
|
||||
|
||||
/frontend/src/pages/PublicDashboard/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/PublicDashboardContainer/ @SigNoz/pulse-frontend
|
||||
|
||||
## Dashboard Libs + Components
|
||||
/frontend/src/lib/uPlotV2/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboard/ @SigNoz/pulse-frontend
|
||||
/frontend/src/lib/dashboardVariables/ @SigNoz/pulse-frontend
|
||||
/frontend/src/components/NewSelect/ @SigNoz/pulse-frontend
|
||||
|
||||
10
.github/workflows/goci.yaml
vendored
10
.github/workflows/goci.yaml
vendored
@@ -93,3 +93,13 @@ jobs:
|
||||
run: |
|
||||
go run cmd/enterprise/*.go generate openapi
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1)
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
- name: install-frontend
|
||||
run: cd frontend && yarn install
|
||||
- name: generate-api-clients
|
||||
run: |
|
||||
cd frontend && yarn generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run yarn generate:api in frontend/ locally and commit."; exit 1)
|
||||
|
||||
7
.github/workflows/integrationci.yaml
vendored
7
.github/workflows/integrationci.yaml
vendored
@@ -42,10 +42,11 @@ jobs:
|
||||
- callbackauthn
|
||||
- cloudintegrations
|
||||
- dashboard
|
||||
- querier
|
||||
- ttl
|
||||
- preference
|
||||
- logspipelines
|
||||
- preference
|
||||
- querier
|
||||
- role
|
||||
- ttl
|
||||
- alerts
|
||||
sqlstore-provider:
|
||||
- postgres
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -228,6 +228,3 @@ cython_debug/
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
|
||||
# cursor files
|
||||
frontend/.cursor/
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -11,5 +11,8 @@
|
||||
"[go]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "golang.go"
|
||||
},
|
||||
"[sql]": {
|
||||
"editor.defaultFormatter": "adpyke.vscode-sql-formatter"
|
||||
}
|
||||
}
|
||||
|
||||
11
README.md
11
README.md
@@ -66,6 +66,17 @@ Read [more](https://signoz.io/metrics-and-dashboards/).
|
||||
|
||||

|
||||
|
||||
### LLM Observability
|
||||
|
||||
Monitor and debug your LLM applications with comprehensive observability. Track LLM calls, analyze token usage, monitor performance, and gain insights into your AI application's behavior in production.
|
||||
|
||||
SigNoz LLM observability helps you understand how your language models are performing, identify issues with prompts and responses, track token usage and costs, and optimize your AI applications for better performance and reliability.
|
||||
|
||||
[Get started with LLM Observability →](https://signoz.io/docs/llm-observability/)
|
||||
|
||||

|
||||
|
||||
|
||||
### Alerts
|
||||
|
||||
Use alerts in SigNoz to get notified when anything unusual happens in your application. You can set alerts on any type of telemetry signal (logs, metrics, traces), create thresholds and set up a notification channel to get notified. Advanced features like alert history and anomaly detection can help you create smarter alerts.
|
||||
|
||||
@@ -18,8 +18,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -78,18 +76,15 @@ 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) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, _ role.Setter, _ role.Granter, 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)
|
||||
},
|
||||
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return noopgateway.NewProviderFactory()
|
||||
},
|
||||
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, _ []role.RegisterTypeable) role.Setter {
|
||||
return implrole.NewSetter(implrole.NewStore(store), authz)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", "error", err)
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/ee/modules/role/implrole"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
@@ -29,8 +28,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
pkgimplrole "github.com/SigNoz/signoz/pkg/modules/role/implrole"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
@@ -118,18 +115,15 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return authNs, nil
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), licensing, dashboardModule)
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, roleSetter, granter, queryParser, querier, licensing)
|
||||
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)
|
||||
},
|
||||
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
|
||||
return httpgateway.NewProviderFactory(licensing)
|
||||
},
|
||||
func(store sqlstore.SQLStore, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
|
||||
return implrole.NewSetter(pkgimplrole.NewStore(store), authz, licensing, registry)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -176,7 +176,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.110.0
|
||||
image: signoz/signoz:v0.111.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
@@ -209,12 +209,11 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.129.13
|
||||
image: signoz/signoz-otel-collector:v0.142.0
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
@@ -233,7 +232,7 @@ services:
|
||||
- signoz
|
||||
schema-migrator:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:v0.129.13
|
||||
image: signoz/signoz-schema-migrator:v0.142.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.110.0
|
||||
image: signoz/signoz:v0.111.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
@@ -150,12 +150,11 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:v0.129.13
|
||||
image: signoz/signoz-otel-collector:v0.142.0
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
configs:
|
||||
- source: otel-collector-config
|
||||
target: /etc/otel-collector-config.yaml
|
||||
@@ -176,7 +175,7 @@ services:
|
||||
- signoz
|
||||
schema-migrator:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:v0.129.13
|
||||
image: signoz/signoz-schema-migrator:v0.142.0
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
@@ -179,7 +179,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.110.0}
|
||||
image: signoz/signoz:${VERSION:-v0.111.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -213,13 +213,12 @@ services:
|
||||
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
@@ -239,7 +238,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -250,7 +249,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -111,7 +111,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.110.0}
|
||||
image: signoz/signoz:${VERSION:-v0.111.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
@@ -144,13 +144,12 @@ services:
|
||||
retries: 3
|
||||
otel-collector:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
- --manager-config=/etc/manager-config.yaml
|
||||
- --copy-path=/var/tmp/collector-config.yaml
|
||||
- --feature-gates=-pkg.translator.prometheus.NormalizeName
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
|
||||
@@ -166,7 +165,7 @@ services:
|
||||
condition: service_healthy
|
||||
schema-migrator-sync:
|
||||
!!merge <<: *common
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: schema-migrator-sync
|
||||
command:
|
||||
- sync
|
||||
@@ -178,7 +177,7 @@ services:
|
||||
restart: on-failure
|
||||
schema-migrator-async:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.13}
|
||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.142.0}
|
||||
container_name: schema-migrator-async
|
||||
command:
|
||||
- async
|
||||
|
||||
@@ -607,6 +607,178 @@ paths:
|
||||
summary: Update auth domain
|
||||
tags:
|
||||
- authdomains
|
||||
/api/v1/fields/keys:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns field keys
|
||||
operationId: GetFieldsKeys
|
||||
parameters:
|
||||
- in: query
|
||||
name: signal
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: source
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: startUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
- in: query
|
||||
name: endUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
- in: query
|
||||
name: fieldContext
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: fieldDataType
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TelemetrytypesGettableFieldKeys'
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get field keys
|
||||
tags:
|
||||
- fields
|
||||
/api/v1/fields/values:
|
||||
get:
|
||||
deprecated: false
|
||||
description: This endpoint returns field values
|
||||
operationId: GetFieldsValues
|
||||
parameters:
|
||||
- in: query
|
||||
name: signal
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: source
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: startUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
- in: query
|
||||
name: endUnixMilli
|
||||
schema:
|
||||
format: int64
|
||||
type: integer
|
||||
- in: query
|
||||
name: fieldContext
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: fieldDataType
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: existingQuery
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/TelemetrytypesGettableFieldValues'
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
description: OK
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: Get field values
|
||||
tags:
|
||||
- fields
|
||||
/api/v1/getResetPasswordToken/{id}:
|
||||
get:
|
||||
deprecated: false
|
||||
@@ -2226,6 +2398,12 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"422":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unprocessable Entity
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
@@ -2285,6 +2463,15 @@ paths:
|
||||
deprecated: false
|
||||
description: This endpoint returns the ingestion keys for a workspace
|
||||
operationId: GetIngestionKeys
|
||||
parameters:
|
||||
- in: query
|
||||
name: page
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: per_page
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -2597,6 +2784,19 @@ paths:
|
||||
deprecated: false
|
||||
description: This endpoint returns the ingestion keys for a workspace
|
||||
operationId: SearchIngestionKeys
|
||||
parameters:
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
schema:
|
||||
type: integer
|
||||
- in: query
|
||||
name: per_page
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
@@ -2643,6 +2843,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2697,6 +2898,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2752,6 +2954,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -2918,6 +3121,7 @@ paths:
|
||||
parameters:
|
||||
- in: query
|
||||
name: metricName
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
@@ -3785,6 +3989,9 @@ components:
|
||||
type: string
|
||||
alertName:
|
||||
type: string
|
||||
required:
|
||||
- alertName
|
||||
- alertId
|
||||
type: object
|
||||
MetricsexplorertypesMetricAlertsResponse:
|
||||
properties:
|
||||
@@ -3793,6 +4000,8 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricAlert'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- alerts
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttribute:
|
||||
properties:
|
||||
@@ -3806,6 +4015,10 @@ components:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- values
|
||||
- valueCount
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttributesRequest:
|
||||
properties:
|
||||
@@ -3817,6 +4030,8 @@ components:
|
||||
start:
|
||||
nullable: true
|
||||
type: integer
|
||||
required:
|
||||
- metricName
|
||||
type: object
|
||||
MetricsexplorertypesMetricAttributesResponse:
|
||||
properties:
|
||||
@@ -3828,6 +4043,9 @@ components:
|
||||
totalKeys:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- attributes
|
||||
- totalKeys
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboard:
|
||||
properties:
|
||||
@@ -3839,6 +4057,11 @@ components:
|
||||
type: string
|
||||
widgetName:
|
||||
type: string
|
||||
required:
|
||||
- dashboardName
|
||||
- dashboardId
|
||||
- widgetId
|
||||
- widgetName
|
||||
type: object
|
||||
MetricsexplorertypesMetricDashboardsResponse:
|
||||
properties:
|
||||
@@ -3847,6 +4070,8 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesMetricDashboard'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- dashboards
|
||||
type: object
|
||||
MetricsexplorertypesMetricHighlightsResponse:
|
||||
properties:
|
||||
@@ -3862,6 +4087,11 @@ components:
|
||||
totalTimeSeries:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- dataPoints
|
||||
- lastReceived
|
||||
- totalTimeSeries
|
||||
- activeTimeSeries
|
||||
type: object
|
||||
MetricsexplorertypesMetricMetadata:
|
||||
properties:
|
||||
@@ -3870,11 +4100,17 @@ components:
|
||||
isMonotonic:
|
||||
type: boolean
|
||||
temporality:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetrictypesTemporality'
|
||||
type:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetrictypesType'
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- description
|
||||
- type
|
||||
- unit
|
||||
- temporality
|
||||
- isMonotonic
|
||||
type: object
|
||||
MetricsexplorertypesStat:
|
||||
properties:
|
||||
@@ -3889,9 +4125,16 @@ components:
|
||||
minimum: 0
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetrictypesType'
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- metricName
|
||||
- description
|
||||
- type
|
||||
- unit
|
||||
- timeseries
|
||||
- samples
|
||||
type: object
|
||||
MetricsexplorertypesStatsRequest:
|
||||
properties:
|
||||
@@ -3909,6 +4152,10 @@ components:
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
type: object
|
||||
MetricsexplorertypesStatsResponse:
|
||||
properties:
|
||||
@@ -3920,6 +4167,9 @@ components:
|
||||
total:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- metrics
|
||||
- total
|
||||
type: object
|
||||
MetricsexplorertypesTreemapEntry:
|
||||
properties:
|
||||
@@ -3931,7 +4181,16 @@ components:
|
||||
totalValue:
|
||||
minimum: 0
|
||||
type: integer
|
||||
required:
|
||||
- metricName
|
||||
- percentage
|
||||
- totalValue
|
||||
type: object
|
||||
MetricsexplorertypesTreemapMode:
|
||||
enum:
|
||||
- timeseries
|
||||
- samples
|
||||
type: string
|
||||
MetricsexplorertypesTreemapRequest:
|
||||
properties:
|
||||
end:
|
||||
@@ -3942,10 +4201,15 @@ components:
|
||||
limit:
|
||||
type: integer
|
||||
mode:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetricsexplorertypesTreemapMode'
|
||||
start:
|
||||
format: int64
|
||||
type: integer
|
||||
required:
|
||||
- start
|
||||
- end
|
||||
- limit
|
||||
- mode
|
||||
type: object
|
||||
MetricsexplorertypesTreemapResponse:
|
||||
properties:
|
||||
@@ -3959,6 +4223,9 @@ components:
|
||||
$ref: '#/components/schemas/MetricsexplorertypesTreemapEntry'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- timeseries
|
||||
- samples
|
||||
type: object
|
||||
MetricsexplorertypesUpdateMetricMetadataRequest:
|
||||
properties:
|
||||
@@ -3969,12 +4236,33 @@ components:
|
||||
metricName:
|
||||
type: string
|
||||
temporality:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetrictypesTemporality'
|
||||
type:
|
||||
type: string
|
||||
$ref: '#/components/schemas/MetrictypesType'
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- metricName
|
||||
- type
|
||||
- description
|
||||
- unit
|
||||
- temporality
|
||||
- isMonotonic
|
||||
type: object
|
||||
MetrictypesTemporality:
|
||||
enum:
|
||||
- delta
|
||||
- cumulative
|
||||
- unspecified
|
||||
type: string
|
||||
MetrictypesType:
|
||||
enum:
|
||||
- gauge
|
||||
- sum
|
||||
- histogram
|
||||
- summary
|
||||
- exponentialhistogram
|
||||
type: string
|
||||
PreferencetypesPreference:
|
||||
properties:
|
||||
allowedScopes:
|
||||
@@ -4067,6 +4355,8 @@ components:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
Querybuildertypesv5QueryData:
|
||||
properties:
|
||||
@@ -4128,6 +4418,68 @@ components:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
TelemetrytypesGettableFieldKeys:
|
||||
properties:
|
||||
complete:
|
||||
type: boolean
|
||||
keys:
|
||||
additionalProperties:
|
||||
items:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
|
||||
type: array
|
||||
nullable: true
|
||||
type: object
|
||||
required:
|
||||
- keys
|
||||
- complete
|
||||
type: object
|
||||
TelemetrytypesGettableFieldValues:
|
||||
properties:
|
||||
complete:
|
||||
type: boolean
|
||||
values:
|
||||
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldValues'
|
||||
required:
|
||||
- values
|
||||
- complete
|
||||
type: object
|
||||
TelemetrytypesTelemetryFieldKey:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
fieldContext:
|
||||
type: string
|
||||
fieldDataType:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
signal:
|
||||
type: string
|
||||
unit:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
TelemetrytypesTelemetryFieldValues:
|
||||
properties:
|
||||
boolValues:
|
||||
items:
|
||||
type: boolean
|
||||
type: array
|
||||
numberValues:
|
||||
items:
|
||||
format: double
|
||||
type: number
|
||||
type: array
|
||||
relatedValues:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
stringValues:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
TypesChangePasswordRequest:
|
||||
properties:
|
||||
newPassword:
|
||||
@@ -4256,6 +4608,9 @@ components:
|
||||
type: string
|
||||
orgId:
|
||||
type: string
|
||||
required:
|
||||
- orgId
|
||||
- email
|
||||
type: object
|
||||
TypesPostableInvite:
|
||||
properties:
|
||||
|
||||
@@ -2,12 +2,18 @@ package openfgaauthz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/authz/openfgaserver"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/authzstore/sqlauthzstore"
|
||||
pkgopenfgaauthz "github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
|
||||
@@ -15,84 +21,320 @@ import (
|
||||
|
||||
type provider struct {
|
||||
pkgAuthzService authz.AuthZ
|
||||
openfgaServer *openfgaserver.Server
|
||||
licensing licensing.Licensing
|
||||
store roletypes.Store
|
||||
registry []authz.RegisterTypeable
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
|
||||
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema)
|
||||
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, licensing, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) (authz.AuthZ, error) {
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, licensing licensing.Licensing, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
|
||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema)
|
||||
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
openfgaServer, err := openfgaserver.NewOpenfgaServer(ctx, pkgAuthzService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &provider{
|
||||
pkgAuthzService: pkgAuthzService,
|
||||
openfgaServer: openfgaServer,
|
||||
licensing: licensing,
|
||||
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
|
||||
registry: registry,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
return provider.pkgAuthzService.Start(ctx)
|
||||
return provider.openfgaServer.Start(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
return provider.pkgAuthzService.Stop(ctx)
|
||||
return provider.openfgaServer.Stop(ctx)
|
||||
}
|
||||
|
||||
func (provider *provider) Check(ctx context.Context, tuple *openfgav1.TupleKey) error {
|
||||
return provider.pkgAuthzService.Check(ctx, tuple)
|
||||
return provider.openfgaServer.Check(ctx, tuple)
|
||||
}
|
||||
|
||||
func (provider *provider) 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (provider *provider) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
return provider.openfgaServer.CheckWithTupleCreation(ctx, claims, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (provider *provider) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, roleSelectors []authtypes.Selector) error {
|
||||
return provider.openfgaServer.CheckWithTupleCreationWithoutClaims(ctx, orgID, relation, typeable, selectors, roleSelectors)
|
||||
}
|
||||
|
||||
func (provider *provider) BatchCheck(ctx context.Context, tuples []*openfgav1.TupleKey) error {
|
||||
return provider.pkgAuthzService.BatchCheck(ctx, tuples)
|
||||
return provider.openfgaServer.BatchCheck(ctx, tuples)
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
return provider.pkgAuthzService.ListObjects(ctx, subject, relation, typeable)
|
||||
return provider.openfgaServer.ListObjects(ctx, subject, relation, typeable)
|
||||
}
|
||||
|
||||
func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
|
||||
return provider.pkgAuthzService.Write(ctx, additions, deletions)
|
||||
return provider.openfgaServer.Write(ctx, additions, deletions)
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
|
||||
return provider.pkgAuthzService.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
|
||||
return provider.pkgAuthzService.GetByOrgIDAndName(ctx, orgID, name)
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
return provider.pkgAuthzService.List(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
|
||||
return provider.pkgAuthzService.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
}
|
||||
|
||||
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
|
||||
return provider.pkgAuthzService.Grant(ctx, orgID, name, subject)
|
||||
}
|
||||
|
||||
func (provider *provider) ModifyGrant(ctx context.Context, orgID valuer.UUID, existingRoleName string, updatedRoleName string, subject string) error {
|
||||
return provider.pkgAuthzService.ModifyGrant(ctx, orgID, existingRoleName, updatedRoleName, subject)
|
||||
}
|
||||
|
||||
func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
|
||||
return provider.pkgAuthzService.Revoke(ctx, orgID, name, subject)
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*roletypes.Role) error {
|
||||
return provider.pkgAuthzService.CreateManagedRoles(ctx, orgID, managedRoles)
|
||||
}
|
||||
|
||||
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
|
||||
grantTuples, err := provider.getManagedRoleGrantTuples(orgID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tuples = append(tuples, grantTuples...)
|
||||
|
||||
managedRoleTuples, err := provider.getManagedRoleTransactionTuples(orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tuples = append(tuples, managedRoleTuples...)
|
||||
|
||||
return provider.Write(ctx, tuples, nil)
|
||||
}
|
||||
|
||||
func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
existingRole, err := provider.store.GetByOrgIDAndName(ctx, role.OrgID, role.Name)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
typeables := make([]authtypes.Typeable, 0)
|
||||
for _, register := range provider.registry {
|
||||
typeables = append(typeables, register.MustGetTypeables()...)
|
||||
}
|
||||
// role module cannot self register itself!
|
||||
typeables = append(typeables, provider.MustGetTypeables()...)
|
||||
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, typeable := range typeables {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, resource := range provider.GetResources(ctx) {
|
||||
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
|
||||
resourceObjects, err := provider.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects = append(objects, resourceObjects...)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return provider.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
additionTuples, err := roletypes.GetAdditionTuples(name, orgID, relation, additions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionTuples, err := roletypes.GetDeletionTuples(name, orgID, relation, deletions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = provider.Write(ctx, additionTuples, deletionTuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := provider.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
err = role.CanEditDelete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := []*openfgav1.TupleKey{}
|
||||
|
||||
// Grant the admin role to the user
|
||||
adminSubject := authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil)
|
||||
adminTuple, err := authtypes.TypeableRole.Tuples(
|
||||
adminSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, adminTuple...)
|
||||
|
||||
// Grant the admin role to the anonymous user
|
||||
anonymousSubject := authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
anonymousTuple, err := authtypes.TypeableRole.Tuples(
|
||||
anonymousSubject,
|
||||
authtypes.RelationAssignee,
|
||||
[]authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAnonymousRoleName),
|
||||
},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, anonymousTuple...)
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleTransactionTuples(orgID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
transactionsByRole := make(map[string][]*authtypes.Transaction)
|
||||
for _, register := range provider.registry {
|
||||
for roleName, txns := range register.MustGetManagedRoleTransactions() {
|
||||
transactionsByRole[roleName] = append(transactionsByRole[roleName], txns...)
|
||||
}
|
||||
}
|
||||
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for roleName, transactions := range transactionsByRole {
|
||||
for _, txn := range transactions {
|
||||
typeable := authtypes.MustNewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
|
||||
txnTuples, err := typeable.Tuples(
|
||||
authtypes.MustNewSubject(
|
||||
authtypes.TypeableRole,
|
||||
roleName,
|
||||
orgID,
|
||||
&authtypes.RelationAssignee,
|
||||
),
|
||||
txn.Relation,
|
||||
[]authtypes.Selector{txn.Object.Selector},
|
||||
orgID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tuples = append(tuples, txnTuples...)
|
||||
}
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
83
ee/authz/openfgaserver/server.go
Normal file
83
ee/authz/openfgaserver/server.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package openfgaserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
openfgav1 "github.com/openfga/api/proto/openfga/v1"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
pkgAuthzService authz.AuthZ
|
||||
}
|
||||
|
||||
func NewOpenfgaServer(ctx context.Context, pkgAuthzService authz.AuthZ) (*Server, error) {
|
||||
|
||||
return &Server{
|
||||
pkgAuthzService: pkgAuthzService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (server *Server) Start(ctx context.Context) error {
|
||||
return server.pkgAuthzService.Start(ctx)
|
||||
}
|
||||
|
||||
func (server *Server) Stop(ctx context.Context) error {
|
||||
return server.pkgAuthzService.Stop(ctx)
|
||||
}
|
||||
|
||||
func (server *Server) Check(ctx context.Context, tuple *openfgav1.TupleKey) error {
|
||||
return server.pkgAuthzService.Check(ctx, tuple)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = server.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) CheckWithTupleCreationWithoutClaims(ctx context.Context, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
|
||||
subject, err := authtypes.NewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.String(), orgID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tuples, err := typeable.Tuples(subject, relation, selectors, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = server.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) BatchCheck(ctx context.Context, tuples []*openfgav1.TupleKey) error {
|
||||
return server.pkgAuthzService.BatchCheck(ctx, tuples)
|
||||
}
|
||||
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
return server.pkgAuthzService.ListObjects(ctx, subject, relation, typeable)
|
||||
}
|
||||
|
||||
func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
|
||||
return server.pkgAuthzService.Write(ctx, additions, deletions)
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -26,13 +25,11 @@ type module struct {
|
||||
pkgDashboardModule dashboard.Module
|
||||
store dashboardtypes.Store
|
||||
settings factory.ScopedProviderSettings
|
||||
roleSetter role.Setter
|
||||
granter role.Granter
|
||||
querier querier.Querier
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, roleSetter role.Setter, granter role.Granter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
|
||||
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
|
||||
|
||||
@@ -40,8 +37,6 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
|
||||
pkgDashboardModule: pkgDashboardModule,
|
||||
store: store,
|
||||
settings: scopedProviderSettings,
|
||||
roleSetter: roleSetter,
|
||||
granter: granter,
|
||||
querier: querier,
|
||||
licensing: licensing,
|
||||
}
|
||||
@@ -61,29 +56,6 @@ func (module *module) CreatePublic(ctx context.Context, orgID valuer.UUID, publi
|
||||
return errors.Newf(errors.TypeAlreadyExists, dashboardtypes.ErrCodePublicDashboardAlreadyExists, "dashboard with id %s is already public", storablePublicDashboard.DashboardID)
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.granter.Grant(ctx, orgID, roletypes.SigNozAnonymousRoleName, authtypes.MustNewSubject(authtypes.TypeableAnonymous, authtypes.AnonymousUser.StringValue(), orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
additionObject := authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
Type: authtypes.TypeMetaResource,
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, []*authtypes.Object{additionObject}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.CreatePublic(ctx, dashboardtypes.NewStorablePublicDashboardFromPublicDashboard(publicDashboard))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -128,6 +100,7 @@ func (module *module) GetPublicDashboardSelectorsAndOrg(ctx context.Context, id
|
||||
|
||||
return []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, id.StringValue()),
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, authtypes.WildCardSelectorString),
|
||||
}, storableDashboard.OrgID, nil
|
||||
}
|
||||
|
||||
@@ -190,29 +163,6 @@ func (module *module) DeletePublic(ctx context.Context, orgID valuer.UUID, dashb
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
publicDashboard, err := module.GetPublic(ctx, orgID, dashboardID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionObject := authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
Type: authtypes.TypeMetaResource,
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -250,10 +200,6 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, m
|
||||
return module.pkgDashboardModule.GetByMetricNames(ctx, orgID, metricNames)
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return module.pkgDashboardModule.MustGetTypeables()
|
||||
}
|
||||
|
||||
func (module *module) List(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
return module.pkgDashboardModule.List(ctx, orgID)
|
||||
}
|
||||
@@ -266,34 +212,27 @@ func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
|
||||
}
|
||||
|
||||
func (module *module) deletePublic(ctx context.Context, orgID valuer.UUID, dashboardID valuer.UUID) error {
|
||||
publicDashboard, err := module.store.GetPublic(ctx, dashboardID.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role, err := module.roleSetter.GetOrCreate(ctx, orgID, roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionObject := authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
Type: authtypes.TypeMetaResource,
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, publicDashboard.ID.String()),
|
||||
)
|
||||
|
||||
err = module.roleSetter.PatchObjects(ctx, orgID, role.ID, authtypes.RelationRead, nil, []*authtypes.Object{deletionObject})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return module.pkgDashboardModule.MustGetTypeables()
|
||||
}
|
||||
|
||||
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
|
||||
return map[string][]*authtypes.Transaction{
|
||||
roletypes.SigNozAnonymousRoleName: {
|
||||
{
|
||||
Relation: authtypes.RelationRead,
|
||||
Object: *authtypes.MustNewObject(
|
||||
authtypes.Resource{
|
||||
Type: authtypes.TypeMetaResource,
|
||||
Name: dashboardtypes.TypeableMetaResourcePublicDashboard.Name(),
|
||||
},
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, "*"),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (module *module) deletePublic(ctx context.Context, _ valuer.UUID, dashboardID valuer.UUID) error {
|
||||
return module.store.DeletePublic(ctx, dashboardID.StringValue())
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package implrole
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/role"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type setter struct {
|
||||
store roletypes.Store
|
||||
authz authz.AuthZ
|
||||
licensing licensing.Licensing
|
||||
registry []role.RegisterTypeable
|
||||
}
|
||||
|
||||
func NewSetter(store roletypes.Store, authz authz.AuthZ, licensing licensing.Licensing, registry []role.RegisterTypeable) role.Setter {
|
||||
return &setter{
|
||||
store: store,
|
||||
authz: authz,
|
||||
licensing: licensing,
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func (setter *setter) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (setter *setter) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
existingRole, err := setter.store.GetByOrgIDAndName(ctx, role.OrgID, role.Name)
|
||||
if err != nil {
|
||||
if !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if existingRole != nil {
|
||||
return roletypes.NewRoleFromStorableRole(existingRole), nil
|
||||
}
|
||||
|
||||
err = setter.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (setter *setter) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
typeables := make([]authtypes.Typeable, 0)
|
||||
for _, register := range setter.registry {
|
||||
typeables = append(typeables, register.MustGetTypeables()...)
|
||||
}
|
||||
// role module cannot self register itself!
|
||||
typeables = append(typeables, setter.MustGetTypeables()...)
|
||||
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, typeable := range typeables {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func (setter *setter) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
|
||||
storableRole, err := setter.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, resource := range setter.GetResources(ctx) {
|
||||
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
|
||||
resourceObjects, err := setter.
|
||||
authz.
|
||||
ListObjects(
|
||||
ctx,
|
||||
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.ID.String(), orgID, &authtypes.RelationAssignee),
|
||||
relation,
|
||||
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objects = append(objects, resourceObjects...)
|
||||
}
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
func (setter *setter) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return setter.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
|
||||
}
|
||||
|
||||
func (setter *setter) PatchObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
additionTuples, err := roletypes.GetAdditionTuples(id, orgID, relation, additions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deletionTuples, err := roletypes.GetDeletionTuples(id, orgID, relation, deletions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setter.authz.Write(ctx, additionTuples, deletionTuples)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (setter *setter) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
|
||||
_, err := setter.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableRole, err := setter.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := roletypes.NewRoleFromStorableRole(storableRole)
|
||||
err = role.CanEditDelete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return setter.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (setter *setter) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ type GetAnomaliesResponse struct {
|
||||
//
|
||||
// ^ ^
|
||||
// | |
|
||||
// (rounded value for past peiod) + (seasonal growth)
|
||||
// (rounded value for past period) + (seasonal growth)
|
||||
//
|
||||
// score = abs(value - prediction) / stddev (current_season_query)
|
||||
type anomalyQueryParams struct {
|
||||
@@ -74,12 +74,12 @@ type anomalyQueryParams struct {
|
||||
// : For daily seasonality, this is the query range params for the (now-2d-5m, now-1d)
|
||||
// : For hourly seasonality, this is the query range params for the (now-2h-5m, now-1h)
|
||||
PastSeasonQuery *v3.QueryRangeParamsV3
|
||||
// Past2SeasonQuery is the query range params for past 2 seasonal period to the current season
|
||||
// Past2SeasonQuery is the query range params for past 2 seasonal periods to the current season
|
||||
// Example: For weekly seasonality, this is the query range params for the (now-3w-5m, now-2w)
|
||||
// : For daily seasonality, this is the query range params for the (now-3d-5m, now-2d)
|
||||
// : For hourly seasonality, this is the query range params for the (now-3h-5m, now-2h)
|
||||
Past2SeasonQuery *v3.QueryRangeParamsV3
|
||||
// Past3SeasonQuery is the query range params for past 3 seasonal period to the current season
|
||||
// Past3SeasonQuery is the query range params for past 3 seasonal periods to the current season
|
||||
// Example: For weekly seasonality, this is the query range params for the (now-4w-5m, now-3w)
|
||||
// : For daily seasonality, this is the query range params for the (now-4d-5m, now-3d)
|
||||
// : For hourly seasonality, this is the query range params for the (now-4h-5m, now-3h)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/apis/fields"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
querierAPI "github.com/SigNoz/signoz/pkg/querier"
|
||||
@@ -56,7 +55,6 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
|
||||
FieldsAPI: fields.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.TelemetryStore),
|
||||
Signoz: signoz,
|
||||
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
|
||||
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
||||
|
||||
@@ -211,7 +211,7 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
|
||||
r := baseapp.NewRouter()
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz, s.signoz.Modules.RoleGetter)
|
||||
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger(), s.signoz.Modules.OrgGetter, s.signoz.Authz)
|
||||
|
||||
r.Use(otelmux.Middleware(
|
||||
"apiserver",
|
||||
@@ -237,7 +237,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
apiHandler.RegisterLogsRoutes(r, am)
|
||||
apiHandler.RegisterIntegrationRoutes(r, am)
|
||||
apiHandler.RegisterCloudIntegrationsRoutes(r, am)
|
||||
apiHandler.RegisterFieldsRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV3Routes(r, am)
|
||||
apiHandler.RegisterInfraMetricsRoutes(r, am)
|
||||
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/common"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/transition"
|
||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
querierV2 "github.com/SigNoz/signoz/pkg/query-service/app/querier/v2"
|
||||
@@ -63,6 +63,8 @@ type AnomalyRule struct {
|
||||
seasonality anomaly.Seasonality
|
||||
}
|
||||
|
||||
var _ baserules.Rule = (*AnomalyRule)(nil)
|
||||
|
||||
func NewAnomalyRule(
|
||||
id string,
|
||||
orgID valuer.UUID,
|
||||
@@ -234,6 +236,11 @@ func (r *AnomalyRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID, t
|
||||
}
|
||||
}
|
||||
|
||||
hasData := len(queryResult.AnomalyScores) > 0
|
||||
if missingDataAlert := r.HandleMissingDataAlert(ctx, ts, hasData); missingDataAlert != nil {
|
||||
return ruletypes.Vector{*missingDataAlert}, nil
|
||||
}
|
||||
|
||||
var resultVector ruletypes.Vector
|
||||
|
||||
scoresJSON, _ := json.Marshal(queryResult.AnomalyScores)
|
||||
@@ -285,6 +292,11 @@ func (r *AnomalyRule) buildAndRunQueryV5(ctx context.Context, orgID valuer.UUID,
|
||||
|
||||
queryResult := transition.ConvertV5TimeSeriesDataToV4Result(qbResult)
|
||||
|
||||
hasData := len(queryResult.AnomalyScores) > 0
|
||||
if missingDataAlert := r.HandleMissingDataAlert(ctx, ts, hasData); missingDataAlert != nil {
|
||||
return ruletypes.Vector{*missingDataAlert}, nil
|
||||
}
|
||||
|
||||
var resultVector ruletypes.Vector
|
||||
|
||||
scoresJSON, _ := json.Marshal(queryResult.AnomalyScores)
|
||||
@@ -480,7 +492,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if a.State == model.StatePending && ts.Sub(a.ActiveAt) >= r.HoldDuration() {
|
||||
if a.State == model.StatePending && ts.Sub(a.ActiveAt) >= r.HoldDuration().Duration() {
|
||||
a.State = model.StateFiring
|
||||
a.FiredAt = ts
|
||||
state := model.StateFiring
|
||||
@@ -543,7 +555,7 @@ func (r *AnomalyRule) String() string {
|
||||
ar := ruletypes.PostableRule{
|
||||
AlertName: r.Name(),
|
||||
RuleCondition: r.Condition(),
|
||||
EvalWindow: ruletypes.Duration(r.EvalWindow()),
|
||||
EvalWindow: r.EvalWindow(),
|
||||
Labels: r.Labels().Map(),
|
||||
Annotations: r.Annotations().Map(),
|
||||
PreferredChannels: r.PreferredChannels(),
|
||||
|
||||
268
ee/query-service/rules/anomaly_test.go
Normal file
268
ee/query-service/rules/anomaly_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/anomaly"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// mockAnomalyProvider is a mock implementation of anomaly.Provider for testing.
|
||||
// We need this because the anomaly provider makes 6 different queries for various
|
||||
// time periods (current, past period, current season, past season, past 2 seasons,
|
||||
// past 3 seasons), making it cumbersome to create mock data.
|
||||
type mockAnomalyProvider struct {
|
||||
responses []*anomaly.GetAnomaliesResponse
|
||||
callCount int
|
||||
}
|
||||
|
||||
func (m *mockAnomalyProvider) GetAnomalies(ctx context.Context, orgID valuer.UUID, req *anomaly.GetAnomaliesRequest) (*anomaly.GetAnomaliesResponse, error) {
|
||||
if m.callCount >= len(m.responses) {
|
||||
return &anomaly.GetAnomaliesResponse{Results: []*v3.Result{}}, nil
|
||||
}
|
||||
resp := m.responses[m.callCount]
|
||||
m.callCount++
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func TestAnomalyRule_NoData_AlertOnAbsent(t *testing.T) {
|
||||
// Test basic AlertOnAbsent functionality (without AbsentFor grace period)
|
||||
|
||||
baseTime := time.Unix(1700000000, 0)
|
||||
evalWindow := valuer.MustParseTextDuration("5m")
|
||||
evalTime := baseTime.Add(5 * time.Minute)
|
||||
|
||||
target := 500.0
|
||||
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Test anomaly no data",
|
||||
AlertType: ruletypes.AlertTypeMetric,
|
||||
RuleType: RuleTypeAnomaly,
|
||||
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
|
||||
EvalWindow: evalWindow,
|
||||
Frequency: valuer.MustParseTextDuration("1m"),
|
||||
}},
|
||||
RuleCondition: &ruletypes.RuleCondition{
|
||||
CompareOp: ruletypes.ValueIsAbove,
|
||||
MatchType: ruletypes.AtleastOnce,
|
||||
Target: &target,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
Expression: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
Temporality: v3.Unspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
SelectedQuery: "A",
|
||||
Seasonality: "daily",
|
||||
Thresholds: &ruletypes.RuleThresholdData{
|
||||
Kind: ruletypes.BasicThresholdKind,
|
||||
Spec: ruletypes.BasicRuleThresholds{{
|
||||
Name: "Test anomaly no data",
|
||||
TargetValue: &target,
|
||||
MatchType: ruletypes.AtleastOnce,
|
||||
CompareOp: ruletypes.ValueIsAbove,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
responseNoData := &anomaly.GetAnomaliesResponse{
|
||||
Results: []*v3.Result{
|
||||
{
|
||||
QueryName: "A",
|
||||
AnomalyScores: []*v3.Series{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
alertOnAbsent bool
|
||||
expectAlerts int
|
||||
}{
|
||||
{
|
||||
description: "AlertOnAbsent=false",
|
||||
alertOnAbsent: false,
|
||||
expectAlerts: 0,
|
||||
},
|
||||
{
|
||||
description: "AlertOnAbsent=true",
|
||||
alertOnAbsent: true,
|
||||
expectAlerts: 1,
|
||||
},
|
||||
}
|
||||
|
||||
logger := instrumentationtest.New().Logger()
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.description, func(t *testing.T) {
|
||||
postableRule.RuleCondition.AlertOnAbsent = c.alertOnAbsent
|
||||
|
||||
telemetryStore := telemetrystoretest.New(telemetrystore.Config{}, nil)
|
||||
options := clickhouseReader.NewOptions("primaryNamespace")
|
||||
reader := clickhouseReader.NewReader(nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
|
||||
rule, err := NewAnomalyRule(
|
||||
"test-anomaly-rule",
|
||||
valuer.GenerateUUID(),
|
||||
&postableRule,
|
||||
reader,
|
||||
nil,
|
||||
logger,
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule.provider = &mockAnomalyProvider{
|
||||
responses: []*anomaly.GetAnomaliesResponse{responseNoData},
|
||||
}
|
||||
|
||||
alertsFound, err := rule.Eval(context.Background(), evalTime)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, c.expectAlerts, alertsFound)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
// Test missing data alert with AbsentFor grace period
|
||||
// 1. Call Eval with data at time t1, to populate lastTimestampWithDatapoints
|
||||
// 2. Call Eval without data at time t2
|
||||
// 3. Alert fires only if t2 - t1 > AbsentFor
|
||||
|
||||
baseTime := time.Unix(1700000000, 0)
|
||||
evalWindow := valuer.MustParseTextDuration("5m")
|
||||
|
||||
// Set target higher than test data so regular threshold alerts don't fire
|
||||
target := 500.0
|
||||
|
||||
postableRule := ruletypes.PostableRule{
|
||||
AlertName: "Test anomaly no data with AbsentFor",
|
||||
AlertType: ruletypes.AlertTypeMetric,
|
||||
RuleType: RuleTypeAnomaly,
|
||||
Evaluation: &ruletypes.EvaluationEnvelope{Kind: ruletypes.RollingEvaluation, Spec: ruletypes.RollingWindow{
|
||||
EvalWindow: evalWindow,
|
||||
Frequency: valuer.MustParseTextDuration("1m"),
|
||||
}},
|
||||
RuleCondition: &ruletypes.RuleCondition{
|
||||
CompareOp: ruletypes.ValueIsAbove,
|
||||
MatchType: ruletypes.AtleastOnce,
|
||||
AlertOnAbsent: true,
|
||||
Target: &target,
|
||||
CompositeQuery: &v3.CompositeQuery{
|
||||
QueryType: v3.QueryTypeBuilder,
|
||||
BuilderQueries: map[string]*v3.BuilderQuery{
|
||||
"A": {
|
||||
QueryName: "A",
|
||||
Expression: "A",
|
||||
DataSource: v3.DataSourceMetrics,
|
||||
Temporality: v3.Unspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
SelectedQuery: "A",
|
||||
Seasonality: "daily",
|
||||
Thresholds: &ruletypes.RuleThresholdData{
|
||||
Kind: ruletypes.BasicThresholdKind,
|
||||
Spec: ruletypes.BasicRuleThresholds{{
|
||||
Name: "Test anomaly no data with AbsentFor",
|
||||
TargetValue: &target,
|
||||
MatchType: ruletypes.AtleastOnce,
|
||||
CompareOp: ruletypes.ValueIsAbove,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
responseNoData := &anomaly.GetAnomaliesResponse{
|
||||
Results: []*v3.Result{
|
||||
{
|
||||
QueryName: "A",
|
||||
AnomalyScores: []*v3.Series{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
absentFor uint64
|
||||
timeBetweenEvals time.Duration
|
||||
expectAlertOnEval2 int
|
||||
}{
|
||||
{
|
||||
description: "WithinGracePeriod",
|
||||
absentFor: 5,
|
||||
timeBetweenEvals: 4 * time.Minute,
|
||||
expectAlertOnEval2: 0,
|
||||
},
|
||||
{
|
||||
description: "AfterGracePeriod",
|
||||
absentFor: 5,
|
||||
timeBetweenEvals: 6 * time.Minute,
|
||||
expectAlertOnEval2: 1,
|
||||
},
|
||||
}
|
||||
|
||||
logger := instrumentationtest.New().Logger()
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.description, func(t *testing.T) {
|
||||
postableRule.RuleCondition.AbsentFor = c.absentFor
|
||||
|
||||
t1 := baseTime.Add(5 * time.Minute)
|
||||
t2 := t1.Add(c.timeBetweenEvals)
|
||||
|
||||
responseWithData := &anomaly.GetAnomaliesResponse{
|
||||
Results: []*v3.Result{
|
||||
{
|
||||
QueryName: "A",
|
||||
AnomalyScores: []*v3.Series{
|
||||
{
|
||||
Labels: map[string]string{"test": "label"},
|
||||
Points: []v3.Point{
|
||||
{Timestamp: baseTime.UnixMilli(), Value: 1.0},
|
||||
{Timestamp: baseTime.Add(time.Minute).UnixMilli(), Value: 1.5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
telemetryStore := telemetrystoretest.New(telemetrystore.Config{}, nil)
|
||||
options := clickhouseReader.NewOptions("primaryNamespace")
|
||||
reader := clickhouseReader.NewReader(nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, reader, nil, logger, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule.provider = &mockAnomalyProvider{
|
||||
responses: []*anomaly.GetAnomaliesResponse{responseWithData, responseNoData},
|
||||
}
|
||||
|
||||
alertsFound1, err := rule.Eval(context.Background(), t1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, alertsFound1, "First eval with data should not alert")
|
||||
|
||||
alertsFound2, err := rule.Eval(context.Background(), t2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, c.expectAlertOnEval2, alertsFound2)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, tr)
|
||||
|
||||
// create ch rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else if opts.Rule.RuleType == ruletypes.RuleTypeProm {
|
||||
|
||||
@@ -72,7 +72,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, pr)
|
||||
|
||||
// create promql rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeProm, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
task = newTask(baserules.TaskTypeProm, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly {
|
||||
// create anomaly rule
|
||||
@@ -96,7 +96,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
rules = append(rules, ar)
|
||||
|
||||
// create anomaly rule task for evaluation
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
task = newTask(baserules.TaskTypeCh, opts.TaskName, evaluation.GetFrequency().Duration(), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
|
||||
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold)
|
||||
@@ -213,8 +213,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
return alertsFound, nil
|
||||
}
|
||||
|
||||
// newTask returns an appropriate group for
|
||||
// rule type
|
||||
// newTask returns an appropriate group for the rule type
|
||||
func newTask(taskType baserules.TaskType, name string, frequency time.Duration, rules []baserules.Rule, opts *baserules.ManagerOptions, notify baserules.NotifyFunc, maintenanceStore ruletypes.MaintenanceStore, orgID valuer.UUID) baserules.Task {
|
||||
if taskType == baserules.TaskTypeCh {
|
||||
return baserules.NewRuleTask(name, "", frequency, rules, opts, notify, maintenanceStore, orgID)
|
||||
|
||||
77
frontend/.cursor/rules/jest-mocking-strategy.mdc
Normal file
77
frontend/.cursor/rules/jest-mocking-strategy.mdc
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
description: Global vs local mock strategy for Jest tests
|
||||
globs:
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/__tests__/**/*.ts"
|
||||
- "**/__tests__/**/*.tsx"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Mock Decision Strategy
|
||||
|
||||
## Global Mocks (20+ test files)
|
||||
|
||||
- Core infrastructure: react-router-dom, react-query, antd
|
||||
- Browser APIs: ResizeObserver, matchMedia, localStorage
|
||||
- Utility libraries: date-fns, lodash
|
||||
- Available: `uplot` → `__mocks__/uplotMock.ts`
|
||||
|
||||
## Local Mocks (5–15 test files)
|
||||
|
||||
- Business logic dependencies
|
||||
- API endpoints with specific responses
|
||||
- Domain-specific components
|
||||
- Error scenarios and edge cases
|
||||
|
||||
## Decision Tree
|
||||
|
||||
```
|
||||
Is it used in 20+ test files?
|
||||
├─ YES → Use Global Mock
|
||||
│ ├─ react-router-dom
|
||||
│ ├─ react-query
|
||||
│ ├─ antd components
|
||||
│ └─ browser APIs
|
||||
│
|
||||
└─ NO → Is it business logic?
|
||||
├─ YES → Use Local Mock
|
||||
│ ├─ API endpoints
|
||||
│ ├─ Custom hooks
|
||||
│ └─ Domain components
|
||||
│
|
||||
└─ NO → Is it test-specific?
|
||||
├─ YES → Use Local Mock
|
||||
│ ├─ Error scenarios
|
||||
│ ├─ Loading states
|
||||
│ └─ Specific data
|
||||
│
|
||||
└─ NO → Consider Global Mock
|
||||
└─ If it becomes frequently used
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
❌ Don't mock global dependencies locally:
|
||||
```ts
|
||||
jest.mock('react-router-dom', () => ({ ... })); // Already globally mocked
|
||||
```
|
||||
|
||||
❌ Don't create global mocks for test-specific data:
|
||||
```ts
|
||||
jest.mock('../api/tracesService', () => ({
|
||||
getTraces: jest.fn(() => specificTestData) // BAD - should be local
|
||||
}));
|
||||
```
|
||||
|
||||
✅ Do use global mocks for infrastructure:
|
||||
```ts
|
||||
import { useLocation } from 'react-router-dom';
|
||||
```
|
||||
|
||||
✅ Do create local mocks for business logic:
|
||||
```ts
|
||||
jest.mock('../api/tracesService', () => ({
|
||||
getTraces: jest.fn(() => mockTracesData)
|
||||
}));
|
||||
```
|
||||
124
frontend/.cursor/rules/jest-test-conventions.mdc
Normal file
124
frontend/.cursor/rules/jest-test-conventions.mdc
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
description: Core Jest/React Testing Library conventions - harness, MSW, interactions, timers
|
||||
globs:
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/__tests__/**/*.ts"
|
||||
- "**/__tests__/**/*.tsx"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Jest Test Conventions
|
||||
|
||||
Expert developer with Jest, React Testing Library, MSW, and TypeScript. Focus on critical functionality, mock dependencies before imports, test multiple scenarios, write maintainable tests.
|
||||
|
||||
**Auto-detect TypeScript**: Check for TypeScript in the project through tsconfig.json or package.json dependencies. Adjust syntax based on this detection.
|
||||
|
||||
## Imports
|
||||
|
||||
Always import from our harness:
|
||||
```ts
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
```
|
||||
For API mocks:
|
||||
```ts
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
```
|
||||
❌ Do not import directly from `@testing-library/react`.
|
||||
|
||||
## Router
|
||||
|
||||
Use the router built into render:
|
||||
```ts
|
||||
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
|
||||
```
|
||||
Only mock `useLocation` / `useParams` if the test depends on them.
|
||||
|
||||
## Hook Mocks
|
||||
|
||||
```ts
|
||||
import useFoo from 'hooks/useFoo';
|
||||
jest.mock('hooks/useFoo');
|
||||
const mockUseFoo = jest.mocked(useFoo);
|
||||
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
|
||||
```
|
||||
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
|
||||
|
||||
## MSW
|
||||
|
||||
Global MSW server runs automatically. Override per-test:
|
||||
```ts
|
||||
server.use(
|
||||
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
|
||||
);
|
||||
```
|
||||
Keep large responses in `mocks-server/__mockdata__/`.
|
||||
|
||||
## Interactions
|
||||
|
||||
- Prefer `userEvent` for real user interactions (click, type, select, tab).
|
||||
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
|
||||
- Always await interactions:
|
||||
```ts
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||
```
|
||||
|
||||
```ts
|
||||
// Example: virtualized list scroll (no userEvent helper)
|
||||
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
|
||||
scroller.scrollTop = targetScrollTop;
|
||||
act(() => { fireEvent.scroll(scroller); });
|
||||
```
|
||||
|
||||
## Timers
|
||||
|
||||
❌ No global fake timers. ✅ Per-test only:
|
||||
```ts
|
||||
jest.useFakeTimers();
|
||||
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
|
||||
await user.type(screen.getByRole('textbox'), 'query');
|
||||
jest.advanceTimersByTime(400);
|
||||
jest.useRealTimers();
|
||||
```
|
||||
|
||||
## Queries
|
||||
|
||||
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`). Fallback: visible text. Last resort: `data-testid`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Critical Functionality**: Prioritize testing business logic and utilities
|
||||
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
|
||||
- **Data Scenarios**: Always test valid, invalid, and edge cases
|
||||
- **Descriptive Names**: Make test intent clear
|
||||
- **Organization**: Group related tests in describe
|
||||
- **Consistency**: Match repo conventions
|
||||
- **Edge Cases**: Test null, undefined, unexpected values
|
||||
- **Limit Scope**: 3–5 focused tests per file
|
||||
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
|
||||
- **No Any**: Enforce type safety
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
❌ Importing RTL directly | ❌ Global fake timers | ❌ Wrapping render in `act(...)` | ❌ Mocking infra locally
|
||||
✅ Use harness | ✅ MSW for API | ✅ userEvent + await | ✅ Pin time only for relative-date tests
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import MyComponent from '../MyComponent';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('renders and interacts', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
server.use(rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 }))));
|
||||
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
```
|
||||
168
frontend/.cursor/rules/jest-typescript-type-safety.mdc
Normal file
168
frontend/.cursor/rules/jest-typescript-type-safety.mdc
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
description: TypeScript type safety for Jest tests - mocks, interfaces, no any
|
||||
globs:
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/__tests__/**/*.ts"
|
||||
- "**/__tests__/**/*.tsx"
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# TypeScript Type Safety for Jest Tests
|
||||
|
||||
**CRITICAL**: All Jest tests MUST be fully type-safe.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Use proper TypeScript interfaces for all mock data
|
||||
- Type all Jest mock functions with `jest.MockedFunction<T>`
|
||||
- Use generic types for React components and hooks
|
||||
- Define proper return types for mock functions
|
||||
- Use `as const` for literal types when needed
|
||||
- Avoid `any` type – use proper typing instead
|
||||
|
||||
## Mock Function Typing
|
||||
|
||||
```ts
|
||||
// ✅ GOOD
|
||||
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
|
||||
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
|
||||
|
||||
// ❌ BAD
|
||||
const mockFetchUser = jest.fn() as any;
|
||||
```
|
||||
|
||||
## Mock Data with Interfaces
|
||||
|
||||
```ts
|
||||
interface User { id: number; name: string; email: string; }
|
||||
interface ApiResponse<T> { data: T; status: number; message: string; }
|
||||
|
||||
const mockUser: User = { id: 1, name: 'John Doe', email: 'john@example.com' };
|
||||
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
|
||||
```
|
||||
|
||||
## Component Props Typing
|
||||
|
||||
```ts
|
||||
interface ComponentProps { title: string; data: User[]; onUserSelect: (user: User) => void; }
|
||||
|
||||
const mockProps: ComponentProps = {
|
||||
title: 'Test',
|
||||
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
|
||||
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
|
||||
};
|
||||
render(<TestComponent {...mockProps} />);
|
||||
```
|
||||
|
||||
## Hook Testing with Types
|
||||
|
||||
```ts
|
||||
interface UseUserDataReturn {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
describe('useUserData', () => {
|
||||
it('should return user data with proper typing', () => {
|
||||
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
|
||||
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
|
||||
const { result } = renderHook(() => useUserData(1));
|
||||
expect(result.current.user).toEqual(mockUser);
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Generic Mock Typing
|
||||
|
||||
```ts
|
||||
interface MockApiResponse<T> { data: T; status: number; }
|
||||
|
||||
const mockFetchData = jest.fn() as jest.MockedFunction<
|
||||
<T>(endpoint: string) => Promise<MockApiResponse<T>>
|
||||
>;
|
||||
mockFetchData<User>('/users').mockResolvedValue({ data: { id: 1, name: 'John' }, status: 200 });
|
||||
```
|
||||
|
||||
## React Testing Library with Types
|
||||
|
||||
```ts
|
||||
type TestComponentProps = ComponentProps<typeof TestComponent>;
|
||||
|
||||
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
|
||||
const defaultProps: TestComponentProps = { title: 'Test', data: [], onSelect: jest.fn(), ...props };
|
||||
return render(<TestComponent {...defaultProps} />);
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling with Types
|
||||
|
||||
```ts
|
||||
interface ApiError { message: string; code: number; details?: Record<string, unknown>; }
|
||||
const mockApiError: ApiError = { message: 'API Error', code: 500, details: { endpoint: '/users' } };
|
||||
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
|
||||
```
|
||||
|
||||
## Global Mock Type Safety
|
||||
|
||||
```ts
|
||||
// In __mocks__/routerMock.ts
|
||||
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
|
||||
pathname: '/traces',
|
||||
search: '',
|
||||
hash: '',
|
||||
state: null,
|
||||
key: 'test-key',
|
||||
...overrides,
|
||||
});
|
||||
// In test files: const location = useLocation(); // Properly typed from global mock
|
||||
```
|
||||
|
||||
## TypeScript Configuration for Jest
|
||||
|
||||
```json
|
||||
// jest.config.ts
|
||||
{
|
||||
"preset": "ts-jest/presets/js-with-ts-esm",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"useESM": true,
|
||||
"isolatedModules": true,
|
||||
"tsconfig": "<rootDir>/tsconfig.jest.json"
|
||||
}
|
||||
},
|
||||
"extensionsToTreatAsEsm": [".ts", ".tsx"],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// tsconfig.jest.json
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "@testing-library/jest-dom"],
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["src/**/*", "**/*.test.ts", "**/*.test.tsx", "__mocks__/**/*"]
|
||||
}
|
||||
```
|
||||
|
||||
## Type Safety Checklist
|
||||
|
||||
- [ ] All mock functions use `jest.MockedFunction<T>`
|
||||
- [ ] All mock data has proper interfaces
|
||||
- [ ] No `any` types in test files
|
||||
- [ ] Generic types are used where appropriate
|
||||
- [ ] Error types are properly defined
|
||||
- [ ] Component props are typed
|
||||
- [ ] Hook return types are defined
|
||||
- [ ] API response types are defined
|
||||
- [ ] Global mocks are type-safe
|
||||
- [ ] Test utilities are properly typed
|
||||
@@ -1,484 +0,0 @@
|
||||
# Persona
|
||||
You are an expert developer with deep knowledge of Jest, React Testing Library, MSW, and TypeScript, tasked with creating unit tests for this repository.
|
||||
|
||||
# Auto-detect TypeScript Usage
|
||||
Check for TypeScript in the project through tsconfig.json or package.json dependencies.
|
||||
Adjust syntax based on this detection.
|
||||
|
||||
# TypeScript Type Safety for Jest Tests
|
||||
**CRITICAL**: All Jest tests MUST be fully type-safe with proper TypeScript types.
|
||||
|
||||
**Type Safety Requirements:**
|
||||
- Use proper TypeScript interfaces for all mock data
|
||||
- Type all Jest mock functions with `jest.MockedFunction<T>`
|
||||
- Use generic types for React components and hooks
|
||||
- Define proper return types for mock functions
|
||||
- Use `as const` for literal types when needed
|
||||
- Avoid `any` type – use proper typing instead
|
||||
|
||||
# Unit Testing Focus
|
||||
Focus on critical functionality (business logic, utility functions, component behavior)
|
||||
Mock dependencies (API calls, external modules) before imports
|
||||
Test multiple data scenarios (valid inputs, invalid inputs, edge cases)
|
||||
Write maintainable tests with descriptive names grouped in describe blocks
|
||||
|
||||
# Global vs Local Mocks
|
||||
**Use Global Mocks for:**
|
||||
- High-frequency dependencies (20+ test files)
|
||||
- Core infrastructure (react-router-dom, react-query, antd)
|
||||
- Standard implementations across the app
|
||||
- Browser APIs (ResizeObserver, matchMedia, localStorage)
|
||||
- Utility libraries (date-fns, lodash)
|
||||
|
||||
**Use Local Mocks for:**
|
||||
- Business logic dependencies (5-15 test files)
|
||||
- Test-specific behavior (different data per test)
|
||||
- API endpoints with specific responses
|
||||
- Domain-specific components
|
||||
- Error scenarios and edge cases
|
||||
|
||||
**Global Mock Files Available (from jest.config.ts):**
|
||||
- `uplot` → `__mocks__/uplotMock.ts`
|
||||
|
||||
# Repo-specific Testing Conventions
|
||||
|
||||
## Imports
|
||||
Always import from our harness:
|
||||
```ts
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
```
|
||||
For API mocks:
|
||||
```ts
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
```
|
||||
Do not import directly from `@testing-library/react`.
|
||||
|
||||
## Router
|
||||
Use the router built into render:
|
||||
```ts
|
||||
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
|
||||
```
|
||||
Only mock `useLocation` / `useParams` if the test depends on them.
|
||||
|
||||
## Hook Mocks
|
||||
Pattern:
|
||||
```ts
|
||||
import useFoo from 'hooks/useFoo';
|
||||
jest.mock('hooks/useFoo');
|
||||
const mockUseFoo = jest.mocked(useFoo);
|
||||
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
|
||||
```
|
||||
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
|
||||
|
||||
## MSW
|
||||
Global MSW server runs automatically.
|
||||
Override per-test:
|
||||
```ts
|
||||
server.use(
|
||||
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
|
||||
);
|
||||
```
|
||||
Keep large responses in `mocks-server/__mockdata_`.
|
||||
|
||||
## Interactions
|
||||
- Prefer `userEvent` for real user interactions (click, type, select, tab).
|
||||
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
|
||||
- Always await interactions:
|
||||
```ts
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||
```
|
||||
|
||||
```ts
|
||||
// Example: virtualized list scroll (no userEvent helper)
|
||||
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
|
||||
scroller.scrollTop = targetScrollTop;
|
||||
act(() => { fireEvent.scroll(scroller); });
|
||||
```
|
||||
|
||||
## Timers
|
||||
❌ No global fake timers.
|
||||
✅ Per-test only, for debounce/throttle:
|
||||
```ts
|
||||
jest.useFakeTimers();
|
||||
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
|
||||
await user.type(screen.getByRole('textbox'), 'query');
|
||||
jest.advanceTimersByTime(400);
|
||||
jest.useRealTimers();
|
||||
```
|
||||
|
||||
## Queries
|
||||
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`).
|
||||
Fallback: visible text.
|
||||
Last resort: `data-testid`.
|
||||
|
||||
# Example Test (using only configured global mocks)
|
||||
```ts
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import MyComponent from '../MyComponent';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('renders and interacts', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
|
||||
);
|
||||
|
||||
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||
|
||||
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
# Anti-patterns
|
||||
❌ Importing RTL directly
|
||||
❌ Using global fake timers
|
||||
❌ Wrapping render in `act(...)`
|
||||
❌ Mocking infra dependencies locally (router, react-query)
|
||||
✅ Use our harness (`tests/test-utils`)
|
||||
✅ Use MSW for API overrides
|
||||
✅ Use userEvent + await
|
||||
✅ Pin time only in tests that assert relative dates
|
||||
|
||||
# Best Practices
|
||||
- **Critical Functionality**: Prioritize testing business logic and utilities
|
||||
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
|
||||
- **Data Scenarios**: Always test valid, invalid, and edge cases
|
||||
- **Descriptive Names**: Make test intent clear
|
||||
- **Organization**: Group related tests in describe
|
||||
- **Consistency**: Match repo conventions
|
||||
- **Edge Cases**: Test null, undefined, unexpected values
|
||||
- **Limit Scope**: 3–5 focused tests per file
|
||||
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
|
||||
- **No Any**: Enforce type safety
|
||||
|
||||
# Example Test
|
||||
```ts
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import MyComponent from '../MyComponent';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
it('renders and interacts', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
|
||||
);
|
||||
|
||||
render(<MyComponent />, undefined, { initialRoute: '/foo' });
|
||||
|
||||
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
|
||||
await user.click(screen.getByRole('button', { name: /refresh/i }));
|
||||
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
# Anti-patterns
|
||||
❌ Importing RTL directly
|
||||
❌ Using global fake timers
|
||||
❌ Wrapping render in `act(...)`
|
||||
❌ Mocking infra dependencies locally (router, react-query)
|
||||
✅ Use our harness (`tests/test-utils`)
|
||||
✅ Use MSW for API overrides
|
||||
✅ Use userEvent + await
|
||||
✅ Pin time only in tests that assert relative dates
|
||||
|
||||
# TypeScript Type Safety Examples
|
||||
|
||||
## Proper Mock Typing
|
||||
```ts
|
||||
// ✅ GOOD - Properly typed mocks
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Type the mock functions
|
||||
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
|
||||
const mockUpdateUser = jest.fn() as jest.MockedFunction<(user: User) => Promise<ApiResponse<User>>>;
|
||||
|
||||
// Mock implementation with proper typing
|
||||
mockFetchUser.mockResolvedValue({
|
||||
data: { id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||
status: 200,
|
||||
message: 'Success'
|
||||
});
|
||||
|
||||
// ❌ BAD - Using any type
|
||||
const mockFetchUser = jest.fn() as any; // Don't do this
|
||||
```
|
||||
|
||||
## React Component Testing with Types
|
||||
```ts
|
||||
// ✅ GOOD - Properly typed component testing
|
||||
interface ComponentProps {
|
||||
title: string;
|
||||
data: User[];
|
||||
onUserSelect: (user: User) => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const TestComponent: React.FC<ComponentProps> = ({ title, data, onUserSelect, isLoading = false }) => {
|
||||
// Component implementation
|
||||
};
|
||||
|
||||
describe('TestComponent', () => {
|
||||
it('should render with proper props', () => {
|
||||
// Arrange - Type the props properly
|
||||
const mockProps: ComponentProps = {
|
||||
title: 'Test Title',
|
||||
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
|
||||
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
// Act
|
||||
render(<TestComponent {...mockProps} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Hook Testing with Types
|
||||
```ts
|
||||
// ✅ GOOD - Properly typed hook testing
|
||||
interface UseUserDataReturn {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
const useUserData = (id: number): UseUserDataReturn => {
|
||||
// Hook implementation
|
||||
};
|
||||
|
||||
describe('useUserData', () => {
|
||||
it('should return user data with proper typing', () => {
|
||||
// Arrange
|
||||
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
|
||||
mockFetchUser.mockResolvedValue({
|
||||
data: mockUser,
|
||||
status: 200,
|
||||
message: 'Success'
|
||||
});
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(() => useUserData(1));
|
||||
|
||||
// Assert
|
||||
expect(result.current.user).toEqual(mockUser);
|
||||
expect(result.current.loading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Global Mock Type Safety
|
||||
```ts
|
||||
// ✅ GOOD - Type-safe global mocks
|
||||
// In __mocks__/routerMock.ts
|
||||
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
|
||||
pathname: '/traces',
|
||||
search: '',
|
||||
hash: '',
|
||||
state: null,
|
||||
key: 'test-key',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
// In test files
|
||||
const location = useLocation(); // Properly typed from global mock
|
||||
expect(location.pathname).toBe('/traces');
|
||||
```
|
||||
|
||||
# TypeScript Configuration for Jest
|
||||
|
||||
## Required Jest Configuration
|
||||
```json
|
||||
// jest.config.ts
|
||||
{
|
||||
"preset": "ts-jest/presets/js-with-ts-esm",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"useESM": true,
|
||||
"isolatedModules": true,
|
||||
"tsconfig": "<rootDir>/tsconfig.jest.json"
|
||||
}
|
||||
},
|
||||
"extensionsToTreatAsEsm": [".ts", ".tsx"],
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript Jest Configuration
|
||||
```json
|
||||
// tsconfig.jest.json
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "@testing-library/jest-dom"],
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"__mocks__/**/*"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Common Type Safety Patterns
|
||||
|
||||
### Mock Function Typing
|
||||
```ts
|
||||
// ✅ GOOD - Proper mock function typing
|
||||
const mockApiCall = jest.fn() as jest.MockedFunction<typeof apiCall>;
|
||||
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
|
||||
|
||||
// ❌ BAD - Using any
|
||||
const mockApiCall = jest.fn() as any;
|
||||
```
|
||||
|
||||
### Generic Mock Typing
|
||||
```ts
|
||||
// ✅ GOOD - Generic mock typing
|
||||
interface MockApiResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
}
|
||||
|
||||
const mockFetchData = jest.fn() as jest.MockedFunction<
|
||||
<T>(endpoint: string) => Promise<MockApiResponse<T>>
|
||||
>;
|
||||
|
||||
// Usage
|
||||
mockFetchData<User>('/users').mockResolvedValue({
|
||||
data: { id: 1, name: 'John' },
|
||||
status: 200
|
||||
});
|
||||
```
|
||||
|
||||
### React Testing Library with Types
|
||||
```ts
|
||||
// ✅ GOOD - Typed testing utilities
|
||||
import { render, screen, RenderResult } from '@testing-library/react';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
type TestComponentProps = ComponentProps<typeof TestComponent>;
|
||||
|
||||
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
|
||||
const defaultProps: TestComponentProps = {
|
||||
title: 'Test',
|
||||
data: [],
|
||||
onSelect: jest.fn(),
|
||||
...props
|
||||
};
|
||||
|
||||
return render(<TestComponent {...defaultProps} />);
|
||||
};
|
||||
```
|
||||
|
||||
### Error Handling with Types
|
||||
```ts
|
||||
// ✅ GOOD - Typed error handling
|
||||
interface ApiError {
|
||||
message: string;
|
||||
code: number;
|
||||
details?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const mockApiError: ApiError = {
|
||||
message: 'API Error',
|
||||
code: 500,
|
||||
details: { endpoint: '/users' }
|
||||
};
|
||||
|
||||
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
|
||||
```
|
||||
|
||||
## Type Safety Checklist
|
||||
- [ ] All mock functions use `jest.MockedFunction<T>`
|
||||
- [ ] All mock data has proper interfaces
|
||||
- [ ] No `any` types in test files
|
||||
- [ ] Generic types are used where appropriate
|
||||
- [ ] Error types are properly defined
|
||||
- [ ] Component props are typed
|
||||
- [ ] Hook return types are defined
|
||||
- [ ] API response types are defined
|
||||
- [ ] Global mocks are type-safe
|
||||
- [ ] Test utilities are properly typed
|
||||
|
||||
# Mock Decision Tree
|
||||
```
|
||||
Is it used in 20+ test files?
|
||||
├─ YES → Use Global Mock
|
||||
│ ├─ react-router-dom
|
||||
│ ├─ react-query
|
||||
│ ├─ antd components
|
||||
│ └─ browser APIs
|
||||
│
|
||||
└─ NO → Is it business logic?
|
||||
├─ YES → Use Local Mock
|
||||
│ ├─ API endpoints
|
||||
│ ├─ Custom hooks
|
||||
│ └─ Domain components
|
||||
│
|
||||
└─ NO → Is it test-specific?
|
||||
├─ YES → Use Local Mock
|
||||
│ ├─ Error scenarios
|
||||
│ ├─ Loading states
|
||||
│ └─ Specific data
|
||||
│
|
||||
└─ NO → Consider Global Mock
|
||||
└─ If it becomes frequently used
|
||||
```
|
||||
|
||||
# Common Anti-Patterns to Avoid
|
||||
|
||||
❌ **Don't mock global dependencies locally:**
|
||||
```js
|
||||
// BAD - This is already globally mocked
|
||||
jest.mock('react-router-dom', () => ({ ... }));
|
||||
```
|
||||
|
||||
❌ **Don't create global mocks for test-specific data:**
|
||||
```js
|
||||
// BAD - This should be local
|
||||
jest.mock('../api/tracesService', () => ({
|
||||
getTraces: jest.fn(() => specificTestData)
|
||||
}));
|
||||
```
|
||||
|
||||
✅ **Do use global mocks for infrastructure:**
|
||||
```js
|
||||
// GOOD - Use global mock
|
||||
import { useLocation } from 'react-router-dom';
|
||||
```
|
||||
|
||||
✅ **Do create local mocks for business logic:**
|
||||
```js
|
||||
// GOOD - Local mock for specific test needs
|
||||
jest.mock('../api/tracesService', () => ({
|
||||
getTraces: jest.fn(() => mockTracesData)
|
||||
}));
|
||||
```
|
||||
@@ -61,6 +61,8 @@ module.exports = {
|
||||
curly: 'error', // Requires curly braces for all control statements
|
||||
eqeqeq: ['error', 'always', { null: 'ignore' }], // Enforces === and !== (allows == null for null/undefined check)
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }], // Warns on console.log, allows console.warn/error
|
||||
// TODO: Change this to error in May 2026
|
||||
'max-params': ['warn', 3], // a function can have max 3 params after which it should become an object
|
||||
|
||||
// TypeScript rules
|
||||
'@typescript-eslint/explicit-function-return-type': 'error', // Requires explicit return types on functions
|
||||
@@ -116,7 +118,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }], // Prevents importing packages not in package.json
|
||||
// 'import/no-cycle': 'warn', // TODO: Enable later to detect circular dependencies
|
||||
'import/no-cycle': 'warn', // Warns about circular dependencies
|
||||
|
||||
// Import sorting rules
|
||||
'simple-import-sort/imports': [
|
||||
@@ -146,6 +148,19 @@ module.exports = {
|
||||
'sonarjs/no-duplicate-string': 'off', // Disabled - can be noisy (enable periodically to check)
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/*.test.{js,jsx,ts,tsx}',
|
||||
'**/*.spec.{js,jsx,ts,tsx}',
|
||||
'**/__tests__/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
rules: {
|
||||
// Tests often have intentional duplication and complexity - disable SonarJS rules
|
||||
'sonarjs/cognitive-complexity': 'off', // Tests can be complex
|
||||
'sonarjs/no-identical-functions': 'off', // Similar test patterns are OK
|
||||
'sonarjs/no-small-switch': 'off', // Small switches are OK in tests
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/api/generated/**/*.ts'],
|
||||
rules: {
|
||||
@@ -153,7 +168,6 @@ module.exports = {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'sonarjs/no-duplicate-string': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
Embrace the spirit of collaboration and contribute to the success of our open-source project by adhering to these frontend development guidelines with precision and passion.
|
||||
|
||||
### Export Style
|
||||
|
||||
- **React components** (`src/components/`, `src/container/`, `src/pages/`): Prefer **default exports** for the main component in each file
|
||||
- **Utilities, hooks, APIs, types, constants** (`src/utils/`, `src/hooks/`, `src/api/`, `src/lib/`, `src/types/`, `src/constants/`): Prefer **named exports** for better tree-shaking and explicit imports
|
||||
|
||||
### React and Components
|
||||
|
||||
- Strive to create small and modular components, ensuring they are divided into individual pieces for improved maintainability and reusability.
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface MockUPlotInstance {
|
||||
export interface MockUPlotPaths {
|
||||
spline: jest.Mock;
|
||||
bars: jest.Mock;
|
||||
linear: jest.Mock;
|
||||
stepped: jest.Mock;
|
||||
}
|
||||
|
||||
// Create mock instance methods
|
||||
@@ -23,10 +25,23 @@ const createMockUPlotInstance = (): MockUPlotInstance => ({
|
||||
setSeries: jest.fn(),
|
||||
});
|
||||
|
||||
// Create mock paths
|
||||
const mockPaths: MockUPlotPaths = {
|
||||
spline: jest.fn(),
|
||||
bars: jest.fn(),
|
||||
// Path builder: (self, seriesIdx, idx0, idx1) => paths or null
|
||||
const createMockPathBuilder = (name: string): jest.Mock =>
|
||||
jest.fn(() => ({
|
||||
name, // To test if the correct pathBuilder is used
|
||||
stroke: jest.fn(),
|
||||
fill: jest.fn(),
|
||||
clip: jest.fn(),
|
||||
}));
|
||||
|
||||
// Create mock paths - linear, spline, stepped needed by UPlotSeriesBuilder.getPathBuilder
|
||||
const mockPaths = {
|
||||
spline: jest.fn(() => createMockPathBuilder('spline')),
|
||||
bars: jest.fn(() => createMockPathBuilder('bars')),
|
||||
linear: jest.fn(() => createMockPathBuilder('linear')),
|
||||
stepped: jest.fn((opts?: { align?: number }) =>
|
||||
createMockPathBuilder(`stepped-(${opts?.align ?? 0})`),
|
||||
),
|
||||
};
|
||||
|
||||
// Mock static methods
|
||||
|
||||
@@ -17,6 +17,8 @@ const config: Config.InitialOptions = {
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^@signozhq/icons$':
|
||||
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
|
||||
},
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"commitlint": "commitlint --edit $1",
|
||||
"test": "jest",
|
||||
"test:changedsince": "jest --changedSince=main --coverage --silent",
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh && prettier --write src/api/generated && (eslint --fix src/api/generated || true)"
|
||||
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.15.0"
|
||||
@@ -51,16 +51,20 @@
|
||||
"@signozhq/checkbox": "0.0.2",
|
||||
"@signozhq/combobox": "0.0.2",
|
||||
"@signozhq/command": "0.0.0",
|
||||
"@signozhq/design-tokens": "1.1.4",
|
||||
"@signozhq/drawer": "0.0.4",
|
||||
"@signozhq/design-tokens": "2.1.1",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
"@signozhq/resizable": "0.0.0",
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/table": "0.3.7",
|
||||
"@signozhq/tabs": "0.0.11",
|
||||
"@signozhq/tooltip": "0.0.2",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"@tanstack/react-virtual": "3.11.2",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
"@uiw/codemirror-theme-dracula": "4.25.4",
|
||||
"@uiw/codemirror-theme-github": "4.24.1",
|
||||
"@uiw/react-codemirror": "4.23.10",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
@@ -105,6 +109,7 @@
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"immer": "11.1.3",
|
||||
"jest": "^27.5.1",
|
||||
"js-base64": "^3.7.2",
|
||||
"less": "^4.1.2",
|
||||
|
||||
1
frontend/public/Logos/convex-logo.svg
Normal file
1
frontend/public/Logos/convex-logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="184" height="188" fill="none" viewBox="0 0 184 188"><path fill="#f3b01c" d="M108.092 130.021c18.166-2.018 35.293-11.698 44.723-27.854-4.466 39.961-48.162 65.218-83.83 49.711-3.286-1.425-6.115-3.796-8.056-6.844-8.016-12.586-10.65-28.601-6.865-43.135 10.817 18.668 32.81 30.111 54.028 28.122"/><path fill="#8d2676" d="M53.401 90.174c-7.364 17.017-7.682 36.94 1.345 53.336-31.77-23.902-31.423-75.052-.388-98.715 2.87-2.187 6.282-3.485 9.86-3.683 14.713-.776 29.662 4.91 40.146 15.507-21.3.212-42.046 13.857-50.963 33.555"/><path fill="#ee342f" d="M114.637 61.855C103.89 46.87 87.069 36.668 68.639 36.358c35.625-16.17 79.446 10.047 84.217 48.807.444 3.598-.139 7.267-1.734 10.512-6.656 13.518-18.998 24.002-33.42 27.882 10.567-19.599 9.263-43.544-3.065-61.704"/></svg>
|
||||
|
After Width: | Height: | Size: 811 B |
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"FORGOT_PASSWORD": "SigNoz | Forgot Password",
|
||||
"HOME": "SigNoz | Home",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
|
||||
312
frontend/public/svgs/dotted-double-line.svg
Normal file
312
frontend/public/svgs/dotted-double-line.svg
Normal file
@@ -0,0 +1,312 @@
|
||||
<svg width="929" height="8" viewBox="0 0 929 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="12" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="18" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="24" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="30" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="36" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="42" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="48" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="54" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="60" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="66" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="72" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="78" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="84" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="90" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="96" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="102" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="108" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="114" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="120" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="126" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="132" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="138" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="144" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="150" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="156" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="162" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="168" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="174" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="180" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="186" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="192" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="198" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="204" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="210" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="216" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="222" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="228" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="234" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="240" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="246" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="252" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="258" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="264" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="270" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="276" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="282" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="288" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="294" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="300" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="306" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="312" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="318" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="324" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="330" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="336" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="342" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="348" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="354" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="360" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="366" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="372" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="378" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="384" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="390" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="396" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="402" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="408" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="414" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="420" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="426" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="432" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="438" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="444" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="450" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="456" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="462" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="468" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="474" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="480" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="486" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="492" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="498" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="504" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="510" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="516" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="522" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="528" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="534" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="540" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="546" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="552" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="558" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="564" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="570" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="576" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="582" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="588" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="594" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="600" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="606" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="612" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="618" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="624" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="630" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="636" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="642" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="648" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="654" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="660" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="666" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="672" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="678" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="684" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="690" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="696" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="702" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="708" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="714" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="720" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="726" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="732" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="738" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="744" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="750" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="756" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="762" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="768" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="774" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="780" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="786" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="792" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="798" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="804" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="810" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="816" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="822" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="828" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="834" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="840" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="846" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="852" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="858" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="864" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="870" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="876" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="882" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="888" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="894" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="900" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="906" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="912" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="918" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="924" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="6" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="12" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="18" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="24" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="30" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="36" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="42" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="48" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="54" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="60" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="66" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="72" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="78" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="84" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="90" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="96" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="102" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="108" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="114" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="120" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="126" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="132" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="138" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="144" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="150" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="156" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="162" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="168" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="174" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="180" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="186" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="192" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="198" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="204" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="210" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="216" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="222" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="228" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="234" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="240" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="246" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="252" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="258" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="264" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="270" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="276" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="282" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="288" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="294" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="300" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="306" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="312" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="318" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="324" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="330" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="336" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="342" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="348" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="354" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="360" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="366" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="372" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="378" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="384" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="390" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="396" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="402" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="408" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="414" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="420" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="426" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="432" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="438" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="444" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="450" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="456" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="462" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="468" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="474" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="480" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="486" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="492" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="498" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="504" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="510" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="516" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="522" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="528" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="534" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="540" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="546" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="552" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="558" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="564" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="570" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="576" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="582" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="588" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="594" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="600" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="606" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="612" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="618" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="624" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="630" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="636" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="642" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="648" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="654" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="660" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="666" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="672" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="678" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="684" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="690" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="696" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="702" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="708" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="714" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="720" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="726" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="732" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="738" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="744" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="750" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="756" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="762" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="768" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="774" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="780" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="786" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="792" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="798" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="804" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="810" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="816" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="822" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="828" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="834" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="840" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="846" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="852" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="858" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="864" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="870" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="876" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="882" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="888" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="894" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="900" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="906" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="912" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="918" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
<rect x="924" y="6" width="2" height="2" rx="1" fill="#242834"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "\n\n---\nRenamed tag files to index.ts...\n"
|
||||
# Rename tag files to index.ts in services directories
|
||||
# tags-split creates: services/tagName/tagName.ts -> rename to services/tagName/index.ts
|
||||
find src/api/generated/services -mindepth 1 -maxdepth 1 -type d | while read -r dir; do
|
||||
@@ -11,4 +12,33 @@ find src/api/generated/services -mindepth 1 -maxdepth 1 -type d | while read -r
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Tag files renamed to index.ts"
|
||||
echo "\n✅ Tag files renamed to index.ts"
|
||||
|
||||
# Format generated files
|
||||
echo "\n\n---\nRunning prettier...\n"
|
||||
if ! prettier --write src/api/generated; then
|
||||
echo "Prettier formatting failed!"
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ Prettier formatting successful"
|
||||
|
||||
|
||||
# Fix linting issues
|
||||
echo "\n\n---\nRunning eslint...\n"
|
||||
if ! yarn lint --fix --quiet src/api/generated; then
|
||||
echo "ESLint check failed! Please fix linting errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ ESLint check successful"
|
||||
|
||||
|
||||
# Check for type errors
|
||||
echo "\n\n---\nChecking for type errors...\n"
|
||||
if ! tsc --noEmit; then
|
||||
echo "Type check failed! Please fix type errors before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
echo "\n✅ Type check successful"
|
||||
|
||||
|
||||
echo "\n\n---\n ✅✅✅ API generation complete!"
|
||||
|
||||
@@ -194,6 +194,10 @@ export const Login = Loadable(
|
||||
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
|
||||
);
|
||||
|
||||
export const ForgotPassword = Loadable(
|
||||
() => import(/* webpackChunkName: "ForgotPassword" */ 'pages/ForgotPassword'),
|
||||
);
|
||||
|
||||
export const UnAuthorized = Loadable(
|
||||
() => import(/* webpackChunkName: "UnAuthorized" */ 'pages/UnAuthorized'),
|
||||
);
|
||||
@@ -249,12 +253,18 @@ export const ShortcutsPage = Loadable(
|
||||
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const InstalledIntegrations = Loadable(
|
||||
export const Integrations = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
|
||||
),
|
||||
);
|
||||
export const IntegrationsDetailsPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "IntegrationsDetailsPage" */ 'pages/IntegrationsDetailsPage'
|
||||
),
|
||||
);
|
||||
|
||||
export const MessagingQueuesMainPage = Loadable(
|
||||
() =>
|
||||
|
||||
@@ -17,9 +17,11 @@ import {
|
||||
DashboardWidget,
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
ForgotPassword,
|
||||
Home,
|
||||
InfrastructureMonitoring,
|
||||
InstalledIntegrations,
|
||||
Integrations,
|
||||
IntegrationsDetailsPage,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LiveLogs,
|
||||
@@ -339,6 +341,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: false,
|
||||
key: 'LOGIN',
|
||||
},
|
||||
{
|
||||
path: ROUTES.FORGOT_PASSWORD,
|
||||
exact: true,
|
||||
component: ForgotPassword,
|
||||
isPrivate: false,
|
||||
key: 'FORGOT_PASSWORD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.UN_AUTHORIZED,
|
||||
exact: true,
|
||||
@@ -381,10 +390,17 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_ACCESS_RESTRICTED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS_DETAIL,
|
||||
exact: true,
|
||||
component: IntegrationsDetailsPage,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS,
|
||||
exact: true,
|
||||
component: InstalledIntegrations,
|
||||
component: Integrations,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS',
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
const dashboardVariablesQuery = async (
|
||||
props: Props,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SuccessResponse<VariableResponseProps> | ErrorResponse> => {
|
||||
try {
|
||||
const { globalTime } = store.getState();
|
||||
@@ -32,7 +33,7 @@ const dashboardVariablesQuery = async (
|
||||
|
||||
payload.variables = { ...payload.variables, ...timeVariables };
|
||||
|
||||
const response = await axios.post(`/variables/query`, payload);
|
||||
const response = await axios.post(`/variables/query`, payload, { signal });
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -19,6 +19,7 @@ export const getFieldValues = async (
|
||||
startUnixMilli?: number,
|
||||
endUnixMilli?: number,
|
||||
existingQuery?: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<SuccessResponseV2<FieldValueResponse>> => {
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
@@ -47,7 +48,10 @@ export const getFieldValues = async (
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get('/fields/values', { params });
|
||||
const response = await axios.get('/fields/values', {
|
||||
params,
|
||||
signal: abortSignal,
|
||||
});
|
||||
|
||||
// Normalize values from different types (stringValues, boolValues, etc.)
|
||||
if (response.data?.data?.values) {
|
||||
|
||||
@@ -39,7 +39,7 @@ type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* This endpoints deletes the public sharing config and disables the public sharing of a dashboard
|
||||
* This endpoint deletes the public sharing config and disables the public sharing of a dashboard
|
||||
* @summary Delete public dashboard
|
||||
*/
|
||||
export const deletePublicDashboard = ({
|
||||
@@ -118,7 +118,7 @@ export const useDeletePublicDashboard = <
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoints returns public sharing config for a dashboard
|
||||
* This endpoint returns public sharing config for a dashboard
|
||||
* @summary Get public dashboard
|
||||
*/
|
||||
export const getPublicDashboard = (
|
||||
@@ -222,7 +222,7 @@ export const invalidateGetPublicDashboard = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoints creates public sharing config and enables public sharing of the dashboard
|
||||
* This endpoint creates public sharing config and enables public sharing of the dashboard
|
||||
* @summary Create public dashboard
|
||||
*/
|
||||
export const createPublicDashboard = (
|
||||
@@ -321,7 +321,7 @@ export const useCreatePublicDashboard = <
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoints updates the public sharing config for a dashboard
|
||||
* This endpoint updates the public sharing config for a dashboard
|
||||
* @summary Update public dashboard
|
||||
*/
|
||||
export const updatePublicDashboard = (
|
||||
@@ -418,7 +418,7 @@ export const useUpdatePublicDashboard = <
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoints returns the sanitized dashboard data for public access
|
||||
* This endpoint returns the sanitized dashboard data for public access
|
||||
* @summary Get public dashboard data
|
||||
*/
|
||||
export const getPublicDashboardData = (
|
||||
|
||||
222
frontend/src/api/generated/services/fields/index.ts
Normal file
222
frontend/src/api/generated/services/fields/index.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* ! 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 { GeneratedAPIInstance } from '../../../index';
|
||||
import type {
|
||||
GetFieldsKeys200,
|
||||
GetFieldsKeysParams,
|
||||
GetFieldsValues200,
|
||||
GetFieldsValuesParams,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
|
||||
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* This endpoint returns field keys
|
||||
* @summary Get field keys
|
||||
*/
|
||||
export const getFieldsKeys = (
|
||||
params?: GetFieldsKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetFieldsKeys200>({
|
||||
url: `/api/v1/fields/keys`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetFieldsKeysQueryKey = (params?: GetFieldsKeysParams) => {
|
||||
return ['getFieldsKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetFieldsKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetFieldsKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getFieldsKeys>>> = ({
|
||||
signal,
|
||||
}) => getFieldsKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetFieldsKeysQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>
|
||||
>;
|
||||
export type GetFieldsKeysQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Get field keys
|
||||
*/
|
||||
|
||||
export function useGetFieldsKeys<
|
||||
TData = Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetFieldsKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get field keys
|
||||
*/
|
||||
export const invalidateGetFieldsKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetFieldsKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetFieldsKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint returns field values
|
||||
* @summary Get field values
|
||||
*/
|
||||
export const getFieldsValues = (
|
||||
params?: GetFieldsValuesParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetFieldsValues200>({
|
||||
url: `/api/v1/fields/values`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetFieldsValuesQueryKey = (params?: GetFieldsValuesParams) => {
|
||||
return ['getFieldsValues', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetFieldsValuesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetFieldsValuesQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getFieldsValues>>> = ({
|
||||
signal,
|
||||
}) => getFieldsValues(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetFieldsValuesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>
|
||||
>;
|
||||
export type GetFieldsValuesQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Get field values
|
||||
*/
|
||||
|
||||
export function useGetFieldsValues<
|
||||
TData = Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getFieldsValues>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetFieldsValuesQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get field values
|
||||
*/
|
||||
export const invalidateGetFieldsValues = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetFieldsValuesParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetFieldsValuesQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
@@ -28,8 +28,10 @@ import type {
|
||||
GatewaytypesPostableIngestionKeyLimitDTO,
|
||||
GatewaytypesUpdatableIngestionKeyLimitDTO,
|
||||
GetIngestionKeys200,
|
||||
GetIngestionKeysParams,
|
||||
RenderErrorResponseDTO,
|
||||
SearchIngestionKeys200,
|
||||
SearchIngestionKeysParams,
|
||||
UpdateIngestionKeyLimitPathParameters,
|
||||
UpdateIngestionKeyPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
@@ -42,35 +44,44 @@ type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
* This endpoint returns the ingestion keys for a workspace
|
||||
* @summary Get ingestion keys for workspace
|
||||
*/
|
||||
export const getIngestionKeys = (signal?: AbortSignal) => {
|
||||
export const getIngestionKeys = (
|
||||
params?: GetIngestionKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetIngestionKeys200>({
|
||||
url: `/api/v2/gateway/ingestion_keys`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetIngestionKeysQueryKey = () => {
|
||||
return ['getIngestionKeys'] as const;
|
||||
export const getGetIngestionKeysQueryKey = (
|
||||
params?: GetIngestionKeysParams,
|
||||
) => {
|
||||
return ['getIngestionKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getGetIngestionKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
>(
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetIngestionKeysQueryKey();
|
||||
const queryKey = queryOptions?.queryKey ?? getGetIngestionKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getIngestionKeys>>> = ({
|
||||
signal,
|
||||
}) => getIngestionKeys(signal);
|
||||
}) => getIngestionKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
@@ -91,14 +102,17 @@ export type GetIngestionKeysQueryError = RenderErrorResponseDTO;
|
||||
export function useGetIngestionKeys<
|
||||
TData = Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeysQueryOptions(options);
|
||||
>(
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetIngestionKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
@@ -114,10 +128,11 @@ export function useGetIngestionKeys<
|
||||
*/
|
||||
export const invalidateGetIngestionKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetIngestionKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetIngestionKeysQueryKey() },
|
||||
{ queryKey: getGetIngestionKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -662,35 +677,45 @@ export const useUpdateIngestionKeyLimit = <
|
||||
* This endpoint returns the ingestion keys for a workspace
|
||||
* @summary Search ingestion keys for workspace
|
||||
*/
|
||||
export const searchIngestionKeys = (signal?: AbortSignal) => {
|
||||
export const searchIngestionKeys = (
|
||||
params?: SearchIngestionKeysParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<SearchIngestionKeys200>({
|
||||
url: `/api/v2/gateway/ingestion_keys/search`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSearchIngestionKeysQueryKey = () => {
|
||||
return ['searchIngestionKeys'] as const;
|
||||
export const getSearchIngestionKeysQueryKey = (
|
||||
params?: SearchIngestionKeysParams,
|
||||
) => {
|
||||
return ['searchIngestionKeys', ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getSearchIngestionKeysQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
>(
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getSearchIngestionKeysQueryKey();
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getSearchIngestionKeysQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>
|
||||
> = ({ signal }) => searchIngestionKeys(signal);
|
||||
> = ({ signal }) => searchIngestionKeys(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
@@ -711,14 +736,17 @@ export type SearchIngestionKeysQueryError = RenderErrorResponseDTO;
|
||||
export function useSearchIngestionKeys<
|
||||
TData = Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getSearchIngestionKeysQueryOptions(options);
|
||||
>(
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof searchIngestionKeys>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getSearchIngestionKeysQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
@@ -734,10 +762,11 @@ export function useSearchIngestionKeys<
|
||||
*/
|
||||
export const invalidateSearchIngestionKeys = async (
|
||||
queryClient: QueryClient,
|
||||
params?: SearchIngestionKeysParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getSearchIngestionKeysQueryKey() },
|
||||
{ queryKey: getSearchIngestionKeysQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* This endpoints returns global config
|
||||
* This endpoint returns global config
|
||||
* @summary Get global config
|
||||
*/
|
||||
export const getGlobalConfig = (signal?: AbortSignal) => {
|
||||
|
||||
@@ -47,7 +47,7 @@ type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
* @summary Get metric alerts
|
||||
*/
|
||||
export const getMetricAlerts = (
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricAlerts200>({
|
||||
@@ -66,7 +66,7 @@ export const getGetMetricAlertsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
@@ -103,7 +103,7 @@ export function useGetMetricAlerts<
|
||||
TData = Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricAlerts>>,
|
||||
@@ -128,7 +128,7 @@ export function useGetMetricAlerts<
|
||||
*/
|
||||
export const invalidateGetMetricAlerts = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricAlertsParams,
|
||||
params: GetMetricAlertsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -144,7 +144,7 @@ export const invalidateGetMetricAlerts = async (
|
||||
* @summary Get metric dashboards
|
||||
*/
|
||||
export const getMetricDashboards = (
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricDashboards200>({
|
||||
@@ -165,7 +165,7 @@ export const getGetMetricDashboardsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
@@ -203,7 +203,7 @@ export function useGetMetricDashboards<
|
||||
TData = Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricDashboards>>,
|
||||
@@ -228,7 +228,7 @@ export function useGetMetricDashboards<
|
||||
*/
|
||||
export const invalidateGetMetricDashboards = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricDashboardsParams,
|
||||
params: GetMetricDashboardsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -244,7 +244,7 @@ export const invalidateGetMetricDashboards = async (
|
||||
* @summary Get metric highlights
|
||||
*/
|
||||
export const getMetricHighlights = (
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricHighlights200>({
|
||||
@@ -265,7 +265,7 @@ export const getGetMetricHighlightsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
@@ -303,7 +303,7 @@ export function useGetMetricHighlights<
|
||||
TData = Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricHighlights>>,
|
||||
@@ -328,7 +328,7 @@ export function useGetMetricHighlights<
|
||||
*/
|
||||
export const invalidateGetMetricHighlights = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricHighlightsParams,
|
||||
params: GetMetricHighlightsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
@@ -526,7 +526,7 @@ export const useGetMetricAttributes = <
|
||||
* @summary Get metric metadata
|
||||
*/
|
||||
export const getMetricMetadata = (
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetMetricMetadata200>({
|
||||
@@ -547,7 +547,7 @@ export const getGetMetricMetadataQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
@@ -585,7 +585,7 @@ export function useGetMetricMetadata<
|
||||
TData = Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getMetricMetadata>>,
|
||||
@@ -610,7 +610,7 @@ export function useGetMetricMetadata<
|
||||
*/
|
||||
export const invalidateGetMetricMetadata = async (
|
||||
queryClient: QueryClient,
|
||||
params?: GetMetricMetadataParams,
|
||||
params: GetMetricMetadataParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
|
||||
433
frontend/src/api/generated/services/role/index.ts
Normal file
433
frontend/src/api/generated/services/role/index.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../index';
|
||||
import type {
|
||||
CreateRole201,
|
||||
DeleteRolePathParameters,
|
||||
GetRole200,
|
||||
GetRolePathParameters,
|
||||
ListRoles200,
|
||||
PatchRolePathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
type AwaitedInput<T> = PromiseLike<T> | T;
|
||||
|
||||
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* This endpoint lists all roles
|
||||
* @summary List roles
|
||||
*/
|
||||
export const listRoles = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListRoles200>({
|
||||
url: `/api/v1/roles`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListRolesQueryKey = () => {
|
||||
return ['listRoles'] as const;
|
||||
};
|
||||
|
||||
export const getListRolesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listRoles>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listRoles>>, TError, TData>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListRolesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listRoles>>> = ({
|
||||
signal,
|
||||
}) => listRoles(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listRoles>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListRolesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listRoles>>
|
||||
>;
|
||||
export type ListRolesQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary List roles
|
||||
*/
|
||||
|
||||
export function useListRoles<
|
||||
TData = Awaited<ReturnType<typeof listRoles>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof listRoles>>, TError, TData>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListRolesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List roles
|
||||
*/
|
||||
export const invalidateListRoles = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListRolesQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a role
|
||||
* @summary Create role
|
||||
*/
|
||||
export const createRole = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<CreateRole201>({
|
||||
url: `/api/v1/roles`,
|
||||
method: 'POST',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateRoleMutationOptions = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createRole'];
|
||||
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 createRole>>,
|
||||
void
|
||||
> = () => {
|
||||
return createRole();
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createRole>>
|
||||
>;
|
||||
|
||||
export type CreateRoleMutationError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Create role
|
||||
*/
|
||||
export const useCreateRole = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createRole>>,
|
||||
TError,
|
||||
void,
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateRoleMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes a role
|
||||
* @summary Delete role
|
||||
*/
|
||||
export const deleteRole = ({ id }: DeleteRolePathParameters) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/roles/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteRoleMutationOptions = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteRole'];
|
||||
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 deleteRole>>,
|
||||
{ pathParams: DeleteRolePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteRole(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteRole>>
|
||||
>;
|
||||
|
||||
export type DeleteRoleMutationError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Delete role
|
||||
*/
|
||||
export const useDeleteRole = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteRole>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteRoleMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint gets a role
|
||||
* @summary Get role
|
||||
*/
|
||||
export const getRole = (
|
||||
{ id }: GetRolePathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetRole200>({
|
||||
url: `/api/v1/roles/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetRoleQueryKey = ({ id }: GetRolePathParameters) => {
|
||||
return ['getRole'] as const;
|
||||
};
|
||||
|
||||
export const getGetRoleQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getRole>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
{ id }: GetRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof getRole>>, TError, TData>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetRoleQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getRole>>> = ({
|
||||
signal,
|
||||
}) => getRole({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<Awaited<ReturnType<typeof getRole>>, TError, TData> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetRoleQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getRole>>
|
||||
>;
|
||||
export type GetRoleQueryError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Get role
|
||||
*/
|
||||
|
||||
export function useGetRole<
|
||||
TData = Awaited<ReturnType<typeof getRole>>,
|
||||
TError = RenderErrorResponseDTO
|
||||
>(
|
||||
{ id }: GetRolePathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof getRole>>, TError, TData>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetRoleQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get role
|
||||
*/
|
||||
export const invalidateGetRole = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetRolePathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetRoleQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint patches a role
|
||||
* @summary Patch role
|
||||
*/
|
||||
export const patchRole = ({ id }: PatchRolePathParameters) => {
|
||||
return GeneratedAPIInstance<string>({
|
||||
url: `/api/v1/roles/${id}`,
|
||||
method: 'PATCH',
|
||||
});
|
||||
};
|
||||
|
||||
export const getPatchRoleMutationOptions = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof patchRole>>,
|
||||
TError,
|
||||
{ pathParams: PatchRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof patchRole>>,
|
||||
TError,
|
||||
{ pathParams: PatchRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['patchRole'];
|
||||
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 patchRole>>,
|
||||
{ pathParams: PatchRolePathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return patchRole(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type PatchRoleMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof patchRole>>
|
||||
>;
|
||||
|
||||
export type PatchRoleMutationError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Patch role
|
||||
*/
|
||||
export const usePatchRole = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof patchRole>>,
|
||||
TError,
|
||||
{ pathParams: PatchRolePathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof patchRole>>,
|
||||
TError,
|
||||
{ pathParams: PatchRolePathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getPatchRoleMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
@@ -650,11 +650,11 @@ export interface MetricsexplorertypesMetricAlertDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alertId?: string;
|
||||
alertId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
alertName?: string;
|
||||
alertName: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAlertsResponseDTO {
|
||||
@@ -662,24 +662,24 @@ export interface MetricsexplorertypesMetricAlertsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
alerts?: MetricsexplorertypesMetricAlertDTO[] | null;
|
||||
alerts: MetricsexplorertypesMetricAlertDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAttributeDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
key?: string;
|
||||
key: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
valueCount?: number;
|
||||
valueCount: number;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
values?: string[] | null;
|
||||
values: string[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricAttributesRequestDTO {
|
||||
@@ -691,7 +691,7 @@ export interface MetricsexplorertypesMetricAttributesRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @nullable true
|
||||
@@ -704,31 +704,31 @@ export interface MetricsexplorertypesMetricAttributesResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
attributes?: MetricsexplorertypesMetricAttributeDTO[] | null;
|
||||
attributes: MetricsexplorertypesMetricAttributeDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
totalKeys?: number;
|
||||
totalKeys: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardId?: string;
|
||||
dashboardId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
dashboardName?: string;
|
||||
dashboardName: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetId?: string;
|
||||
widgetId: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
widgetName?: string;
|
||||
widgetName: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricDashboardsResponseDTO {
|
||||
@@ -736,7 +736,7 @@ export interface MetricsexplorertypesMetricDashboardsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
dashboards?: MetricsexplorertypesMetricDashboardDTO[] | null;
|
||||
dashboards: MetricsexplorertypesMetricDashboardDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricHighlightsResponseDTO {
|
||||
@@ -744,74 +744,65 @@ export interface MetricsexplorertypesMetricHighlightsResponseDTO {
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
activeTimeSeries?: number;
|
||||
activeTimeSeries: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
dataPoints?: number;
|
||||
dataPoints: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
lastReceived?: number;
|
||||
lastReceived: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalTimeSeries?: number;
|
||||
totalTimeSeries: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesMetricMetadataDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isMonotonic?: boolean;
|
||||
isMonotonic: boolean;
|
||||
temporality: MetrictypesTemporalityDTO;
|
||||
type: MetrictypesTypeDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
temporality?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesStatDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
samples?: number;
|
||||
samples: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
timeseries?: number;
|
||||
timeseries: number;
|
||||
type: MetrictypesTypeDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesStatsRequestDTO {
|
||||
@@ -819,12 +810,12 @@ export interface MetricsexplorertypesStatsRequestDTO {
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end?: number;
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit?: number;
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
@@ -834,7 +825,7 @@ export interface MetricsexplorertypesStatsRequestDTO {
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start?: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesStatsResponseDTO {
|
||||
@@ -842,51 +833,52 @@ export interface MetricsexplorertypesStatsResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
metrics?: MetricsexplorertypesStatDTO[] | null;
|
||||
metrics: MetricsexplorertypesStatDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
total?: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesTreemapEntryDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
percentage?: number;
|
||||
percentage: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @minimum 0
|
||||
*/
|
||||
totalValue?: number;
|
||||
totalValue: number;
|
||||
}
|
||||
|
||||
export enum MetricsexplorertypesTreemapModeDTO {
|
||||
timeseries = 'timeseries',
|
||||
samples = 'samples',
|
||||
}
|
||||
export interface MetricsexplorertypesTreemapRequestDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end?: number;
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
mode?: string;
|
||||
limit: number;
|
||||
mode: MetricsexplorertypesTreemapModeDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start?: number;
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesTreemapResponseDTO {
|
||||
@@ -894,41 +886,47 @@ export interface MetricsexplorertypesTreemapResponseDTO {
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
samples?: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
samples: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
timeseries?: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
timeseries: MetricsexplorertypesTreemapEntryDTO[] | null;
|
||||
}
|
||||
|
||||
export interface MetricsexplorertypesUpdateMetricMetadataRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
description: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
isMonotonic?: boolean;
|
||||
isMonotonic: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
temporality: MetrictypesTemporalityDTO;
|
||||
type: MetrictypesTypeDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
temporality?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export enum MetrictypesTemporalityDTO {
|
||||
delta = 'delta',
|
||||
cumulative = 'cumulative',
|
||||
unspecified = 'unspecified',
|
||||
}
|
||||
export enum MetrictypesTypeDTO {
|
||||
gauge = 'gauge',
|
||||
sum = 'sum',
|
||||
histogram = 'histogram',
|
||||
summary = 'summary',
|
||||
exponentialhistogram = 'exponentialhistogram',
|
||||
}
|
||||
export interface PreferencetypesPreferenceDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -1051,7 +1049,7 @@ export interface Querybuildertypesv5OrderByKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -1110,6 +1108,112 @@ export interface RenderErrorResponseDTO {
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface RoletypesRoleDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys = {
|
||||
[key: string]: TelemetrytypesTelemetryFieldKeyDTO[];
|
||||
} | null;
|
||||
|
||||
export interface TelemetrytypesGettableFieldKeysDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
complete: boolean;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
keys: TelemetrytypesGettableFieldKeysDTOKeys;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesGettableFieldValuesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
complete: boolean;
|
||||
values: TelemetrytypesTelemetryFieldValuesDTO;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesTelemetryFieldKeyDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface TelemetrytypesTelemetryFieldValuesDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
boolValues?: boolean[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
numberValues?: number[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
relatedValues?: string[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
stringValues?: string[];
|
||||
}
|
||||
|
||||
export interface TypesChangePasswordRequestDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1310,6 +1414,21 @@ export interface TypesPostableAcceptInviteDTO {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableForgotPasswordDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
frontendBaseURL?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
}
|
||||
|
||||
export interface TypesPostableInviteDTO {
|
||||
/**
|
||||
* @type string
|
||||
@@ -1341,6 +1460,11 @@ export interface TypesPostableResetPasswordDTO {
|
||||
}
|
||||
|
||||
export interface TypesResetPasswordTokenDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
expiresAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -1537,6 +1661,132 @@ export type DeleteAuthDomainPathParameters = {
|
||||
export type UpdateAuthDomainPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetFieldsKeysParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
searchText?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsKeys200 = {
|
||||
data?: TelemetrytypesGettableFieldKeysDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsValuesParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
signal?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
source?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
startUnixMilli?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
* @description undefined
|
||||
*/
|
||||
endUnixMilli?: number;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldContext?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
fieldDataType?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
searchText?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
existingQuery?: string;
|
||||
};
|
||||
|
||||
export type GetFieldsValues200 = {
|
||||
data?: TelemetrytypesGettableFieldValuesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetResetPasswordTokenPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
@@ -1682,6 +1932,42 @@ export type GetPublicDashboardWidgetQueryRange200 = {
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type ListRoles200 = {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
data?: RoletypesRoleDTO[];
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type CreateRole201 = {
|
||||
data?: TypesIdentifiableDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type DeleteRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type GetRole200 = {
|
||||
data?: RoletypesRoleDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type PatchRolePathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type ListUsers200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -1762,6 +2048,19 @@ export type GetFeatures200 = {
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type GetIngestionKeysParams = {
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
per_page?: number;
|
||||
};
|
||||
|
||||
export type GetIngestionKeys200 = {
|
||||
data?: GatewaytypesGettableIngestionKeysDTO;
|
||||
/**
|
||||
@@ -1801,6 +2100,24 @@ export type DeleteIngestionKeyLimitPathParameters = {
|
||||
export type UpdateIngestionKeyLimitPathParameters = {
|
||||
limitId: string;
|
||||
};
|
||||
export type SearchIngestionKeysParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @description undefined
|
||||
*/
|
||||
per_page?: number;
|
||||
};
|
||||
|
||||
export type SearchIngestionKeys200 = {
|
||||
data?: GatewaytypesGettableIngestionKeysDTO;
|
||||
/**
|
||||
@@ -1814,7 +2131,7 @@ export type GetMetricAlertsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricAlerts200 = {
|
||||
@@ -1830,7 +2147,7 @@ export type GetMetricDashboardsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricDashboards200 = {
|
||||
@@ -1846,7 +2163,7 @@ export type GetMetricHighlightsParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricHighlights200 = {
|
||||
@@ -1873,7 +2190,7 @@ export type GetMetricMetadataParams = {
|
||||
* @type string
|
||||
* @description undefined
|
||||
*/
|
||||
metricName?: string;
|
||||
metricName: string;
|
||||
};
|
||||
|
||||
export type GetMetricMetadata200 = {
|
||||
|
||||
@@ -40,6 +40,7 @@ import type {
|
||||
TypesChangePasswordRequestDTO,
|
||||
TypesPostableAcceptInviteDTO,
|
||||
TypesPostableAPIKeyDTO,
|
||||
TypesPostableForgotPasswordDTO,
|
||||
TypesPostableInviteDTO,
|
||||
TypesPostableResetPasswordDTO,
|
||||
TypesStorableAPIKeyDTO,
|
||||
@@ -1567,3 +1568,87 @@ export const invalidateGetMyUser = async (
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint initiates the forgot password flow by sending a reset password email
|
||||
* @summary Forgot password
|
||||
*/
|
||||
export const forgotPassword = (
|
||||
typesPostableForgotPasswordDTO: TypesPostableForgotPasswordDTO,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/factor_password/forgot`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesPostableForgotPasswordDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getForgotPasswordMutationOptions = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: TypesPostableForgotPasswordDTO },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: TypesPostableForgotPasswordDTO },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['forgotPassword'];
|
||||
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 forgotPassword>>,
|
||||
{ data: TypesPostableForgotPasswordDTO }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return forgotPassword(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ForgotPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof forgotPassword>>
|
||||
>;
|
||||
export type ForgotPasswordMutationBody = TypesPostableForgotPasswordDTO;
|
||||
export type ForgotPasswordMutationError = RenderErrorResponseDTO;
|
||||
|
||||
/**
|
||||
* @summary Forgot password
|
||||
*/
|
||||
export const useForgotPassword = <
|
||||
TError = RenderErrorResponseDTO,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: TypesPostableForgotPasswordDTO },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof forgotPassword>>,
|
||||
TError,
|
||||
{ data: TypesPostableForgotPasswordDTO },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getForgotPasswordMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
ServiceData,
|
||||
UpdateServiceConfigPayload,
|
||||
UpdateServiceConfigResponse,
|
||||
} from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
|
||||
import {
|
||||
AccountConfigPayload,
|
||||
AccountConfigResponse,
|
||||
ConnectionParams,
|
||||
AWSAccountConfigPayload,
|
||||
ConnectionUrlResponse,
|
||||
} from 'types/api/integrations/aws';
|
||||
import { ConnectionParams } from 'types/api/integrations/types';
|
||||
|
||||
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
|
||||
const response = await axios.get('/cloud-integrations/aws/accounts');
|
||||
@@ -60,7 +60,7 @@ export const generateConnectionUrl = async (params: {
|
||||
|
||||
export const updateAccountConfig = async (
|
||||
accountId: string,
|
||||
payload: AccountConfigPayload,
|
||||
payload: AWSAccountConfigPayload,
|
||||
): Promise<AccountConfigResponse> => {
|
||||
const response = await axios.post<AccountConfigResponse>(
|
||||
`/cloud-integrations/aws/accounts/${accountId}/config`,
|
||||
|
||||
122
frontend/src/api/integration/index.ts
Normal file
122
frontend/src/api/integration/index.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
CloudAccount,
|
||||
ServiceData,
|
||||
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
|
||||
import {
|
||||
AzureCloudAccountConfig,
|
||||
AzureService,
|
||||
AzureServiceConfigPayload,
|
||||
} from 'container/Integrations/types';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
AccountConfigResponse,
|
||||
AWSAccountConfigPayload,
|
||||
} from 'types/api/integrations/aws';
|
||||
import {
|
||||
AzureAccountConfig,
|
||||
ConnectionParams,
|
||||
IAzureDeploymentCommands,
|
||||
} from 'types/api/integrations/types';
|
||||
|
||||
export const getCloudIntegrationAccounts = async (
|
||||
cloudServiceId: string,
|
||||
): Promise<CloudAccount[]> => {
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/${cloudServiceId}/accounts`,
|
||||
);
|
||||
|
||||
return response.data.data.accounts;
|
||||
};
|
||||
|
||||
export const getCloudIntegrationServices = async (
|
||||
cloudServiceId: string,
|
||||
cloudAccountId?: string,
|
||||
): Promise<AzureService[]> => {
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/${cloudServiceId}/services`,
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
|
||||
return response.data.data.services;
|
||||
};
|
||||
|
||||
export const getCloudIntegrationServiceDetails = async (
|
||||
cloudServiceId: string,
|
||||
serviceId: string,
|
||||
cloudAccountId?: string,
|
||||
): Promise<ServiceData> => {
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/${cloudServiceId}/services/${serviceId}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateAccountConfig = async (
|
||||
cloudServiceId: string,
|
||||
accountId: string,
|
||||
payload: AWSAccountConfigPayload | AzureAccountConfig,
|
||||
): Promise<AccountConfigResponse> => {
|
||||
const response = await axios.post<AccountConfigResponse>(
|
||||
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/config`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateServiceConfig = async (
|
||||
cloudServiceId: string,
|
||||
serviceId: string,
|
||||
payload: AzureServiceConfigPayload,
|
||||
): Promise<AzureServiceConfigPayload> => {
|
||||
const response = await axios.post<AzureServiceConfigPayload>(
|
||||
`/cloud-integrations/${cloudServiceId}/services/${serviceId}/config`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getConnectionParams = async (
|
||||
cloudServiceId: string,
|
||||
): Promise<ConnectionParams> => {
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/${cloudServiceId}/accounts/generate-connection-params`,
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getAzureDeploymentCommands = async (params: {
|
||||
agent_config: ConnectionParams;
|
||||
account_config: AzureCloudAccountConfig;
|
||||
}): Promise<IAzureDeploymentCommands> => {
|
||||
const response = await axios.post(
|
||||
`/cloud-integrations/azure/accounts/generate-connection-url`,
|
||||
params,
|
||||
);
|
||||
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const removeIntegrationAccount = async ({
|
||||
cloudServiceId,
|
||||
accountId,
|
||||
}: {
|
||||
cloudServiceId: string;
|
||||
accountId: string;
|
||||
}): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/disconnect`,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
3
frontend/src/auto-import-registry.d.ts
vendored
3
frontend/src/auto-import-registry.d.ts
vendored
@@ -17,10 +17,13 @@ import '@signozhq/callout';
|
||||
import '@signozhq/checkbox';
|
||||
import '@signozhq/combobox';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/drawer';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/sonner';
|
||||
import '@signozhq/table';
|
||||
import '@signozhq/tabs';
|
||||
import '@signozhq/tooltip';
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
background: var(--bg-ink-400, #121317);
|
||||
border: 1px solid var(--bg-ink-200, #23262e);
|
||||
background: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-ink-200);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
color: var(--text-neutral-dark-100, #adb4c2);
|
||||
color: var(--text-neutral-dark-100);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
.auth-footer-link-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-neutral-dark-50, #eceef2);
|
||||
color: var(--text-neutral-dark-50);
|
||||
}
|
||||
|
||||
.auth-footer-link-status {
|
||||
@@ -84,14 +84,14 @@
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-ink-200, #23262e);
|
||||
background: var(--bg-ink-200);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.auth-footer-content {
|
||||
background: var(--bg-base-white, #ffffff);
|
||||
border-color: var(--bg-vanilla-300, #e9e9e9);
|
||||
background: var(--bg-base-white);
|
||||
border-color: var(--bg-vanilla-300);
|
||||
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
@@ -102,14 +102,14 @@
|
||||
}
|
||||
|
||||
.auth-footer-text {
|
||||
color: var(--text-neutral-light-200, #80828d);
|
||||
color: var(--text-neutral-light-200);
|
||||
}
|
||||
|
||||
.auth-footer-link-icon {
|
||||
color: var(--text-neutral-light-100, #62636c);
|
||||
color: var(--text-neutral-light-100);
|
||||
}
|
||||
|
||||
.auth-footer-separator {
|
||||
background: var(--bg-vanilla-300, #e9e9e9);
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
font-size: 15.4px;
|
||||
font-weight: 500;
|
||||
line-height: 17.5px;
|
||||
color: var(--text-neutral-dark-50, #eceef2);
|
||||
color: var(--text-neutral-dark-50);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
gap: 8px;
|
||||
height: 32px;
|
||||
padding: 10px 16px;
|
||||
background: var(--bg-ink-400, #121317);
|
||||
background: var(--bg-ink-400);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
@@ -52,13 +52,13 @@
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--text-neutral-dark-100, #adb4c2);
|
||||
color: var(--text-neutral-dark-100);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-neutral-dark-100, #adb4c2);
|
||||
color: var(--text-neutral-dark-100);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -68,15 +68,15 @@
|
||||
|
||||
.lightMode {
|
||||
.auth-header-logo-text {
|
||||
color: var(--text-neutral-light-100, #62636c);
|
||||
color: var(--text-neutral-light-100);
|
||||
}
|
||||
|
||||
.auth-header-help-button {
|
||||
background: var(--bg-vanilla-200, #f5f5f5);
|
||||
background: var(--bg-vanilla-200);
|
||||
|
||||
span,
|
||||
svg {
|
||||
color: var(--text-neutral-light-200, #80828d);
|
||||
color: var(--text-neutral-light-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
background: var(--bg-neutral-dark-1000, #0a0c10);
|
||||
background: var(--bg-neutral-dark-1000);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
.bg-dot-pattern {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
var(--bg-neutral-dark-50, #eceef2) 1px,
|
||||
var(--bg-neutral-dark-50) 1px,
|
||||
transparent 1px
|
||||
);
|
||||
background-size: 12px 12px;
|
||||
@@ -85,8 +85,8 @@
|
||||
height: 100%;
|
||||
background-image: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--bg-ink-200, #23262e) 0px,
|
||||
var(--bg-ink-200, #23262e) 4px,
|
||||
var(--bg-ink-200) 0px,
|
||||
var(--bg-ink-200) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
.lightMode {
|
||||
.auth-page-wrapper {
|
||||
background: var(--bg-base-white, #ffffff);
|
||||
background: var(--bg-base-white);
|
||||
}
|
||||
|
||||
.bg-dot-pattern {
|
||||
@@ -172,8 +172,8 @@
|
||||
.auth-page-line-right {
|
||||
background-image: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--bg-vanilla-300, #e9e9e9) 0px,
|
||||
var(--bg-vanilla-300, #e9e9e9) 4px,
|
||||
var(--bg-vanilla-300) 0px,
|
||||
var(--bg-vanilla-300) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
.cloud-integration-accounts {
|
||||
padding: 0px 16px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.selected-cloud-integration-account-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.selected-cloud-integration-account-section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--Slate-400, #1d212d);
|
||||
background: var(--Ink-400, #121317);
|
||||
|
||||
.selected-cloud-integration-account-section-header-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.selected-cloud-integration-account-status {
|
||||
display: flex;
|
||||
border-right: 1px solid var(--Slate-400, #1d212d);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.selected-cloud-integration-account-section-header-title-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.azure-cloud-account-selector {
|
||||
.ant-select {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
.ant-select-selector {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-cloud-integration-account-settings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
line-height: 32px;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.account-settings-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.add-new-cloud-integration-account-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
line-height: 32px;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { Select } from 'antd';
|
||||
import ConnectNewAzureAccount from 'container/Integrations/CloudIntegration/AzureServices/AzureAccount/ConnectNewAzureAccount';
|
||||
import EditAzureAccount from 'container/Integrations/CloudIntegration/AzureServices/AzureAccount/EditAzureAccount';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { CloudAccount } from 'container/Integrations/types';
|
||||
import { useGetConnectionParams } from 'hooks/integration/useGetConnectionParams';
|
||||
import useAxiosError from 'hooks/useAxiosError';
|
||||
import { Dot, PencilLine, Plus } from 'lucide-react';
|
||||
|
||||
import './CloudIntegrationAccounts.styles.scss';
|
||||
|
||||
export type DrawerMode = 'edit' | 'add';
|
||||
|
||||
export default function CloudIntegrationAccounts({
|
||||
selectedAccount,
|
||||
accounts,
|
||||
isLoadingAccounts,
|
||||
onSelectAccount,
|
||||
refetchAccounts,
|
||||
}: {
|
||||
selectedAccount: CloudAccount | null;
|
||||
accounts: CloudAccount[];
|
||||
isLoadingAccounts: boolean;
|
||||
onSelectAccount: (account: CloudAccount) => void;
|
||||
refetchAccounts: () => void;
|
||||
}): JSX.Element {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const [mode, setMode] = useState<DrawerMode>('add');
|
||||
|
||||
const handleDrawerOpenChange = (open: boolean): void => {
|
||||
setIsDrawerOpen(open);
|
||||
};
|
||||
|
||||
const handleEditAccount = (): void => {
|
||||
setMode('edit');
|
||||
setIsDrawerOpen(true);
|
||||
};
|
||||
|
||||
const handleAddNewAccount = (): void => {
|
||||
setMode('add');
|
||||
setIsDrawerOpen(true);
|
||||
};
|
||||
|
||||
const handleError = useAxiosError();
|
||||
|
||||
const {
|
||||
data: connectionParams,
|
||||
isLoading: isConnectionParamsLoading,
|
||||
} = useGetConnectionParams({
|
||||
cloudServiceId: INTEGRATION_TYPES.AZURE,
|
||||
options: { onError: handleError },
|
||||
});
|
||||
|
||||
const handleSelectAccount = (value: string): void => {
|
||||
const account = accounts.find(
|
||||
(account) => account.cloud_account_id === value,
|
||||
);
|
||||
|
||||
if (account) {
|
||||
onSelectAccount(account);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAccountConnected = (): void => {
|
||||
refetchAccounts();
|
||||
};
|
||||
|
||||
const handleAccountUpdated = (): void => {
|
||||
refetchAccounts();
|
||||
};
|
||||
|
||||
const renderDrawerContent = (): JSX.Element => {
|
||||
return (
|
||||
<div className="cloud-integration-accounts-drawer-content">
|
||||
{mode === 'edit' ? (
|
||||
<div className="edit-account-content">
|
||||
<EditAzureAccount
|
||||
selectedAccount={selectedAccount as CloudAccount}
|
||||
connectionParams={connectionParams || {}}
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
onAccountUpdated={handleAccountUpdated}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="add-new-account-content">
|
||||
<ConnectNewAzureAccount
|
||||
connectionParams={connectionParams || {}}
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
onAccountConnected={handleAccountConnected}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="cloud-integration-accounts">
|
||||
{selectedAccount && (
|
||||
<div className="selected-cloud-integration-account-section">
|
||||
<div className="selected-cloud-integration-account-section-header">
|
||||
<div className="selected-cloud-integration-account-section-header-title">
|
||||
<div className="selected-cloud-integration-account-status">
|
||||
<Dot size={24} color={Color.BG_FOREST_500} />
|
||||
</div>
|
||||
<div className="selected-cloud-integration-account-section-header-title-text">
|
||||
Subscription ID :
|
||||
<span className="azure-cloud-account-selector">
|
||||
<Select
|
||||
value={selectedAccount?.cloud_account_id}
|
||||
options={accounts.map((account) => ({
|
||||
label: account.cloud_account_id,
|
||||
value: account.cloud_account_id,
|
||||
}))}
|
||||
onChange={handleSelectAccount}
|
||||
loading={isLoadingAccounts}
|
||||
placeholder="Select Account"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="selected-cloud-integration-account-settings">
|
||||
<Button
|
||||
variant="link"
|
||||
color="secondary"
|
||||
prefixIcon={<PencilLine size={14} />}
|
||||
onClick={handleEditAccount}
|
||||
>
|
||||
Edit Account
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
color="secondary"
|
||||
prefixIcon={<Plus size={14} />}
|
||||
onClick={handleAddNewAccount}
|
||||
>
|
||||
Add New Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="account-settings-container">
|
||||
<DrawerWrapper
|
||||
open={isDrawerOpen}
|
||||
onOpenChange={handleDrawerOpenChange}
|
||||
type="panel"
|
||||
header={{
|
||||
title: mode === 'add' ? 'Connect with Azure' : 'Edit Azure Account',
|
||||
}}
|
||||
content={renderDrawerContent()}
|
||||
showCloseButton
|
||||
allowOutsideClick={mode === 'edit'}
|
||||
direction="right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import CloudIntegrationAccounts from './CloudIntegrationAccounts';
|
||||
|
||||
export default CloudIntegrationAccounts;
|
||||
@@ -0,0 +1,62 @@
|
||||
.cloud-integrations-header-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
|
||||
.cloud-integrations-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
padding-bottom: 0px;
|
||||
gap: 16px;
|
||||
|
||||
.cloud-integrations-title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.cloud-integrations-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.cloud-integrations-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
color: var(--Vanilla-100, #fff);
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 32px; /* 200% */
|
||||
letter-spacing: -0.08px;
|
||||
}
|
||||
|
||||
.cloud-integrations-description {
|
||||
color: var(--Vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.cloud-integrations-header {
|
||||
.cloud-integrations-title {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.cloud-integrations-description {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
AWS_INTEGRATION,
|
||||
AZURE_INTEGRATION,
|
||||
} from 'container/Integrations/constants';
|
||||
import { CloudAccount, IntegrationType } from 'container/Integrations/types';
|
||||
|
||||
import CloudIntegrationAccounts from '../CloudIntegrationAccounts';
|
||||
|
||||
import './CloudIntegrationsHeader.styles.scss';
|
||||
|
||||
export default function CloudIntegrationsHeader({
|
||||
cloudServiceId,
|
||||
selectedAccount,
|
||||
accounts,
|
||||
isLoadingAccounts,
|
||||
onSelectAccount,
|
||||
refetchAccounts,
|
||||
}: {
|
||||
selectedAccount: CloudAccount | null;
|
||||
accounts: CloudAccount[] | [];
|
||||
isLoadingAccounts: boolean;
|
||||
onSelectAccount: (account: CloudAccount) => void;
|
||||
cloudServiceId: IntegrationType;
|
||||
refetchAccounts: () => void;
|
||||
}): JSX.Element {
|
||||
const INTEGRATION_DATA =
|
||||
cloudServiceId === IntegrationType.AWS_SERVICES
|
||||
? AWS_INTEGRATION
|
||||
: AZURE_INTEGRATION;
|
||||
|
||||
return (
|
||||
<div className="cloud-integrations-header-section">
|
||||
<div className="cloud-integrations-header">
|
||||
<div className="cloud-integrations-title-section">
|
||||
<div className="cloud-integrations-title">
|
||||
<img
|
||||
className="cloud-integrations-icon"
|
||||
src={INTEGRATION_DATA.icon}
|
||||
alt={INTEGRATION_DATA.icon_alt}
|
||||
/>
|
||||
|
||||
{INTEGRATION_DATA.title}
|
||||
</div>
|
||||
<div className="cloud-integrations-description">
|
||||
{INTEGRATION_DATA.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="cloud-integrations-accounts-list">
|
||||
<CloudIntegrationAccounts
|
||||
selectedAccount={selectedAccount}
|
||||
accounts={accounts}
|
||||
isLoadingAccounts={isLoadingAccounts}
|
||||
onSelectAccount={onSelectAccount}
|
||||
refetchAccounts={refetchAccounts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import CloudIntegrationsHeader from './CloudIntegrationsHeader';
|
||||
|
||||
export default CloudIntegrationsHeader;
|
||||
@@ -0,0 +1,35 @@
|
||||
.cloud-service-data-collected {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.cloud-service-data-collected-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.cloud-service-data-collected-table-heading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
color: var(--Vanilla-400, #c0c1c3);
|
||||
|
||||
/* Bifrost (Ancient)/Content/sm */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.cloud-service-data-collected-table-logs {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--Slate-400, #1d212d);
|
||||
background: var(--Ink-400, #121317);
|
||||
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Table } from 'antd';
|
||||
import { ServiceData } from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
|
||||
import { BarChart2, ScrollText } from 'lucide-react';
|
||||
|
||||
import { ServiceData } from './types';
|
||||
import './CloudServiceDataCollected.styles.scss';
|
||||
|
||||
function CloudServiceDataCollected({
|
||||
logsData,
|
||||
@@ -61,26 +63,32 @@ function CloudServiceDataCollected({
|
||||
return (
|
||||
<div className="cloud-service-data-collected">
|
||||
{logsData && logsData.length > 0 && (
|
||||
<div className="cloud-service-data-collected__table">
|
||||
<div className="cloud-service-data-collected__table-heading">Logs</div>
|
||||
<div className="cloud-service-data-collected-table">
|
||||
<div className="cloud-service-data-collected-table-heading">
|
||||
<ScrollText size={14} />
|
||||
Logs
|
||||
</div>
|
||||
<Table
|
||||
columns={logsColumns}
|
||||
dataSource={logsData}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...tableProps}
|
||||
className="cloud-service-data-collected__table-logs"
|
||||
className="cloud-service-data-collected-table-logs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{metricsData && metricsData.length > 0 && (
|
||||
<div className="cloud-service-data-collected__table">
|
||||
<div className="cloud-service-data-collected__table-heading">Metrics</div>
|
||||
<div className="cloud-service-data-collected-table">
|
||||
<div className="cloud-service-data-collected-table-heading">
|
||||
<BarChart2 size={14} />
|
||||
Metrics
|
||||
</div>
|
||||
<Table
|
||||
columns={metricsColumns}
|
||||
dataSource={metricsData}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...tableProps}
|
||||
className="cloud-service-data-collected__table-metrics"
|
||||
className="cloud-service-data-collected-table-metrics"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
39
frontend/src/components/CodeBlock/CodeBlock.styles.scss
Normal file
39
frontend/src/components/CodeBlock/CodeBlock.styles.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
.code-block-container {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.code-block-copy-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 8px;
|
||||
transform: translateY(-50%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 8px;
|
||||
color: var(--bg-vanilla-100);
|
||||
transition: color 0.15s ease;
|
||||
|
||||
&.copied {
|
||||
background-color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
|
||||
// CodeMirror wrapper
|
||||
.code-block-editor {
|
||||
border-radius: 4px;
|
||||
|
||||
.cm-editor {
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
frontend/src/components/CodeBlock/CodeBlock.tsx
Normal file
145
frontend/src/components/CodeBlock/CodeBlock.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { dracula } from '@uiw/codemirror-theme-dracula';
|
||||
import { githubLight } from '@uiw/codemirror-theme-github';
|
||||
import CodeMirror, {
|
||||
EditorState,
|
||||
EditorView,
|
||||
Extension,
|
||||
} from '@uiw/react-codemirror';
|
||||
import cx from 'classnames';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import './CodeBlock.styles.scss';
|
||||
|
||||
export type CodeBlockLanguage =
|
||||
| 'javascript'
|
||||
| 'typescript'
|
||||
| 'js'
|
||||
| 'ts'
|
||||
| 'json'
|
||||
| 'bash'
|
||||
| 'shell'
|
||||
| 'text';
|
||||
|
||||
export type CodeBlockTheme = 'light' | 'dark' | 'auto';
|
||||
|
||||
interface CodeBlockProps {
|
||||
/** The code content to display */
|
||||
value: string;
|
||||
/** Language for syntax highlighting */
|
||||
language?: CodeBlockLanguage;
|
||||
/** Theme: 'light' | 'dark' | 'auto' (follows app dark mode when 'auto') */
|
||||
theme?: CodeBlockTheme;
|
||||
/** Show line numbers */
|
||||
lineNumbers?: boolean;
|
||||
/** Show copy button */
|
||||
showCopyButton?: boolean;
|
||||
/** Custom class name for the container */
|
||||
className?: string;
|
||||
/** Max height in pixels - enables scrolling when content exceeds */
|
||||
maxHeight?: number | string;
|
||||
/** Callback when copy is clicked */
|
||||
onCopy?: (copiedText: string) => void;
|
||||
}
|
||||
|
||||
const LANGUAGE_EXTENSION_MAP: Record<
|
||||
CodeBlockLanguage,
|
||||
ReturnType<typeof javascript> | undefined
|
||||
> = {
|
||||
javascript: javascript({ jsx: true }),
|
||||
typescript: javascript({ jsx: true }),
|
||||
js: javascript({ jsx: true }),
|
||||
ts: javascript({ jsx: true }),
|
||||
json: javascript(), // JSON is valid JS; proper json() would require @codemirror/lang-json
|
||||
bash: undefined,
|
||||
shell: undefined,
|
||||
text: undefined,
|
||||
};
|
||||
|
||||
function CodeBlock({
|
||||
value,
|
||||
language = 'text',
|
||||
theme: themeProp = 'auto',
|
||||
lineNumbers = true,
|
||||
showCopyButton = true,
|
||||
className,
|
||||
maxHeight,
|
||||
onCopy,
|
||||
}: CodeBlockProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const resolvedDark = themeProp === 'auto' ? isDarkMode : themeProp === 'dark';
|
||||
const theme = resolvedDark ? dracula : githubLight;
|
||||
|
||||
const extensions = useMemo((): Extension[] => {
|
||||
const langExtension = LANGUAGE_EXTENSION_MAP[language];
|
||||
return [
|
||||
EditorState.readOnly.of(true),
|
||||
EditorView.editable.of(false),
|
||||
EditorView.lineWrapping,
|
||||
...(langExtension ? [langExtension] : []),
|
||||
];
|
||||
}, [language]);
|
||||
|
||||
const handleCopy = useCallback((): void => {
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
setIsCopied(true);
|
||||
onCopy?.(value);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
});
|
||||
}, [value, onCopy]);
|
||||
|
||||
return (
|
||||
<div className={cx('code-block-container', className)}>
|
||||
{showCopyButton && (
|
||||
<Button
|
||||
variant="solid"
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className={cx('code-block-copy-btn', { copied: isCopied })}
|
||||
onClick={handleCopy}
|
||||
aria-label={isCopied ? 'Copied' : 'Copy code'}
|
||||
title={isCopied ? 'Copied' : 'Copy code'}
|
||||
>
|
||||
{isCopied ? <CheckOutlined /> : <CopyOutlined />}
|
||||
</Button>
|
||||
)}
|
||||
<CodeMirror
|
||||
className="code-block-editor"
|
||||
value={value}
|
||||
theme={theme}
|
||||
readOnly
|
||||
editable={false}
|
||||
extensions={extensions}
|
||||
basicSetup={{
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter: false,
|
||||
highlightActiveLine: false,
|
||||
highlightSelectionMatches: true,
|
||||
drawSelection: true,
|
||||
syntaxHighlighting: true,
|
||||
bracketMatching: true,
|
||||
history: false,
|
||||
foldGutter: false,
|
||||
autocompletion: false,
|
||||
defaultKeymap: false,
|
||||
searchKeymap: true,
|
||||
historyKeymap: false,
|
||||
foldKeymap: false,
|
||||
completionKeymap: false,
|
||||
closeBrackets: false,
|
||||
indentOnInput: false,
|
||||
}}
|
||||
style={{
|
||||
maxHeight: maxHeight ?? 'auto',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CodeBlock;
|
||||
2
frontend/src/components/CodeBlock/index.ts
Normal file
2
frontend/src/components/CodeBlock/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { CodeBlockLanguage, CodeBlockTheme } from './CodeBlock';
|
||||
export { default as CodeBlock } from './CodeBlock';
|
||||
@@ -3,8 +3,8 @@ import { useLocation } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/sonner';
|
||||
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState('feedback');
|
||||
|
||||
@@ -5,8 +5,8 @@ import { toast } from '@signozhq/sonner';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
@@ -31,7 +31,7 @@ jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('pages/Integrations/utils', () => ({
|
||||
jest.mock('container/Integrations/utils', () => ({
|
||||
handleContactSupport: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
enableRegexOption = false,
|
||||
isDynamicVariable = false,
|
||||
showRetryButton = true,
|
||||
waitingMessage,
|
||||
...rest
|
||||
}) => {
|
||||
// ===== State & Refs =====
|
||||
@@ -140,10 +141,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
}, [selectedValues, allAvailableValues, enableAllSelection]);
|
||||
|
||||
// Define allOptionShown earlier in the code
|
||||
const allOptionShown = useMemo(
|
||||
() => value === ALL_SELECTED_VALUE || value === 'ALL',
|
||||
[value],
|
||||
);
|
||||
const allOptionShown = value === ALL_SELECTED_VALUE;
|
||||
|
||||
// Value passed to the underlying Ant Select component
|
||||
const displayValue = useMemo(
|
||||
@@ -1684,6 +1682,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
{!loading &&
|
||||
!errorMessage &&
|
||||
!noDataMessage &&
|
||||
!waitingMessage &&
|
||||
!(showIncompleteDataMessage && isScrolledToBottom) && (
|
||||
<section className="navigate">
|
||||
<ArrowDown size={8} className="icons" />
|
||||
@@ -1701,7 +1700,17 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
<div className="navigation-text">Refreshing values...</div>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && !loading && (
|
||||
{!loading && waitingMessage && (
|
||||
<div className="navigation-loading">
|
||||
<div className="navigation-icons">
|
||||
<LoadingOutlined />
|
||||
</div>
|
||||
<div className="navigation-text" title={waitingMessage}>
|
||||
{waitingMessage}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && !loading && !waitingMessage && (
|
||||
<div className="navigation-error">
|
||||
<div className="navigation-text">
|
||||
{errorMessage || SOMETHING_WENT_WRONG}
|
||||
@@ -1723,6 +1732,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
{showIncompleteDataMessage &&
|
||||
isScrolledToBottom &&
|
||||
!loading &&
|
||||
!waitingMessage &&
|
||||
!errorMessage && (
|
||||
<div className="navigation-text-incomplete">
|
||||
Don't see the value? Use search
|
||||
@@ -1765,6 +1775,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
|
||||
isDarkMode,
|
||||
isDynamicVariable,
|
||||
showRetryButton,
|
||||
waitingMessage,
|
||||
]);
|
||||
|
||||
// Custom handler for dropdown visibility changes
|
||||
|
||||
@@ -63,6 +63,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
showIncompleteDataMessage = false,
|
||||
showRetryButton = true,
|
||||
isDynamicVariable = false,
|
||||
waitingMessage,
|
||||
...rest
|
||||
}) => {
|
||||
// ===== State & Refs =====
|
||||
@@ -568,6 +569,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
{!loading &&
|
||||
!errorMessage &&
|
||||
!noDataMessage &&
|
||||
!waitingMessage &&
|
||||
!(showIncompleteDataMessage && isScrolledToBottom) && (
|
||||
<section className="navigate">
|
||||
<ArrowDown size={8} className="icons" />
|
||||
@@ -583,6 +585,16 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
<div className="navigation-text">Refreshing values...</div>
|
||||
</div>
|
||||
)}
|
||||
{!loading && waitingMessage && (
|
||||
<div className="navigation-loading">
|
||||
<div className="navigation-icons">
|
||||
<LoadingOutlined />
|
||||
</div>
|
||||
<div className="navigation-text" title={waitingMessage}>
|
||||
{waitingMessage}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && !loading && (
|
||||
<div className="navigation-error">
|
||||
<div className="navigation-text">
|
||||
@@ -605,6 +617,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
{showIncompleteDataMessage &&
|
||||
isScrolledToBottom &&
|
||||
!loading &&
|
||||
!waitingMessage &&
|
||||
!errorMessage && (
|
||||
<div className="navigation-text-incomplete">
|
||||
Don't see the value? Use search
|
||||
@@ -641,6 +654,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
||||
showRetryButton,
|
||||
isDarkMode,
|
||||
isDynamicVariable,
|
||||
waitingMessage,
|
||||
]);
|
||||
|
||||
// Handle dropdown visibility changes
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface CustomSelectProps extends Omit<SelectProps, 'options'> {
|
||||
showIncompleteDataMessage?: boolean;
|
||||
showRetryButton?: boolean;
|
||||
isDynamicVariable?: boolean;
|
||||
waitingMessage?: string;
|
||||
}
|
||||
|
||||
export interface CustomTagProps {
|
||||
@@ -66,4 +67,5 @@ export interface CustomMultiSelectProps
|
||||
enableRegexOption?: boolean;
|
||||
isDynamicVariable?: boolean;
|
||||
showRetryButton?: boolean;
|
||||
waitingMessage?: string;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IBuilderTraceOperator } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { QueryBuilderV2Provider } from './QueryBuilderV2Context';
|
||||
import { clearPreviousQuery } from './QueryV2/previousQuery.utils';
|
||||
import QueryFooter from './QueryV2/QueryFooter/QueryFooter';
|
||||
import { QueryV2 } from './QueryV2/QueryV2';
|
||||
import TraceOperator from './QueryV2/TraceOperator/TraceOperator';
|
||||
@@ -24,6 +25,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||
version,
|
||||
onSignalSourceChange,
|
||||
signalSourceChangeEnabled = false,
|
||||
savePreviousQuery = false,
|
||||
}: QueryBuilderProps): JSX.Element {
|
||||
const {
|
||||
currentQuery,
|
||||
@@ -61,6 +63,14 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||
newPanelType,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// always clear on mount and unmount to avoid stale data
|
||||
clearPreviousQuery();
|
||||
return (): void => {
|
||||
clearPreviousQuery();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isMultiQueryAllowed = useMemo(
|
||||
() => !isListViewPanel || showTraceOperator,
|
||||
[showTraceOperator, isListViewPanel],
|
||||
@@ -200,6 +210,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||
onSignalSourceChange={onSignalSourceChange || ((): void => {})}
|
||||
signalSourceChangeEnabled={signalSourceChangeEnabled}
|
||||
queriesCount={1}
|
||||
savePreviousQuery={savePreviousQuery}
|
||||
/>
|
||||
) : (
|
||||
currentQuery.builder.queryData.map((query, index) => (
|
||||
@@ -222,6 +233,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
|
||||
onSignalSourceChange={onSignalSourceChange || ((): void => {})}
|
||||
signalSourceChangeEnabled={signalSourceChangeEnabled}
|
||||
queriesCount={currentQuery.builder.queryData.length}
|
||||
savePreviousQuery={savePreviousQuery}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,13 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
|
||||
import {
|
||||
getPreviousQueryFromKey,
|
||||
getQueryKey,
|
||||
removeKeyFromPreviousQuery,
|
||||
saveAsPreviousQuery,
|
||||
} from '../previousQuery.utils';
|
||||
|
||||
import './MetricsSelect.styles.scss';
|
||||
|
||||
export const SOURCE_OPTIONS: SelectOption<string, string>[] = [
|
||||
@@ -27,6 +34,7 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
signalSource,
|
||||
onSignalSourceChange,
|
||||
signalSourceChangeEnabled = false,
|
||||
savePreviousQuery = false,
|
||||
}: {
|
||||
query: IBuilderQuery;
|
||||
index: number;
|
||||
@@ -34,6 +42,7 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
signalSource: 'meter' | '';
|
||||
onSignalSourceChange: (value: string) => void;
|
||||
signalSourceChangeEnabled: boolean;
|
||||
savePreviousQuery: boolean;
|
||||
}): JSX.Element {
|
||||
const [attributeKeys, setAttributeKeys] = useState<BaseAutocompleteData[]>([]);
|
||||
|
||||
@@ -50,7 +59,11 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
[handleChangeAggregatorAttribute, attributeKeys],
|
||||
);
|
||||
|
||||
const { updateAllQueriesOperators, handleSetQueryData } = useQueryBuilder();
|
||||
const {
|
||||
updateAllQueriesOperators,
|
||||
handleSetQueryData,
|
||||
panelType,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const source = useMemo(
|
||||
() => (signalSource === 'meter' ? 'meter' : 'metrics'),
|
||||
@@ -79,22 +92,63 @@ export const MetricsSelect = memo(function MetricsSelect({
|
||||
[updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
const getDefaultQueryFromSource = useCallback(
|
||||
(selectedSource: string): IBuilderQuery => {
|
||||
const isMeter = selectedSource === 'meter';
|
||||
const baseQuery = isMeter
|
||||
? defaultMeterQuery.builder.queryData[0]
|
||||
: defaultMetricsQuery.builder.queryData[0];
|
||||
|
||||
return {
|
||||
...baseQuery,
|
||||
source: isMeter ? 'meter' : '',
|
||||
queryName: query.queryName,
|
||||
};
|
||||
},
|
||||
[defaultMeterQuery, defaultMetricsQuery, query.queryName],
|
||||
);
|
||||
|
||||
const handleSignalSourceChange = (value: string): void => {
|
||||
let newQueryData: IBuilderQuery;
|
||||
|
||||
if (savePreviousQuery) {
|
||||
const queryName = query.queryName || '';
|
||||
const dataSource = query.dataSource || '';
|
||||
const currSignalSource = query.source ?? '';
|
||||
const newSignalSource = value === 'meter' ? 'meter' : '';
|
||||
|
||||
const currQueryKey = getQueryKey({
|
||||
queryName: queryName,
|
||||
dataSource: dataSource,
|
||||
signalSource: currSignalSource,
|
||||
panelType: panelType || '',
|
||||
});
|
||||
|
||||
// save the current query key in session storage
|
||||
saveAsPreviousQuery(currQueryKey, query);
|
||||
|
||||
const newQueryKey = getQueryKey({
|
||||
queryName: queryName,
|
||||
dataSource: dataSource,
|
||||
signalSource: newSignalSource,
|
||||
panelType: panelType || '',
|
||||
});
|
||||
const savedQuery: IBuilderQuery | null = getPreviousQueryFromKey(
|
||||
newQueryKey,
|
||||
);
|
||||
|
||||
// remove the new query key from session storage
|
||||
removeKeyFromPreviousQuery(newQueryKey);
|
||||
|
||||
newQueryData = savedQuery
|
||||
? savedQuery
|
||||
: getDefaultQueryFromSource(newSignalSource);
|
||||
} else {
|
||||
newQueryData = getDefaultQueryFromSource(value);
|
||||
}
|
||||
|
||||
onSignalSourceChange(value);
|
||||
handleSetQueryData(
|
||||
index,
|
||||
value === 'meter'
|
||||
? {
|
||||
...defaultMeterQuery.builder.queryData[0],
|
||||
source: 'meter',
|
||||
queryName: query.queryName,
|
||||
}
|
||||
: {
|
||||
...defaultMetricsQuery.builder.queryData[0],
|
||||
source: '',
|
||||
queryName: query.queryName,
|
||||
},
|
||||
);
|
||||
handleSetQueryData(index, newQueryData);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -28,17 +28,16 @@ import {
|
||||
QUERY_BUILDER_OPERATORS_BY_KEY_TYPE,
|
||||
queryOperatorSuggestions,
|
||||
} from 'constants/antlrQueryConstants';
|
||||
import { useDashboardVariablesByType } from 'hooks/dashboard/useDashboardVariablesByType';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { debounce, isNull } from 'lodash-es';
|
||||
import { Info, TriangleAlert } from 'lucide-react';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
IDetailedError,
|
||||
IQueryContext,
|
||||
IValidationResult,
|
||||
} from 'types/antlrQueryTypes';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@@ -207,14 +206,9 @@ function QuerySearch({
|
||||
const lastValueRef = useRef<string>('');
|
||||
const isMountedRef = useRef<boolean>(true);
|
||||
|
||||
const { selectedDashboard } = useDashboard();
|
||||
|
||||
const dynamicVariables = useMemo(
|
||||
() =>
|
||||
Object.values(selectedDashboard?.data?.variables || {})?.filter(
|
||||
(variable: IDashboardVariable) => variable.type === 'DYNAMIC',
|
||||
),
|
||||
[selectedDashboard],
|
||||
const dashboardDynamicVariables = useDashboardVariablesByType(
|
||||
'DYNAMIC',
|
||||
'values',
|
||||
);
|
||||
|
||||
// Add back the generateOptions function and useEffect
|
||||
@@ -1069,7 +1063,7 @@ function QuerySearch({
|
||||
);
|
||||
|
||||
// Add dynamic variables suggestions for the current key
|
||||
const variableName = dynamicVariables?.find(
|
||||
const variableName = dashboardDynamicVariables?.find(
|
||||
(variable) => variable?.dynamicVariablesAttribute === keyName,
|
||||
)?.name;
|
||||
|
||||
|
||||
@@ -42,10 +42,12 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
onSignalSourceChange,
|
||||
signalSourceChangeEnabled = false,
|
||||
queriesCount = 1,
|
||||
savePreviousQuery = false,
|
||||
}: QueryProps & {
|
||||
onSignalSourceChange: (value: string) => void;
|
||||
signalSourceChangeEnabled: boolean;
|
||||
queriesCount: number;
|
||||
savePreviousQuery: boolean;
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
): JSX.Element {
|
||||
@@ -67,6 +69,7 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
filterConfigs,
|
||||
isListViewPanel,
|
||||
entityVersion: version,
|
||||
savePreviousQuery,
|
||||
});
|
||||
|
||||
const handleToggleDisableQuery = useCallback(() => {
|
||||
@@ -234,6 +237,7 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
signalSource={signalSource as 'meter' | ''}
|
||||
onSignalSourceChange={onSignalSourceChange}
|
||||
signalSourceChangeEnabled={signalSourceChangeEnabled}
|
||||
savePreviousQuery={savePreviousQuery}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -11,9 +11,14 @@ import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryFunction } from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { UseQueryOperations } from 'types/common/operations.types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
import {
|
||||
DataSource,
|
||||
QueryBuilderContextType,
|
||||
QueryFunctionsTypes,
|
||||
} from 'types/common/queryBuilder';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
@@ -45,12 +50,17 @@ const mockedUseQueryOperations = jest.mocked(
|
||||
|
||||
describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
let handleRunQueryMock: jest.MockedFunction<() => void>;
|
||||
let handleQueryFunctionsUpdatesMock: jest.MockedFunction<() => void>;
|
||||
let baseQBContext: QueryBuilderContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockCloneQuery = jest.fn() as jest.MockedFunction<
|
||||
(type: string, q: IBuilderQuery) => void
|
||||
>;
|
||||
handleRunQueryMock = jest.fn() as jest.MockedFunction<() => void>;
|
||||
handleQueryFunctionsUpdatesMock = jest.fn() as jest.MockedFunction<
|
||||
() => void
|
||||
>;
|
||||
const baseQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.LOGS,
|
||||
@@ -91,7 +101,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
const updateQueriesData: QueryBuilderContextType['updateQueriesData'] = (q) =>
|
||||
q;
|
||||
|
||||
mockedUseQueryBuilder.mockReturnValue(({
|
||||
const baseContext = ({
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
@@ -124,7 +134,10 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
} as unknown) as QueryBuilderContextType);
|
||||
} as unknown) as QueryBuilderContextType;
|
||||
|
||||
baseQBContext = baseContext;
|
||||
mockedUseQueryBuilder.mockReturnValue(baseQBContext);
|
||||
|
||||
mockedUseQueryOperations.mockReturnValue({
|
||||
isTracePanelType: false,
|
||||
@@ -139,7 +152,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
handleDeleteQuery: jest.fn(),
|
||||
handleChangeQueryData: (jest.fn() as unknown) as ReturnType<UseQueryOperations>['handleChangeQueryData'],
|
||||
handleChangeFormulaData: jest.fn(),
|
||||
handleQueryFunctionsUpdates: jest.fn(),
|
||||
handleQueryFunctionsUpdates: handleQueryFunctionsUpdatesMock,
|
||||
listOfAdditionalFormulaFilters: [],
|
||||
});
|
||||
});
|
||||
@@ -199,4 +212,56 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
|
||||
expect(handleRunQueryMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fx button is disabled when functions already exist', () => {
|
||||
const currentQueryBase = baseQBContext.currentQuery as Query;
|
||||
const supersetQueryBase = baseQBContext.supersetQuery as Query;
|
||||
|
||||
mockedUseQueryBuilder.mockReturnValueOnce({
|
||||
...baseQBContext,
|
||||
currentQuery: {
|
||||
...currentQueryBase,
|
||||
builder: {
|
||||
...currentQueryBase.builder,
|
||||
queryData: [
|
||||
{
|
||||
...currentQueryBase.builder.queryData[0],
|
||||
functions: [
|
||||
{ name: QueryFunctionsTypes.TIME_SHIFT, args: [] } as QueryFunction,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
supersetQuery: {
|
||||
...supersetQueryBase,
|
||||
builder: {
|
||||
...supersetQueryBase.builder,
|
||||
queryData: [
|
||||
{
|
||||
...supersetQueryBase.builder.queryData[0],
|
||||
functions: [
|
||||
{ name: QueryFunctionsTypes.TIME_SHIFT, args: [] } as QueryFunction,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(<QueryBuilderV2 panelType={PANEL_TYPES.TABLE} version="v4" />);
|
||||
|
||||
const fxButton = document.querySelector('.function-btn') as HTMLButtonElement;
|
||||
expect(fxButton).toBeInTheDocument();
|
||||
expect(fxButton).toBeDisabled();
|
||||
|
||||
const deleteButton = document.querySelector(
|
||||
'.query-function-delete-btn',
|
||||
) as HTMLButtonElement;
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
userEvent.click(deleteButton);
|
||||
waitFor(() => {
|
||||
expect(fxButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const PREVIOUS_QUERY_KEY = 'previousQuery';
|
||||
|
||||
function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
|
||||
try {
|
||||
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
|
||||
} catch {
|
||||
// ignore quota or serialization errors
|
||||
}
|
||||
}
|
||||
|
||||
export const getQueryKey = ({
|
||||
queryName,
|
||||
dataSource,
|
||||
signalSource,
|
||||
panelType,
|
||||
}: {
|
||||
queryName: string;
|
||||
dataSource: string;
|
||||
signalSource: string;
|
||||
panelType: string;
|
||||
}): string => {
|
||||
const qn = queryName || '';
|
||||
const ds = dataSource || '';
|
||||
const ss = signalSource === 'meter' ? 'meter' : '';
|
||||
const pt = panelType || '';
|
||||
return `${qn}:${ds}:${ss}:${pt}`;
|
||||
};
|
||||
|
||||
export const getPreviousQueryFromKey = (key: string): IBuilderQuery | null => {
|
||||
const previousQuery = getPreviousQueryFromStore();
|
||||
return previousQuery?.[key] ?? null;
|
||||
};
|
||||
|
||||
export const saveAsPreviousQuery = (
|
||||
key: string,
|
||||
query: IBuilderQuery,
|
||||
): void => {
|
||||
const previousQuery = getPreviousQueryFromStore();
|
||||
previousQuery[key] = query;
|
||||
writePreviousQueryToStore(previousQuery);
|
||||
};
|
||||
|
||||
export const removeKeyFromPreviousQuery = (key: string): void => {
|
||||
const previousQuery = getPreviousQueryFromStore();
|
||||
delete previousQuery[key];
|
||||
writePreviousQueryToStore(previousQuery);
|
||||
};
|
||||
|
||||
export const clearPreviousQuery = (): void => {
|
||||
try {
|
||||
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,369 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import {
|
||||
Having,
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { QueryBuilderV2 } from '../QueryBuilderV2';
|
||||
import {
|
||||
clearPreviousQuery,
|
||||
PREVIOUS_QUERY_KEY,
|
||||
} from '../QueryV2/previousQuery.utils';
|
||||
|
||||
// Local mocks for domain-specific heavy child components
|
||||
jest.mock(
|
||||
'../QueryV2/QueryAggregation/QueryAggregation',
|
||||
() =>
|
||||
function QueryAggregation(): JSX.Element {
|
||||
return <div>QueryAggregation</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'../QueryV2/MerticsAggregateSection/MetricsAggregateSection',
|
||||
() =>
|
||||
function MetricsAggregateSection(): JSX.Element {
|
||||
return <div>MetricsAggregateSection</div>;
|
||||
},
|
||||
);
|
||||
// Mock networked children to avoid axios during unit tests
|
||||
jest.mock(
|
||||
'../QueryV2/QuerySearch/QuerySearch',
|
||||
() =>
|
||||
function QuerySearch(): JSX.Element {
|
||||
return <div>QuerySearch</div>;
|
||||
},
|
||||
);
|
||||
jest.mock('container/QueryBuilder/filters', () => ({
|
||||
AggregatorFilter: (): JSX.Element => <div />,
|
||||
}));
|
||||
// Mock hooks
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder');
|
||||
|
||||
const mockedUseQueryBuilder = jest.mocked(useQueryBuilder);
|
||||
|
||||
describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
let handleSetQueryDataMock: jest.MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
const metricsQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateOperator: '',
|
||||
aggregations: [
|
||||
{
|
||||
timeAggregation: '',
|
||||
metricName: 'test_metric',
|
||||
temporality: '',
|
||||
spaceAggregation: '',
|
||||
},
|
||||
],
|
||||
timeAggregation: '',
|
||||
spaceAggregation: '',
|
||||
temporality: '',
|
||||
functions: [],
|
||||
filter: {
|
||||
expression: 'service = "test"',
|
||||
},
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
having: [] as Having[],
|
||||
limit: 10,
|
||||
stepInterval: null,
|
||||
orderBy: [],
|
||||
legend: 'A',
|
||||
source: '',
|
||||
};
|
||||
|
||||
const currentQueryObj: Query = {
|
||||
id: 'test',
|
||||
unit: undefined,
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: {
|
||||
queryData: [metricsQuery],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
};
|
||||
|
||||
(mockedUseQueryBuilder as any).mockReturnValue({
|
||||
currentQuery: currentQueryObj,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
setLastUsedQuery: jest.fn(),
|
||||
supersetQuery: currentQueryObj,
|
||||
setSupersetQuery: jest.fn(),
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
isEnabledQuery: true,
|
||||
handleSetQueryData: handleSetQueryDataMock,
|
||||
handleSetTraceOperatorData: jest.fn(),
|
||||
handleSetFormulaData: jest.fn(),
|
||||
handleSetQueryItemData: jest.fn(),
|
||||
handleSetConfig: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
removeAllQueryBuilderEntities: jest.fn(),
|
||||
removeQueryTypeItemByIndex: jest.fn(),
|
||||
addNewBuilderQuery: jest.fn(),
|
||||
addNewFormula: jest.fn(),
|
||||
removeTraceOperator: jest.fn(),
|
||||
addTraceOperator: jest.fn(),
|
||||
cloneQuery: jest.fn(),
|
||||
addNewQueryItem: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
handleRunQuery: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
handleOnUnitsChange: jest.fn(),
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
clearPreviousQuery();
|
||||
});
|
||||
|
||||
it('savePreviousQuery=true: metrics → meter saves previous query in session storage with appropriate key and query', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(
|
||||
<QueryBuilderV2
|
||||
panelType={PANEL_TYPES.TABLE}
|
||||
version="v5"
|
||||
signalSourceChangeEnabled
|
||||
savePreviousQuery
|
||||
/>,
|
||||
);
|
||||
const trigger = document.querySelector(
|
||||
'.metrics-container .source-selector .ant-select-selector',
|
||||
) as HTMLElement;
|
||||
|
||||
await user.click(trigger);
|
||||
|
||||
// wait for dropdown and choose Meter
|
||||
const meterOption = await screen.findByText('Meter');
|
||||
await user.click(meterOption);
|
||||
|
||||
expect(handleSetQueryDataMock).toHaveBeenCalled();
|
||||
const [, arg] = handleSetQueryDataMock.mock.calls[0];
|
||||
expect(arg.queryName).toBe('A');
|
||||
expect(arg.dataSource).toBe(DataSource.METRICS);
|
||||
expect(arg.source).toBe('meter');
|
||||
|
||||
// verify previousQuery store has the expected key and filter expression
|
||||
const storeRaw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
expect(storeRaw).not.toBeNull();
|
||||
const store = JSON.parse(storeRaw || '{}') as Record<string, IBuilderQuery>;
|
||||
expect(Object.keys(store)).toContain('A:metrics::table');
|
||||
expect(store['A:metrics::table']?.filter?.expression).toBe(
|
||||
'service = "test"',
|
||||
);
|
||||
});
|
||||
|
||||
it('savePreviousQuery=false: metrics → meter does not write to sessionStorage and applies defaults', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
// render WITHOUT savePreviousQuery enabled
|
||||
render(
|
||||
<QueryBuilderV2
|
||||
panelType={PANEL_TYPES.TABLE}
|
||||
version="v5"
|
||||
signalSourceChangeEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
// open Source and choose Meter
|
||||
const trigger = document.querySelector(
|
||||
'.metrics-container .source-selector .ant-select-selector',
|
||||
) as HTMLElement;
|
||||
await user.click(trigger);
|
||||
const meterOption = await screen.findByText('Meter');
|
||||
await user.click(meterOption);
|
||||
|
||||
// assert no session storage written
|
||||
const storeRaw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
// either null or empty object string
|
||||
expect(storeRaw === null || storeRaw === '{}').toBe(true);
|
||||
|
||||
// and query updated to defaults (at least source should be 'meter')
|
||||
expect(handleSetQueryDataMock).toHaveBeenCalled();
|
||||
const [, arg] = handleSetQueryDataMock.mock.calls[0];
|
||||
expect(arg.queryName).toBe('A');
|
||||
expect(arg.dataSource).toBe(DataSource.METRICS);
|
||||
expect(arg.source).toBe('meter');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DataSource change - Logs to Traces', () => {
|
||||
let handleSetQueryDataMock: jest.MockedFunction<
|
||||
(index: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
clearPreviousQuery();
|
||||
handleSetQueryDataMock = (jest.fn() as unknown) as jest.MockedFunction<
|
||||
(i: number, q: IBuilderQuery) => void
|
||||
>;
|
||||
|
||||
const logsQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: DataSource.LOGS,
|
||||
aggregateOperator: '',
|
||||
aggregations: [],
|
||||
timeAggregation: '',
|
||||
spaceAggregation: '',
|
||||
temporality: '',
|
||||
functions: [],
|
||||
filter: { expression: 'body CONTAINS "error"' },
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
having: [] as Having[],
|
||||
limit: 100,
|
||||
stepInterval: null,
|
||||
orderBy: [],
|
||||
legend: 'L',
|
||||
source: '',
|
||||
};
|
||||
|
||||
const logsCurrentQuery: Query = {
|
||||
id: 'test-logs',
|
||||
unit: undefined,
|
||||
queryType: EQueryType.CLICKHOUSE,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
builder: {
|
||||
queryData: [logsQuery],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
};
|
||||
|
||||
mockedUseQueryBuilder.mockReset();
|
||||
mockedUseQueryBuilder.mockReturnValue({
|
||||
currentQuery: logsCurrentQuery,
|
||||
stagedQuery: null,
|
||||
lastUsedQuery: null,
|
||||
setLastUsedQuery: jest.fn(),
|
||||
supersetQuery: logsCurrentQuery,
|
||||
setSupersetQuery: jest.fn(),
|
||||
initialDataSource: null,
|
||||
panelType: PANEL_TYPES.TABLE,
|
||||
isEnabledQuery: true,
|
||||
handleSetQueryData: handleSetQueryDataMock,
|
||||
handleSetTraceOperatorData: jest.fn(),
|
||||
handleSetFormulaData: jest.fn(),
|
||||
handleSetQueryItemData: jest.fn(),
|
||||
handleSetConfig: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
removeAllQueryBuilderEntities: jest.fn(),
|
||||
removeQueryTypeItemByIndex: jest.fn(),
|
||||
addNewBuilderQuery: jest.fn(),
|
||||
addNewFormula: jest.fn(),
|
||||
removeTraceOperator: jest.fn(),
|
||||
addTraceOperator: jest.fn(),
|
||||
cloneQuery: jest.fn(),
|
||||
addNewQueryItem: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
handleRunQuery: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
handleOnUnitsChange: jest.fn(),
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
clearPreviousQuery();
|
||||
});
|
||||
|
||||
it('updates query dataSource to TRACES saves previous query in session storage', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<QueryBuilderV2
|
||||
panelType={PANEL_TYPES.TABLE}
|
||||
version="v5"
|
||||
signalSourceChangeEnabled
|
||||
savePreviousQuery
|
||||
/>,
|
||||
);
|
||||
|
||||
const logsTrigger = document.querySelector(
|
||||
'.query-data-source .ant-select-selector',
|
||||
) as HTMLElement;
|
||||
await user.click(logsTrigger);
|
||||
|
||||
const tracesContent = screen.getByText('Traces', {
|
||||
selector: '.ant-select-item-option-content',
|
||||
});
|
||||
|
||||
// click the option (prefer the option element; fallback to content)
|
||||
await user.click(
|
||||
(tracesContent.closest('[role="option"]') as HTMLElement) || tracesContent,
|
||||
);
|
||||
|
||||
// verify previousQuery store saved the current LOGS snapshot before switch
|
||||
const storeRaw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
expect(storeRaw).not.toBeNull();
|
||||
const store = JSON.parse(storeRaw || '{}') as Record<string, IBuilderQuery>;
|
||||
expect(Object.keys(store)).toContain('A:logs::table');
|
||||
expect(store['A:logs::table']?.filter?.expression).toBe(
|
||||
'body CONTAINS "error"',
|
||||
);
|
||||
});
|
||||
it('updates query dataSource to TRACES does not saves previous query in session storage', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<QueryBuilderV2
|
||||
panelType={PANEL_TYPES.TABLE}
|
||||
version="v5"
|
||||
signalSourceChangeEnabled
|
||||
/>,
|
||||
);
|
||||
|
||||
const logsTrigger = document.querySelector(
|
||||
'.query-data-source .ant-select-selector',
|
||||
) as HTMLElement;
|
||||
await user.click(logsTrigger);
|
||||
|
||||
const tracesContent = screen.getByText('Traces', {
|
||||
selector: '.ant-select-item-option-content',
|
||||
});
|
||||
|
||||
// click the option (prefer the option element; fallback to content)
|
||||
await user.click(
|
||||
(tracesContent.closest('[role="option"]') as HTMLElement) || tracesContent,
|
||||
);
|
||||
|
||||
// Assert that no snapshot was written
|
||||
const storeRaw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
expect(storeRaw === null || storeRaw === '{}').toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import {
|
||||
clearPreviousQuery,
|
||||
getPreviousQueryFromKey,
|
||||
getQueryKey,
|
||||
PREVIOUS_QUERY_KEY,
|
||||
removeKeyFromPreviousQuery,
|
||||
saveAsPreviousQuery,
|
||||
} from '../QueryV2/previousQuery.utils';
|
||||
|
||||
describe('previousQuery.utils', () => {
|
||||
const sampleQuery: IBuilderQuery = {
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics' as any,
|
||||
aggregateOperator: '',
|
||||
aggregations: [],
|
||||
timeAggregation: '',
|
||||
spaceAggregation: '',
|
||||
temporality: '',
|
||||
functions: [],
|
||||
filter: { expression: 'service = "test"' },
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
having: [],
|
||||
limit: 10,
|
||||
stepInterval: null,
|
||||
orderBy: [],
|
||||
legend: 'A',
|
||||
source: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch {
|
||||
// jsdom environment
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('getQueryKey normalizes non-meter signal to empty string', () => {
|
||||
const k1 = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: 'metrics', // should normalize to ''
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
const k2 = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
expect(k1).toBe('A:metrics::TABLE');
|
||||
expect(k2).toBe('A:metrics::TABLE');
|
||||
|
||||
const km = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: 'meter',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
expect(km).toBe('A:metrics:meter:TABLE');
|
||||
});
|
||||
|
||||
it('returns null for missing key when store is empty', () => {
|
||||
expect(getPreviousQueryFromKey('missing:key')).toBeNull();
|
||||
});
|
||||
|
||||
it('saveAsPreviousQuery writes and getPreviousQueryFromKey reads the same object', () => {
|
||||
const key = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
saveAsPreviousQuery(key, sampleQuery);
|
||||
|
||||
const fromStore = getPreviousQueryFromKey(key);
|
||||
expect(fromStore).toEqual(sampleQuery);
|
||||
});
|
||||
|
||||
it('saveAsPreviousQuery merges multiple entries and removeKeyFromPreviousQuery deletes one', () => {
|
||||
const k1 = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
const k2 = getQueryKey({
|
||||
queryName: 'B',
|
||||
dataSource: 'metrics',
|
||||
signalSource: 'meter',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
|
||||
saveAsPreviousQuery(k1, sampleQuery);
|
||||
saveAsPreviousQuery(k2, {
|
||||
...sampleQuery,
|
||||
queryName: 'B',
|
||||
source: 'meter' as any,
|
||||
});
|
||||
|
||||
expect(getPreviousQueryFromKey(k1)?.queryName).toBe('A');
|
||||
expect(getPreviousQueryFromKey(k2)?.queryName).toBe('B');
|
||||
|
||||
removeKeyFromPreviousQuery(k1);
|
||||
expect(getPreviousQueryFromKey(k1)).toBeNull();
|
||||
expect(getPreviousQueryFromKey(k2)?.queryName).toBe('B');
|
||||
});
|
||||
|
||||
it('clearPreviousQuery removes the store key', () => {
|
||||
const key = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
saveAsPreviousQuery(key, sampleQuery);
|
||||
|
||||
expect(sessionStorage.getItem(PREVIOUS_QUERY_KEY)).not.toBeNull();
|
||||
clearPreviousQuery();
|
||||
expect(sessionStorage.getItem(PREVIOUS_QUERY_KEY)).toBeNull();
|
||||
});
|
||||
|
||||
it('handles malformed JSON in store gracefully', () => {
|
||||
sessionStorage.setItem(PREVIOUS_QUERY_KEY, 'not valid json');
|
||||
|
||||
// Should not throw and behave as empty store
|
||||
expect(getPreviousQueryFromKey('any:key')).toBeNull();
|
||||
|
||||
// After a save, it should overwrite the bad value with valid JSON
|
||||
const validKey = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
expect(() => saveAsPreviousQuery(validKey, sampleQuery)).not.toThrow();
|
||||
const parsed = JSON.parse(sessionStorage.getItem(PREVIOUS_QUERY_KEY) || '{}');
|
||||
expect(parsed[validKey]).toBeTruthy();
|
||||
});
|
||||
|
||||
it('write errors (e.g., quota) are caught and do not throw', () => {
|
||||
const spy = jest
|
||||
.spyOn(window.sessionStorage.__proto__, 'setItem')
|
||||
.mockImplementation(() => {
|
||||
throw new Error('quota exceeded');
|
||||
});
|
||||
|
||||
const key = getQueryKey({
|
||||
queryName: 'A',
|
||||
dataSource: 'metrics',
|
||||
signalSource: '',
|
||||
panelType: 'TABLE',
|
||||
});
|
||||
|
||||
expect(() => saveAsPreviousQuery(key, sampleQuery)).not.toThrow();
|
||||
|
||||
// Since write failed, reading should still behave as empty
|
||||
spy.mockRestore();
|
||||
expect(getPreviousQueryFromKey(key)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -648,7 +648,13 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
||||
) : (
|
||||
<Typography.Text
|
||||
className="value-string"
|
||||
ellipsis={{ tooltip: { placement: 'top' } }}
|
||||
ellipsis={{
|
||||
tooltip: {
|
||||
placement: 'top',
|
||||
mouseEnterDelay: 0.2,
|
||||
mouseLeaveDelay: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{String(value)}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -282,11 +282,11 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
size="small"
|
||||
style={{ marginLeft: 'auto' }}
|
||||
checked={showIP ?? true}
|
||||
onClick={(): void => {
|
||||
onChange={(checked): void => {
|
||||
logEvent('API Monitoring: Show IP addresses clicked', {
|
||||
showIP: !(showIP ?? true),
|
||||
showIP: checked,
|
||||
});
|
||||
setParams({ showIP });
|
||||
setParams({ showIP: checked });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import {
|
||||
ApiMonitoringParams,
|
||||
useApiMonitoringParams,
|
||||
} from 'container/ApiMonitoring/queryParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import {
|
||||
otherFiltersResponse,
|
||||
@@ -18,10 +22,15 @@ import { QuickFiltersConfig } from './constants';
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: jest.fn(),
|
||||
}));
|
||||
jest.mock('container/ApiMonitoring/queryParams');
|
||||
|
||||
const handleFilterVisibilityChange = jest.fn();
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const putHandler = jest.fn();
|
||||
const mockSetApiMonitoringParams = jest.fn() as jest.MockedFunction<
|
||||
(newParams: Partial<ApiMonitoringParams>, replace?: boolean) => void
|
||||
>;
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
@@ -84,6 +93,28 @@ TestQuickFilters.defaultProps = {
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
function TestQuickFiltersApiMonitoring({
|
||||
signal = SignalType.LOGS,
|
||||
config = QuickFiltersConfig,
|
||||
}: {
|
||||
signal?: SignalType;
|
||||
config?: IQuickFiltersConfig[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.API_MONITORING}
|
||||
config={config}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
signal={signal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TestQuickFiltersApiMonitoring.defaultProps = {
|
||||
signal: '',
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
});
|
||||
@@ -112,6 +143,10 @@ beforeEach(() => {
|
||||
lastUsedQuery: 0,
|
||||
redirectWithQueryBuilderData,
|
||||
});
|
||||
mockUseApiMonitoringParams.mockReturnValue([
|
||||
{ showIP: true } as ApiMonitoringParams,
|
||||
mockSetApiMonitoringParams,
|
||||
]);
|
||||
setupServer();
|
||||
});
|
||||
|
||||
@@ -251,6 +286,24 @@ describe('Quick Filters', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
it('toggles Show IP addresses and updates API Monitoring params', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<TestQuickFiltersApiMonitoring />);
|
||||
|
||||
// Switch should be rendered and initially checked
|
||||
expect(screen.getByText('Show IP addresses')).toBeInTheDocument();
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetApiMonitoringParams).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ showIP: false }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quick Filters with custom filters', () => {
|
||||
|
||||
@@ -54,4 +54,5 @@ export enum QueryParams {
|
||||
version = 'version',
|
||||
source = 'source',
|
||||
showClassicCreateAlertsPage = 'showClassicCreateAlertsPage',
|
||||
isTestAlert = 'isTestAlert',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
export const REACT_QUERY_KEY = {
|
||||
// Cloud Integration Query Keys
|
||||
CLOUD_INTEGRATION_ACCOUNTS: 'CLOUD_INTEGRATION_ACCOUNTS',
|
||||
CLOUD_INTEGRATION_SERVICES: 'CLOUD_INTEGRATION_SERVICES',
|
||||
CLOUD_INTEGRATION_SERVICE_DETAILS: 'CLOUD_INTEGRATION_SERVICE_DETAILS',
|
||||
CLOUD_INTEGRATION_ACCOUNT_STATUS: 'CLOUD_INTEGRATION_ACCOUNT_STATUS',
|
||||
CLOUD_INTEGRATION_UPDATE_ACCOUNT_CONFIG:
|
||||
'CLOUD_INTEGRATION_UPDATE_ACCOUNT_CONFIG',
|
||||
CLOUD_INTEGRATION_UPDATE_SERVICE_CONFIG:
|
||||
'CLOUD_INTEGRATION_UPDATE_SERVICE_CONFIG',
|
||||
CLOUD_INTEGRATION_GENERATE_CONNECTION_URL:
|
||||
'CLOUD_INTEGRATION_GENERATE_CONNECTION_URL',
|
||||
CLOUD_INTEGRATION_GET_CONNECTION_PARAMS:
|
||||
'CLOUD_INTEGRATION_GET_CONNECTION_PARAMS',
|
||||
CLOUD_INTEGRATION_GET_DEPLOYMENT_COMMANDS:
|
||||
'CLOUD_INTEGRATION_GET_DEPLOYMENT_COMMANDS',
|
||||
GET_PUBLIC_DASHBOARD: 'GET_PUBLIC_DASHBOARD',
|
||||
GET_PUBLIC_DASHBOARD_META: 'GET_PUBLIC_DASHBOARD_META',
|
||||
GET_PUBLIC_DASHBOARD_WIDGET_DATA: 'GET_PUBLIC_DASHBOARD_WIDGET_DATA',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const ROUTES = {
|
||||
SIGN_UP: '/signup',
|
||||
LOGIN: '/login',
|
||||
FORGOT_PASSWORD: '/forgot-password',
|
||||
HOME: '/home',
|
||||
SERVICE_METRICS: '/services/:servicename',
|
||||
SERVICE_TOP_LEVEL_OPERATIONS: '/services/:servicename/top-level-operations',
|
||||
@@ -63,6 +64,7 @@ const ROUTES = {
|
||||
WORKSPACE_SUSPENDED: '/workspace-suspended',
|
||||
SHORTCUTS: '/settings/shortcuts',
|
||||
INTEGRATIONS: '/integrations',
|
||||
INTEGRATIONS_DETAIL: '/integrations/:integrationId',
|
||||
MESSAGING_QUEUES_BASE: '/messaging-queues',
|
||||
MESSAGING_QUEUES_KAFKA: '/messaging-queues/kafka',
|
||||
MESSAGING_QUEUES_KAFKA_DETAIL: '/messaging-queues/kafka/detail',
|
||||
|
||||
@@ -34,159 +34,230 @@ const themeColors = {
|
||||
cyan: '#00FFFF',
|
||||
},
|
||||
chartcolors: {
|
||||
radicalRed: '#FF1A66',
|
||||
// Blues (3)
|
||||
dodgerBlue: '#2F80ED',
|
||||
mediumOrchid: '#BB6BD9',
|
||||
seaBuckthorn: '#F2994A',
|
||||
seaGreen: '#219653',
|
||||
turquoiseBlue: '#56CCF2',
|
||||
festivalOrange: '#F2C94C',
|
||||
silver: '#BDBDBD',
|
||||
outrageousOrange: '#FF6633',
|
||||
roseBud: '#FFB399',
|
||||
canary: '#FFFF99',
|
||||
deepSkyBlue: '#00B3E6',
|
||||
goldTips: '#E6B333',
|
||||
royalBlue: '#3366E6',
|
||||
avocado: '#999966',
|
||||
mintGreen: '#99FF99',
|
||||
chestnut: '#B34D4D',
|
||||
lima: '#80B300',
|
||||
olive: '#809900',
|
||||
beautyBush: '#E6B3B3',
|
||||
danube: '#6680B3',
|
||||
oliveDrab: '#66991A',
|
||||
lavenderRose: '#FF99E6',
|
||||
electricLime: '#CCFF1A',
|
||||
robin: '#3F5ECC',
|
||||
harleyOrange: '#E6331A',
|
||||
turquoise: '#33FFCC',
|
||||
gladeGreen: '#66994D',
|
||||
hemlock: '#66664D',
|
||||
vidaLoca: '#4D8000',
|
||||
rust: '#B33300',
|
||||
red: '#FF0000', // Adding more colors, we need to get better colors from design team
|
||||
blue: '#0000FF',
|
||||
green: '#00FF00',
|
||||
yellow: '#FFFF00',
|
||||
purple: '#800080',
|
||||
cyan: '#00FFFF',
|
||||
magenta: '#FF00FF',
|
||||
orange: '#FFA500',
|
||||
pink: '#FFC0CB',
|
||||
brown: '#A52A2A',
|
||||
teal: '#008080',
|
||||
lime: '#00FF00',
|
||||
maroon: '#800000',
|
||||
navy: '#000080',
|
||||
aquamarine: '#7FFFD4',
|
||||
darkSeaGreen: '#8FBC8F',
|
||||
gray: '#808080',
|
||||
skyBlue: '#87CEEB',
|
||||
indigo: '#4B0082',
|
||||
slateGray: '#708090',
|
||||
chocolate: '#D2691E',
|
||||
tomato: '#FF6347',
|
||||
steelBlue: '#4682B4',
|
||||
peru: '#CD853F',
|
||||
darkOliveGreen: '#556B2F',
|
||||
indianRed: '#CD5C5C',
|
||||
mediumSlateBlue: '#7B68EE',
|
||||
rosyBrown: '#BC8F8F',
|
||||
darkSlateGray: '#2F4F4F',
|
||||
mediumAquamarine: '#66CDAA',
|
||||
lavender: '#E6E6FA',
|
||||
thistle: '#D8BFD8',
|
||||
salmon: '#FA8072',
|
||||
darkSalmon: '#E9967A',
|
||||
paleVioletRed: '#DB7093',
|
||||
mediumPurple: '#9370DB',
|
||||
darkOrchid: '#9932CC',
|
||||
lawnGreen: '#7CFC00',
|
||||
|
||||
// Teals / Cyans (3)
|
||||
turquoise: '#00CEC9',
|
||||
lagoon: '#1ABC9C',
|
||||
cyanBright: '#22A6F2',
|
||||
|
||||
// Greens (3)
|
||||
emeraldGreen: '#27AE60',
|
||||
mediumSeaGreen: '#3CB371',
|
||||
lightCoral: '#F08080',
|
||||
gold: '#FFD700',
|
||||
sandyBrown: '#F4A460',
|
||||
darkKhaki: '#BDB76B',
|
||||
cornflowerBlue: '#6495ED',
|
||||
mediumVioletRed: '#C71585',
|
||||
paleGreen: '#98FB98',
|
||||
limeGreen: '#A3E635',
|
||||
|
||||
// Yellows / Golds (3)
|
||||
festivalYellow: '#F2C94C',
|
||||
sunflower: '#FFD93D',
|
||||
warmAmber: '#FFCA28',
|
||||
|
||||
// Oranges (3)
|
||||
festivalOrange: '#F2994A',
|
||||
coralOrange: '#E17055',
|
||||
pumpkin: '#FF7F50',
|
||||
|
||||
// Reds (3)
|
||||
radicalRed: '#FF1A66',
|
||||
crimsonRed: '#EB5757',
|
||||
fireRed: '#E10600',
|
||||
|
||||
// Pinks (3)
|
||||
hotPink: '#E84393',
|
||||
rosePink: '#FD79A8',
|
||||
blush: '#FF7EB6',
|
||||
|
||||
// Purples / Violets (3)
|
||||
mediumPurple: '#BB6BD9',
|
||||
royalPurple: '#9B51E0',
|
||||
orchid: '#DA77F2',
|
||||
|
||||
// Accent / Neon / Unique Colors (3)
|
||||
neonViolet: '#C77DFF',
|
||||
electricPurple: '#6C5CE7',
|
||||
arcticBlue: '#48DBFB',
|
||||
|
||||
// Extended palette — systematic variations to reach 100 colors
|
||||
blue1: '#1F63E0',
|
||||
blue2: '#3A7AED',
|
||||
blue3: '#5A9DF5',
|
||||
cyan1: '#00B0AA',
|
||||
cyan2: '#33D6C2',
|
||||
cyan3: '#66E9DA',
|
||||
green1: '#1E8449',
|
||||
green2: '#2ECC71',
|
||||
green3: '#58D68D',
|
||||
yellow1: '#F1C40F',
|
||||
yellow2: '#F7DC6F',
|
||||
yellow3: '#F9E79F',
|
||||
orange1: '#D35400',
|
||||
orange2: '#E67E22',
|
||||
orange3: '#F5B041',
|
||||
red1: '#C0392B',
|
||||
red2: '#E74C3C',
|
||||
red3: '#EC7063',
|
||||
pink1: '#D81B60',
|
||||
pink2: '#E91E63',
|
||||
pink3: '#F06292',
|
||||
purple1: '#8E44AD',
|
||||
purple2: '#9B59B6',
|
||||
purple3: '#BB8FCE',
|
||||
teal1: '#009688',
|
||||
teal2: '#1ABC9C',
|
||||
teal3: '#48C9B0',
|
||||
lime1: '#A3E635',
|
||||
lime2: '#B9F18D',
|
||||
lime3: '#D4FFB0',
|
||||
gold1: '#F39C12',
|
||||
gold2: '#F1C40F',
|
||||
gold3: '#F7DC6F',
|
||||
coral1: '#E67E22',
|
||||
coral2: '#F39C12',
|
||||
coral3: '#F5B041',
|
||||
crimson1: '#C0392B',
|
||||
crimson2: '#E74C3C',
|
||||
crimson3: '#EC7063',
|
||||
violet1: '#8E44AD',
|
||||
violet2: '#9B59B6',
|
||||
violet3: '#BB8FCE',
|
||||
aqua1: '#00BFFF',
|
||||
aqua2: '#1E90FF',
|
||||
aqua3: '#63B8FF',
|
||||
forest1: '#27AE60',
|
||||
forest2: '#2ECC71',
|
||||
forest3: '#58D68D',
|
||||
blush1: '#FF6F91',
|
||||
blush2: '#FF85A2',
|
||||
blush3: '#FFA0B3',
|
||||
lavender1: '#9B59B6',
|
||||
lavender2: '#AF7AC5',
|
||||
lavender3: '#C39BD3',
|
||||
tomato1: '#E74C3C',
|
||||
tomato2: '#EC7063',
|
||||
tomato3: '#F1948A',
|
||||
salmon1: '#FF6B6B',
|
||||
salmon2: '#FF8787',
|
||||
salmon3: '#FFA1A1',
|
||||
mustard1: '#F1C40F',
|
||||
mustard2: '#F7DC6F',
|
||||
mustard3: '#F9E79F',
|
||||
teal4: '#1ABC9C',
|
||||
teal5: '#48C9B0',
|
||||
teal6: '#76D7C4',
|
||||
magenta1: '#D6336C',
|
||||
magenta2: '#E84393',
|
||||
magenta3: '#F06292',
|
||||
violet4: '#7D3C98',
|
||||
violet5: '#8E44AD',
|
||||
violet6: '#9B59B6',
|
||||
green4: '#229954',
|
||||
green5: '#27AE60',
|
||||
green6: '#52BE80',
|
||||
blue4: '#2874A6',
|
||||
blue5: '#2E86C1',
|
||||
blue6: '#3498DB',
|
||||
red4: '#C0392B',
|
||||
red5: '#E74C3C',
|
||||
red6: '#EC7063',
|
||||
orange4: '#D35400',
|
||||
orange5: '#E67E22',
|
||||
orange6: '#EB984E',
|
||||
pink4: '#C2185B',
|
||||
pink5: '#D81B60',
|
||||
pink6: '#E91E63',
|
||||
gold4: '#B7950B',
|
||||
gold5: '#F1C40F',
|
||||
gold6: '#F4D03F',
|
||||
},
|
||||
lightModeColor: {
|
||||
radicalRed: '#FF1A66',
|
||||
dodgerBlueDark: '#0C6EED',
|
||||
steelgrey: '#2f4b7c',
|
||||
steelpurple: '#665191',
|
||||
steelindigo: '#a05195',
|
||||
steelpink: '#d45087',
|
||||
steelcoral: '#f95d6a',
|
||||
steelorange: '#ff7c43',
|
||||
steelgold: '#ffa600',
|
||||
steelrust: '#de425b',
|
||||
steelgreen: '#41967e',
|
||||
mediumOrchidDark: '#C326FD',
|
||||
seaBuckthornDark: '#E66E05',
|
||||
seaGreen: '#219653',
|
||||
turquoiseBlueDark: '#0099CC',
|
||||
silverDark: '#757575',
|
||||
outrageousOrangeDark: '#F9521A',
|
||||
roseBudDark: '#EB6437',
|
||||
deepSkyBlueDark: '#0595BD',
|
||||
royalBlue: '#3366E6',
|
||||
avocadoDark: '#8E8E29',
|
||||
mintGreenDark: '#00C700',
|
||||
chestnut: '#B34D4D',
|
||||
limaDark: '#6E9900',
|
||||
olive: '#809900',
|
||||
beautyBushDark: '#E25555',
|
||||
danube: '#6680B3',
|
||||
oliveDrab: '#66991A',
|
||||
lavenderRoseDark: '#F024BD',
|
||||
electricLimeDark: '#84A800',
|
||||
robin: '#3F5ECC',
|
||||
harleyOrange: '#E6331A',
|
||||
gladeGreen: '#66994D',
|
||||
hemlock: '#66664D',
|
||||
vidaLoca: '#4D8000',
|
||||
rust: '#B33300',
|
||||
red: '#FF0000', // Adding more colors, we need to get better colors from design team
|
||||
blue: '#0000FF',
|
||||
green: '#00FF00',
|
||||
purple: '#800080',
|
||||
magentaDark: '#EB00EB',
|
||||
pinkDark: '#FF3D5E',
|
||||
brown: '#A52A2A',
|
||||
teal: '#008080',
|
||||
limeDark: '#07A207',
|
||||
maroon: '#800000',
|
||||
navy: '#000080',
|
||||
gray: '#808080',
|
||||
skyBlueDark: '#0CA7E4',
|
||||
indigo: '#4B0082',
|
||||
slateGray: '#708090',
|
||||
chocolate: '#D2691E',
|
||||
tomato: '#FF6347',
|
||||
steelBlue: '#4682B4',
|
||||
peruDark: '#D16E0A',
|
||||
darkOliveGreen: '#556B2F',
|
||||
indianRed: '#CD5C5C',
|
||||
mediumSlateBlue: '#7B68EE',
|
||||
rosyBrownDark: '#CB4848',
|
||||
darkSlateGray: '#2F4F4F',
|
||||
fuchsia: '#FF0AFF',
|
||||
salmonDark: '#FF432E',
|
||||
darkSalmonDark: '#D26541',
|
||||
paleVioletRedDark: '#E83089',
|
||||
mediumPurple: '#9370DB',
|
||||
darkOrchid: '#9932CC',
|
||||
mediumSeaGreenDark: '#109E50',
|
||||
lightCoralDark: '#F85959',
|
||||
gold: '#FFD700',
|
||||
sandyBrownDark: '#D97117',
|
||||
darkKhakiDark: '#99900A',
|
||||
cornflowerBlueDark: '#3371E6',
|
||||
mediumVioletRed: '#C71585',
|
||||
paleGreenDark: '#0D910D',
|
||||
radicalRed: '#D81B60',
|
||||
|
||||
dodgerBlueDark: '#1E5BD9',
|
||||
steelgrey: '#344B6B',
|
||||
steelpurple: '#5E548E',
|
||||
steelindigo: '#8E4A7C',
|
||||
steelpink: '#B63A6F',
|
||||
steelcoral: '#E14B5A',
|
||||
steelorange: '#E76F2F',
|
||||
steelgold: '#E09B00',
|
||||
steelrust: '#C93A50',
|
||||
steelgreen: '#2F7D69',
|
||||
|
||||
mediumOrchidDark: '#8E24AA',
|
||||
seaBuckthornDark: '#C75A00',
|
||||
seaGreen: '#1E7F5A',
|
||||
turquoiseBlueDark: '#007EA7',
|
||||
silverDark: '#5F5F5F',
|
||||
outrageousOrangeDark: '#E64A19',
|
||||
roseBudDark: '#D84315',
|
||||
deepSkyBlueDark: '#0277BD',
|
||||
royalBlue: '#2A4FDB',
|
||||
|
||||
avocadoDark: '#6B6B1E',
|
||||
mintGreenDark: '#2E9E55',
|
||||
chestnut: '#8B3A3A',
|
||||
limaDark: '#5C7F00',
|
||||
olive: '#6E7F00',
|
||||
beautyBushDark: '#C93C3C',
|
||||
|
||||
danube: '#4F6FB3',
|
||||
oliveDrab: '#4F7F1A',
|
||||
lavenderRoseDark: '#B0178F',
|
||||
electricLimeDark: '#6B8F00',
|
||||
robin: '#2F4FCC',
|
||||
|
||||
harleyOrange: '#CC2E12',
|
||||
gladeGreen: '#4F7F46',
|
||||
hemlock: '#5C5C45',
|
||||
vidaLoca: '#3D6B00',
|
||||
rust: '#993300',
|
||||
|
||||
red: '#C62828',
|
||||
blue: '#1A237E',
|
||||
green: '#1B7F3A',
|
||||
purple: '#6A1B9A',
|
||||
magentaDark: '#B000B5',
|
||||
pinkDark: '#C2185B',
|
||||
|
||||
brown: '#7A3A1E',
|
||||
teal: '#006D6F',
|
||||
limeDark: '#4C8C2B',
|
||||
maroon: '#6D1B1B',
|
||||
navy: '#0D1B5E',
|
||||
gray: '#616161',
|
||||
|
||||
skyBlueDark: '#0288D1',
|
||||
indigo: '#303F9F',
|
||||
slateGray: '#556B7C',
|
||||
chocolate: '#9C4A1A',
|
||||
tomato: '#E53935',
|
||||
steelBlue: '#3A6EA5',
|
||||
|
||||
peruDark: '#B35E00',
|
||||
darkOliveGreen: '#445B1F',
|
||||
indianRed: '#B04040',
|
||||
mediumSlateBlue: '#5C6BC0',
|
||||
rosyBrownDark: '#A94444',
|
||||
darkSlateGray: '#2E4A4A',
|
||||
|
||||
fuchsia: '#C511C5',
|
||||
salmonDark: '#E64A3C',
|
||||
darkSalmonDark: '#C85A3A',
|
||||
paleVioletRedDark: '#C2186A',
|
||||
|
||||
mediumPurple: '#7E57C2',
|
||||
darkOrchid: '#7B1FA2',
|
||||
mediumSeaGreenDark: '#2E8B57',
|
||||
lightCoralDark: '#E57373',
|
||||
|
||||
gold: '#D4AF37',
|
||||
sandyBrownDark: '#C76A15',
|
||||
darkKhakiDark: '#8A7F00',
|
||||
cornflowerBlueDark: '#355FCC',
|
||||
mediumVioletRed: '#AD1457',
|
||||
paleGreenDark: '#2E7D32',
|
||||
},
|
||||
errorColor: '#d32f2f',
|
||||
royalGrey: '#888888',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Spin, Table, Typography } from 'antd';
|
||||
import { Spin, Table } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
@@ -14,11 +14,13 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useListOverview } from 'hooks/thirdPartyApis/useListOverview';
|
||||
import { get } from 'lodash-es';
|
||||
import { MoveUpRight } from 'lucide-react';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { HandleChangeQueryDataV5 } from 'types/common/operations.types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
|
||||
import { ApiMonitoringHardcodedAttributeKeys } from '../../constants';
|
||||
import { DEFAULT_PARAMS, useApiMonitoringParams } from '../../queryParams';
|
||||
@@ -125,51 +127,67 @@ function DomainList(): JSX.Element {
|
||||
hardcodedAttributeKeys={ApiMonitoringHardcodedAttributeKeys}
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
className={cx('api-monitoring-domain-list-table')}
|
||||
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
|
||||
columns={columnsConfig}
|
||||
loading={{
|
||||
spinning: isFetching || isLoading,
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
locale={{
|
||||
emptyText:
|
||||
isFetching || isLoading ? null : (
|
||||
<div className="no-filtered-domains-message-container">
|
||||
<div className="no-filtered-domains-message-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
{!isFetching && !isLoading && formattedDataForTable.length === 0 && (
|
||||
<div className="no-filtered-domains-message-container">
|
||||
<div className="no-filtered-domains-message-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-domains-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="no-filtered-domains-message">
|
||||
<div className="no-domain-title">
|
||||
No External API calls detected with applied filters.
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
onRow={(record, index): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
if (index !== undefined) {
|
||||
const dataIndex = formattedDataForTable.findIndex(
|
||||
(item) => item.key === record.key,
|
||||
);
|
||||
setSelectedDomainIndex(dataIndex);
|
||||
setParams({ selectedDomain: record.domainName });
|
||||
logEvent('API Monitoring: Domain name row clicked', {});
|
||||
}
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
||||
}
|
||||
/>
|
||||
<div className="no-domain-subtitle">
|
||||
Ensure all HTTP client spans are being sent with kind as{' '}
|
||||
<span className="attribute">Client</span> and url set in{' '}
|
||||
<span className="attribute">url.full</span> or{' '}
|
||||
<span className="attribute">http.url</span> attribute.
|
||||
</div>
|
||||
<a
|
||||
href={DOCLINKS.EXTERNAL_API_MONITORING}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="external-api-doc-link"
|
||||
>
|
||||
Learn how External API monitoring works in SigNoz{' '}
|
||||
<MoveUpRight size={14} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(isFetching || isLoading || formattedDataForTable.length > 0) && (
|
||||
<Table
|
||||
className="api-monitoring-domain-list-table"
|
||||
dataSource={isFetching || isLoading ? [] : formattedDataForTable}
|
||||
columns={columnsConfig}
|
||||
loading={{
|
||||
spinning: isFetching || isLoading,
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
onRow={(record, index): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
if (index !== undefined) {
|
||||
const dataIndex = formattedDataForTable.findIndex(
|
||||
(item) => item.key === record.key,
|
||||
);
|
||||
setSelectedDomainIndex(dataIndex);
|
||||
setParams({ selectedDomain: record.domainName });
|
||||
logEvent('API Monitoring: Domain name row clicked', {});
|
||||
}
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{selectedDomainIndex !== -1 && (
|
||||
<DomainDetails
|
||||
domainData={formattedDataForTable[selectedDomainIndex]}
|
||||
|
||||
@@ -180,10 +180,59 @@
|
||||
|
||||
.no-filtered-domains-message {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
|
||||
.no-domain-title {
|
||||
color: var(--bg-vanilla-100, #fff);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.no-domain-subtitle {
|
||||
color: var(--bg-vanilla-400, #c0c1c3);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
|
||||
.attribute {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
|
||||
.external-api-doc-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.no-filtered-domains-message-container {
|
||||
.no-filtered-domains-message-content {
|
||||
.no-filtered-domains-message {
|
||||
.no-domain-title {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
|
||||
.no-domain-subtitle {
|
||||
color: var(--text-ink-400);
|
||||
|
||||
.attribute {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.api-monitoring-domain-list-table {
|
||||
.ant-table {
|
||||
.ant-table-thead > tr > th {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
|
||||
.app-content {
|
||||
width: calc(100% - 64px); // width of the sidebar
|
||||
width: calc(100% - 54px); // width of the sidebar
|
||||
z-index: 0;
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import {
|
||||
IntegrationType,
|
||||
RequestIntegrationBtn,
|
||||
} from 'pages/Integrations/RequestIntegrationBtn';
|
||||
|
||||
import Header from './Header/Header';
|
||||
import HeroSection from './HeroSection/HeroSection';
|
||||
import ServicesTabs from './ServicesSection/ServicesTabs';
|
||||
|
||||
function CloudIntegrationPage(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<HeroSection />
|
||||
<RequestIntegrationBtn
|
||||
type={IntegrationType.AWS_SERVICES}
|
||||
message="Can't find the AWS service you're looking for? Request more integrations"
|
||||
/>
|
||||
<ServicesTabs />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloudIntegrationPage;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import AccountActions from './components/AccountActions';
|
||||
|
||||
import './HeroSection.style.scss';
|
||||
|
||||
function HeroSection(): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
return (
|
||||
<div
|
||||
className="hero-section"
|
||||
style={
|
||||
isDarkMode
|
||||
? {
|
||||
backgroundImage: `url('/Images/integrations-hero-bg.png')`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="hero-section__icon">
|
||||
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
|
||||
</div>
|
||||
<div className="hero-section__details">
|
||||
<div className="title">Amazon Web Services</div>
|
||||
<div className="description">
|
||||
One-click setup for AWS monitoring with SigNoz
|
||||
</div>
|
||||
<AccountActions />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeroSection;
|
||||
@@ -338,7 +338,7 @@ describe('CreateAlertV2 utils', () => {
|
||||
const props = getCreateAlertLocalStateFromAlertDef(args);
|
||||
expect(props).toBeDefined();
|
||||
expect(props).toMatchObject({
|
||||
basicAlertState: {
|
||||
basic: {
|
||||
...INITIAL_ALERT_STATE,
|
||||
name: 'test-alert',
|
||||
labels: {
|
||||
@@ -348,10 +348,10 @@ describe('CreateAlertV2 utils', () => {
|
||||
yAxisUnit: UniversalYAxisUnit.MINUTES,
|
||||
},
|
||||
// as we have already verified these utils in their respective tests
|
||||
thresholdState: expect.any(Object),
|
||||
advancedOptionsState: expect.any(Object),
|
||||
evaluationWindowState: expect.any(Object),
|
||||
notificationSettingsState: expect.any(Object),
|
||||
threshold: expect.any(Object),
|
||||
advancedOptions: expect.any(Object),
|
||||
evaluationWindow: expect.any(Object),
|
||||
notificationSettings: expect.any(Object),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,3 +196,11 @@ export const INITIAL_NOTIFICATION_SETTINGS_STATE: NotificationSettingsState = {
|
||||
description: NOTIFICATION_MESSAGE_PLACEHOLDER,
|
||||
routingPolicies: false,
|
||||
};
|
||||
|
||||
export const INITIAL_CREATE_ALERT_STATE = {
|
||||
basic: INITIAL_ALERT_STATE,
|
||||
threshold: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettings: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
};
|
||||
|
||||
@@ -17,26 +17,22 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
import { INITIAL_CREATE_ALERT_STATE } from './constants';
|
||||
import {
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
INITIAL_ALERT_STATE,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
} from './constants';
|
||||
import {
|
||||
AdvancedOptionsAction,
|
||||
AlertThresholdAction,
|
||||
AlertThresholdMatchType,
|
||||
CreateAlertAction,
|
||||
CreateAlertSlice,
|
||||
EvaluationWindowAction,
|
||||
ICreateAlertContextProps,
|
||||
ICreateAlertProviderProps,
|
||||
NotificationSettingsAction,
|
||||
} from './types';
|
||||
import {
|
||||
advancedOptionsReducer,
|
||||
alertCreationReducer,
|
||||
alertThresholdReducer,
|
||||
buildInitialAlertDef,
|
||||
evaluationWindowReducer,
|
||||
createAlertReducer,
|
||||
getInitialAlertTypeFromURL,
|
||||
notificationSettingsReducer,
|
||||
} from './utils';
|
||||
|
||||
const CreateAlertContext = createContext<ICreateAlertContextProps | null>(null);
|
||||
@@ -65,10 +61,65 @@ export function CreateAlertProvider(
|
||||
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
|
||||
const [alertState, setAlertState] = useReducer(alertCreationReducer, {
|
||||
...INITIAL_ALERT_STATE,
|
||||
yAxisUnit: currentQuery.unit,
|
||||
});
|
||||
const [createAlertState, setCreateAlertState] = useReducer(
|
||||
createAlertReducer,
|
||||
{
|
||||
...INITIAL_CREATE_ALERT_STATE,
|
||||
basic: {
|
||||
...INITIAL_CREATE_ALERT_STATE.basic,
|
||||
yAxisUnit: currentQuery.unit,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const setAlertState = useCallback(
|
||||
(action: CreateAlertAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.BASIC,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setThresholdState = useCallback(
|
||||
(action: AlertThresholdAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setEvaluationWindow = useCallback(
|
||||
(action: EvaluationWindowAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.EVALUATION_WINDOW,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const setAdvancedOptions = useCallback(
|
||||
(action: AdvancedOptionsAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.ADVANCED_OPTIONS,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
const setNotificationSettings = useCallback(
|
||||
(action: NotificationSettingsAction) => {
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.NOTIFICATION_SETTINGS,
|
||||
action,
|
||||
});
|
||||
},
|
||||
[setCreateAlertState],
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
@@ -104,92 +155,56 @@ export function CreateAlertProvider(
|
||||
[redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
const [thresholdState, setThresholdState] = useReducer(
|
||||
alertThresholdReducer,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
);
|
||||
|
||||
const [evaluationWindow, setEvaluationWindow] = useReducer(
|
||||
evaluationWindowReducer,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
);
|
||||
|
||||
const [advancedOptions, setAdvancedOptions] = useReducer(
|
||||
advancedOptionsReducer,
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
);
|
||||
|
||||
const [notificationSettings, setNotificationSettings] = useReducer(
|
||||
notificationSettingsReducer,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setThresholdState({
|
||||
type: 'RESET',
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'RESET',
|
||||
},
|
||||
});
|
||||
|
||||
if (thresholdsFromURL) {
|
||||
try {
|
||||
const thresholds = JSON.parse(thresholdsFromURL);
|
||||
setThresholdState({
|
||||
type: 'SET_THRESHOLDS',
|
||||
payload: thresholds,
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'SET_THRESHOLDS',
|
||||
payload: thresholds,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error parsing thresholds from URL:', error);
|
||||
}
|
||||
|
||||
setEvaluationWindow({
|
||||
type: 'SET_INITIAL_STATE_FOR_METER',
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.EVALUATION_WINDOW,
|
||||
action: {
|
||||
type: 'SET_INITIAL_STATE_FOR_METER',
|
||||
},
|
||||
});
|
||||
|
||||
setThresholdState({
|
||||
type: 'SET_MATCH_TYPE',
|
||||
payload: AlertThresholdMatchType.IN_TOTAL,
|
||||
setCreateAlertState({
|
||||
slice: CreateAlertSlice.THRESHOLD,
|
||||
action: {
|
||||
type: 'SET_MATCH_TYPE',
|
||||
payload: AlertThresholdMatchType.IN_TOTAL,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [alertType, thresholdsFromURL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditMode && initialAlertState) {
|
||||
setAlertState({
|
||||
setCreateAlertState({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.basicAlertState,
|
||||
});
|
||||
setThresholdState({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.thresholdState,
|
||||
});
|
||||
setEvaluationWindow({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.evaluationWindowState,
|
||||
});
|
||||
setAdvancedOptions({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.advancedOptionsState,
|
||||
});
|
||||
setNotificationSettings({
|
||||
type: 'SET_INITIAL_STATE',
|
||||
payload: initialAlertState.notificationSettingsState,
|
||||
payload: initialAlertState,
|
||||
});
|
||||
}
|
||||
}, [initialAlertState, isEditMode]);
|
||||
|
||||
const discardAlertRule = useCallback(() => {
|
||||
setAlertState({
|
||||
type: 'RESET',
|
||||
});
|
||||
setThresholdState({
|
||||
type: 'RESET',
|
||||
});
|
||||
setEvaluationWindow({
|
||||
type: 'RESET',
|
||||
});
|
||||
setAdvancedOptions({
|
||||
type: 'RESET',
|
||||
});
|
||||
setNotificationSettings({
|
||||
setCreateAlertState({
|
||||
type: 'RESET',
|
||||
});
|
||||
handleAlertTypeChange(AlertTypes.METRICS_BASED_ALERT);
|
||||
@@ -212,17 +227,17 @@ export function CreateAlertProvider(
|
||||
|
||||
const contextValue: ICreateAlertContextProps = useMemo(
|
||||
() => ({
|
||||
alertState,
|
||||
alertState: createAlertState.basic,
|
||||
setAlertState,
|
||||
alertType,
|
||||
setAlertType: handleAlertTypeChange,
|
||||
thresholdState,
|
||||
thresholdState: createAlertState.threshold,
|
||||
setThresholdState,
|
||||
evaluationWindow,
|
||||
evaluationWindow: createAlertState.evaluationWindow,
|
||||
setEvaluationWindow,
|
||||
advancedOptions,
|
||||
advancedOptions: createAlertState.advancedOptions,
|
||||
setAdvancedOptions,
|
||||
notificationSettings,
|
||||
notificationSettings: createAlertState.notificationSettings,
|
||||
setNotificationSettings,
|
||||
discardAlertRule,
|
||||
createAlertRule,
|
||||
@@ -234,13 +249,14 @@ export function CreateAlertProvider(
|
||||
isEditMode: isEditMode || false,
|
||||
}),
|
||||
[
|
||||
alertState,
|
||||
createAlertState,
|
||||
setAlertState,
|
||||
setThresholdState,
|
||||
setEvaluationWindow,
|
||||
setAdvancedOptions,
|
||||
setNotificationSettings,
|
||||
alertType,
|
||||
handleAlertTypeChange,
|
||||
thresholdState,
|
||||
evaluationWindow,
|
||||
advancedOptions,
|
||||
notificationSettings,
|
||||
discardAlertRule,
|
||||
createAlertRule,
|
||||
isCreatingAlertRule,
|
||||
|
||||
@@ -9,8 +9,6 @@ import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
import { Labels } from 'types/api/alerts/def';
|
||||
|
||||
import { GetCreateAlertLocalStateFromAlertDefReturn } from '../types';
|
||||
|
||||
export interface ICreateAlertContextProps {
|
||||
alertState: AlertState;
|
||||
setAlertState: Dispatch<CreateAlertAction>;
|
||||
@@ -52,7 +50,7 @@ export interface ICreateAlertContextProps {
|
||||
export interface ICreateAlertProviderProps {
|
||||
children: React.ReactNode;
|
||||
initialAlertType: AlertTypes;
|
||||
initialAlertState?: GetCreateAlertLocalStateFromAlertDefReturn;
|
||||
initialAlertState?: CreateAlertState;
|
||||
isEditMode?: boolean;
|
||||
ruleId?: string;
|
||||
}
|
||||
@@ -272,3 +270,31 @@ export type NotificationSettingsAction =
|
||||
| { type: 'SET_ROUTING_POLICIES'; payload: boolean }
|
||||
| { type: 'SET_INITIAL_STATE'; payload: NotificationSettingsState }
|
||||
| { type: 'RESET' };
|
||||
|
||||
export type CreateAlertState = {
|
||||
basic: AlertState;
|
||||
threshold: AlertThresholdState;
|
||||
advancedOptions: AdvancedOptionsState;
|
||||
evaluationWindow: EvaluationWindowState;
|
||||
notificationSettings: NotificationSettingsState;
|
||||
};
|
||||
|
||||
export enum CreateAlertSlice {
|
||||
BASIC = 'basic',
|
||||
THRESHOLD = 'threshold',
|
||||
ADVANCED_OPTIONS = 'advancedOptions',
|
||||
EVALUATION_WINDOW = 'evaluationWindow',
|
||||
NOTIFICATION_SETTINGS = 'notificationSettings',
|
||||
}
|
||||
|
||||
export type CreateAlertReducerAction =
|
||||
| { slice: CreateAlertSlice.BASIC; action: CreateAlertAction }
|
||||
| { slice: CreateAlertSlice.THRESHOLD; action: AlertThresholdAction }
|
||||
| { slice: CreateAlertSlice.ADVANCED_OPTIONS; action: AdvancedOptionsAction }
|
||||
| { slice: CreateAlertSlice.EVALUATION_WINDOW; action: EvaluationWindowAction }
|
||||
| {
|
||||
slice: CreateAlertSlice.NOTIFICATION_SETTINGS;
|
||||
action: NotificationSettingsAction;
|
||||
}
|
||||
| { type: 'RESET' }
|
||||
| { type: 'SET_INITIAL_STATE'; payload: CreateAlertState };
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
INITIAL_ALERT_STATE,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
INITIAL_CREATE_ALERT_STATE,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
} from './constants';
|
||||
@@ -28,6 +29,9 @@ import {
|
||||
AlertThresholdAction,
|
||||
AlertThresholdState,
|
||||
CreateAlertAction,
|
||||
CreateAlertReducerAction,
|
||||
CreateAlertSlice,
|
||||
CreateAlertState,
|
||||
EvaluationWindowAction,
|
||||
EvaluationWindowState,
|
||||
NotificationSettingsAction,
|
||||
@@ -251,3 +255,57 @@ export const notificationSettingsReducer = (
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const createAlertReducer = (
|
||||
state: CreateAlertState,
|
||||
action: CreateAlertReducerAction,
|
||||
): CreateAlertState => {
|
||||
// Global actions
|
||||
if ('type' in action) {
|
||||
switch (action.type) {
|
||||
case 'RESET':
|
||||
return INITIAL_CREATE_ALERT_STATE;
|
||||
case 'SET_INITIAL_STATE':
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// Slice actions
|
||||
switch (action.slice) {
|
||||
case CreateAlertSlice.BASIC:
|
||||
return { ...state, basic: alertCreationReducer(state.basic, action.action) };
|
||||
case CreateAlertSlice.THRESHOLD:
|
||||
return {
|
||||
...state,
|
||||
threshold: alertThresholdReducer(state.threshold, action.action),
|
||||
};
|
||||
case CreateAlertSlice.ADVANCED_OPTIONS:
|
||||
return {
|
||||
...state,
|
||||
advancedOptions: advancedOptionsReducer(
|
||||
state.advancedOptions,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
case CreateAlertSlice.EVALUATION_WINDOW:
|
||||
return {
|
||||
...state,
|
||||
evaluationWindow: evaluationWindowReducer(
|
||||
state.evaluationWindow,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
case CreateAlertSlice.NOTIFICATION_SETTINGS:
|
||||
return {
|
||||
...state,
|
||||
notificationSettings: notificationSettingsReducer(
|
||||
state.notificationSettings,
|
||||
action.action,
|
||||
),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,11 +21,11 @@ import {
|
||||
AlertThresholdMatchType,
|
||||
AlertThresholdOperator,
|
||||
AlertThresholdState,
|
||||
CreateAlertState,
|
||||
EvaluationWindowState,
|
||||
NotificationSettingsState,
|
||||
} from './context/types';
|
||||
import { EVALUATION_WINDOW_TIMEFRAME } from './EvaluationSettings/constants';
|
||||
import { GetCreateAlertLocalStateFromAlertDefReturn } from './types';
|
||||
|
||||
export function Spinner(): JSX.Element | null {
|
||||
const { isCreatingAlertRule, isUpdatingAlertRule } = useCreateAlertState();
|
||||
@@ -265,14 +265,14 @@ export function getThresholdStateFromAlertDef(
|
||||
|
||||
export function getCreateAlertLocalStateFromAlertDef(
|
||||
alertDef: PostableAlertRuleV2 | undefined,
|
||||
): GetCreateAlertLocalStateFromAlertDefReturn {
|
||||
): CreateAlertState {
|
||||
if (!alertDef) {
|
||||
return {
|
||||
basicAlertState: INITIAL_ALERT_STATE,
|
||||
thresholdState: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptionsState: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindowState: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettingsState: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
basic: INITIAL_ALERT_STATE,
|
||||
threshold: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
notificationSettings: INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
};
|
||||
}
|
||||
// Basic alert state
|
||||
@@ -294,10 +294,10 @@ export function getCreateAlertLocalStateFromAlertDef(
|
||||
);
|
||||
|
||||
return {
|
||||
basicAlertState,
|
||||
thresholdState,
|
||||
advancedOptionsState,
|
||||
evaluationWindowState,
|
||||
notificationSettingsState,
|
||||
basic: basicAlertState,
|
||||
threshold: thresholdState,
|
||||
advancedOptions: advancedOptionsState,
|
||||
evaluationWindow: evaluationWindowState,
|
||||
notificationSettings: notificationSettingsState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const listViewInitialLogQuery: Query = {
|
||||
},
|
||||
};
|
||||
|
||||
export const listViewInitialTraceQuery = {
|
||||
export const listViewInitialTraceQuery: Query = {
|
||||
// it should be the above commented query
|
||||
...initialQueriesMap.traces,
|
||||
builder: {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user