mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-28 14:40:32 +01:00
Compare commits
152 Commits
platform-p
...
infraM/v2_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9203602abd | ||
|
|
c10d278ec4 | ||
|
|
8b0e8f666e | ||
|
|
1d89b03f10 | ||
|
|
ffd493617f | ||
|
|
3f95d35ad5 | ||
|
|
9e5678d6b3 | ||
|
|
7d702763cc | ||
|
|
3a61a78986 | ||
|
|
941016f12c | ||
|
|
46e833faba | ||
|
|
4bd7492629 | ||
|
|
500ce85ccb | ||
|
|
d1c9864f52 | ||
|
|
f80b650390 | ||
|
|
401701e036 | ||
|
|
64c356b484 | ||
|
|
97885babe8 | ||
|
|
3bec0df0ad | ||
|
|
24fe9a986d | ||
|
|
520e92049c | ||
|
|
78e3916aea | ||
|
|
92d297ac9d | ||
|
|
eff29aefba | ||
|
|
ca73453c9e | ||
|
|
1d836d674f | ||
|
|
83724b0cde | ||
|
|
a4266fa703 | ||
|
|
3050e37ec7 | ||
|
|
bdbaa32485 | ||
|
|
44add7b7cd | ||
|
|
feea9e9b36 | ||
|
|
e94767bda8 | ||
|
|
bb10f51cc5 | ||
|
|
cd16081a1e | ||
|
|
c23a53c8d2 | ||
|
|
dbfe47e757 | ||
|
|
e29b032e70 | ||
|
|
55b2215025 | ||
|
|
e90378e618 | ||
|
|
4592b78f48 | ||
|
|
d81c99feae | ||
|
|
65a456ff9e | ||
|
|
264577b673 | ||
|
|
b35c6676f9 | ||
|
|
c78c9a42db | ||
|
|
1095caa123 | ||
|
|
9043b49762 | ||
|
|
d4084a7494 | ||
|
|
27c564b3bf | ||
|
|
f02c491828 | ||
|
|
3d53b8f77f | ||
|
|
dffe94fec4 | ||
|
|
b7d4f18aae | ||
|
|
9ad2ec428a | ||
|
|
c9360fcf13 | ||
|
|
b5ab45db20 | ||
|
|
08f76aca78 | ||
|
|
983d4fe4f2 | ||
|
|
833af794c3 | ||
|
|
21b51d1fcc | ||
|
|
56f22682c8 | ||
|
|
9c8359940c | ||
|
|
4050880275 | ||
|
|
5e775f64f2 | ||
|
|
0189f23f46 | ||
|
|
49a36d4e3d | ||
|
|
9407d658ab | ||
|
|
5035712485 | ||
|
|
bab17c3615 | ||
|
|
37b44f4db9 | ||
|
|
99dd6e5f1e | ||
|
|
9c7131fa6a | ||
|
|
ad889a2e1d | ||
|
|
a4f6d0cbf5 | ||
|
|
589bed7c16 | ||
|
|
93843a1f48 | ||
|
|
88c43108fc | ||
|
|
ed4cf540e8 | ||
|
|
9e2dfa9033 | ||
|
|
d98d5d68ee | ||
|
|
2cb1c3b73b | ||
|
|
ae7ca497ad | ||
|
|
a579916961 | ||
|
|
4a16d56abf | ||
|
|
642b5ac3f0 | ||
|
|
a12112619c | ||
|
|
014785f1bc | ||
|
|
58ee797b10 | ||
|
|
82d236742f | ||
|
|
397e1ad5be | ||
|
|
8d6b25ca9b | ||
|
|
5fa6bd8b8d | ||
|
|
bd9977483b | ||
|
|
50fbdfeeef | ||
|
|
e2b1b73e87 | ||
|
|
cb9f3fd3e5 | ||
|
|
232acc343d | ||
|
|
2025afdccc | ||
|
|
d2f4d4af93 | ||
|
|
47ff7bbb8e | ||
|
|
724071c5dc | ||
|
|
4d24979358 | ||
|
|
042943b10a | ||
|
|
48a9be7ec8 | ||
|
|
a9504b2120 | ||
|
|
8755887c4a | ||
|
|
4cb4662b3a | ||
|
|
e6900dabc8 | ||
|
|
c1ba389b63 | ||
|
|
3a1f40234f | ||
|
|
2e4891fa63 | ||
|
|
04ebc0bec7 | ||
|
|
271f9b81ed | ||
|
|
6fa815c294 | ||
|
|
63ec518efb | ||
|
|
c4ca20dd90 | ||
|
|
e56cc4222b | ||
|
|
07d2944d7c | ||
|
|
dea01ae36a | ||
|
|
62ea5b54e2 | ||
|
|
e549a7e42f | ||
|
|
90e2ebb11f | ||
|
|
61baa1be7a | ||
|
|
b946fa665f | ||
|
|
2e049556e4 | ||
|
|
492a5e70d7 | ||
|
|
ba1f2771e8 | ||
|
|
7458fb4855 | ||
|
|
5f55f3938b | ||
|
|
3e8102485c | ||
|
|
861c682ea5 | ||
|
|
c8e5895dff | ||
|
|
82d72e7edb | ||
|
|
a3f8ecaaf1 | ||
|
|
19aada656c | ||
|
|
b21bb4280f | ||
|
|
bc0a4fdb5c | ||
|
|
37fb0e9254 | ||
|
|
aecfa1a174 | ||
|
|
b869d23d94 | ||
|
|
6ee3d44f76 | ||
|
|
462e554107 | ||
|
|
66afa73e6f | ||
|
|
54c604bcf4 | ||
|
|
c1be02ba54 | ||
|
|
d3c7ba8f45 | ||
|
|
039c4a0496 | ||
|
|
51a94b6bbc | ||
|
|
bbfbb94f52 | ||
|
|
d1eb9ef16f | ||
|
|
3db00f8bc3 |
56
.github/CODEOWNERS
vendored
56
.github/CODEOWNERS
vendored
@@ -52,49 +52,49 @@ go.mod @therealpandey
|
||||
|
||||
# Querier Owners
|
||||
|
||||
/pkg/querier/ @srikanthccv
|
||||
/pkg/variables/ @srikanthccv
|
||||
/pkg/types/querybuildertypes/ @srikanthccv
|
||||
/pkg/types/telemetrytypes/ @srikanthccv
|
||||
/pkg/querybuilder/ @srikanthccv
|
||||
/pkg/telemetrylogs/ @srikanthccv
|
||||
/pkg/telemetrymetadata/ @srikanthccv
|
||||
/pkg/telemetrymetrics/ @srikanthccv
|
||||
/pkg/telemetrytraces/ @srikanthccv
|
||||
/pkg/querier/ @srikanthccv @therealpandey
|
||||
/pkg/variables/ @srikanthccv @therealpandey
|
||||
/pkg/types/querybuildertypes/ @srikanthccv @therealpandey
|
||||
/pkg/types/telemetrytypes/ @srikanthccv @therealpandey
|
||||
/pkg/querybuilder/ @srikanthccv @therealpandey
|
||||
/pkg/telemetrylogs/ @srikanthccv @therealpandey
|
||||
/pkg/telemetrymetadata/ @srikanthccv @therealpandey
|
||||
/pkg/telemetrymetrics/ @srikanthccv @therealpandey
|
||||
/pkg/telemetrytraces/ @srikanthccv @therealpandey
|
||||
|
||||
# Metrics
|
||||
|
||||
/pkg/types/metrictypes/ @srikanthccv
|
||||
/pkg/types/metricsexplorertypes/ @srikanthccv
|
||||
/pkg/modules/metricsexplorer/ @srikanthccv
|
||||
/pkg/prometheus/ @srikanthccv
|
||||
/pkg/types/metrictypes/ @srikanthccv @therealpandey
|
||||
/pkg/types/metricsexplorertypes/ @srikanthccv @therealpandey
|
||||
/pkg/modules/metricsexplorer/ @srikanthccv @therealpandey
|
||||
/pkg/prometheus/ @srikanthccv @therealpandey
|
||||
|
||||
# APM
|
||||
|
||||
/pkg/types/servicetypes/ @srikanthccv
|
||||
/pkg/types/apdextypes/ @srikanthccv
|
||||
/pkg/modules/apdex/ @srikanthccv
|
||||
/pkg/modules/services/ @srikanthccv
|
||||
/pkg/types/servicetypes/ @srikanthccv @therealpandey
|
||||
/pkg/types/apdextypes/ @srikanthccv @therealpandey
|
||||
/pkg/modules/apdex/ @srikanthccv @therealpandey
|
||||
/pkg/modules/services/ @srikanthccv @therealpandey
|
||||
|
||||
# Dashboard
|
||||
|
||||
/pkg/types/dashboardtypes/ @srikanthccv
|
||||
/pkg/modules/dashboard/ @srikanthccv
|
||||
/pkg/types/dashboardtypes/ @srikanthccv @therealpandey
|
||||
/pkg/modules/dashboard/ @srikanthccv @therealpandey
|
||||
|
||||
# Rule/Alertmanager
|
||||
|
||||
/pkg/types/ruletypes/ @srikanthccv
|
||||
/pkg/types/alertmanagertypes @srikanthccv
|
||||
/pkg/alertmanager/ @srikanthccv
|
||||
/pkg/ruler/ @srikanthccv
|
||||
/pkg/modules/rulestatehistory/ @srikanthccv
|
||||
/pkg/types/rulestatehistorytypes/ @srikanthccv
|
||||
/pkg/types/ruletypes/ @srikanthccv @therealpandey
|
||||
/pkg/types/alertmanagertypes @srikanthccv @therealpandey
|
||||
/pkg/alertmanager/ @srikanthccv @therealpandey
|
||||
/pkg/ruler/ @srikanthccv @therealpandey
|
||||
/pkg/modules/rulestatehistory/ @srikanthccv @therealpandey
|
||||
/pkg/types/rulestatehistorytypes/ @srikanthccv @therealpandey
|
||||
|
||||
# Correlation-adjacent
|
||||
|
||||
/pkg/contextlinks/ @srikanthccv
|
||||
/pkg/types/parsertypes/ @srikanthccv
|
||||
/pkg/queryparser/ @srikanthccv
|
||||
/pkg/contextlinks/ @srikanthccv @therealpandey
|
||||
/pkg/types/parsertypes/ @srikanthccv @therealpandey
|
||||
/pkg/queryparser/ @srikanthccv @therealpandey
|
||||
|
||||
# AuthN / AuthZ Owners
|
||||
|
||||
|
||||
3
.github/workflows/integrationci.yaml
vendored
3
.github/workflows/integrationci.yaml
vendored
@@ -51,6 +51,7 @@ jobs:
|
||||
- role
|
||||
- rootuser
|
||||
- serviceaccount
|
||||
- querier_json_body
|
||||
- ttl
|
||||
sqlstore-provider:
|
||||
- postgres
|
||||
@@ -61,7 +62,7 @@ jobs:
|
||||
- 25.5.6
|
||||
- 25.12.5
|
||||
schema-migrator-version:
|
||||
- v0.142.0
|
||||
- v0.144.3
|
||||
postgres-version:
|
||||
- 15
|
||||
if: |
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"oxc.typeAware": true,
|
||||
"oxc.tsConfigPath": "./frontend/tsconfig.json",
|
||||
"oxc.configPath": "./frontend/.oxlintrc.json",
|
||||
"oxc.fmt.configPath": "./frontend/.oxfmtrc.json",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "oxc.oxc-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
@@ -19,4 +21,3 @@
|
||||
"python-envs.defaultEnvManager": "ms-python.python:system",
|
||||
"python-envs.pythonProjects": []
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/auditor"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/authzregistry"
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaschema"
|
||||
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
|
||||
@@ -93,14 +92,13 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
|
||||
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzRegistry := authzregistry.NewAuthzRegistry()
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, authzRegistry), nil
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/auditor"
|
||||
"github.com/SigNoz/signoz/pkg/authn"
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/authz/authzregistry"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
@@ -138,14 +137,12 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
|
||||
return authNs, nil
|
||||
},
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
|
||||
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authzRegistry := authzregistry.NewAuthzRegistry()
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, authzRegistry), nil
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, dashboardModule), nil
|
||||
},
|
||||
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
|
||||
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
|
||||
@@ -170,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
|
||||
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
|
||||
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
|
||||
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
|
||||
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
|
||||
|
||||
1222
docs/api/openapi.yml
1222
docs/api/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -1,200 +0,0 @@
|
||||
# Authorization (FGA)
|
||||
|
||||
SigNoz uses OpenFGA for fine-grained authorization. Resources are modeled as FGA objects — the authz system checks whether a principal (user or service account) has a specific relation (read, update, delete, etc.) on a specific resource.
|
||||
|
||||
This guide explains how to enable FGA for a new entity.
|
||||
|
||||
## Overview
|
||||
|
||||
Enabling FGA for an entity involves four steps:
|
||||
|
||||
1. **Define typeables** — Declare the resource type identities in `authtypes`
|
||||
2. **Register in the authz registry** — Define which managed roles get which permissions
|
||||
3. **Switch routes to the Check middleware** — Replace role-based middleware with resource-level FGA checks
|
||||
4. **Add a migration** — Backfill FGA tuples for existing organizations
|
||||
|
||||
## Step 1: Define typeables in `authtypes`
|
||||
|
||||
Add the typeable vars to the var block in `pkg/types/authtypes/typeable.go`, alongside the existing typeables. Every FGA-managed entity needs two typeables:
|
||||
|
||||
- A **collection typeable** (`metaresources`) — for `create` and `list` operations
|
||||
- An **instance typeable** (`metaresource`) — for `read`, `update`, and `delete` operations
|
||||
|
||||
```go
|
||||
// pkg/types/authtypes/typeable.go — add to the existing var block
|
||||
var (
|
||||
// ... existing typeables ...
|
||||
|
||||
TypeableMetaResourceMyEntity = MustNewTypeableMetaResource(MustNewName("my-entity"))
|
||||
TypeableMetaResourcesMyEntities = MustNewTypeableMetaResources(MustNewName("my-entities"))
|
||||
)
|
||||
```
|
||||
|
||||
These produce FGA objects like:
|
||||
- `metaresource:organization/{orgID}/my-entity/{entityID}` — individual instance
|
||||
- `metaresources:organization/{orgID}/my-entities/*` — collection
|
||||
|
||||
Use kebab-case for names. The collection name is typically the plural form.
|
||||
|
||||
## Step 2: Register in the authz registry
|
||||
|
||||
Create a new file `pkg/authz/authzregistry/myentity.go`. Each registry file exports two functions:
|
||||
|
||||
- `myEntityTypeables()` — returns the typeables for this entity
|
||||
- `myEntityTransactions()` — returns the managed role → transaction mapping
|
||||
|
||||
Use `serviceaccount.go` or `dashboard.go` as a reference. Here is the pattern:
|
||||
|
||||
```go
|
||||
package authzregistry
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
|
||||
func myEntityTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{
|
||||
authtypes.TypeableMetaResourceMyEntity,
|
||||
authtypes.TypeableMetaResourcesMyEntities,
|
||||
}
|
||||
}
|
||||
|
||||
func myEntityTransactions() map[string][]*authtypes.Transaction {
|
||||
return map[string][]*authtypes.Transaction{
|
||||
authtypes.SigNozAdminRoleName: {
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourcesMyEntities, authtypes.RelationCreate),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourcesMyEntities, authtypes.RelationList),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourceMyEntity, authtypes.RelationRead),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourceMyEntity, authtypes.RelationUpdate),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourceMyEntity, authtypes.RelationDelete),
|
||||
},
|
||||
authtypes.SigNozEditorRoleName: {
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourcesMyEntities, authtypes.RelationList),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourceMyEntity, authtypes.RelationRead),
|
||||
},
|
||||
authtypes.SigNozViewerRoleName: {
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourcesMyEntities, authtypes.RelationList),
|
||||
authtypes.MustNewWildcardTransaction(authtypes.TypeableMetaResourceMyEntity, authtypes.RelationRead),
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`MustNewWildcardTransaction(typeable, relation)` creates a transaction granting the relation on all instances (`*`) of that resource type. It validates that the relation is valid for the type and generates a unique ID.
|
||||
|
||||
Then wire it into `pkg/authz/authzregistry/registry.go`:
|
||||
|
||||
```go
|
||||
func collectTypeables() []authtypes.Typeable {
|
||||
typeables := make([]authtypes.Typeable, 0)
|
||||
typeables = append(typeables, roleTypeables()...)
|
||||
typeables = append(typeables, dashboardTypeables()...)
|
||||
typeables = append(typeables, serviceAccountTypeables()...)
|
||||
typeables = append(typeables, myEntityTypeables()...) // <-- add this
|
||||
return typeables
|
||||
}
|
||||
|
||||
func collectTransactions() map[string][]*authtypes.Transaction {
|
||||
transactions := make(map[string][]*authtypes.Transaction)
|
||||
|
||||
sources := []map[string][]*authtypes.Transaction{
|
||||
dashboardTransactions(),
|
||||
serviceAccountTransactions(),
|
||||
myEntityTransactions(), // <-- add this
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
for roleName, txns := range source {
|
||||
transactions[roleName] = append(transactions[roleName], txns...)
|
||||
}
|
||||
}
|
||||
|
||||
return transactions
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Switch routes to the Check middleware
|
||||
|
||||
In your route file (e.g., `pkg/apiserver/signozapiserver/myentity.go`), replace `AdminAccess` / `EditAccess` / `ViewAccess` with the `Check` middleware:
|
||||
|
||||
```go
|
||||
provider.authZ.Check(
|
||||
handler, // the HTTP handler func
|
||||
authtypes.RelationRead, // the relation to check
|
||||
authtypes.TypeableMetaResourceMyEntity, // the typeable
|
||||
selectorCallback, // extracts resource ID from the request
|
||||
roles, // role names for community edition fallback
|
||||
)
|
||||
```
|
||||
|
||||
### Selector callbacks
|
||||
|
||||
You need two callbacks — one for collection operations, one for instance operations:
|
||||
|
||||
```go
|
||||
// For create/list — wildcard selector on the collection.
|
||||
func myEntityCollectionSelector(_ *http.Request, _ authtypes.Claims) ([]authtypes.Selector, error) {
|
||||
return []authtypes.Selector{
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResources, authtypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// For read/update/delete — specific instance ID + wildcard.
|
||||
func myEntityInstanceSelector(req *http.Request, _ authtypes.Claims) ([]authtypes.Selector, error) {
|
||||
id := mux.Vars(req)["id"]
|
||||
idSelector, err := authtypes.NewSelector(authtypes.TypeMetaResource, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []authtypes.Selector{
|
||||
idSelector,
|
||||
authtypes.MustNewSelector(authtypes.TypeMetaResource, authtypes.WildCardSelectorString),
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
The instance callback includes a wildcard selector so that roles with wildcard permission (`*`) also match. Use `NewSelector` (not `MustNewSelector`) for user-supplied path parameters to avoid panics on malformed input.
|
||||
|
||||
### Role fallback
|
||||
|
||||
The `roles` parameter is used by the **community edition**, where `CheckWithTupleCreation` only checks role membership (ignoring resource selectors). Pass the role names that should have access:
|
||||
|
||||
```go
|
||||
var myEntityAdminRoles = []string{authtypes.SigNozAdminRoleName}
|
||||
var myEntityReadRoles = []string{authtypes.SigNozAdminRoleName, authtypes.SigNozEditorRoleName, authtypes.SigNozViewerRoleName}
|
||||
```
|
||||
|
||||
### OpenAPI security schemes
|
||||
|
||||
Use `newScopedSecuritySchemes` with the exact FGA scope, generated via `Typeable.Scope(relation)`:
|
||||
|
||||
```go
|
||||
SecuritySchemes: newScopedSecuritySchemes([]string{
|
||||
authtypes.TypeableMetaResourceMyEntity.Scope(authtypes.RelationRead),
|
||||
}),
|
||||
// produces: ["my-entity:read"]
|
||||
```
|
||||
|
||||
## Step 4: Add a migration for existing organizations
|
||||
|
||||
New organizations get FGA tuples automatically during bootstrap (via `CreateManagedUserRoleTransactions`). Existing organizations need a SQL migration to backfill the tuples.
|
||||
|
||||
Create a migration file in `pkg/sqlmigration/` (use the next available number). Follow the pattern in `078_add_sa_managed_role_txn.go`:
|
||||
|
||||
1. Select the OpenFGA store ID
|
||||
2. Iterate all organizations
|
||||
3. For each org × tuple, insert into the `tuple` and `changelog` tables
|
||||
4. Use `ON CONFLICT DO NOTHING` for idempotency
|
||||
5. Handle both PostgreSQL and SQLite dialects
|
||||
|
||||
Register the migration in `pkg/signoz/provider.go`.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Typeable vars added to `pkg/types/authtypes/typeable.go`
|
||||
- [ ] Registry file created in `pkg/authz/authzregistry/` with `*Typeables()` and `*Transactions()` functions
|
||||
- [ ] Functions wired into `collectTypeables()` and `collectTransactions()` in `registry.go`
|
||||
- [ ] Routes switched from `AdminAccess`/`EditAccess`/`ViewAccess` to `Check` middleware
|
||||
- [ ] Selector callbacks use `NewSelector` (not `MustNewSelector`) for user-supplied IDs
|
||||
- [ ] OpenAPI `SecuritySchemes` use `newScopedSecuritySchemes` with exact scope strings
|
||||
- [ ] Migration backfills FGA tuples for existing organizations
|
||||
- [ ] `make go-build-community` and `make go-build-enterprise` compile
|
||||
- [ ] `make go-test` passes
|
||||
@@ -11,7 +11,6 @@ We adhere to three primary style guides as our foundation:
|
||||
We **recommend** (almost enforce) reviewing these guides before contributing to the codebase. They provide valuable insights into writing idiomatic Go code and will help you understand our approach to backend development. In addition, we have a few additional rules that make certain areas stricter than the above which can be found in area-specific files in this package:
|
||||
|
||||
- [Abstractions](abstractions.md) - When to introduce new types and intermediate representations
|
||||
- [Authorization](authz.md) - Enabling FGA for new entities
|
||||
- [Errors](errors.md) - Structured error handling
|
||||
- [Endpoint](endpoint.md) - HTTP endpoint patterns
|
||||
- [Flagger](flagger.md) - Feature flag patterns
|
||||
|
||||
@@ -25,19 +25,19 @@ type provider struct {
|
||||
openfgaServer *openfgaserver.Server
|
||||
licensing licensing.Licensing
|
||||
store authtypes.RoleStore
|
||||
registry authz.Registry
|
||||
registry []authz.RegisterTypeable
|
||||
settings factory.ScopedProviderSettings
|
||||
onBeforeRoleDelete []authz.OnBeforeRoleDelete
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry authz.Registry) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, 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, openfgaDataStore, licensing, onBeforeRoleDelete, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry authz.Registry) (authz.AuthZ, error) {
|
||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore, registry)
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
|
||||
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore)
|
||||
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -210,7 +210,12 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
|
||||
|
||||
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
|
||||
resources := make([]*authtypes.Resource, 0)
|
||||
for _, typeable := range provider.registry.GetTypeables() {
|
||||
for _, register := range provider.registry {
|
||||
for _, typeable := range register.MustGetTypeables() {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
}
|
||||
for _, typeable := range provider.MustGetTypeables() {
|
||||
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
|
||||
}
|
||||
|
||||
@@ -229,7 +234,7 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
|
||||
}
|
||||
|
||||
objects := make([]*authtypes.Object, 0)
|
||||
for _, objectType := range provider.registry.GetUniqueTypes() {
|
||||
for _, objectType := range provider.getUniqueTypes() {
|
||||
if !slices.Contains(authtypes.TypeableRelations[objectType], relation) {
|
||||
continue
|
||||
}
|
||||
@@ -313,6 +318,9 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return provider.store.Delete(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return []authtypes.Typeable{authtypes.TypeableRole, authtypes.TypeableResourcesRoles}
|
||||
}
|
||||
|
||||
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) ([]*openfgav1.TupleKey, error) {
|
||||
tuples := []*openfgav1.TupleKey{}
|
||||
@@ -351,8 +359,15 @@ func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID va
|
||||
}
|
||||
|
||||
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 provider.registry.GetManagedRoleTransactions() {
|
||||
for roleName, transactions := range transactionsByRole {
|
||||
for _, txn := range transactions {
|
||||
typeable := authtypes.MustNewTypeableFromType(txn.Object.Resource.Type, txn.Object.Resource.Name)
|
||||
txnTuples, err := typeable.Tuples(
|
||||
@@ -380,7 +395,7 @@ func (provider *provider) deleteTuples(ctx context.Context, roleName string, org
|
||||
subject := authtypes.MustNewSubject(authtypes.TypeableRole, roleName, orgID, &authtypes.RelationAssignee)
|
||||
|
||||
tuples := make([]*openfgav1.TupleKey, 0)
|
||||
for _, objectType := range provider.registry.GetUniqueTypes() {
|
||||
for _, objectType := range provider.getUniqueTypes() {
|
||||
typeTuples, err := provider.ReadTuples(ctx, &openfgav1.ReadRequestTupleKey{
|
||||
User: subject,
|
||||
Object: objectType.StringValue() + ":",
|
||||
@@ -410,3 +425,27 @@ func (provider *provider) deleteTuples(ctx context.Context, roleName string, org
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) getUniqueTypes() []authtypes.Type {
|
||||
seen := make(map[string]struct{})
|
||||
uniqueTypes := make([]authtypes.Type, 0)
|
||||
for _, register := range provider.registry {
|
||||
for _, typeable := range register.MustGetTypeables() {
|
||||
typeKey := typeable.Type().StringValue()
|
||||
if _, ok := seen[typeKey]; ok {
|
||||
continue
|
||||
}
|
||||
seen[typeKey] = struct{}{}
|
||||
uniqueTypes = append(uniqueTypes, typeable.Type())
|
||||
}
|
||||
}
|
||||
for _, typeable := range provider.MustGetTypeables() {
|
||||
typeKey := typeable.Type().StringValue()
|
||||
if _, ok := seen[typeKey]; ok {
|
||||
continue
|
||||
}
|
||||
seen[typeKey] = struct{}{}
|
||||
uniqueTypes = append(uniqueTypes, typeable.Type())
|
||||
}
|
||||
|
||||
return uniqueTypes
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
|
||||
return &awscloudprovider{serviceDefinitions: defStore}, nil
|
||||
}
|
||||
|
||||
// TODO: move URL construction logic to cloudintegrationtypes and add unit tests for it.
|
||||
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
|
||||
u, _ := url.Parse(baseURL)
|
||||
|
||||
@@ -2,27 +2,48 @@ package implcloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
)
|
||||
|
||||
type azurecloudprovider struct{}
|
||||
type azurecloudprovider struct {
|
||||
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
|
||||
}
|
||||
|
||||
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
|
||||
return &azurecloudprovider{}
|
||||
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
|
||||
return &azurecloudprovider{
|
||||
serviceDefinitions: defStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
panic("implement me")
|
||||
connectionArtifact, err := cloudintegrationtypes.NewAzureConnectionArtifact(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cloudintegrationtypes.ConnectionArtifact{
|
||||
Azure: connectionArtifact,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id.
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) BuildIntegrationConfig(
|
||||
@@ -30,5 +51,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
|
||||
account *cloudintegrationtypes.Account,
|
||||
services []*cloudintegrationtypes.StorableCloudIntegrationService,
|
||||
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
|
||||
panic("implement me")
|
||||
sort.Slice(services, func(i, j int) bool {
|
||||
return services[i].Type.StringValue() < services[j].Type.StringValue()
|
||||
})
|
||||
|
||||
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
|
||||
|
||||
for _, storedSvc := range services {
|
||||
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
strategy := svcDef.TelemetryCollectionStrategy.Azure
|
||||
if strategy == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
|
||||
if !logsEnabled && !metricsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
|
||||
ResourceProvider: strategy.ResourceProvider,
|
||||
ResourceType: strategy.ResourceType,
|
||||
}
|
||||
|
||||
if metricsEnabled && strategy.Metrics != nil {
|
||||
entry.Metrics = strategy.Metrics
|
||||
}
|
||||
|
||||
if logsEnabled && strategy.Logs != nil {
|
||||
entry.Logs = strategy.Logs
|
||||
}
|
||||
|
||||
strategies = append(strategies, entry)
|
||||
}
|
||||
|
||||
return &cloudintegrationtypes.ProviderIntegrationConfig{
|
||||
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
|
||||
account.Config.Azure.DeploymentRegion,
|
||||
account.Config.Azure.ResourceGroups,
|
||||
strategies,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -429,9 +429,13 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
|
||||
}
|
||||
|
||||
// NOTE: not adding stats for services for now.
|
||||
// get connected accounts for Azure
|
||||
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
if err == nil {
|
||||
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
|
||||
}
|
||||
|
||||
// TODO: add more cloud providers when supported
|
||||
// NOTE: not adding stats for services for now.
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -472,7 +476,7 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName, serviceaccount.WithSkipEscalationGuard())
|
||||
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -217,6 +217,28 @@ func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valu
|
||||
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, isAdmin, lock)
|
||||
}
|
||||
|
||||
func (module *module) MustGetTypeables() []authtypes.Typeable {
|
||||
return module.pkgDashboardModule.MustGetTypeables()
|
||||
}
|
||||
|
||||
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
|
||||
return map[string][]*authtypes.Transaction{
|
||||
authtypes.SigNozAnonymousRoleName: {
|
||||
{
|
||||
ID: valuer.GenerateUUID(),
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||
@@ -142,7 +141,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationServiceAccount(ctx context.Cont
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||
}
|
||||
err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName, serviceaccount.WithSkipEscalationGuard())
|
||||
err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"react",
|
||||
"react-perf",
|
||||
"typescript",
|
||||
"unicorn",
|
||||
"jsx-a11y",
|
||||
"import",
|
||||
"jest",
|
||||
@@ -206,6 +205,8 @@
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
// Requires explicit return types on functions
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/no-useless-default-assignment": "off", // provide unsafe fixes in our codebase due to bad typing
|
||||
"@typescript-eslint/no-duplicate-type-constituents": "off", // provide fixes that breaks some assumptions, eg: type A = L, B = L, C = A | B (removes B)
|
||||
// Disallows require() in TypeScript (use import instead)
|
||||
// Disabled - using TypeScript instead
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
@@ -312,14 +313,6 @@
|
||||
"name": "react-redux",
|
||||
"message": "[State mgmt] react-redux is deprecated. Migrate to Zustand, nuqs, or react-query."
|
||||
},
|
||||
{
|
||||
"name": "xstate",
|
||||
"message": "[State mgmt] xstate is deprecated. Migrate to Zustand or react-query."
|
||||
},
|
||||
{
|
||||
"name": "@xstate/react",
|
||||
"message": "[State mgmt] @xstate/react is deprecated. Migrate to Zustand or react-query."
|
||||
},
|
||||
{
|
||||
"name": "react",
|
||||
"importNames": [
|
||||
@@ -338,82 +331,6 @@
|
||||
"react/no-array-index-key": "warn",
|
||||
// TODO: Changed to warn during oxlint migration, should be changed to error,
|
||||
|
||||
"unicorn/error-message": "warn",
|
||||
"unicorn/escape-case": "warn",
|
||||
"unicorn/new-for-builtins": "warn",
|
||||
"unicorn/no-abusive-eslint-disable": "warn",
|
||||
"unicorn/no-console-spaces": "warn",
|
||||
"unicorn/no-instanceof-array": "warn",
|
||||
"unicorn/no-invalid-remove-event-listener": "warn",
|
||||
"unicorn/no-new-array": "warn",
|
||||
"unicorn/no-new-buffer": "warn",
|
||||
"unicorn/no-thenable": "warn",
|
||||
"unicorn/no-unreadable-array-destructuring": "warn",
|
||||
"unicorn/no-useless-fallback-in-spread": "warn",
|
||||
"unicorn/no-useless-length-check": "warn",
|
||||
"unicorn/no-useless-promise-resolve-reject": "warn",
|
||||
"unicorn/no-useless-spread": "warn",
|
||||
"unicorn/no-zero-fractions": "warn",
|
||||
"unicorn/number-literal-case": "warn",
|
||||
"unicorn/prefer-array-find": "warn",
|
||||
"unicorn/prefer-array-flat": "warn",
|
||||
"unicorn/prefer-array-flat-map": "warn",
|
||||
"unicorn/prefer-array-index-of": "warn",
|
||||
"unicorn/prefer-array-some": "warn",
|
||||
"unicorn/prefer-at": "warn",
|
||||
"unicorn/prefer-code-point": "warn",
|
||||
"unicorn/prefer-date-now": "warn",
|
||||
"unicorn/prefer-default-parameters": "warn",
|
||||
"unicorn/prefer-includes": "warn",
|
||||
"unicorn/prefer-modern-math-apis": "warn",
|
||||
"unicorn/prefer-native-coercion-functions": "warn",
|
||||
"unicorn/prefer-node-protocol": "off",
|
||||
"unicorn/prefer-number-properties": "warn",
|
||||
"unicorn/prefer-optional-catch-binding": "warn",
|
||||
"unicorn/prefer-regexp-test": "warn",
|
||||
"unicorn/prefer-set-has": "warn",
|
||||
"unicorn/prefer-string-replace-all": "warn",
|
||||
"unicorn/prefer-string-slice": "warn",
|
||||
"unicorn/prefer-string-starts-ends-with": "warn",
|
||||
"unicorn/prefer-string-trim-start-end": "warn",
|
||||
"unicorn/prefer-type-error": "warn",
|
||||
"unicorn/require-array-join-separator": "warn",
|
||||
"unicorn/require-number-to-fixed-digits-argument": "warn",
|
||||
"unicorn/throw-new-error": "warn",
|
||||
"unicorn/consistent-function-scoping": "warn",
|
||||
"unicorn/explicit-length-check": "warn",
|
||||
"unicorn/filename-case": [
|
||||
"warn",
|
||||
{
|
||||
"case": "kebabCase"
|
||||
}
|
||||
],
|
||||
"unicorn/no-array-for-each": "warn",
|
||||
"unicorn/no-lonely-if": "warn",
|
||||
"unicorn/no-negated-condition": "warn",
|
||||
"unicorn/no-null": "warn",
|
||||
"unicorn/no-object-as-default-parameter": "warn",
|
||||
"unicorn/no-static-only-class": "warn",
|
||||
"unicorn/no-this-assignment": "warn",
|
||||
"unicorn/no-unreadable-iife": "warn",
|
||||
"unicorn/no-useless-switch-case": "warn",
|
||||
"unicorn/no-useless-undefined": "warn",
|
||||
"unicorn/prefer-add-event-listener": "warn",
|
||||
"unicorn/prefer-dom-node-append": "warn",
|
||||
"unicorn/prefer-dom-node-dataset": "warn",
|
||||
"unicorn/prefer-dom-node-remove": "warn",
|
||||
"unicorn/prefer-dom-node-text-content": "warn",
|
||||
"unicorn/prefer-keyboard-event-key": "warn",
|
||||
"unicorn/prefer-math-trunc": "warn",
|
||||
"unicorn/prefer-modern-dom-apis": "warn",
|
||||
"unicorn/prefer-negative-index": "warn",
|
||||
"unicorn/prefer-prototype-methods": "warn",
|
||||
"unicorn/prefer-query-selector": "warn",
|
||||
"unicorn/prefer-reflect-apply": "warn",
|
||||
"unicorn/prefer-set-size": "warn",
|
||||
"unicorn/prefer-spread": "warn",
|
||||
"unicorn/prefer-ternary": "warn",
|
||||
"unicorn/require-post-message-target-origin": "warn",
|
||||
"oxc/bad-array-method-on-arguments": "error",
|
||||
"oxc/bad-bitwise-operator": "error",
|
||||
"oxc/bad-comparison-sequence": "error",
|
||||
@@ -538,8 +455,8 @@
|
||||
"sonarjs/no-nested-template-literals": "error",
|
||||
// Avoids nested template literals
|
||||
"sonarjs/no-redundant-boolean": "warn", // TODO: Change to error after migration
|
||||
// Removes redundant boolean literals
|
||||
"sonarjs/no-redundant-jump": "error",
|
||||
// Removes redundant boolean literals - turned off because it clashes with unicorn rules
|
||||
"sonarjs/no-redundant-jump": "off",
|
||||
// Removes unnecessary returns/continues
|
||||
"sonarjs/no-same-line-conditional": "error",
|
||||
// Prevents same-line conditionals
|
||||
|
||||
5
frontend/knip.json
Normal file
5
frontend/knip.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"project": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"ignore": ["src/api/generated/**/*.ts"]
|
||||
}
|
||||
@@ -63,7 +63,6 @@
|
||||
"@visx/shape": "3.5.0",
|
||||
"@visx/tooltip": "3.3.0",
|
||||
"@vitejs/plugin-react": "5.1.4",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.11.0",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
@@ -146,7 +145,6 @@
|
||||
"vite": "npm:rolldown-vite@7.3.1",
|
||||
"vite-plugin-html": "3.2.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"xstate": "^4.31.0",
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.11"
|
||||
},
|
||||
@@ -214,9 +212,9 @@
|
||||
"msw": "1.3.2",
|
||||
"npm-run-all": "latest",
|
||||
"orval": "7.18.0",
|
||||
"oxfmt": "0.41.0",
|
||||
"oxlint": "1.59.0",
|
||||
"oxlint-tsgolint": "0.20.0",
|
||||
"oxfmt": "0.47.0",
|
||||
"oxlint": "1.62.0",
|
||||
"oxlint-tsgolint": "0.22.1",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-scss": "4.0.9",
|
||||
@@ -240,9 +238,12 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"oxfmt --check",
|
||||
"oxlint --fix",
|
||||
"oxfmt --write",
|
||||
"sh scripts/typecheck-staged.sh"
|
||||
],
|
||||
"*.(scss|css)": [
|
||||
"stylelint"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"roles": "Roles",
|
||||
"role_details": "Role Details",
|
||||
"members": "Members",
|
||||
"service_accounts": "Service Accounts"
|
||||
"service_accounts": "Service Accounts",
|
||||
"mcp_server": "MCP Server"
|
||||
}
|
||||
|
||||
@@ -53,5 +53,6 @@
|
||||
"METER": "SigNoz | Meter",
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
|
||||
"MCP_SERVER": "SigNoz | MCP Server"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"roles": "Roles",
|
||||
"role_details": "Role Details",
|
||||
"members": "Members",
|
||||
"service_accounts": "Service Accounts"
|
||||
"service_accounts": "Service Accounts",
|
||||
"mcp_server": "MCP Server"
|
||||
}
|
||||
|
||||
@@ -76,5 +76,6 @@
|
||||
"METER": "SigNoz | Meter",
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts"
|
||||
"SERVICE_ACCOUNTS_SETTINGS": "SigNoz | Service Accounts",
|
||||
"MCP_SERVER": "SigNoz | MCP Server"
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('getFieldKeys API', () => {
|
||||
const result = await getFieldKeys('traces');
|
||||
|
||||
// Verify the returned structure matches SuccessResponseV2 format
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
httpStatusCode: 200,
|
||||
data: mockSuccessResponse.data.data,
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ describe('getFieldValues API', () => {
|
||||
const result = await getFieldValues('traces', 'service.name');
|
||||
|
||||
// Verify the returned structure matches SuccessResponseV2 format
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
httpStatusCode: 200,
|
||||
data: expect.objectContaining({
|
||||
values: expect.any(Object),
|
||||
|
||||
@@ -13,7 +13,13 @@ import type {
|
||||
|
||||
import type {
|
||||
InframonitoringtypesPostableHostsDTO,
|
||||
InframonitoringtypesPostableNamespacesDTO,
|
||||
InframonitoringtypesPostableNodesDTO,
|
||||
InframonitoringtypesPostablePodsDTO,
|
||||
ListHosts200,
|
||||
ListNamespaces200,
|
||||
ListNodes200,
|
||||
ListPods200,
|
||||
RenderErrorResponseDTO,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
@@ -21,7 +27,7 @@ import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary.
|
||||
* Returns a paginated list of hosts with key infrastructure metrics: CPU usage (%), memory usage (%), I/O wait (%), disk usage (%), and 15-minute load average. Each host includes its current status (active/inactive based on metrics reported in the last 10 minutes) and metadata attributes (e.g., os.type). Supports filtering via a filter expression, filtering by host status, custom groupBy to aggregate hosts by any attribute, ordering by any of the five metrics, and pagination via offset/limit. The response type is 'list' for the default host.name grouping or 'grouped_list' for custom groupBy keys. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (cpu, memory, wait, load15, diskUsage) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Hosts for Infra Monitoring
|
||||
*/
|
||||
export const listHosts = (
|
||||
@@ -104,3 +110,255 @@ export const useListHosts = <
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes namespaces with key aggregated pod metrics: CPU usage and memory working set (summed across pods in the group), plus per-group pod counts bucketed by each pod's latest k8s.pod.phase value in the window (pendingPodCount, runningPodCount, succeededPodCount, failedPodCount, unknownPodCount). Each namespace includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.namespace.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / memory, and pagination via offset/limit. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Namespaces for Infra Monitoring
|
||||
*/
|
||||
export const listNamespaces = (
|
||||
inframonitoringtypesPostableNamespacesDTO: BodyType<InframonitoringtypesPostableNamespacesDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListNamespaces200>({
|
||||
url: `/api/v2/infra_monitoring/namespaces`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostableNamespacesDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListNamespacesMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNamespaces>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNamespacesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNamespaces>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNamespacesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listNamespaces'];
|
||||
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 listNamespaces>>,
|
||||
{ data: BodyType<InframonitoringtypesPostableNamespacesDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listNamespaces(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListNamespacesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listNamespaces>>
|
||||
>;
|
||||
export type ListNamespacesMutationBody =
|
||||
BodyType<InframonitoringtypesPostableNamespacesDTO>;
|
||||
export type ListNamespacesMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Namespaces for Infra Monitoring
|
||||
*/
|
||||
export const useListNamespaces = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNamespaces>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNamespacesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listNamespaces>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNamespacesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getListNamespacesMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes nodes with key metrics: CPU usage, CPU allocatable, memory working set, memory allocatable, and per-group readyNodesCount / notReadyNodesCount derived from each node's latest k8s.node.condition_ready value in the window. Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is 'list' for the default k8s.node.name grouping (each row is one node with its current condition string: ready / not_ready / '') or 'grouped_list' for custom groupBy keys (each row aggregates nodes in the group with readyNodesCount and notReadyNodesCount; condition stays empty). Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Nodes for Infra Monitoring
|
||||
*/
|
||||
export const listNodes = (
|
||||
inframonitoringtypesPostableNodesDTO: BodyType<InframonitoringtypesPostableNodesDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListNodes200>({
|
||||
url: `/api/v2/infra_monitoring/nodes`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostableNodesDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListNodesMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listNodes'];
|
||||
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 listNodes>>,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listNodes(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListNodesMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listNodes>>
|
||||
>;
|
||||
export type ListNodesMutationBody =
|
||||
BodyType<InframonitoringtypesPostableNodesDTO>;
|
||||
export type ListNodesMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Nodes for Infra Monitoring
|
||||
*/
|
||||
export const useListNodes = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listNodes>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostableNodesDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getListNodesMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts: pendingPodCount, runningPodCount, succeededPodCount, failedPodCount, unknownPodCount derived from each pod's latest phase in the window). Also reports missing required metrics and whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
|
||||
* @summary List Pods for Infra Monitoring
|
||||
*/
|
||||
export const listPods = (
|
||||
inframonitoringtypesPostablePodsDTO: BodyType<InframonitoringtypesPostablePodsDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListPods200>({
|
||||
url: `/api/v2/infra_monitoring/pods`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: inframonitoringtypesPostablePodsDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListPodsMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listPods>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listPods>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['listPods'];
|
||||
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 listPods>>,
|
||||
{ data: BodyType<InframonitoringtypesPostablePodsDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return listPods(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ListPodsMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listPods>>
|
||||
>;
|
||||
export type ListPodsMutationBody =
|
||||
BodyType<InframonitoringtypesPostablePodsDTO>;
|
||||
export type ListPodsMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List Pods for Infra Monitoring
|
||||
*/
|
||||
export const useListPods = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof listPods>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof listPods>>,
|
||||
TError,
|
||||
{ data: BodyType<InframonitoringtypesPostablePodsDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getListPodsMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
|
||||
@@ -3243,6 +3243,248 @@ export interface InframonitoringtypesHostsDTO {
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNamespaceRecordDTOMeta = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesNamespaceRecordDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
failedPodCount: number;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesNamespaceRecordDTOMeta;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
namespaceCPU: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
namespaceMemory: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
namespaceName: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
pendingPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
runningPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
succeededPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknownPodCount: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNamespacesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
records: InframonitoringtypesNamespaceRecordDTO[] | null;
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesNodeConditionDTO {
|
||||
ready = 'ready',
|
||||
not_ready = 'not_ready',
|
||||
'' = '',
|
||||
}
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNodeRecordDTOMeta = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesNodeRecordDTO {
|
||||
condition: InframonitoringtypesNodeConditionDTO;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesNodeRecordDTOMeta;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeCPU: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeCPUAllocatable: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeMemory: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
nodeMemoryAllocatable: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
nodeName: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
notReadyNodesCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
readyNodesCount: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesNodesDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
records: InframonitoringtypesNodeRecordDTO[] | null;
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export enum InframonitoringtypesPodPhaseDTO {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
succeeded = 'succeeded',
|
||||
failed = 'failed',
|
||||
unknown = 'unknown',
|
||||
'' = '',
|
||||
}
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesPodRecordDTOMeta = {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
failedPodCount: number;
|
||||
/**
|
||||
* @type object
|
||||
* @nullable true
|
||||
*/
|
||||
meta: InframonitoringtypesPodRecordDTOMeta;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
pendingPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
podAge: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podCPU: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podCPULimit: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podCPURequest: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podMemory: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podMemoryLimit: number;
|
||||
/**
|
||||
* @type number
|
||||
* @format double
|
||||
*/
|
||||
podMemoryRequest: number;
|
||||
podPhase: InframonitoringtypesPodPhaseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
podUID: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
runningPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
succeededPodCount: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
unknownPodCount: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPodsDTO {
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
records: InframonitoringtypesPodRecordDTO[] | null;
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
total: number;
|
||||
type: InframonitoringtypesResponseTypeDTO;
|
||||
warning?: Querybuildertypesv5QueryWarnDataDTO;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableHostsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3271,6 +3513,90 @@ export interface InframonitoringtypesPostableHostsDTO {
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableNamespacesDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
orderBy?: Querybuildertypesv5OrderByDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostableNodesDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
orderBy?: Querybuildertypesv5OrderByDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesPostablePodsDTO {
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
end: number;
|
||||
filter?: Querybuildertypesv5FilterDTO;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
groupBy?: Querybuildertypesv5GroupByKeyDTO[] | null;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
offset?: number;
|
||||
orderBy?: Querybuildertypesv5OrderByDTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
start: number;
|
||||
}
|
||||
|
||||
export interface InframonitoringtypesRequiredMetricsCheckDTO {
|
||||
/**
|
||||
* @type array
|
||||
@@ -3701,10 +4027,7 @@ export interface PromotetypesPromotePathDTO {
|
||||
}
|
||||
|
||||
export interface PromotetypesWrappedIndexDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
column_type?: string;
|
||||
fieldDataType?: TelemetrytypesFieldDataTypeDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
@@ -5475,6 +5798,187 @@ export interface Sigv4SigV4ConfigDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export enum SpantypesFieldContextDTO {
|
||||
attribute = 'attribute',
|
||||
resource = 'resource',
|
||||
}
|
||||
export interface SpantypesGettableSpanMapperGroupsDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
items: SpantypesSpanMapperGroupDTO[];
|
||||
}
|
||||
|
||||
export interface SpantypesPostableSpanMapperDTO {
|
||||
config: SpantypesSpanMapperConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled?: boolean;
|
||||
field_context: SpantypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SpantypesPostableSpanMapperGroupDTO {
|
||||
category: SpantypesSpanMapperGroupCategoryDTO;
|
||||
condition: SpantypesSpanMapperGroupConditionDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SpantypesSpanMapperDTO {
|
||||
config: SpantypesSpanMapperConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
field_context: SpantypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
group_id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface SpantypesSpanMapperConfigDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
sources: SpantypesSpanMapperSourceDTO[] | null;
|
||||
}
|
||||
|
||||
export interface SpantypesSpanMapperGroupDTO {
|
||||
category: SpantypesSpanMapperGroupCategoryDTO;
|
||||
condition: SpantypesSpanMapperGroupConditionDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
orgId: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface SpantypesSpanMapperGroupCategoryDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface SpantypesSpanMapperGroupConditionDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
attributes: string[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
resource: string[] | null;
|
||||
}
|
||||
|
||||
export enum SpantypesSpanMapperOperationDTO {
|
||||
move = 'move',
|
||||
copy = 'copy',
|
||||
}
|
||||
export interface SpantypesSpanMapperSourceDTO {
|
||||
context: SpantypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
key: string;
|
||||
operation: SpantypesSpanMapperOperationDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface SpantypesUpdatableSpanMapperDTO {
|
||||
config?: SpantypesSpanMapperConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
field_context?: SpantypesFieldContextDTO;
|
||||
}
|
||||
|
||||
export interface SpantypesUpdatableSpanMapperGroupDTO {
|
||||
condition?: SpantypesSpanMapperGroupConditionDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
* @nullable true
|
||||
*/
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export enum TelemetrytypesFieldContextDTO {
|
||||
metric = 'metric',
|
||||
log = 'log',
|
||||
@@ -6925,6 +7429,71 @@ export type GetMyServiceAccount200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListSpanMapperGroupsParams = {
|
||||
/**
|
||||
* @description undefined
|
||||
*/
|
||||
category?: SpantypesSpanMapperGroupCategoryDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
* @description undefined
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
};
|
||||
|
||||
export type ListSpanMapperGroups200 = {
|
||||
data: SpantypesGettableSpanMapperGroupsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateSpanMapperGroup201 = {
|
||||
data: SpantypesSpanMapperGroupDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteSpanMapperGroupPathParameters = {
|
||||
groupId: string;
|
||||
};
|
||||
export type UpdateSpanMapperGroupPathParameters = {
|
||||
groupId: string;
|
||||
};
|
||||
export type ListSpanMappersPathParameters = {
|
||||
groupId: string;
|
||||
};
|
||||
export type ListSpanMappers200 = {
|
||||
data: SpantypesGettableSpanMapperGroupsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateSpanMapperPathParameters = {
|
||||
groupId: string;
|
||||
};
|
||||
export type CreateSpanMapper201 = {
|
||||
data: SpantypesSpanMapperDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteSpanMapperPathParameters = {
|
||||
groupId: string;
|
||||
mapperId: string;
|
||||
};
|
||||
export type UpdateSpanMapperPathParameters = {
|
||||
groupId: string;
|
||||
mapperId: string;
|
||||
};
|
||||
export type ListUsersDeprecated200 = {
|
||||
/**
|
||||
* @type array
|
||||
@@ -7107,6 +7676,30 @@ export type ListHosts200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListNamespaces200 = {
|
||||
data: InframonitoringtypesNamespacesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListNodes200 = {
|
||||
data: InframonitoringtypesNodesDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListPods200 = {
|
||||
data: InframonitoringtypesPodsDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type Livez200 = {
|
||||
data: FactoryResponseDTO;
|
||||
/**
|
||||
|
||||
787
frontend/src/api/generated/services/spanmapper/index.ts
Normal file
787
frontend/src/api/generated/services/spanmapper/index.ts
Normal file
@@ -0,0 +1,787 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
|
||||
import type {
|
||||
CreateSpanMapper201,
|
||||
CreateSpanMapperGroup201,
|
||||
CreateSpanMapperPathParameters,
|
||||
DeleteSpanMapperGroupPathParameters,
|
||||
DeleteSpanMapperPathParameters,
|
||||
ListSpanMapperGroups200,
|
||||
ListSpanMapperGroupsParams,
|
||||
ListSpanMappers200,
|
||||
ListSpanMappersPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
SpantypesPostableSpanMapperDTO,
|
||||
SpantypesPostableSpanMapperGroupDTO,
|
||||
SpantypesUpdatableSpanMapperDTO,
|
||||
SpantypesUpdatableSpanMapperGroupDTO,
|
||||
UpdateSpanMapperGroupPathParameters,
|
||||
UpdateSpanMapperPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
|
||||
|
||||
/**
|
||||
* Returns all span attribute mapping groups for the authenticated org.
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
export const listSpanMapperGroups = (
|
||||
params?: ListSpanMapperGroupsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListSpanMapperGroups200>({
|
||||
url: `/api/v1/span_mapper_groups`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListSpanMapperGroupsQueryKey = (
|
||||
params?: ListSpanMapperGroupsParams,
|
||||
) => {
|
||||
return [`/api/v1/span_mapper_groups`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getListSpanMapperGroupsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListSpanMapperGroupsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMapperGroups>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListSpanMapperGroupsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listSpanMapperGroups>>
|
||||
> = ({ signal }) => listSpanMapperGroups(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMapperGroups>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListSpanMapperGroupsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listSpanMapperGroups>>
|
||||
>;
|
||||
export type ListSpanMapperGroupsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
|
||||
export function useListSpanMapperGroups<
|
||||
TData = Awaited<ReturnType<typeof listSpanMapperGroups>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
params?: ListSpanMapperGroupsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMapperGroups>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListSpanMapperGroupsQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
export const invalidateListSpanMapperGroups = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListSpanMapperGroupsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListSpanMapperGroupsQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new span attribute mapping group for the org.
|
||||
* @summary Create a span attribute mapping group
|
||||
*/
|
||||
export const createSpanMapperGroup = (
|
||||
spantypesPostableSpanMapperGroupDTO: BodyType<SpantypesPostableSpanMapperGroupDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateSpanMapperGroup201>({
|
||||
url: `/api/v1/span_mapper_groups`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesPostableSpanMapperGroupDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateSpanMapperGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapperGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapperGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createSpanMapperGroup'];
|
||||
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 createSpanMapperGroup>>,
|
||||
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createSpanMapperGroup(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateSpanMapperGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createSpanMapperGroup>>
|
||||
>;
|
||||
export type CreateSpanMapperGroupMutationBody =
|
||||
BodyType<SpantypesPostableSpanMapperGroupDTO>;
|
||||
export type CreateSpanMapperGroupMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create a span attribute mapping group
|
||||
*/
|
||||
export const useCreateSpanMapperGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapperGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createSpanMapperGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpantypesPostableSpanMapperGroupDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateSpanMapperGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Hard-deletes a mapping group and cascades to all its mappers.
|
||||
* @summary Delete a span attribute mapping group
|
||||
*/
|
||||
export const deleteSpanMapperGroup = ({
|
||||
groupId,
|
||||
}: DeleteSpanMapperGroupPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteSpanMapperGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperGroupPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperGroupPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteSpanMapperGroup'];
|
||||
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 deleteSpanMapperGroup>>,
|
||||
{ pathParams: DeleteSpanMapperGroupPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteSpanMapperGroup(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteSpanMapperGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteSpanMapperGroup>>
|
||||
>;
|
||||
|
||||
export type DeleteSpanMapperGroupMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a span attribute mapping group
|
||||
*/
|
||||
export const useDeleteSpanMapperGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperGroupPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteSpanMapperGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperGroupPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteSpanMapperGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Partially updates an existing mapping group's name, condition, or enabled state.
|
||||
* @summary Update a span attribute mapping group
|
||||
*/
|
||||
export const updateSpanMapperGroup = (
|
||||
{ groupId }: UpdateSpanMapperGroupPathParameters,
|
||||
spantypesUpdatableSpanMapperGroupDTO: BodyType<SpantypesUpdatableSpanMapperGroupDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesUpdatableSpanMapperGroupDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateSpanMapperGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperGroupPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperGroupPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateSpanMapperGroup'];
|
||||
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 updateSpanMapperGroup>>,
|
||||
{
|
||||
pathParams: UpdateSpanMapperGroupPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateSpanMapperGroup(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateSpanMapperGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateSpanMapperGroup>>
|
||||
>;
|
||||
export type UpdateSpanMapperGroupMutationBody =
|
||||
BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
export type UpdateSpanMapperGroupMutationError =
|
||||
ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update a span attribute mapping group
|
||||
*/
|
||||
export const useUpdateSpanMapperGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperGroupPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateSpanMapperGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperGroupPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateSpanMapperGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns all mappers belonging to a mapping group.
|
||||
* @summary List span mappers for a group
|
||||
*/
|
||||
export const listSpanMappers = (
|
||||
{ groupId }: ListSpanMappersPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListSpanMappers200>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListSpanMappersQueryKey = ({
|
||||
groupId,
|
||||
}: ListSpanMappersPathParameters) => {
|
||||
return [`/api/v1/span_mapper_groups/${groupId}/span_mappers`] as const;
|
||||
};
|
||||
|
||||
export const getListSpanMappersQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listSpanMappers>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ groupId }: ListSpanMappersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMappers>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListSpanMappersQueryKey({ groupId });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listSpanMappers>>> = ({
|
||||
signal,
|
||||
}) => listSpanMappers({ groupId }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!groupId,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMappers>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListSpanMappersQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listSpanMappers>>
|
||||
>;
|
||||
export type ListSpanMappersQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List span mappers for a group
|
||||
*/
|
||||
|
||||
export function useListSpanMappers<
|
||||
TData = Awaited<ReturnType<typeof listSpanMappers>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
>(
|
||||
{ groupId }: ListSpanMappersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanMappers>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListSpanMappersQueryOptions({ groupId }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List span mappers for a group
|
||||
*/
|
||||
export const invalidateListSpanMappers = async (
|
||||
queryClient: QueryClient,
|
||||
{ groupId }: ListSpanMappersPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListSpanMappersQueryKey({ groupId }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new mapper to the specified mapping group.
|
||||
* @summary Create a span mapper
|
||||
*/
|
||||
export const createSpanMapper = (
|
||||
{ groupId }: CreateSpanMapperPathParameters,
|
||||
spantypesPostableSpanMapperDTO: BodyType<SpantypesPostableSpanMapperDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateSpanMapper201>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesPostableSpanMapperDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateSpanMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createSpanMapper'];
|
||||
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 createSpanMapper>>,
|
||||
{
|
||||
pathParams: CreateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return createSpanMapper(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateSpanMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createSpanMapper>>
|
||||
>;
|
||||
export type CreateSpanMapperMutationBody =
|
||||
BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
export type CreateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create a span mapper
|
||||
*/
|
||||
export const useCreateSpanMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesPostableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateSpanMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Hard-deletes a mapper from a mapping group.
|
||||
* @summary Delete a span mapper
|
||||
*/
|
||||
export const deleteSpanMapper = ({
|
||||
groupId,
|
||||
mapperId,
|
||||
}: DeleteSpanMapperPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteSpanMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteSpanMapper'];
|
||||
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 deleteSpanMapper>>,
|
||||
{ pathParams: DeleteSpanMapperPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteSpanMapper(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteSpanMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteSpanMapper>>
|
||||
>;
|
||||
|
||||
export type DeleteSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a span mapper
|
||||
*/
|
||||
export const useDeleteSpanMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteSpanMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteSpanMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteSpanMapperPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteSpanMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Partially updates an existing mapper's field context, config, or enabled state.
|
||||
* @summary Update a span mapper
|
||||
*/
|
||||
export const updateSpanMapper = (
|
||||
{ groupId, mapperId }: UpdateSpanMapperPathParameters,
|
||||
spantypesUpdatableSpanMapperDTO: BodyType<SpantypesUpdatableSpanMapperDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_mapper_groups/${groupId}/span_mappers/${mapperId}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spantypesUpdatableSpanMapperDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateSpanMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateSpanMapper'];
|
||||
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 updateSpanMapper>>,
|
||||
{
|
||||
pathParams: UpdateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateSpanMapper(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateSpanMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateSpanMapper>>
|
||||
>;
|
||||
export type UpdateSpanMapperMutationBody =
|
||||
BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
export type UpdateSpanMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update a span mapper
|
||||
*/
|
||||
export const useUpdateSpanMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateSpanMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateSpanMapperPathParameters;
|
||||
data: BodyType<SpantypesUpdatableSpanMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateSpanMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
GlobalConfigData,
|
||||
GlobalConfigDataProps,
|
||||
} from 'types/api/globalConfig/types';
|
||||
|
||||
const getGlobalConfig = async (): Promise<
|
||||
SuccessResponseV2<GlobalConfigData>
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get<GlobalConfigDataProps>(`/global/config`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getGlobalConfig;
|
||||
@@ -76,10 +76,10 @@ describe('interceptorRejected', () => {
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
|
||||
expect(JSON.parse(retryCallConfig.data)).toEqual(arrayPayload);
|
||||
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(arrayPayload);
|
||||
});
|
||||
|
||||
it('should preserve object payload structure when retrying a 401 request', async () => {
|
||||
@@ -112,9 +112,9 @@ describe('interceptorRejected', () => {
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data)).toEqual(objectPayload);
|
||||
expect(JSON.parse(retryCallConfig.data)).toStrictEqual(objectPayload);
|
||||
});
|
||||
|
||||
it('should handle undefined data gracefully when retrying', async () => {
|
||||
@@ -145,7 +145,7 @@ describe('interceptorRejected', () => {
|
||||
}
|
||||
|
||||
const mockAxiosFn = axios as unknown as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
expect(mockAxiosFn.mock.calls).toHaveLength(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(retryCallConfig.data).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
const q = result.payload.data.result[0];
|
||||
expect(q.queryName).toBe('A');
|
||||
expect(q.legend).toBe('{{service.name}}');
|
||||
expect(q.series?.[0]).toEqual(
|
||||
expect(q.series?.[0]).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
labels: { 'service.name': 'adservice' },
|
||||
values: [
|
||||
@@ -186,7 +186,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
|
||||
expect(result.payload.data.resultType).toBe('scalar');
|
||||
const [tableEntry] = result.payload.data.result;
|
||||
expect(tableEntry.table?.columns).toEqual([
|
||||
expect(tableEntry.table?.columns).toStrictEqual([
|
||||
{
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
@@ -202,7 +202,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
},
|
||||
{ name: 'F1', queryName: 'F1', isValueColumn: true, id: 'F1' },
|
||||
]);
|
||||
expect(tableEntry.table?.rows?.[0]).toEqual({
|
||||
expect(tableEntry.table?.rows?.[0]).toStrictEqual({
|
||||
data: {
|
||||
'service.name': 'adservice',
|
||||
'A.count()': 606,
|
||||
@@ -257,7 +257,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
|
||||
expect(result.payload.data.resultType).toBe('scalar');
|
||||
const [tableEntry] = result.payload.data.result;
|
||||
expect(tableEntry.table?.columns).toEqual([
|
||||
expect(tableEntry.table?.columns).toStrictEqual([
|
||||
{
|
||||
name: 'service.name',
|
||||
queryName: 'A',
|
||||
@@ -267,7 +267,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
// Single aggregation: name resolves to legend, id resolves to queryName
|
||||
{ name: '{{service.name}}', queryName: 'A', isValueColumn: true, id: 'A' },
|
||||
]);
|
||||
expect(tableEntry.table?.rows?.[0]).toEqual({
|
||||
expect(tableEntry.table?.rows?.[0]).toStrictEqual({
|
||||
data: {
|
||||
'service.name': 'adservice',
|
||||
A: 580,
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A', F1: 'Formula Legend' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -154,7 +154,10 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
);
|
||||
|
||||
// Legend map combines builder and formulas
|
||||
expect(result.legendMap).toEqual({ A: 'Legend A', F1: 'Formula Legend' });
|
||||
expect(result.legendMap).toStrictEqual({
|
||||
A: 'Legend A',
|
||||
F1: 'Formula Legend',
|
||||
});
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
|
||||
@@ -166,10 +169,10 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
expect(payload.formatOptions?.fillGaps).toBe(true);
|
||||
|
||||
// Variables mapped as { key: { value } }
|
||||
expect(payload.variables).toEqual({
|
||||
svc: { value: 'api' },
|
||||
count: { value: 5 },
|
||||
flag: { value: true },
|
||||
expect(payload.variables).toStrictEqual({
|
||||
svc: { value: 'api', type: undefined },
|
||||
count: { value: 5, type: undefined },
|
||||
flag: { value: true, type: undefined },
|
||||
});
|
||||
|
||||
// Queries include one builder_query and one builder_formula
|
||||
@@ -226,7 +229,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'LP' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -255,7 +258,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.legendMap).toEqual({ A: 'LP' });
|
||||
expect(result.legendMap).toStrictEqual({ A: 'LP' });
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.requestType).toBe('time_series');
|
||||
@@ -296,7 +299,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { Q: 'LC' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -324,7 +327,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.legendMap).toEqual({ Q: 'LC' });
|
||||
expect(result.legendMap).toStrictEqual({ Q: 'LC' });
|
||||
|
||||
const payload: QueryRangePayloadV5 = result.queryPayload;
|
||||
expect(payload.requestType).toBe('scalar');
|
||||
@@ -353,7 +356,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: {},
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -397,7 +400,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -471,7 +474,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: 'Legend A' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -585,7 +588,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect(result).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
legendMap: { A: '{{service.name}}' },
|
||||
queryPayload: expect.objectContaining({
|
||||
@@ -684,7 +687,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
const result = prepareQueryRangePayloadV5(props);
|
||||
|
||||
expect(result.legendMap).toEqual({ A: 'Legend A' });
|
||||
expect(result.legendMap).toStrictEqual({ A: 'Legend A' });
|
||||
expect(result.queryPayload.compositeQuery.queries).toHaveLength(1);
|
||||
|
||||
const builderQuery = result.queryPayload.compositeQuery.queries.find(
|
||||
@@ -694,7 +697,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
|
||||
expect(logSpec.name).toBe('A');
|
||||
expect(logSpec.signal).toBe('logs');
|
||||
expect(logSpec.filter).toEqual({
|
||||
expect(logSpec.filter).toStrictEqual({
|
||||
expression:
|
||||
"service.name = 'payment-service' AND http.status_code >= 400 AND message contains 'error'",
|
||||
});
|
||||
@@ -731,7 +734,9 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.filter).toEqual({ expression: 'http.status_code >= 500' });
|
||||
expect(logSpec.filter).toStrictEqual({
|
||||
expression: 'http.status_code >= 500',
|
||||
});
|
||||
});
|
||||
|
||||
it('derives expression from filters when filter is undefined', () => {
|
||||
@@ -775,7 +780,9 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.filter).toEqual({ expression: "service.name = 'checkout'" });
|
||||
expect(logSpec.filter).toStrictEqual({
|
||||
expression: "service.name = 'checkout'",
|
||||
});
|
||||
});
|
||||
|
||||
it('prefers filter.expression over filters when both are present', () => {
|
||||
@@ -819,7 +826,9 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.filter).toEqual({ expression: "service.name = 'frontend'" });
|
||||
expect(logSpec.filter).toStrictEqual({
|
||||
expression: "service.name = 'frontend'",
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty expression when neither filter nor filters provided', () => {
|
||||
@@ -853,7 +862,7 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.filter).toEqual({ expression: '' });
|
||||
expect(logSpec.filter).toStrictEqual({ expression: '' });
|
||||
});
|
||||
|
||||
it('returns empty expression when filters provided with empty items', () => {
|
||||
@@ -887,6 +896,6 @@ describe('prepareQueryRangePayloadV5', () => {
|
||||
(q) => q.type === 'builder_query',
|
||||
) as QueryEnvelope;
|
||||
const logSpec = builderQuery.spec as LogBuilderQuery;
|
||||
expect(logSpec.filter).toEqual({ expression: '' });
|
||||
expect(logSpec.filter).toStrictEqual({ expression: '' });
|
||||
});
|
||||
});
|
||||
|
||||
3
frontend/src/assets/Logos/baseten.svg
Normal file
3
frontend/src/assets/Logos/baseten.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" height="34" viewBox="0 0 131 34">
|
||||
<path fill="currentColor" d="M.36 8.6h16.7v5.6H6.04c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h11.02v5.6h-5.33c-.2 0-.35.16-.35.35v4.9c0 .2.16.35.35.35h4.98c.2 0 .35-.15.35-.35V25.4h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34v-5.6h5.34c.2 0 .35-.16.35-.35v-4.9c0-.2-.16-.35-.35-.35h-5.34V3.35c0-.2-.16-.35-.35-.35H.36c-.2 0-.36.16-.36.35v4.9c0 .2.16.35.36.35ZM44.41 14.7c-.5-.5-1.1-.9-1.76-1.18a5.62 5.62 0 0 0-4.6.17c-.73.37-1.32.91-1.75 1.62h-.17V8.59H34.1v16.83h2.04v-1.81h.17c.21.36.47.67.77.94.31.25.65.48 1.01.67.37.18.77.31 1.18.39a6.2 6.2 0 0 0 3.39-.24 5.36 5.36 0 0 0 3.02-3.1c.29-.75.44-1.62.44-2.6v-.47c0-.96-.16-1.83-.47-2.58-.3-.75-.7-1.4-1.23-1.9v-.01Zm-5.87.66a3.9 3.9 0 0 1 4.34.84c.36.35.64.8.83 1.3.2.5.3 1.07.3 1.7v.47c0 .64-.1 1.23-.3 1.74a3.75 3.75 0 0 1-2.06 2.15 4.27 4.27 0 0 1-3.12-.03 3.86 3.86 0 0 1-2.09-2.2c-.2-.52-.3-1.11-.3-1.75v-.29c0-.62.1-1.2.3-1.7v-.01c.21-.53.5-.99.84-1.36.36-.37.78-.66 1.26-.86ZM97.04 8.59H95v4.86h-2.94v1.86H95v8.17c0 .56.17 1.03.53 1.4.37.35.84.54 1.4.54h4.18v-1.87h-3.5c-.2 0-.33-.05-.43-.15-.1-.1-.14-.27-.14-.49v-7.6h4.65v-1.86h-4.65V8.59ZM114.61 15a5.48 5.48 0 0 0-1.8-1.33 5.6 5.6 0 0 0-2.57-.56 6.17 6.17 0 0 0-4.26 1.7 5.6 5.6 0 0 0-1.72 4.2v.57c0 .9.15 1.75.44 2.5a5.58 5.58 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.35 5.35 0 0 0 1.91-2.73l.03-.07-1.94-.52-.02.07c-.11.33-.27.64-.46.94-.17.27-.4.52-.7.74-.28.22-.63.39-1.04.51-.41.13-.9.19-1.46.19a3.8 3.8 0 0 1-2.84-1.05 4.07 4.07 0 0 1-1.1-2.7h9.68v-1.6c0-.54-.11-1.12-.34-1.75a5.04 5.04 0 0 0-1.03-1.74Zm-8.25 3.21a3.8 3.8 0 0 1 1.22-2.25 4.19 4.19 0 0 1 3.99-.7c.44.16.83.38 1.17.66.34.27.62.62.82 1.02.21.38.34.8.38 1.27h-7.58ZM129.09 14.42a4.47 4.47 0 0 0-3.37-1.3c-.93 0-1.73.2-2.4.59-.64.39-1.15.97-1.52 1.74h-.17v-2h-2.04v11.97h2.04v-6.23c0-1.26.32-2.28.95-3.02a3.31 3.31 0 0 1 2.65-1.14c.94 0 1.7.3 2.24.9.56.6.83 1.52.83 2.74v6.75h2.04v-7.13c0-1.71-.42-3.02-1.25-3.87ZM88.1 15a5.48 5.48 0 0 0-1.78-1.33 5.6 5.6 0 0 0-2.58-.56 6.17 6.17 0 0 0-4.27 1.7 5.59 5.59 0 0 0-1.71 4.2v.56c0 .92.14 1.76.44 2.51a5.6 5.6 0 0 0 5.5 3.67c1.55 0 2.8-.35 3.72-1.04a5.36 5.36 0 0 0 1.91-2.73l.03-.07-1.94-.52-.03.07c-.1.32-.26.64-.45.94-.17.27-.4.52-.7.74-.29.21-.64.39-1.05.51-.4.12-.9.19-1.45.19a3.8 3.8 0 0 1-2.85-1.05 4.07 4.07 0 0 1-1.09-2.7h9.68v-1.61c0-.53-.12-1.12-.34-1.74A5.03 5.03 0 0 0 88.1 15Zm-8.24 3.21a3.83 3.83 0 0 1 1.22-2.25 4.2 4.2 0 0 1 3.99-.7c.44.16.83.38 1.16.66.35.27.62.62.83 1.02.2.38.33.8.37 1.27h-7.57ZM73.65 19.42a6.11 6.11 0 0 0-3.23-1.02 6.63 6.63 0 0 1-2.68-.58c-.47-.3-.7-.7-.7-1.25 0-.27.08-.5.21-.7.14-.2.33-.38.56-.52a4.05 4.05 0 0 1 1.78-.42c.85 0 1.54.21 2.06.63.53.41.83 1 .91 1.73l.01.1 1.95-.47-.01-.07c-.07-.45-.22-.91-.45-1.36a3.46 3.46 0 0 0-.94-1.2 4.6 4.6 0 0 0-1.52-.84 6.05 6.05 0 0 0-2.1-.34c-.58 0-1.13.07-1.65.22-.52.14-1 .36-1.43.65-.41.3-.74.66-.99 1.1a2.9 2.9 0 0 0-.37 1.49v.14c0 1.06.38 1.87 1.14 2.42a6.2 6.2 0 0 0 3.24.97c1.18.08 2.04.26 2.56.54.5.28.75.72.75 1.36 0 .6-.25 1.06-.78 1.4-.52.32-1.22.48-2.1.48a3.68 3.68 0 0 1-2.46-.79 3.13 3.13 0 0 1-1.04-2.14v-.09l-1.93.46h-.02v.07a4.4 4.4 0 0 0 3.1 4c.7.24 1.52.36 2.46.36.7 0 1.35-.09 1.93-.27.6-.16 1.12-.4 1.53-.72A3.38 3.38 0 0 0 74.8 22v-.14c0-1.07-.39-1.9-1.14-2.44ZM60.25 23.4c-.1-.1-.14-.27-.14-.49v-9.46h-2.05v1.85h-.16a3.78 3.78 0 0 0-1.61-1.63 4.62 4.62 0 0 0-2.26-.56c-.77 0-1.5.14-2.19.41a5.27 5.27 0 0 0-3.02 3.12c-.29.75-.44 1.63-.44 2.6v.38c0 .99.15 1.87.44 2.63.3.75.7 1.4 1.2 1.93a5.48 5.48 0 0 0 4.05 1.57c.8 0 1.51-.2 2.2-.58.69-.38 1.24-.97 1.63-1.75h.16v.06c0 .56.18 1.03.53 1.4.37.35.85.54 1.41.54h1.36v-1.87h-.68c-.2 0-.34-.05-.43-.15Zm-4.46.13c-.46.2-.97.3-1.52.3a3.68 3.68 0 0 1-2.75-1.09 4.42 4.42 0 0 1-1.05-3.12v-.38c0-.62.1-1.2.29-1.71a3.65 3.65 0 0 1 5-2.17c.47.2.88.49 1.21.86.35.37.62.83.8 1.36.2.5.3 1.08.3 1.7v.3c0 .63-.1 1.23-.3 1.76-.18.5-.45.96-.78 1.33-.33.37-.73.66-1.2.86Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
13
frontend/src/assets/Logos/dbos.svg
Normal file
13
frontend/src/assets/Logos/dbos.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg width="109" height="24" viewBox="0 0 109 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_125_22125)">
|
||||
<path d="M0 -2.08616e-07V24H17.9352C22.9911 24 26.0999 21.0858 26.0999 17.04V6.96C26.0999 2.91432 22.9911 -2.08616e-07 17.9352 -2.08616e-07H0ZM6.76413 5.82864H19.1992V18.1714H6.76413V5.82864Z" fill="currentColor"/>
|
||||
<path d="M46.7659 18.6172H35.0824V14.16H46.7659V18.6172ZM46.595 5.38296V9.5658H35.0824V5.38296H46.595ZM50.2846 12.1373V11.5886C52.5734 10.5258 53.7008 8.8458 53.7008 6.13728C53.7008 2.64012 50.9337 0.000116183 45.5361 0.000116183H28.3184V24H45.7752C51.1728 24 53.9399 21.8401 53.9399 18.0685C53.9399 15.0172 52.6418 13.2001 50.2846 12.1373Z" fill="currentColor"/>
|
||||
<path d="M62.397 18.1714H74.8319V5.82864H62.397V18.1714ZM63.6609 24C58.6049 24 55.4961 21.0858 55.4961 17.04V6.96012C55.4961 2.91432 58.6049 0.000116183 63.6609 0.000116183H73.568C78.6238 0.000116183 81.7326 2.91432 81.7326 6.96012V17.04C81.7326 21.0858 78.6238 24 73.568 24H63.6609Z" fill="currentColor"/>
|
||||
<path d="M101.66 15.12L90.8995 14.3658C85.5361 13.9886 83.418 11.1772 83.418 7.47432V6.96012C83.418 2.91432 86.5266 0.000116183 91.5827 0.000116183H100.157C105.214 0.000116183 108.323 2.91432 108.323 6.96012V7.98864H101.968V5.14284H90.2504V8.43432L100.601 9.18864C105.999 9.56568 108.493 12.8572 108.493 16.5257V17.04C108.493 20.7428 105.384 24 100.328 24H91.5827C86.5266 24 83.418 20.7428 83.418 17.04V16.0115H89.7722V18.8572H101.66V15.12Z" fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_125_22125">
|
||||
<rect width="108.494" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
frontend/src/assets/Logos/opencode.svg
Normal file
1
frontend/src/assets/Logos/opencode.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width='234' height='42' viewBox='0 0 234 42' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M18 30H6V18H18V30Z' fill='#CFCECD'/><path d='M18 12H6V30H18V12ZM24 36H0V6H24V36Z' fill='#656363'/><path d='M48 30H36V18H48V30Z' fill='#CFCECD'/><path d='M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z' fill='#656363'/><path d='M84 24V30H66V24H84Z' fill='#CFCECD'/><path d='M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z' fill='#656363'/><path d='M108 36H96V18H108V36Z' fill='#CFCECD'/><path d='M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z' fill='#656363'/><path d='M144 30H126V18H144V30Z' fill='#CFCECD'/><path d='M144 12H126V30H144V36H120V6H144V12Z' fill='#211E1E'/><path d='M168 30H156V18H168V30Z' fill='#CFCECD'/><path d='M168 12H156V30H168V12ZM174 36H150V6H174V36Z' fill='#211E1E'/><path d='M198 30H186V18H198V30Z' fill='#CFCECD'/><path d='M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z' fill='#211E1E'/><path d='M234 24V30H216V24H234Z' fill='#CFCECD'/><path d='M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z' fill='#211E1E'/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -74,7 +74,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: radial-gradient(circle, #fff 10%, transparent 0);
|
||||
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
|
||||
background-size: 12px 12px;
|
||||
opacity: 1;
|
||||
|
||||
|
||||
@@ -99,36 +99,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.auth-error-container {
|
||||
.error-content {
|
||||
&__error-code {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__message-badge-label-text {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
&__message-item {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&::before {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll-hint-text {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes horizontal-shaking {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
|
||||
@@ -87,23 +87,3 @@
|
||||
background: var(--l3-background);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.auth-footer-content {
|
||||
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.auth-footer-icon {
|
||||
filter: brightness(0) saturate(100%) invert(25%) sepia(8%) saturate(518%)
|
||||
hue-rotate(192deg) brightness(80%) contrast(95%);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.auth-footer-text {
|
||||
color: var(--text-neutral-light-200);
|
||||
}
|
||||
|
||||
.auth-footer-link-icon {
|
||||
color: var(--text-neutral-light-100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,28 +143,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.bg-dot-pattern {
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
var(--l3-background) 1px,
|
||||
transparent 1px
|
||||
);
|
||||
background-size: 12px 12px;
|
||||
}
|
||||
|
||||
.auth-page-gradient {
|
||||
background: radial-gradient(
|
||||
ellipse at center top,
|
||||
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
opacity: 0.8;
|
||||
filter: blur(200px);
|
||||
|
||||
@media (min-width: 768px) {
|
||||
filter: blur(300px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: linear-gradient(0deg, transparent 0%, transparent 100%), #0b0c0e;
|
||||
|
||||
.ant-card-body {
|
||||
height: 100%;
|
||||
@@ -238,18 +237,7 @@
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.celery-task-graph-grid-container {
|
||||
.celery-task-graph-worker-count {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.configure-option-Info {
|
||||
border: 1px dashed var(--bg-robin-400);
|
||||
background-color: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
SA_QUERY_PARAMS.CREATE_SA,
|
||||
parseAsBoolean.withDefault(false),
|
||||
);
|
||||
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
|
||||
|
||||
const { showErrorModal, isErrorModalVisible } = useErrorModal();
|
||||
|
||||
@@ -50,11 +51,12 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
const { mutate: createServiceAccount, isLoading: isSubmitting } =
|
||||
useCreateServiceAccount({
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
onSuccess: async (response) => {
|
||||
toast.success('Service account created successfully');
|
||||
reset();
|
||||
await setIsOpen(null);
|
||||
await invalidateListServiceAccounts(queryClient);
|
||||
await setSelectedAccountId(response.data.id);
|
||||
},
|
||||
onError: (err) => {
|
||||
const errMessage = convertToApiError(
|
||||
@@ -67,7 +69,7 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
|
||||
function handleClose(): void {
|
||||
reset();
|
||||
setIsOpen(null);
|
||||
void setIsOpen(null);
|
||||
}
|
||||
|
||||
function handleCreate(values: FormValues): void {
|
||||
|
||||
@@ -77,11 +77,11 @@
|
||||
width: 280px;
|
||||
|
||||
&::placeholder {
|
||||
color: white;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&:focus::placeholder {
|
||||
color: rgba($color: #ffffff, $alpha: 0.4);
|
||||
color: rgba($color: var(--l1-foreground), $alpha: 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,42 +113,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.time-options-container {
|
||||
.time-options-item {
|
||||
&.active {
|
||||
background-color: rgba($color: #ffffff, $alpha: 0.2);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba($color: #ffffff, $alpha: 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba($color: #ffffff, $alpha: 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeSelection-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
padding-left: 0px !important;
|
||||
|
||||
input::placeholder {
|
||||
color: var(---bg-ink-300);
|
||||
}
|
||||
|
||||
input:focus::placeholder {
|
||||
color: rgba($color: #000000, $alpha: 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-time-popover__footer {
|
||||
border-top: 1px solid var(--l1-border);
|
||||
padding: 8px 14px;
|
||||
@@ -300,34 +264,3 @@
|
||||
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.timezone-container {
|
||||
.timezone {
|
||||
background: rgb(179 179 179 / 15%);
|
||||
&__icon {
|
||||
stroke: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-time-picker {
|
||||
.timeSelection-input {
|
||||
&:hover {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timezone-badge {
|
||||
background: rgb(179 179 179 / 15%);
|
||||
}
|
||||
|
||||
.time-input-suffix-icon-badge {
|
||||
background: rgb(179 179 179 / 15%);
|
||||
|
||||
&:hover {
|
||||
background: rgb(179 179 179 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,20 +129,3 @@ $item-spacing: 8px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.timezone-picker {
|
||||
&__search {
|
||||
.search-icon {
|
||||
stroke: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
.timezone-name-wrapper {
|
||||
&__selected-icon {
|
||||
.check-icon {
|
||||
stroke: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ describe.each([
|
||||
const callArgs = mockDownloadExportData.mock.calls[0][0];
|
||||
const query = callArgs.body.compositeQuery.queries[0];
|
||||
expect(query.spec.groupBy).toBeUndefined();
|
||||
expect(query.spec.having).toEqual({ expression: '' });
|
||||
expect(query.spec.having).toStrictEqual({ expression: '' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,7 +238,7 @@ describe.each([
|
||||
expect(mockDownloadExportData).toHaveBeenCalledTimes(1);
|
||||
const callArgs = mockDownloadExportData.mock.calls[0][0];
|
||||
const query = callArgs.body.compositeQuery.queries[0];
|
||||
expect(query.spec.selectFields).toEqual([
|
||||
expect(query.spec.selectFields).toStrictEqual([
|
||||
expect.objectContaining({
|
||||
name: 'http.status',
|
||||
fieldDataType: 'int64',
|
||||
|
||||
@@ -6,39 +6,39 @@ jest.mock('react-dnd', () => ({
|
||||
}));
|
||||
|
||||
describe('Utils testing of DraggableTableRow component', () => {
|
||||
test('Should dropHandler return true', () => {
|
||||
it('Should dropHandler return true', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dropDataTruthy = dropHandler(monitor);
|
||||
|
||||
expect(dropDataTruthy).toEqual({ isOver: true });
|
||||
expect(dropDataTruthy).toStrictEqual({ isOver: true });
|
||||
});
|
||||
|
||||
test('Should dropHandler return false', () => {
|
||||
it('Should dropHandler return false', () => {
|
||||
const monitor = {
|
||||
isOver: jest.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dropDataFalsy = dropHandler(monitor);
|
||||
|
||||
expect(dropDataFalsy).toEqual({ isOver: false });
|
||||
expect(dropDataFalsy).toStrictEqual({ isOver: false });
|
||||
});
|
||||
|
||||
test('Should dragHandler return true', () => {
|
||||
it('Should dragHandler return true', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(true),
|
||||
} as never;
|
||||
const dragDataTruthy = dragHandler(monitor);
|
||||
|
||||
expect(dragDataTruthy).toEqual({ isDragging: true });
|
||||
expect(dragDataTruthy).toStrictEqual({ isDragging: true });
|
||||
});
|
||||
|
||||
test('Should dragHandler return false', () => {
|
||||
it('Should dragHandler return false', () => {
|
||||
const monitor = {
|
||||
isDragging: jest.fn().mockReturnValueOnce(false),
|
||||
} as never;
|
||||
const dragDataFalsy = dragHandler(monitor);
|
||||
|
||||
expect(dragDataFalsy).toEqual({ isDragging: false });
|
||||
expect(dragDataFalsy).toStrictEqual({ isDragging: false });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
.dropdown-button {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-button--dark {
|
||||
color: #000;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, MenuProps } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import './DropDown.styles.scss';
|
||||
|
||||
@@ -12,8 +11,6 @@ function DropDown({
|
||||
element: JSX.Element[];
|
||||
onDropDownItemClick?: MenuProps['onClick'];
|
||||
}): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const items: MenuProps['items'] = element.map(
|
||||
(e: JSX.Element, index: number) => ({
|
||||
label: e,
|
||||
@@ -35,7 +32,7 @@ function DropDown({
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
|
||||
className={`dropdown-button`}
|
||||
onClick={(e): void => {
|
||||
e.preventDefault();
|
||||
setDdOpen(true);
|
||||
|
||||
@@ -361,9 +361,9 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /delete member/i }));
|
||||
|
||||
expect(
|
||||
await screen.findByText(/are you sure you want to delete/i),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(/are you sure you want to delete/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const confirmBtns = screen.getAllByRole('button', { name: /delete member/i });
|
||||
await user.click(confirmBtns[confirmBtns.length - 1]);
|
||||
@@ -441,9 +441,9 @@ describe('EditMemberDrawer', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /revoke invite/i }));
|
||||
|
||||
expect(
|
||||
await screen.findByText(/Are you sure you want to revoke the invite/i),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(/Are you sure you want to revoke the invite/i),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const confirmBtns = screen.getAllByRole('button', { name: /revoke invite/i });
|
||||
await user.click(confirmBtns[confirmBtns.length - 1]);
|
||||
|
||||
@@ -64,7 +64,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => ({
|
||||
// li.style.marginTop = '5px';
|
||||
|
||||
li.onclick = (): void => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const { type } = chart.config;
|
||||
if (type === 'pie' || type === 'doughnut') {
|
||||
// Pie and doughnut charts only have a single dataset and visibility is per item
|
||||
|
||||
@@ -9,65 +9,65 @@ describe('xAxisConfig for Chart', () => {
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'millisecond');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.millisecond,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.millisecond);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'second');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.second,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.second);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'minute');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.minute,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.minute);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'hour');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.hour,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.hour);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'day');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.day,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.day);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'week');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.week,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.week);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'month');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.month,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.month);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'year');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.year,
|
||||
);
|
||||
expect(
|
||||
convertTimeRange(start.valueOf(), end.valueOf()).unitName,
|
||||
).toStrictEqual(TIME_UNITS.year);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ const testFullPrecisionGetYAxisFormattedValue = (
|
||||
): string => getYAxisFormattedValue(value, format, PrecisionOptionsEnum.FULL);
|
||||
|
||||
describe('getYAxisFormattedValue - none (full precision legacy assertions)', () => {
|
||||
test('large integers and decimals', () => {
|
||||
it('large integers and decimals', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('250034', 'none')).toBe(
|
||||
'250034',
|
||||
);
|
||||
@@ -22,7 +22,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('preserves leading zeros after decimal until first non-zero', () => {
|
||||
it('preserves leading zeros after decimal until first non-zero', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1.0000234', 'none')).toBe(
|
||||
'1.0000234',
|
||||
);
|
||||
@@ -31,7 +31,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('trims to three significant decimals and removes trailing zeros', () => {
|
||||
it('trims to three significant decimals and removes trailing zeros', () => {
|
||||
expect(
|
||||
testFullPrecisionGetYAxisFormattedValue('0.000000250034', 'none'),
|
||||
).toBe('0.000000250034');
|
||||
@@ -55,7 +55,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
).toBe('0.00000025');
|
||||
});
|
||||
|
||||
test('whole numbers normalize', () => {
|
||||
it('whole numbers normalize', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1000', 'none')).toBe('1000');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('99.5458', 'none')).toBe(
|
||||
'99.5458',
|
||||
@@ -68,7 +68,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('strip redundant decimal zeros', () => {
|
||||
it('strip redundant decimal zeros', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1000.000', 'none')).toBe(
|
||||
'1000',
|
||||
);
|
||||
@@ -78,7 +78,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1.000', 'none')).toBe('1');
|
||||
});
|
||||
|
||||
test('edge values', () => {
|
||||
it('edge values', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0', 'none')).toBe('0');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('-0', 'none')).toBe('0');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('Infinity', 'none')).toBe('∞');
|
||||
@@ -92,7 +92,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('abc123', 'none')).toBe('NaN');
|
||||
});
|
||||
|
||||
test('small decimals keep precision as-is', () => {
|
||||
it('small decimals keep precision as-is', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.0001', 'none')).toBe(
|
||||
'0.0001',
|
||||
);
|
||||
@@ -104,7 +104,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('simple decimals preserved', () => {
|
||||
it('simple decimals preserved', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.1', 'none')).toBe('0.1');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.2', 'none')).toBe('0.2');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.3', 'none')).toBe('0.3');
|
||||
@@ -115,7 +115,7 @@ describe('getYAxisFormattedValue - none (full precision legacy assertions)', ()
|
||||
});
|
||||
|
||||
describe('getYAxisFormattedValue - units (full precision legacy assertions)', () => {
|
||||
test('ms', () => {
|
||||
it('ms', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1500', 'ms')).toBe('1.5 s');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('500', 'ms')).toBe('500 ms');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('60000', 'ms')).toBe('1 min');
|
||||
@@ -127,19 +127,19 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('s', () => {
|
||||
it('s', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('90', 's')).toBe('1.5 mins');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('30', 's')).toBe('30 s');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('3600', 's')).toBe('1 hour');
|
||||
});
|
||||
|
||||
test('m', () => {
|
||||
it('m', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('90', 'm')).toBe('1.5 hours');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('30', 'm')).toBe('30 min');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1440', 'm')).toBe('1 day');
|
||||
});
|
||||
|
||||
test('bytes', () => {
|
||||
it('bytes', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1024', 'bytes')).toBe(
|
||||
'1 KiB',
|
||||
);
|
||||
@@ -149,7 +149,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('mbytes', () => {
|
||||
it('mbytes', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1024', 'mbytes')).toBe(
|
||||
'1 GiB',
|
||||
);
|
||||
@@ -161,7 +161,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('kbytes', () => {
|
||||
it('kbytes', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1024', 'kbytes')).toBe(
|
||||
'1 MiB',
|
||||
);
|
||||
@@ -173,7 +173,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('short', () => {
|
||||
it('short', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1000', 'short')).toBe('1 K');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('1500', 'short')).toBe(
|
||||
'1.5 K',
|
||||
@@ -201,7 +201,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('percent', () => {
|
||||
it('percent', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.15', 'percent')).toBe(
|
||||
'0.15%',
|
||||
);
|
||||
@@ -235,7 +235,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
).toBe('1.005555555595959%');
|
||||
});
|
||||
|
||||
test('ratio', () => {
|
||||
it('ratio', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0.5', 'ratio')).toBe(
|
||||
'0.5 ratio',
|
||||
);
|
||||
@@ -247,7 +247,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('temperature units', () => {
|
||||
it('temperature units', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('25', 'celsius')).toBe(
|
||||
'25 °C',
|
||||
);
|
||||
@@ -267,13 +267,13 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('ms edge cases', () => {
|
||||
it('ms edge cases', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0', 'ms')).toBe('0 ms');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('-1500', 'ms')).toBe('-1.5 s');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('Infinity', 'ms')).toBe('∞');
|
||||
});
|
||||
|
||||
test('bytes edge cases', () => {
|
||||
it('bytes edge cases', () => {
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('0', 'bytes')).toBe('0 B');
|
||||
expect(testFullPrecisionGetYAxisFormattedValue('-1024', 'bytes')).toBe(
|
||||
'-1 KiB',
|
||||
@@ -282,7 +282,7 @@ describe('getYAxisFormattedValue - units (full precision legacy assertions)', ()
|
||||
});
|
||||
|
||||
describe('getYAxisFormattedValue - precision option tests', () => {
|
||||
test('precision 0 drops decimal part', () => {
|
||||
it('precision 0 drops decimal part', () => {
|
||||
expect(getYAxisFormattedValue('1.2345', 'none', 0)).toBe('1');
|
||||
expect(getYAxisFormattedValue('0.9999', 'none', 0)).toBe('0');
|
||||
expect(getYAxisFormattedValue('12345.6789', 'none', 0)).toBe('12345');
|
||||
@@ -294,7 +294,7 @@ describe('getYAxisFormattedValue - precision option tests', () => {
|
||||
// with unit
|
||||
expect(getYAxisFormattedValue('4353.81', 'ms', 0)).toBe('4 s');
|
||||
});
|
||||
test('precision 1,2,3,4 decimals', () => {
|
||||
it('precision 1,2,3,4 decimals', () => {
|
||||
expect(getYAxisFormattedValue('1.2345', 'none', 1)).toBe('1.2');
|
||||
expect(getYAxisFormattedValue('1.2345', 'none', 2)).toBe('1.23');
|
||||
expect(getYAxisFormattedValue('1.2345', 'none', 3)).toBe('1.234');
|
||||
@@ -345,7 +345,7 @@ describe('getYAxisFormattedValue - precision option tests', () => {
|
||||
expect(getYAxisFormattedValue('0.123456', 'percent', 4)).toBe('0.1235%'); // approximation
|
||||
});
|
||||
|
||||
test('precision full uses up to DEFAULT_SIGNIFICANT_DIGITS significant digits', () => {
|
||||
it('precision full uses up to DEFAULT_SIGNIFICANT_DIGITS significant digits', () => {
|
||||
expect(
|
||||
getYAxisFormattedValue(
|
||||
'0.00002625429914148441',
|
||||
|
||||
@@ -90,11 +90,11 @@ describe('InviteMembersModal', () => {
|
||||
screen.getByRole('button', { name: /invite team members/i }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
await expect(
|
||||
screen.findByText(
|
||||
'Please enter valid emails and select roles for team members',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows email-only message when email is invalid but role is selected', async () => {
|
||||
@@ -112,9 +112,9 @@ describe('InviteMembersModal', () => {
|
||||
screen.getByRole('button', { name: /invite team members/i }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('Please enter valid emails for team members'),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('Please enter valid emails for team members'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows role-only message when email is valid but role is missing', async () => {
|
||||
@@ -130,9 +130,9 @@ describe('InviteMembersModal', () => {
|
||||
screen.getByRole('button', { name: /invite team members/i }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('Please select roles for team members'),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('Please select roles for team members'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
|
||||
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
TextSelect,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
@@ -79,6 +81,10 @@ function LogDetailInner({
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
|
||||
|
||||
const [isFilterVisible, setIsFilterVisible] = useState<boolean>(false);
|
||||
const { featureFlags } = useAppContext();
|
||||
const isBodyJsonQueryEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.BODY_JSON_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const [filters, setFilters] = useState<TagFilter | null>(null);
|
||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||
@@ -208,11 +214,29 @@ function LogDetailInner({
|
||||
}
|
||||
};
|
||||
|
||||
const logBody = useMemo(() => {
|
||||
if (!isBodyJsonQueryEnabled) {
|
||||
return log?.body || '';
|
||||
}
|
||||
|
||||
try {
|
||||
const json = JSON.parse(log?.body || '');
|
||||
|
||||
if (typeof json?.message === 'string' && json.message !== '') {
|
||||
return json.message;
|
||||
}
|
||||
|
||||
return log?.body || '';
|
||||
} catch (error) {
|
||||
return log?.body || '';
|
||||
}
|
||||
}, [isBodyJsonQueryEnabled, log?.body]);
|
||||
|
||||
const htmlBody = useMemo(
|
||||
() => ({
|
||||
__html: getSanitizedLogBody(log?.body || '', { shouldEscapeHtml: true }),
|
||||
__html: getSanitizedLogBody(logBody || '', { shouldEscapeHtml: true }),
|
||||
}),
|
||||
[log?.body],
|
||||
[logBody],
|
||||
);
|
||||
|
||||
const handleJSONCopy = (): void => {
|
||||
@@ -418,7 +442,7 @@ function LogDetailInner({
|
||||
<div className="log-detail-drawer__log">
|
||||
<Divider type="vertical" className={cx('log-type-indicator', logType)} />
|
||||
<Tooltip
|
||||
title={removeEscapeCharacters(log?.body)}
|
||||
title={removeEscapeCharacters(logBody)}
|
||||
placement="left"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('getLogIndicatorType', () => {
|
||||
expect(getLogIndicatorType(log)).toBe('TRACE');
|
||||
});
|
||||
|
||||
it('severity_text should be used when severity_number is absent ', () => {
|
||||
it('severity_text should be used when severity_number is absent', () => {
|
||||
const log = {
|
||||
date: '2024-02-29T12:34:46Z',
|
||||
timestamp: 1646115296,
|
||||
|
||||
@@ -75,7 +75,7 @@ function OptionsMenu({
|
||||
};
|
||||
|
||||
const handleSearchValueChange = useDebouncedFn((event): void => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const value = event?.target?.value || '';
|
||||
|
||||
if (addColumn && addColumn?.onSearch) {
|
||||
|
||||
@@ -50,7 +50,7 @@ function Code({
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
style={a11yDark}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
@@ -115,7 +115,7 @@ function MarkdownRenderer({
|
||||
className={className}
|
||||
rehypePlugins={[rehypeRaw as any]}
|
||||
components={{
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
a: Link,
|
||||
pre: ({ children }) =>
|
||||
Pre({
|
||||
|
||||
@@ -196,17 +196,3 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.members-table {
|
||||
.ant-table-tbody {
|
||||
> tr.members-table-row--tinted > td {
|
||||
background: rgba(0, 0, 0, 0.015);
|
||||
}
|
||||
|
||||
> tr:hover > td {
|
||||
background: rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,22 +167,3 @@
|
||||
padding-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.config-btn {
|
||||
&.missing-config-btn {
|
||||
background: var(--bg-amber-100);
|
||||
color: var(--bg-amber-500);
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-amber-600) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.missing-config-btn {
|
||||
.config-btn-content {
|
||||
border-right: 1px solid var(--bg-amber-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 1. CUSTOM VALUES SUPPORT =====
|
||||
describe('Custom Values Support (CS)', () => {
|
||||
test('CS-01: Custom values persist in selected state', async () => {
|
||||
it('CS-01: Custom values persist in selected state', async () => {
|
||||
const { rerender } = renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -87,7 +87,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(screen.getByText('another-custom')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('CS-02: Partial matches create custom values', async () => {
|
||||
it('CS-02: Partial matches create custom values', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -129,7 +129,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('CS-03: Exact match filtering behavior', async () => {
|
||||
it('CS-03: Exact match filtering behavior', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -176,7 +176,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('CS-04: Search filtering with "end" pattern', async () => {
|
||||
it('CS-04: Search filtering with "end" pattern', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -234,7 +234,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('CS-05: Comma-separated values behavior', async () => {
|
||||
it('CS-05: Comma-separated values behavior', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -281,7 +281,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 2. SEARCH AND FILTERING =====
|
||||
describe('Search and Filtering (SF)', () => {
|
||||
test('SF-01: Selected values pushed to top', async () => {
|
||||
it('SF-01: Selected values pushed to top', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -305,7 +305,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-02: Filtering with search text', async () => {
|
||||
it('SF-02: Filtering with search text', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -350,7 +350,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-03: Highlighting search matches', async () => {
|
||||
it('SF-03: Highlighting search matches', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -381,7 +381,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-04: Search with no results', async () => {
|
||||
it('SF-04: Search with no results', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -424,7 +424,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 3. KEYBOARD NAVIGATION =====
|
||||
describe('Keyboard Navigation (KN)', () => {
|
||||
test('KN-01: Arrow key navigation in dropdown', async () => {
|
||||
it('KN-01: Arrow key navigation in dropdown', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -465,7 +465,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-02: Tab navigation to dropdown', async () => {
|
||||
it('KN-02: Tab navigation to dropdown', async () => {
|
||||
renderWithVirtuoso(
|
||||
<div>
|
||||
<input data-testid="prev-input" />
|
||||
@@ -515,7 +515,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-03: Enter selection in dropdown', async () => {
|
||||
it('KN-03: Enter selection in dropdown', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -540,7 +540,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(mockOnChange).toHaveBeenCalledWith(['frontend'], ['frontend']);
|
||||
});
|
||||
|
||||
test('KN-04: Chip deletion with keyboard', async () => {
|
||||
it('KN-04: Chip deletion with keyboard', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -586,7 +586,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 5. UI/UX BEHAVIORS =====
|
||||
describe('UI/UX Behaviors (UI)', () => {
|
||||
test('UI-01: Loading state does not block interaction', async () => {
|
||||
it('UI-01: Loading state does not block interaction', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -603,7 +603,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-02: Component remains editable in all states', async () => {
|
||||
it('UI-02: Component remains editable in all states', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -634,7 +634,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('UI-03: Toggle/Only labels in dropdown', async () => {
|
||||
it('UI-03: Toggle/Only labels in dropdown', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -656,7 +656,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-04: Should display values with loading info at bottom', async () => {
|
||||
it('UI-04: Should display values with loading info at bottom', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -677,7 +677,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-05: Error state display in footer', async () => {
|
||||
it('UI-05: Error state display in footer', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -696,7 +696,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-06: No data state display', async () => {
|
||||
it('UI-06: No data state display', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={[]}
|
||||
@@ -716,7 +716,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 6. CLEAR ACTIONS =====
|
||||
describe('Clear Actions (CA)', () => {
|
||||
test('CA-01: Ctrl+A selects all chips', async () => {
|
||||
it('CA-01: Ctrl+A selects all chips', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -760,7 +760,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('CA-02: Clear icon removes all selections', async () => {
|
||||
it('CA-02: Clear icon removes all selections', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -777,7 +777,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('CA-03: Individual chip removal', async () => {
|
||||
it('CA-03: Individual chip removal', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -790,7 +790,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
const removeButtons = document.querySelectorAll(
|
||||
'.ant-select-selection-item-remove',
|
||||
);
|
||||
expect(removeButtons.length).toBe(2);
|
||||
expect(removeButtons).toHaveLength(2);
|
||||
|
||||
await user.click(removeButtons[1] as Element);
|
||||
|
||||
@@ -804,7 +804,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 7. SAVE AND SELECTION TRIGGERS =====
|
||||
describe('Save and Selection Triggers (ST)', () => {
|
||||
test('ST-01: ESC triggers save action', async () => {
|
||||
it('ST-01: ESC triggers save action', async () => {
|
||||
const mockDropdownChange = jest.fn();
|
||||
|
||||
renderWithVirtuoso(
|
||||
@@ -837,7 +837,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('ST-02: Mouse selection works', async () => {
|
||||
it('ST-02: Mouse selection works', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -859,7 +859,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('ST-03: ENTER in input field creates custom value', async () => {
|
||||
it('ST-03: ENTER in input field creates custom value', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -892,7 +892,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('ST-04: Search text persistence', async () => {
|
||||
it('ST-04: Search text persistence', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -932,7 +932,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 8. SPECIAL OPTIONS AND STATES =====
|
||||
describe('Special Options and States (SO)', () => {
|
||||
test('SO-01: ALL option appears first and separated', async () => {
|
||||
it('SO-01: ALL option appears first and separated', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -954,7 +954,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SO-02: ALL selection behavior', async () => {
|
||||
it('SO-02: ALL selection behavior', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -981,7 +981,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('SO-03: ALL tag display when all selected', () => {
|
||||
it('SO-03: ALL tag display when all selected', () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -996,7 +996,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(screen.queryByText('frontend')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('SO-04: Footer information display', async () => {
|
||||
it('SO-04: Footer information display', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1017,7 +1017,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== GROUPED OPTIONS SUPPORT =====
|
||||
describe('Grouped Options Support', () => {
|
||||
test('handles grouped options correctly', async () => {
|
||||
it('handles grouped options correctly', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockGroupedOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1041,7 +1041,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== ACCESSIBILITY TESTS =====
|
||||
describe('Accessibility', () => {
|
||||
test('has proper ARIA attributes', async () => {
|
||||
it('has proper ARIA attributes', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1058,7 +1058,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('supports screen reader navigation', async () => {
|
||||
it('supports screen reader navigation', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1079,7 +1079,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 9. ADVANCED KEYBOARD NAVIGATION =====
|
||||
describe('Advanced Keyboard Navigation (AKN)', () => {
|
||||
test('AKN-01: Shift + Arrow + Del chip deletion', async () => {
|
||||
it('AKN-01: Shift + Arrow + Del chip deletion', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -1137,7 +1137,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).toHaveFocus();
|
||||
});
|
||||
|
||||
test('AKN-03: Mouse out closes dropdown', async () => {
|
||||
it('AKN-03: Mouse out closes dropdown', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1164,7 +1164,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 10. ADVANCED FILTERING AND HIGHLIGHTING =====
|
||||
describe('Advanced Filtering and Highlighting (AFH)', () => {
|
||||
test('AFH-01: Highlighted values pushed to top', async () => {
|
||||
it('AFH-01: Highlighted values pushed to top', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1220,7 +1220,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('AFH-02: Distinction between selection Enter and save Enter', async () => {
|
||||
it('AFH-02: Distinction between selection Enter and save Enter', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1267,7 +1267,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 11. ADVANCED CLEAR ACTIONS =====
|
||||
describe('Advanced Clear Actions (ACA)', () => {
|
||||
test('ACA-01: Clear action waiting behavior', async () => {
|
||||
it('ACA-01: Clear action waiting behavior', async () => {
|
||||
const mockOnChangeWithDelay = jest.fn().mockImplementation(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
@@ -1300,7 +1300,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 12. ADVANCED UI STATES =====
|
||||
describe('Advanced UI States (AUS)', () => {
|
||||
test('AUS-01: No data with previous value selected', async () => {
|
||||
it('AUS-01: No data with previous value selected', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={[]}
|
||||
@@ -1322,7 +1322,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(screen.getByText('previous-value')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('AUS-02: Always editable accessibility', async () => {
|
||||
it('AUS-02: Always editable accessibility', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -1338,7 +1338,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('AUS-03: Sufficient space for search value', async () => {
|
||||
it('AUS-03: Sufficient space for search value', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -1372,7 +1372,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 13. REGEX AND CUSTOM VALUES =====
|
||||
describe('Regex and Custom Values (RCV)', () => {
|
||||
test('RCV-01: Regex pattern support', async () => {
|
||||
it('RCV-01: Regex pattern support', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
@@ -1418,7 +1418,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('RCV-02: Custom values treated as normal dropdown values', async () => {
|
||||
it('RCV-02: Custom values treated as normal dropdown values', async () => {
|
||||
const customOptions = [
|
||||
...mockOptions,
|
||||
{ label: 'custom-value', value: 'custom-value', type: 'custom' as const },
|
||||
@@ -1456,7 +1456,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 14. DROPDOWN PERSISTENCE =====
|
||||
describe('Dropdown Persistence (DP)', () => {
|
||||
test('DP-01: Dropdown stays open for non-save actions', async () => {
|
||||
it('DP-01: Dropdown stays open for non-save actions', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 1. CUSTOM VALUES SUPPORT =====
|
||||
describe('Custom Values Support (CS)', () => {
|
||||
test('CS-02: Partial matches create custom values', async () => {
|
||||
it('CS-02: Partial matches create custom values', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={mockOptions}
|
||||
@@ -110,7 +110,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('CS-03: Exact match behavior', async () => {
|
||||
it('CS-03: Exact match behavior', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -161,7 +161,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 2. SEARCH AND FILTERING =====
|
||||
describe('Search and Filtering (SF)', () => {
|
||||
test('SF-01: Selected values pushed to top', async () => {
|
||||
it('SF-01: Selected values pushed to top', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={mockOptions}
|
||||
@@ -185,7 +185,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-02: Real-time search filtering', async () => {
|
||||
it('SF-02: Real-time search filtering', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -228,7 +228,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-03: Search highlighting', async () => {
|
||||
it('SF-03: Search highlighting', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -257,7 +257,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('SF-04: Search with partial matches', async () => {
|
||||
it('SF-04: Search with partial matches', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -298,7 +298,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 3. KEYBOARD NAVIGATION =====
|
||||
describe('Keyboard Navigation (KN)', () => {
|
||||
test('KN-01: Arrow key navigation in dropdown', async () => {
|
||||
it('KN-01: Arrow key navigation in dropdown', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -329,7 +329,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-02: Tab navigation to dropdown', async () => {
|
||||
it('KN-02: Tab navigation to dropdown', async () => {
|
||||
render(
|
||||
<div>
|
||||
<input data-testid="prev-input" />
|
||||
@@ -355,7 +355,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-03: Enter selection in dropdown', async () => {
|
||||
it('KN-03: Enter selection in dropdown', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -376,7 +376,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-04: Space key selection', async () => {
|
||||
it('KN-04: Space key selection', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -396,7 +396,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('KN-05: Tab navigation within dropdown', async () => {
|
||||
it('KN-05: Tab navigation within dropdown', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -417,7 +417,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 4. UI/UX BEHAVIORS =====
|
||||
describe('UI/UX Behaviors (UI)', () => {
|
||||
test('UI-01: Loading state does not block interaction', async () => {
|
||||
it('UI-01: Loading state does not block interaction', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -429,7 +429,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).toHaveFocus();
|
||||
});
|
||||
|
||||
test('UI-02: Component remains editable in all states', () => {
|
||||
it('UI-02: Component remains editable in all states', () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={mockOptions}
|
||||
@@ -444,7 +444,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('UI-03: Loading state display in footer', async () => {
|
||||
it('UI-03: Loading state display in footer', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -458,7 +458,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-04: Error state display in footer', async () => {
|
||||
it('UI-04: Error state display in footer', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={mockOptions}
|
||||
@@ -477,7 +477,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('UI-05: No data state display', async () => {
|
||||
it('UI-05: No data state display', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={[]}
|
||||
@@ -497,7 +497,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 6. SAVE AND SELECTION TRIGGERS =====
|
||||
describe('Save and Selection Triggers (ST)', () => {
|
||||
test('ST-01: Mouse selection works', async () => {
|
||||
it('ST-01: Mouse selection works', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -520,7 +520,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 7. GROUPED OPTIONS SUPPORT =====
|
||||
describe('Grouped Options Support', () => {
|
||||
test('handles grouped options correctly', async () => {
|
||||
it('handles grouped options correctly', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockGroupedOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -541,7 +541,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('grouped option selection works', async () => {
|
||||
it('grouped option selection works', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockGroupedOptions} onChange={mockOnChange} />,
|
||||
);
|
||||
@@ -566,7 +566,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 8. ACCESSIBILITY =====
|
||||
describe('Accessibility', () => {
|
||||
test('has proper ARIA attributes', async () => {
|
||||
it('has proper ARIA attributes', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -580,7 +580,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('supports screen reader navigation', async () => {
|
||||
it('supports screen reader navigation', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -596,7 +596,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('has proper focus management', async () => {
|
||||
it('has proper focus management', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -617,7 +617,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 10. EDGE CASES =====
|
||||
describe('Edge Cases', () => {
|
||||
test('handles special characters in options', async () => {
|
||||
it('handles special characters in options', async () => {
|
||||
const specialOptions = [
|
||||
{ label: 'Option with spaces', value: 'option-with-spaces' },
|
||||
{ label: 'Option-with-dashes', value: 'option-with-dashes' },
|
||||
@@ -638,7 +638,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('handles extremely long option labels', async () => {
|
||||
it('handles extremely long option labels', async () => {
|
||||
const longLabelOptions = [
|
||||
{
|
||||
label:
|
||||
@@ -663,7 +663,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 11. ADVANCED KEYBOARD NAVIGATION =====
|
||||
describe('Advanced Keyboard Navigation (AKN)', () => {
|
||||
test('AKN-01: Mouse out closes dropdown', async () => {
|
||||
it('AKN-01: Mouse out closes dropdown', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -684,7 +684,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('AKN-02: TAB navigation from input to dropdown', async () => {
|
||||
it('AKN-02: TAB navigation from input to dropdown', async () => {
|
||||
render(
|
||||
<div>
|
||||
<input data-testid="prev-input" />
|
||||
@@ -722,7 +722,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 12. ADVANCED FILTERING AND HIGHLIGHTING =====
|
||||
describe('Advanced Filtering and Highlighting (AFH)', () => {
|
||||
test('AFH-01: Highlighted values pushed to top', async () => {
|
||||
it('AFH-01: Highlighted values pushed to top', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -776,7 +776,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('AFH-02: Distinction between selection Enter and save Enter', async () => {
|
||||
it('AFH-02: Distinction between selection Enter and save Enter', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -830,7 +830,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 13. ADVANCED CLEAR ACTIONS =====
|
||||
describe('Advanced Clear Actions (ACA)', () => {
|
||||
test('ACA-01: Clear action waiting behavior', async () => {
|
||||
it('ACA-01: Clear action waiting behavior', async () => {
|
||||
const mockOnChangeWithDelay = jest.fn().mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
@@ -860,7 +860,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
expect(mockOnChangeWithDelay).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('ACA-02: Single select clear behavior like text input', async () => {
|
||||
it('ACA-02: Single select clear behavior like text input', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={mockOptions}
|
||||
@@ -883,7 +883,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 14. ADVANCED UI STATES =====
|
||||
describe('Advanced UI States (AUS)', () => {
|
||||
test('AUS-01: No data with previous value selected', async () => {
|
||||
it('AUS-01: No data with previous value selected', async () => {
|
||||
render(
|
||||
<CustomSelect
|
||||
options={[]}
|
||||
@@ -905,7 +905,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
expect(screen.getAllByText('previous-value')).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('AUS-02: Always editable accessibility', async () => {
|
||||
it('AUS-02: Always editable accessibility', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -921,7 +921,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test('AUS-03: Sufficient space for search value', async () => {
|
||||
it('AUS-03: Sufficient space for search value', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -950,7 +950,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('AUS-04: No spinners blocking user interaction', async () => {
|
||||
it('AUS-04: No spinners blocking user interaction', async () => {
|
||||
render(
|
||||
<CustomSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
);
|
||||
@@ -976,7 +976,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 15. REGEX AND CUSTOM VALUES =====
|
||||
describe('Regex and Custom Values (RCV)', () => {
|
||||
test('RCV-01: Regex pattern support', async () => {
|
||||
it('RCV-01: Regex pattern support', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
@@ -1019,7 +1019,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('RCV-02: Custom values treated as normal dropdown values', async () => {
|
||||
it('RCV-02: Custom values treated as normal dropdown values', async () => {
|
||||
const customOptions = [
|
||||
...mockOptions,
|
||||
{ label: 'custom-value', value: 'custom-value', type: 'custom' as const },
|
||||
@@ -1051,7 +1051,7 @@ describe('CustomSelect - Comprehensive Tests', () => {
|
||||
|
||||
// ===== 16. DROPDOWN PERSISTENCE =====
|
||||
describe('Dropdown Persistence (DP)', () => {
|
||||
test('DP-01: Dropdown closes only on save actions', async () => {
|
||||
it('DP-01: Dropdown closes only on save actions', async () => {
|
||||
render(<CustomSelect options={mockOptions} onChange={mockOnChange} />);
|
||||
|
||||
const combobox = screen.getByRole('combobox');
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 1. INTEGRATION WITH CUSTOMSELECT =====
|
||||
describe('CustomSelect Integration (VI)', () => {
|
||||
test('VI-01: Single select variable integration', async () => {
|
||||
it('VI-01: Single select variable integration', async () => {
|
||||
const variable = createMockVariable({
|
||||
multiSelect: false,
|
||||
type: 'CUSTOM',
|
||||
@@ -130,7 +130,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 2. INTEGRATION WITH CUSTOMMULTISELECT =====
|
||||
describe('CustomMultiSelect Integration (VI)', () => {
|
||||
test('VI-02: Multi select variable integration', async () => {
|
||||
it('VI-02: Multi select variable integration', async () => {
|
||||
const variable = createMockVariable({
|
||||
multiSelect: true,
|
||||
type: 'CUSTOM',
|
||||
@@ -174,7 +174,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 3. TEXTBOX VARIABLE TYPE =====
|
||||
describe('Textbox Variable Integration', () => {
|
||||
test('VI-03: Textbox variable handling', async () => {
|
||||
it('VI-03: Textbox variable handling', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'TEXTBOX',
|
||||
selectedValue: 'initial-value',
|
||||
@@ -219,7 +219,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 4. VALUE PERSISTENCE AND STATE MANAGEMENT =====
|
||||
describe('Value Persistence and State Management', () => {
|
||||
test('VI-04: All selected state handling', () => {
|
||||
it('VI-04: All selected state handling', () => {
|
||||
const variable = createMockVariable({
|
||||
multiSelect: true,
|
||||
type: 'CUSTOM',
|
||||
@@ -243,7 +243,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
expect(screen.getByText('ALL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('VI-05: Dropdown behavior with temporary selections', async () => {
|
||||
it('VI-05: Dropdown behavior with temporary selections', async () => {
|
||||
const variable = createMockVariable({
|
||||
multiSelect: true,
|
||||
type: 'CUSTOM',
|
||||
@@ -277,7 +277,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 6. ACCESSIBILITY AND USER EXPERIENCE =====
|
||||
describe('Accessibility and User Experience', () => {
|
||||
test('VI-06: Variable description tooltip', async () => {
|
||||
it('VI-06: Variable description tooltip', async () => {
|
||||
const variable = createMockVariable({
|
||||
description: 'This variable controls the service selection',
|
||||
type: 'CUSTOM',
|
||||
@@ -310,7 +310,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('VI-07: Variable name display', () => {
|
||||
it('VI-07: Variable name display', () => {
|
||||
const variable = createMockVariable({
|
||||
name: 'service_name',
|
||||
type: 'CUSTOM',
|
||||
@@ -331,7 +331,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
expect(screen.getByText('$service_name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('VI-08: Max tag count behavior', async () => {
|
||||
it('VI-08: Max tag count behavior', async () => {
|
||||
const variable = createMockVariable({
|
||||
multiSelect: true,
|
||||
type: 'CUSTOM',
|
||||
@@ -365,7 +365,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 8. SEARCH INTERACTION TESTS =====
|
||||
describe('Search Interaction Tests', () => {
|
||||
test('VI-14: Search persistence across dropdown open/close', async () => {
|
||||
it('VI-14: Search persistence across dropdown open/close', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'CUSTOM',
|
||||
customValue: 'option1,option2,option3',
|
||||
@@ -417,7 +417,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 9. ADVANCED KEYBOARD NAVIGATION =====
|
||||
describe('Advanced Keyboard Navigation (VI)', () => {
|
||||
test('VI-15: Shift + Arrow + Del chip deletion in multiselect', async () => {
|
||||
it('VI-15: Shift + Arrow + Del chip deletion in multiselect', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'CUSTOM',
|
||||
customValue: 'option1,option2,option3',
|
||||
@@ -461,7 +461,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 11. ADVANCED UI STATES =====
|
||||
describe('Advanced UI States (VI)', () => {
|
||||
test('VI-19: No data with previous value selected in variable', async () => {
|
||||
it('VI-19: No data with previous value selected in variable', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'CUSTOM',
|
||||
customValue: '',
|
||||
@@ -499,7 +499,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
expect(combobox).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('VI-20: Always editable accessibility in variable', async () => {
|
||||
it('VI-20: Always editable accessibility in variable', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'CUSTOM',
|
||||
customValue: 'option1,option2',
|
||||
@@ -530,7 +530,7 @@ describe('VariableItem Integration Tests', () => {
|
||||
|
||||
// ===== 13. DROPDOWN PERSISTENCE =====
|
||||
describe('Dropdown Persistence (VI)', () => {
|
||||
test('VI-24: Dropdown stays open for non-save actions in variable', async () => {
|
||||
it('VI-24: Dropdown stays open for non-save actions in variable', async () => {
|
||||
const variable = createMockVariable({
|
||||
type: 'CUSTOM',
|
||||
customValue: 'option1,option2,option3',
|
||||
|
||||
@@ -16,7 +16,7 @@ $custom-border-color: #2c3044;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
color: color-mix(in srgb, var(--border) 45%, transparent);
|
||||
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
|
||||
}
|
||||
|
||||
// Base styles are for dark mode
|
||||
@@ -48,10 +48,6 @@ $custom-border-color: #2c3044;
|
||||
visibility: visible !important;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
|
||||
.lightMode & {
|
||||
color: rgba(0, 0, 0, 0.85) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-select-focused .ant-select-selection-placeholder {
|
||||
@@ -67,10 +63,6 @@ $custom-border-color: #2c3044;
|
||||
color: var(--l2-foreground);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
|
||||
.lightMode & {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selector {
|
||||
@@ -114,7 +106,7 @@ $custom-border-color: #2c3044;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
color: color-mix(in srgb, var(--border) 45%, transparent);
|
||||
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
|
||||
}
|
||||
|
||||
// Customize tags in multiselect (dark mode by default)
|
||||
@@ -217,7 +209,7 @@ $custom-border-color: #2c3044;
|
||||
.empty-message {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--border) 45%, transparent);
|
||||
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +567,7 @@ $custom-border-color: #2c3044;
|
||||
.empty-message {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
color: color-mix(in srgb, var(--border) 45%, transparent);
|
||||
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
|
||||
}
|
||||
|
||||
.status-message {
|
||||
@@ -983,10 +975,6 @@ $custom-border-color: #2c3044;
|
||||
transition:
|
||||
opacity 0.2s ease,
|
||||
visibility 0.2s ease;
|
||||
|
||||
.lightMode & {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within .all-text {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('OverflowInputToolTip', () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('shows tooltip when content overflows and input is clamped at maxAutoWidth', async () => {
|
||||
it('shows tooltip when content overflows and input is clamped at maxAutoWidth', async () => {
|
||||
mockOverflow(150, 250); // clientWidth >= maxAutoWidth (150), scrollWidth > clientWidth
|
||||
|
||||
render(<OverflowInputToolTip value="Very long overflowing text" />);
|
||||
@@ -64,7 +64,7 @@ describe('OverflowInputToolTip', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does NOT show tooltip when content does not overflow', async () => {
|
||||
it('does NOT show tooltip when content does not overflow', async () => {
|
||||
mockOverflow(150, 100); // content fits (scrollWidth <= clientWidth)
|
||||
|
||||
render(<OverflowInputToolTip value="Short text" />);
|
||||
@@ -76,7 +76,7 @@ describe('OverflowInputToolTip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('does NOT show tooltip when content overflows but input is NOT at maxAutoWidth', async () => {
|
||||
it('does NOT show tooltip when content overflows but input is NOT at maxAutoWidth', async () => {
|
||||
mockOverflow(100, 250); // clientWidth < maxAutoWidth (150), scrollWidth > clientWidth
|
||||
|
||||
render(<OverflowInputToolTip value="Long but input not clamped" />);
|
||||
@@ -88,7 +88,7 @@ describe('OverflowInputToolTip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('uncontrolled input allows typing', async () => {
|
||||
it('uncontrolled input allows typing', async () => {
|
||||
render(<OverflowInputToolTip defaultValue="Init" />);
|
||||
|
||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
||||
@@ -97,7 +97,7 @@ describe('OverflowInputToolTip', () => {
|
||||
expect(input).toHaveValue('InitABC');
|
||||
});
|
||||
|
||||
test('disabled input never shows tooltip even if overflowing', async () => {
|
||||
it('disabled input never shows tooltip even if overflowing', async () => {
|
||||
mockOverflow(150, 300);
|
||||
|
||||
render(<OverflowInputToolTip value="Overflowing!" disabled />);
|
||||
@@ -109,7 +109,7 @@ describe('OverflowInputToolTip', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('renders mirror span and input correctly (structural assertions instead of snapshot)', () => {
|
||||
it('renders mirror span and input correctly (structural assertions instead of snapshot)', () => {
|
||||
const { container } = render(<OverflowInputToolTip value="Snapshot" />);
|
||||
const mirror = container.querySelector('.overflow-input-mirror');
|
||||
const input = container.querySelector('input') as HTMLInputElement | null;
|
||||
|
||||
@@ -249,57 +249,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.query-aggregation-container {
|
||||
.aggregation-container {
|
||||
.query-aggregation-select-container {
|
||||
.query-aggregation-select-editor {
|
||||
.cm-editor {
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
backdrop-filter: none;
|
||||
|
||||
ul {
|
||||
li {
|
||||
&:hover,
|
||||
&[aria-selected='true'] {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--l2-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--l1-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.cm-function {
|
||||
color: var(--primary-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.query-aggregation-error-popover {
|
||||
.ant-popover-inner {
|
||||
background-color: var(--l1-background);
|
||||
border: none;
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.query-aggregation-error-popover {
|
||||
.ant-popover-inner {
|
||||
background-color: var(--l1-border);
|
||||
|
||||
@@ -121,44 +121,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.qb-trace-operator {
|
||||
&-arrow {
|
||||
&::before {
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--l3-background),
|
||||
var(--l3-background) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
}
|
||||
&::after {
|
||||
background-color: var(--l3-background);
|
||||
}
|
||||
}
|
||||
&.non-list-view {
|
||||
&::before {
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
var(--l3-background),
|
||||
var(--l3-background) 4px,
|
||||
transparent 4px,
|
||||
transparent 8px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&-label-with-input {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
.label {
|
||||
color: var(--l1-foreground) !important;
|
||||
border-right: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('traceOperatorContextUtils', () => {
|
||||
null,
|
||||
);
|
||||
|
||||
expect(context).toEqual({
|
||||
expect(context).toStrictEqual({
|
||||
tokenType: TraceOperatorGrammarLexer.IDENTIFIER,
|
||||
text: 'test',
|
||||
start: 0,
|
||||
@@ -62,7 +62,7 @@ describe('traceOperatorContextUtils', () => {
|
||||
false,
|
||||
);
|
||||
|
||||
expect(context).toEqual({
|
||||
expect(context).toStrictEqual({
|
||||
tokenType: TraceOperatorGrammarLexer.IDENTIFIER,
|
||||
text: 'test',
|
||||
start: 0,
|
||||
@@ -193,7 +193,7 @@ describe('traceOperatorContextUtils', () => {
|
||||
it('should return default context for empty query', () => {
|
||||
const result = getTraceOperatorContextAtCursor('', 0);
|
||||
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
tokenType: -1,
|
||||
text: '',
|
||||
start: 0,
|
||||
@@ -211,7 +211,7 @@ describe('traceOperatorContextUtils', () => {
|
||||
it('should return default context for null query', () => {
|
||||
const result = getTraceOperatorContextAtCursor(null as any, 0);
|
||||
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
tokenType: -1,
|
||||
text: '',
|
||||
start: 0,
|
||||
@@ -229,7 +229,7 @@ describe('traceOperatorContextUtils', () => {
|
||||
it('should return default context for undefined query', () => {
|
||||
const result = getTraceOperatorContextAtCursor(undefined as any, 0);
|
||||
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
tokenType: -1,
|
||||
text: '',
|
||||
start: 0,
|
||||
|
||||
@@ -8,21 +8,21 @@ const makeTraceOperator = (expression: string): IBuilderTraceOperator =>
|
||||
describe('getInvolvedQueriesInTraceOperator', () => {
|
||||
it('returns empty array for empty input', () => {
|
||||
const result = getInvolvedQueriesInTraceOperator([]);
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('extracts identifiers from expression', () => {
|
||||
const result = getInvolvedQueriesInTraceOperator([
|
||||
makeTraceOperator('A => B'),
|
||||
]);
|
||||
expect(result).toEqual(['A', 'B']);
|
||||
expect(result).toStrictEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it('extracts identifiers from complex expression', () => {
|
||||
const result = getInvolvedQueriesInTraceOperator([
|
||||
makeTraceOperator('A => (NOT B || C)'),
|
||||
]);
|
||||
expect(result).toEqual(['A', 'B', 'C']);
|
||||
expect(result).toStrictEqual(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
it('filters out querynames from complex expression', () => {
|
||||
@@ -31,7 +31,7 @@ describe('getInvolvedQueriesInTraceOperator', () => {
|
||||
'(A1 && (NOT B2 || (C3 -> (D4 && E5)))) => ((F6 || G7) && (NOT (H8 -> I9)))',
|
||||
),
|
||||
]);
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
'A1',
|
||||
'B2',
|
||||
'C3',
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('previousQuery.utils', () => {
|
||||
saveAsPreviousQuery(key, sampleQuery);
|
||||
|
||||
const fromStore = getPreviousQueryFromKey(key);
|
||||
expect(fromStore).toEqual(sampleQuery);
|
||||
expect(fromStore).toStrictEqual(sampleQuery);
|
||||
});
|
||||
|
||||
it('saveAsPreviousQuery merges multiple entries and removeKeyFromPreviousQuery deletes one', () => {
|
||||
|
||||
@@ -22,18 +22,20 @@ describe('convertFiltersToExpression', () => {
|
||||
|
||||
it('should handle empty, null, and undefined inputs', () => {
|
||||
// Test null and undefined
|
||||
expect(convertFiltersToExpression(null as any)).toEqual({ expression: '' });
|
||||
expect(convertFiltersToExpression(undefined as any)).toEqual({
|
||||
expect(convertFiltersToExpression(null as any)).toStrictEqual({
|
||||
expression: '',
|
||||
});
|
||||
expect(convertFiltersToExpression(undefined as any)).toStrictEqual({
|
||||
expression: '',
|
||||
});
|
||||
|
||||
// Test empty filters
|
||||
expect(convertFiltersToExpression({ items: [], op: 'AND' })).toEqual({
|
||||
expect(convertFiltersToExpression({ items: [], op: 'AND' })).toStrictEqual({
|
||||
expression: '',
|
||||
});
|
||||
expect(
|
||||
convertFiltersToExpression({ items: undefined, op: 'AND' } as any),
|
||||
).toEqual({ expression: '' });
|
||||
).toStrictEqual({ expression: '' });
|
||||
});
|
||||
|
||||
it('should convert basic comparison operators with proper value formatting', () => {
|
||||
@@ -92,7 +94,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"service = 'api-gateway' AND status != 'error' AND duration > 100 AND count <= 50 AND is_active = true AND enabled = false AND count = 0 AND regex REGEXP '.*'",
|
||||
});
|
||||
@@ -124,7 +126,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"message = 'user\\'s data' AND description = '' AND path = '/api/v1/users'",
|
||||
});
|
||||
@@ -162,7 +164,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"service in ['api-gateway', 'user-service', 'auth-service'] AND status in ['success'] AND tags in [] AND name in ['John\\'s', 'Mary\\'s', 'Bob']",
|
||||
});
|
||||
@@ -224,7 +226,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"service NOT IN ['api-gateway', 'user-service'] AND message NOT LIKE 'error' AND path NOT REGEXP '/api/.*' AND service NOT IN ['api-gateway'] AND user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||
});
|
||||
@@ -268,7 +270,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"user_id exists AND user_id exists AND has(tags, 'production') AND hasAny(tags, ['production', 'staging']) AND hasAll(tags, ['production', 'monitoring'])",
|
||||
});
|
||||
@@ -312,7 +314,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"service = 'api-gateway' AND status = 'success' AND service in ['api-gateway']",
|
||||
});
|
||||
@@ -362,7 +364,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"service in ['api-gateway', 'user-service'] AND user_id exists AND has(tags, 'production') AND duration > 100 AND status NOT IN ['error', 'timeout'] AND method = 'POST'",
|
||||
});
|
||||
@@ -412,7 +414,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"count = 0 AND score > 100 AND limit >= 50 AND threshold < 1000 AND max_value <= 999 AND values in ['1', '2', '3', '4', '5']",
|
||||
});
|
||||
@@ -456,7 +458,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"is_active = true AND is_deleted = false AND email = 'user@example.com' AND description = 'Contains \"quotes\" and \\'apostrophes\\'' AND path = '/api/v1/users/123?filter=true'",
|
||||
});
|
||||
@@ -506,7 +508,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"has(tags, 'production') AND hasAny(labels, ['env:prod', 'service:api']) AND hasAll(metadata, ['version:1.0', 'team:backend']) AND services in ['api-gateway', 'user-service', 'auth-service', 'payment-service'] AND excluded_services NOT IN ['legacy-service', 'deprecated-service'] AND status_codes in ['200', '201', '400', '500']",
|
||||
});
|
||||
@@ -544,7 +546,7 @@ describe('convertFiltersToExpression', () => {
|
||||
};
|
||||
|
||||
const result = convertFiltersToExpression(filters);
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
expression:
|
||||
"user_id NOT EXISTS AND description NOT CONTAINS 'error' AND NOT has(tags, 'production') AND NOT hasAny(labels, ['env:prod', 'service:api'])",
|
||||
});
|
||||
@@ -568,7 +570,7 @@ describe('convertFiltersToExpression', () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.filters).toEqual(filters);
|
||||
expect(result.filters).toStrictEqual(filters);
|
||||
expect(result.filter.expression).toBe("service.name = 'test-service'");
|
||||
});
|
||||
|
||||
@@ -583,7 +585,7 @@ describe('convertFiltersToExpression', () => {
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(result.filters).toEqual(filters);
|
||||
expect(result.filters).toStrictEqual(filters);
|
||||
expect(result.filter.expression).toBe('');
|
||||
});
|
||||
|
||||
@@ -611,7 +613,7 @@ describe('convertFiltersToExpression', () => {
|
||||
expect(result.filter).toBeDefined();
|
||||
expect(result.filter.expression).toBe("service.name = 'updated-service'");
|
||||
// Ensure parser can parse the existing query
|
||||
expect(extractQueryPairs(existingQuery)).toEqual(
|
||||
expect(extractQueryPairs(existingQuery)).toStrictEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: 'service.name',
|
||||
@@ -805,7 +807,7 @@ describe('convertAggregationToExpression', () => {
|
||||
temporality: 'delta',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
metricName: 'test_metric',
|
||||
timeAggregation: 'avg',
|
||||
@@ -825,9 +827,11 @@ describe('convertAggregationToExpression', () => {
|
||||
spaceAggregation: 'noop',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
metricName: 'test_metric',
|
||||
reduceTo: undefined,
|
||||
temporality: undefined,
|
||||
timeAggregation: 'count',
|
||||
spaceAggregation: 'count',
|
||||
},
|
||||
@@ -841,9 +845,11 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.METRICS,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
metricName: '',
|
||||
reduceTo: undefined,
|
||||
temporality: undefined,
|
||||
timeAggregation: 'sum',
|
||||
spaceAggregation: 'sum',
|
||||
},
|
||||
@@ -858,7 +864,7 @@ describe('convertAggregationToExpression', () => {
|
||||
alias: 'trace_alias',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'count(test_metric)',
|
||||
alias: 'trace_alias',
|
||||
@@ -874,7 +880,7 @@ describe('convertAggregationToExpression', () => {
|
||||
alias: 'log_alias',
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'avg(test_metric)',
|
||||
alias: 'log_alias',
|
||||
@@ -889,7 +895,7 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.TRACES,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'count()',
|
||||
},
|
||||
@@ -903,7 +909,7 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.LOGS,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'sum(test_metric)',
|
||||
},
|
||||
@@ -917,9 +923,11 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.METRICS,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
metricName: 'test_metric',
|
||||
reduceTo: undefined,
|
||||
temporality: undefined,
|
||||
timeAggregation: 'max',
|
||||
spaceAggregation: 'max',
|
||||
},
|
||||
@@ -933,7 +941,7 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.METRICS,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
metricName: 'test_metric',
|
||||
timeAggregation: 'sum',
|
||||
@@ -951,7 +959,7 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.TRACES,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'count()',
|
||||
},
|
||||
@@ -965,7 +973,7 @@ describe('convertAggregationToExpression', () => {
|
||||
dataSource: DataSource.LOGS,
|
||||
});
|
||||
|
||||
expect(result).toEqual([
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
expression: 'count()',
|
||||
},
|
||||
|
||||
@@ -471,6 +471,6 @@ describe('CheckboxFilter - User Flows', () => {
|
||||
|
||||
expect(filterForServiceName.key.key).toBe(SERVICE_NAME_KEY);
|
||||
expect(filterForServiceName.op).toBe('in');
|
||||
expect(filterForServiceName.value).toEqual(['mq-kafka', 'otel-demo']);
|
||||
expect(filterForServiceName.value).toStrictEqual(['mq-kafka', 'otel-demo']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: radial-gradient(circle, #fff 10%, transparent 0);
|
||||
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
|
||||
background-size: 12px 12px;
|
||||
opacity: 1;
|
||||
|
||||
|
||||
@@ -323,7 +323,9 @@ describe('Quick Filters with custom filters', () => {
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
await user.click(settingsButton);
|
||||
|
||||
expect(await screen.findByText('Edit quick filters')).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('Edit quick filters'),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const addedSection = screen.getByText(ADDED_FILTERS_LABEL).parentElement!;
|
||||
expect(addedSection).toContainElement(
|
||||
@@ -454,7 +456,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
});
|
||||
|
||||
const requestBody = putHandler.mock.calls[0][0];
|
||||
expect(requestBody.filters).toEqual(
|
||||
expect(requestBody.filters).toStrictEqual(
|
||||
expect.arrayContaining([
|
||||
expect.not.objectContaining({ key: FILTER_OS_DESCRIPTION }),
|
||||
]),
|
||||
@@ -535,12 +537,16 @@ describe('Quick Filters refetch behavior', () => {
|
||||
);
|
||||
|
||||
const { unmount } = render(<TestQuickFilters signal={SIGNAL} />);
|
||||
expect(await screen.findByText(FILTER_SERVICE_NAME)).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
unmount();
|
||||
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
expect(await screen.findByText(FILTER_SERVICE_NAME)).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
expect(getCalls).toBe(2);
|
||||
});
|
||||
@@ -578,7 +584,9 @@ describe('Quick Filters refetch behavior', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
|
||||
expect(await screen.findByText(FILTER_SERVICE_NAME)).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
@@ -628,7 +636,9 @@ describe('Quick Filters refetch behavior', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
|
||||
expect(await screen.findByText(FILTER_SERVICE_NAME)).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
@@ -657,6 +667,8 @@ describe('Quick Filters refetch behavior', () => {
|
||||
|
||||
render(<TestQuickFilters signal={SIGNAL} config={[]} />);
|
||||
|
||||
expect(await screen.findByText('No filters found')).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText('No filters found'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ const testRoutes: RouteTabProps['routes'] = [
|
||||
];
|
||||
|
||||
describe('RouteTab component', () => {
|
||||
test('renders correctly', () => {
|
||||
it('renders correctly', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
@@ -39,7 +39,7 @@ describe('RouteTab component', () => {
|
||||
expect(screen.getByRole('tab', { name: 'Tab2' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders correct number of tabs', () => {
|
||||
it('renders correct number of tabs', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
@@ -47,10 +47,10 @@ describe('RouteTab component', () => {
|
||||
</Router>,
|
||||
);
|
||||
const tabs = screen.getAllByRole('tab');
|
||||
expect(tabs.length).toBe(testRoutes.length);
|
||||
expect(tabs).toHaveLength(testRoutes.length);
|
||||
});
|
||||
|
||||
test('sets provided activeKey as active tab', () => {
|
||||
it('sets provided activeKey as active tab', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
@@ -62,7 +62,7 @@ describe('RouteTab component', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('navigates to correct route on tab click', () => {
|
||||
it('navigates to correct route on tab click', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
@@ -74,7 +74,7 @@ describe('RouteTab component', () => {
|
||||
expect(history.location.pathname).toBe('/tab2');
|
||||
});
|
||||
|
||||
test('calls onChangeHandler on tab change', () => {
|
||||
it('calls onChangeHandler on tab change', () => {
|
||||
const onChangeHandler = jest.fn();
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__tab-group {
|
||||
[data-slot='toggle-group'] {
|
||||
border-radius: 2px;
|
||||
|
||||
@@ -70,9 +70,9 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
it('renders key data from prop when edit-key param is set', async () => {
|
||||
renderModal();
|
||||
|
||||
expect(
|
||||
await screen.findByDisplayValue('Original Key Name'),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByDisplayValue('Original Key Name'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Save Changes/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
|
||||
const latestUrlUpdate =
|
||||
onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]?.[0];
|
||||
expect(latestUrlUpdate).toEqual(
|
||||
expect(latestUrlUpdate).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
queryString: expect.any(String),
|
||||
}),
|
||||
@@ -134,9 +134,9 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
await user.click(screen.getByRole('button', { name: /Revoke Key/i }));
|
||||
|
||||
// Same dialog, now showing revoke confirmation
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: /Revoke Original Key Name/i }),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByRole('dialog', { name: /Revoke Original Key Name/i }),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Revoking this key will permanently invalidate it/i),
|
||||
).toBeInTheDocument();
|
||||
|
||||
@@ -104,7 +104,9 @@ describe('ServiceAccountDrawer', () => {
|
||||
it('renders Overview tab by default: editable name input, locked email, Save disabled when not dirty', async () => {
|
||||
renderDrawer();
|
||||
|
||||
expect(await screen.findByDisplayValue('CI Bot')).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByDisplayValue('CI Bot'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('ci-bot@signoz.io')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Save Changes/i })).toBeDisabled();
|
||||
});
|
||||
@@ -272,11 +274,11 @@ describe('ServiceAccountDrawer', () => {
|
||||
|
||||
renderDrawer();
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
await expect(
|
||||
screen.findByText(
|
||||
/An unexpected error occurred while fetching service account details/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,11 +351,11 @@ describe('ServiceAccountDrawer – save-error UX', () => {
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await user.click(saveBtn);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/Name update.*name update failed/i, undefined, {
|
||||
await expect(
|
||||
screen.findByText(/Name update.*name update failed/i, undefined, {
|
||||
timeout: 5000,
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('role add failure shows SaveErrorItem with the role name context', async () => {
|
||||
@@ -385,15 +387,11 @@ describe('ServiceAccountDrawer – save-error UX', () => {
|
||||
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
||||
await user.click(saveBtn);
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
/Role 'signoz-viewer'.*role assign failed/i,
|
||||
undefined,
|
||||
{
|
||||
timeout: 5000,
|
||||
},
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByText(/Role 'signoz-viewer'.*role assign failed/i, undefined, {
|
||||
timeout: 5000,
|
||||
}),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('role add retries on 429 then succeeds without showing an error', async () => {
|
||||
|
||||
@@ -197,17 +197,3 @@
|
||||
background-color: var(--l1-border);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.sa-table {
|
||||
.ant-table-tbody {
|
||||
> tr.sa-table-row--tinted > td {
|
||||
background: rgba(0, 0, 0, 0.015);
|
||||
}
|
||||
|
||||
> tr:hover > td {
|
||||
background: rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
transform: translateX(-50%);
|
||||
background: var(--l2-background);
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
background 120ms ease,
|
||||
width 120ms ease;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.cursorColResize:hover .tanstackResizeHandleLine {
|
||||
|
||||
@@ -63,7 +63,9 @@ describe('TanStackCustomTableRow', () => {
|
||||
</tbody>
|
||||
</table>,
|
||||
);
|
||||
expect(await screen.findByTestId('mocked-row-cells')).toBeInTheDocument();
|
||||
await expect(
|
||||
screen.findByTestId('mocked-row-cells'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies active class when isRowActive returns true', () => {
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('TanStackRowCells', () => {
|
||||
</tbody>
|
||||
</table>,
|
||||
);
|
||||
expect(await screen.findByText('expanded-r1')).toBeInTheDocument();
|
||||
await expect(screen.findByText('expanded-r1')).resolves.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('new tab click', () => {
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('useColumnState', () => {
|
||||
renderHook(() => useColumnState({ storageKey: TEST_KEY, columns }));
|
||||
|
||||
const state = useColumnStore.getState().tables[TEST_KEY];
|
||||
expect(state.hiddenColumnIds).toEqual(['b']);
|
||||
expect(state.hiddenColumnIds).toStrictEqual(['b']);
|
||||
});
|
||||
|
||||
it('does not initialize without storageKey', () => {
|
||||
@@ -61,7 +61,7 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns }),
|
||||
);
|
||||
|
||||
expect(result.current.columnVisibility).toEqual({ b: false });
|
||||
expect(result.current.columnVisibility).toStrictEqual({ b: false });
|
||||
});
|
||||
|
||||
it('applies visibilityBehavior for grouped state', () => {
|
||||
@@ -79,13 +79,15 @@ describe('useColumnState', () => {
|
||||
const { result: notGrouped } = renderHook(() =>
|
||||
useColumnState({ storageKey: TEST_KEY, columns, isGrouped: false }),
|
||||
);
|
||||
expect(notGrouped.current.columnVisibility).toEqual({ grouped: false });
|
||||
expect(notGrouped.current.columnVisibility).toStrictEqual({
|
||||
grouped: false,
|
||||
});
|
||||
|
||||
// Grouped
|
||||
const { result: grouped } = renderHook(() =>
|
||||
useColumnState({ storageKey: TEST_KEY, columns, isGrouped: true }),
|
||||
);
|
||||
expect(grouped.current.columnVisibility).toEqual({ ungrouped: false });
|
||||
expect(grouped.current.columnVisibility).toStrictEqual({ ungrouped: false });
|
||||
});
|
||||
|
||||
it('combines store hidden + visibilityBehavior', () => {
|
||||
@@ -103,7 +105,10 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns, isGrouped: true }),
|
||||
);
|
||||
|
||||
expect(result.current.columnVisibility).toEqual({ a: false, b: false });
|
||||
expect(result.current.columnVisibility).toStrictEqual({
|
||||
a: false,
|
||||
b: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,7 +124,7 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns }),
|
||||
);
|
||||
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toEqual([
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toStrictEqual([
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
@@ -138,7 +143,7 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns }),
|
||||
);
|
||||
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toEqual([
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toStrictEqual([
|
||||
'c',
|
||||
'a',
|
||||
'b',
|
||||
@@ -157,7 +162,7 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns }),
|
||||
);
|
||||
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toEqual([
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toStrictEqual([
|
||||
'pinned',
|
||||
'b',
|
||||
'a',
|
||||
@@ -181,7 +186,7 @@ describe('useColumnState', () => {
|
||||
result.current.hideColumn('a');
|
||||
});
|
||||
|
||||
expect(result.current.columnVisibility).toEqual({ a: false });
|
||||
expect(result.current.columnVisibility).toStrictEqual({ a: false });
|
||||
});
|
||||
|
||||
it('showColumn shows a column', () => {
|
||||
@@ -195,13 +200,13 @@ describe('useColumnState', () => {
|
||||
useColumnState({ storageKey: TEST_KEY, columns }),
|
||||
);
|
||||
|
||||
expect(result.current.columnVisibility).toEqual({ a: false });
|
||||
expect(result.current.columnVisibility).toStrictEqual({ a: false });
|
||||
|
||||
act(() => {
|
||||
result.current.showColumn('a');
|
||||
});
|
||||
|
||||
expect(result.current.columnVisibility).toEqual({});
|
||||
expect(result.current.columnVisibility).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('setColumnSizing updates sizing', () => {
|
||||
@@ -219,7 +224,7 @@ describe('useColumnState', () => {
|
||||
result.current.setColumnSizing({ a: 200 });
|
||||
});
|
||||
|
||||
expect(result.current.columnSizing).toEqual({ a: 200 });
|
||||
expect(result.current.columnSizing).toStrictEqual({ a: 200 });
|
||||
});
|
||||
|
||||
it('setColumnOrder updates order from column array', () => {
|
||||
@@ -237,7 +242,7 @@ describe('useColumnState', () => {
|
||||
result.current.setColumnOrder([col('c'), col('a'), col('b')]);
|
||||
});
|
||||
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toEqual([
|
||||
expect(result.current.sortedColumns.map((c) => c.id)).toStrictEqual([
|
||||
'c',
|
||||
'a',
|
||||
'b',
|
||||
|
||||
@@ -29,9 +29,9 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const state = useColumnStore.getState().tables[TEST_KEY];
|
||||
expect(state.hiddenColumnIds).toEqual(['b']);
|
||||
expect(state.columnOrder).toEqual([]);
|
||||
expect(state.columnSizing).toEqual({});
|
||||
expect(state.hiddenColumnIds).toStrictEqual(['b']);
|
||||
expect(state.columnOrder).toStrictEqual([]);
|
||||
expect(state.columnSizing).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('does not reinitialize if already exists', () => {
|
||||
@@ -124,7 +124,9 @@ describe('useColumnStore', () => {
|
||||
.getState()
|
||||
.setColumnSizing(TEST_KEY, { col1: 200, col2: 300 });
|
||||
});
|
||||
expect(useColumnStore.getState().tables[TEST_KEY].columnSizing).toEqual({
|
||||
expect(
|
||||
useColumnStore.getState().tables[TEST_KEY].columnSizing,
|
||||
).toStrictEqual({
|
||||
col1: 200,
|
||||
col2: 300,
|
||||
});
|
||||
@@ -144,11 +146,9 @@ describe('useColumnStore', () => {
|
||||
.getState()
|
||||
.setColumnOrder(TEST_KEY, ['col2', 'col1', 'col3']);
|
||||
});
|
||||
expect(useColumnStore.getState().tables[TEST_KEY].columnOrder).toEqual([
|
||||
'col2',
|
||||
'col1',
|
||||
'col3',
|
||||
]);
|
||||
expect(useColumnStore.getState().tables[TEST_KEY].columnOrder).toStrictEqual(
|
||||
['col2', 'col1', 'col3'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,9 +172,9 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const state = useColumnStore.getState().tables[TEST_KEY];
|
||||
expect(state.hiddenColumnIds).toEqual(['a']);
|
||||
expect(state.columnOrder).toEqual([]);
|
||||
expect(state.columnSizing).toEqual({});
|
||||
expect(state.hiddenColumnIds).toStrictEqual(['a']);
|
||||
expect(state.columnOrder).toStrictEqual([]);
|
||||
expect(state.columnSizing).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,7 +195,7 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const state = useColumnStore.getState().tables[TEST_KEY];
|
||||
expect(state.hiddenColumnIds).toEqual(['col1', 'col3']);
|
||||
expect(state.hiddenColumnIds).toStrictEqual(['col1', 'col3']);
|
||||
expect(state.hiddenColumnIds).not.toContain('col2');
|
||||
});
|
||||
|
||||
@@ -216,7 +216,7 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const stateAfter = useColumnStore.getState().tables[TEST_KEY];
|
||||
expect(stateAfter.hiddenColumnIds).toEqual(hiddenBefore);
|
||||
expect(stateAfter.hiddenColumnIds).toStrictEqual(hiddenBefore);
|
||||
});
|
||||
|
||||
it('does nothing for unknown storage key', () => {
|
||||
@@ -242,7 +242,7 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useHiddenColumnIds(TEST_KEY));
|
||||
expect(result.current).toEqual(['a']);
|
||||
expect(result.current).toStrictEqual(['a']);
|
||||
});
|
||||
|
||||
it('useHiddenColumnIds returns a stable snapshot for persisted state', () => {
|
||||
@@ -270,7 +270,7 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useColumnSizing(TEST_KEY));
|
||||
expect(result.current).toEqual({ col1: 150 });
|
||||
expect(result.current).toStrictEqual({ col1: 150 });
|
||||
});
|
||||
|
||||
it('useColumnOrder returns order', () => {
|
||||
@@ -280,7 +280,7 @@ describe('useColumnStore', () => {
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useColumnOrder(TEST_KEY));
|
||||
expect(result.current).toEqual(['c', 'b', 'a']);
|
||||
expect(result.current).toStrictEqual(['c', 'b', 'a']);
|
||||
});
|
||||
|
||||
it('returns empty defaults for unknown storageKey', () => {
|
||||
@@ -288,9 +288,9 @@ describe('useColumnStore', () => {
|
||||
const { result: sizing } = renderHook(() => useColumnSizing('unknown'));
|
||||
const { result: order } = renderHook(() => useColumnOrder('unknown'));
|
||||
|
||||
expect(hidden.current).toEqual([]);
|
||||
expect(sizing.current).toEqual({});
|
||||
expect(order.current).toEqual([]);
|
||||
expect(hidden.current).toStrictEqual([]);
|
||||
expect(sizing.current).toStrictEqual({});
|
||||
expect(order.current).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,7 +80,10 @@ describe('useTableParams (local mode — enableQueryParams not set)', () => {
|
||||
act(() => {
|
||||
result.current.setOrderBy({ columnName: 'cpu', order: 'desc' });
|
||||
});
|
||||
expect(result.current.orderBy).toEqual({ columnName: 'cpu', order: 'desc' });
|
||||
expect(result.current.orderBy).toStrictEqual({
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,7 +146,10 @@ describe('useTableParams (URL mode — enableQueryParams set)', () => {
|
||||
const orderBy = JSON.stringify({ columnName: 'name', order: 'desc' });
|
||||
const wrapper = createNuqsWrapper({ order_by: orderBy });
|
||||
const { result } = renderHook(() => useTableParams(true), { wrapper });
|
||||
expect(result.current.orderBy).toEqual({ columnName: 'name', order: 'desc' });
|
||||
expect(result.current.orderBy).toStrictEqual({
|
||||
columnName: 'name',
|
||||
order: 'desc',
|
||||
});
|
||||
});
|
||||
|
||||
it('updates URL when setPage is called', () => {
|
||||
@@ -178,7 +184,7 @@ describe('useTableParams (URL mode — enableQueryParams set)', () => {
|
||||
.filter(Boolean)
|
||||
.pop();
|
||||
expect(lastOrderBy).toBeDefined();
|
||||
expect(JSON.parse(lastOrderBy!)).toEqual({
|
||||
expect(JSON.parse(lastOrderBy!)).toStrictEqual({
|
||||
columnName: 'value',
|
||||
order: 'asc',
|
||||
});
|
||||
@@ -207,7 +213,7 @@ describe('useTableParams (URL mode — enableQueryParams set)', () => {
|
||||
result.current.setExpanded({ 'row-1': true });
|
||||
});
|
||||
|
||||
expect(result.current.expanded).toEqual({ 'row-1': true });
|
||||
expect(result.current.expanded).toStrictEqual({ 'row-1': true });
|
||||
});
|
||||
|
||||
it('toggles sort order correctly: null → asc → desc → null', () => {
|
||||
@@ -222,13 +228,19 @@ describe('useTableParams (URL mode — enableQueryParams set)', () => {
|
||||
act(() => {
|
||||
result.current.setOrderBy({ columnName: 'id', order: 'asc' });
|
||||
});
|
||||
expect(result.current.orderBy).toEqual({ columnName: 'id', order: 'asc' });
|
||||
expect(result.current.orderBy).toStrictEqual({
|
||||
columnName: 'id',
|
||||
order: 'asc',
|
||||
});
|
||||
|
||||
// Second click: asc → desc
|
||||
act(() => {
|
||||
result.current.setOrderBy({ columnName: 'id', order: 'desc' });
|
||||
});
|
||||
expect(result.current.orderBy).toEqual({ columnName: 'id', order: 'desc' });
|
||||
expect(result.current.orderBy).toStrictEqual({
|
||||
columnName: 'id',
|
||||
order: 'desc',
|
||||
});
|
||||
|
||||
// Third click: desc → null
|
||||
act(() => {
|
||||
|
||||
@@ -186,29 +186,3 @@
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.warning-content {
|
||||
&__warning-code {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
&__warning-message {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
&__message-item {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
&__message-badge {
|
||||
&-label-text {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
.key-value-label__value {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
&__docs-button {
|
||||
background: var(--l1-background);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { formatUniversalUnit } from '../formatter';
|
||||
|
||||
describe('formatUniversalUnit', () => {
|
||||
describe('Time', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Days
|
||||
[31, UniversalYAxisUnit.DAYS, '4.43 weeks'],
|
||||
[7, UniversalYAxisUnit.DAYS, '1 week'],
|
||||
@@ -48,7 +48,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Data', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Bytes
|
||||
[864, UniversalYAxisUnit.BYTES, '864 B'],
|
||||
[1000, UniversalYAxisUnit.BYTES, '1 kB'],
|
||||
@@ -91,7 +91,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Data rate', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Bytes/second
|
||||
[864, UniversalYAxisUnit.BYTES_SECOND, '864 B/s'],
|
||||
[1000, UniversalYAxisUnit.BYTES_SECOND, '1 kB/s'],
|
||||
@@ -134,7 +134,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Bit', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Bits
|
||||
[1, UniversalYAxisUnit.BITS, '1 b'],
|
||||
[250, UniversalYAxisUnit.BITS, '250 b'],
|
||||
@@ -186,7 +186,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Bit rate', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Bits/second
|
||||
[512, UniversalYAxisUnit.BITS_SECOND, '512 b/s'],
|
||||
[1000, UniversalYAxisUnit.BITS_SECOND, '1 kb/s'],
|
||||
@@ -236,7 +236,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Count', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[100, UniversalYAxisUnit.COUNT, '100'],
|
||||
[875, UniversalYAxisUnit.COUNT, '875'],
|
||||
[1000, UniversalYAxisUnit.COUNT, '1 K'],
|
||||
@@ -256,7 +256,7 @@ describe('formatUniversalUnit', () => {
|
||||
expect(formatUniversalUnit(value, unit)).toBe(expected);
|
||||
});
|
||||
|
||||
test.each([
|
||||
it.each([
|
||||
[100, UniversalYAxisUnit.COUNT_SECOND, '100 c/s'],
|
||||
[875, UniversalYAxisUnit.COUNT_SECOND, '875 c/s'],
|
||||
[1000, UniversalYAxisUnit.COUNT_SECOND, '1K c/s'],
|
||||
@@ -267,7 +267,7 @@ describe('formatUniversalUnit', () => {
|
||||
expect(formatUniversalUnit(value, unit)).toBe(expected);
|
||||
});
|
||||
|
||||
test.each([
|
||||
it.each([
|
||||
[100, UniversalYAxisUnit.COUNT_MINUTE, '100 c/m'],
|
||||
[875, UniversalYAxisUnit.COUNT_MINUTE, '875 c/m'],
|
||||
[1000, UniversalYAxisUnit.COUNT_MINUTE, '1K c/m'],
|
||||
@@ -280,7 +280,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Operations units', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[780, UniversalYAxisUnit.OPS_SECOND, '780 ops/s'],
|
||||
[1000, UniversalYAxisUnit.OPS_SECOND, '1K ops/s'],
|
||||
[520, UniversalYAxisUnit.OPS_MINUTE, '520 ops/m'],
|
||||
@@ -297,7 +297,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Request units', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[615, UniversalYAxisUnit.REQUESTS_SECOND, '615 req/s'],
|
||||
[1000, UniversalYAxisUnit.REQUESTS_SECOND, '1K req/s'],
|
||||
[480, UniversalYAxisUnit.REQUESTS_MINUTE, '480 req/m'],
|
||||
@@ -311,7 +311,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Read/Write units', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[505, UniversalYAxisUnit.READS_SECOND, '505 rd/s'],
|
||||
[1000, UniversalYAxisUnit.READS_SECOND, '1K rd/s'],
|
||||
[610, UniversalYAxisUnit.WRITES_SECOND, '610 wr/s'],
|
||||
@@ -335,7 +335,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('IO Operations units', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[777, UniversalYAxisUnit.IOOPS_SECOND, '777 io/s'],
|
||||
[1000, UniversalYAxisUnit.IOOPS_SECOND, '1K io/s'],
|
||||
[2500, UniversalYAxisUnit.IOOPS_SECOND, '2.5K io/s'],
|
||||
@@ -363,7 +363,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Time (additional)', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[900, UniversalYAxisUnit.DURATION_MS, '900 milliseconds'],
|
||||
[1000, UniversalYAxisUnit.DURATION_MS, '1 second'],
|
||||
[1, UniversalYAxisUnit.DURATION_MS, '1 millisecond'],
|
||||
@@ -388,7 +388,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Data (IEC/Binary)', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Bytes
|
||||
[900, UniversalYAxisUnit.BYTES_IEC, '900 B'],
|
||||
[1024, UniversalYAxisUnit.BYTES_IEC, '1 KiB'],
|
||||
@@ -430,7 +430,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Data Rate (IEC/Binary)', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Kibibytes/second
|
||||
[900, UniversalYAxisUnit.KIBIBYTES_SECOND, '900 KiB/s'],
|
||||
[1024, UniversalYAxisUnit.KIBIBYTES_SECOND, '1 MiB/s'],
|
||||
@@ -473,7 +473,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Bits (IEC)', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[900, UniversalYAxisUnit.BITS_IEC, '900 b'],
|
||||
[1024, UniversalYAxisUnit.BITS_IEC, '1 Kib'],
|
||||
[1080, UniversalYAxisUnit.BITS_IEC, '1.05 Kib'],
|
||||
@@ -483,7 +483,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Hash Rate', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// Hashes/second
|
||||
[412, UniversalYAxisUnit.HASH_RATE_HASHES_PER_SECOND, '412 H/s'],
|
||||
[1000, UniversalYAxisUnit.HASH_RATE_HASHES_PER_SECOND, '1 kH/s'],
|
||||
@@ -518,7 +518,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Miscellaneous', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[742, UniversalYAxisUnit.MISC_STRING, '742'],
|
||||
[688, UniversalYAxisUnit.MISC_SHORT, '688'],
|
||||
[555, UniversalYAxisUnit.MISC_HUMIDITY, '555 %H'],
|
||||
@@ -534,7 +534,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Acceleration', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[
|
||||
875,
|
||||
UniversalYAxisUnit.ACCELERATION_METERS_PER_SECOND_SQUARED,
|
||||
@@ -553,7 +553,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Angular', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[415, UniversalYAxisUnit.ANGULAR_DEGREE, '415 °'],
|
||||
[732, UniversalYAxisUnit.ANGULAR_RADIAN, '732 rad'],
|
||||
[128, UniversalYAxisUnit.ANGULAR_GRADIAN, '128 grad'],
|
||||
@@ -565,7 +565,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Area', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[210, UniversalYAxisUnit.AREA_SQUARE_METERS, '210 m²'],
|
||||
[152, UniversalYAxisUnit.AREA_SQUARE_FEET, '152 ft²'],
|
||||
[64, UniversalYAxisUnit.AREA_SQUARE_MILES, '64 mi²'],
|
||||
@@ -575,7 +575,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('FLOPs', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
// FLOPS
|
||||
[150, UniversalYAxisUnit.FLOPS_FLOPS, '150 FLOPS'],
|
||||
[1000, UniversalYAxisUnit.FLOPS_FLOPS, '1 kFLOPS'],
|
||||
@@ -613,7 +613,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Concentration', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[415, UniversalYAxisUnit.CONCENTRATION_PPM, '415 ppm'],
|
||||
[1000, UniversalYAxisUnit.CONCENTRATION_PPM, '1000 ppm'],
|
||||
[732, UniversalYAxisUnit.CONCENTRATION_PPB, '732 ppb'],
|
||||
@@ -650,7 +650,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Currency', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[812, UniversalYAxisUnit.CURRENCY_USD, '$812'],
|
||||
[645, UniversalYAxisUnit.CURRENCY_GBP, '£645'],
|
||||
[731, UniversalYAxisUnit.CURRENCY_EUR, '€731'],
|
||||
@@ -688,7 +688,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Power/Electrical', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[715, UniversalYAxisUnit.POWER_WATT, '715 W'],
|
||||
[1000, UniversalYAxisUnit.POWER_WATT, '1 kW'],
|
||||
[1080, UniversalYAxisUnit.POWER_WATT, '1.08 kW'],
|
||||
@@ -744,7 +744,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Flow', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[512, UniversalYAxisUnit.FLOW_GALLONS_PER_MINUTE, '512 gpm'],
|
||||
[1000, UniversalYAxisUnit.FLOW_GALLONS_PER_MINUTE, '1000 gpm'],
|
||||
[678, UniversalYAxisUnit.FLOW_CUBIC_METERS_PER_SECOND, '678 cms'],
|
||||
@@ -766,7 +766,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Force', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[845, UniversalYAxisUnit.FORCE_NEWTON_METERS, '845 Nm'],
|
||||
[1000, UniversalYAxisUnit.FORCE_NEWTON_METERS, '1 kNm'],
|
||||
[1080, UniversalYAxisUnit.FORCE_NEWTON_METERS, '1.08 kNm'],
|
||||
@@ -782,7 +782,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Mass', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[120, UniversalYAxisUnit.MASS_MILLIGRAM, '120 mg'],
|
||||
[120000, UniversalYAxisUnit.MASS_MILLIGRAM, '120 g'],
|
||||
[987, UniversalYAxisUnit.MASS_GRAM, '987 g'],
|
||||
@@ -796,7 +796,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Length', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[88, UniversalYAxisUnit.LENGTH_MILLIMETER, '88 mm'],
|
||||
[100, UniversalYAxisUnit.LENGTH_MILLIMETER, '100 mm'],
|
||||
[1000, UniversalYAxisUnit.LENGTH_MILLIMETER, '1 m'],
|
||||
@@ -812,7 +812,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Pressure', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[45, UniversalYAxisUnit.PRESSURE_MILLIBAR, '45 mbar'],
|
||||
[1013, UniversalYAxisUnit.PRESSURE_MILLIBAR, '1.01 bar'],
|
||||
[27, UniversalYAxisUnit.PRESSURE_BAR, '27 bar'],
|
||||
@@ -828,7 +828,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Radiation', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[452, UniversalYAxisUnit.RADIATION_BECQUEREL, '452 Bq'],
|
||||
[37, UniversalYAxisUnit.RADIATION_CURIE, '37 Ci'],
|
||||
[128, UniversalYAxisUnit.RADIATION_GRAY, '128 Gy'],
|
||||
@@ -849,7 +849,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Rotation Speed', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[345, UniversalYAxisUnit.ROTATION_SPEED_REVOLUTIONS_PER_MINUTE, '345 rpm'],
|
||||
[789, UniversalYAxisUnit.ROTATION_SPEED_HERTZ, '789 Hz'],
|
||||
[789000, UniversalYAxisUnit.ROTATION_SPEED_HERTZ, '789 kHz'],
|
||||
@@ -861,7 +861,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Temperature', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[37, UniversalYAxisUnit.TEMPERATURE_CELSIUS, '37 °C'],
|
||||
[451, UniversalYAxisUnit.TEMPERATURE_FAHRENHEIT, '451 °F'],
|
||||
[310, UniversalYAxisUnit.TEMPERATURE_KELVIN, '310 K'],
|
||||
@@ -871,7 +871,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Velocity', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[900, UniversalYAxisUnit.VELOCITY_METERS_PER_SECOND, '900 m/s'],
|
||||
[456, UniversalYAxisUnit.VELOCITY_KILOMETERS_PER_HOUR, '456 km/h'],
|
||||
[789, UniversalYAxisUnit.VELOCITY_MILES_PER_HOUR, '789 mph'],
|
||||
@@ -882,7 +882,7 @@ describe('formatUniversalUnit', () => {
|
||||
});
|
||||
|
||||
describe('Volume', () => {
|
||||
test.each([
|
||||
it.each([
|
||||
[1200, UniversalYAxisUnit.VOLUME_MILLILITER, '1.2 L'],
|
||||
[9000000, UniversalYAxisUnit.VOLUME_MILLILITER, '9 kL'],
|
||||
[9, UniversalYAxisUnit.VOLUME_LITER, '9 L'],
|
||||
|
||||
@@ -16,8 +16,8 @@ describe('YAxisUnitSelector utils', () => {
|
||||
|
||||
it('returns null or self for unknown units', () => {
|
||||
expect(mapMetricUnitToUniversalUnit('unknown_unit')).toBe('unknown_unit');
|
||||
expect(mapMetricUnitToUniversalUnit('')).toBe(null);
|
||||
expect(mapMetricUnitToUniversalUnit(undefined)).toBe(null);
|
||||
expect(mapMetricUnitToUniversalUnit('')).toBeNull();
|
||||
expect(mapMetricUnitToUniversalUnit(undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('YAxisUnitSelector utils', () => {
|
||||
},
|
||||
];
|
||||
const mergedCategories = mergeCategories(categories1, categories2);
|
||||
expect(mergedCategories).toEqual([
|
||||
expect(mergedCategories).toStrictEqual([
|
||||
{
|
||||
name: YAxisCategoryNames.Data,
|
||||
units: [
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('CmdKPalette', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders navigation and settings groups and items', () => {
|
||||
it('renders navigation and settings groups and items', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
expect(screen.getByText('Navigation')).toBeInTheDocument();
|
||||
@@ -160,7 +160,7 @@ describe('CmdKPalette', () => {
|
||||
expect(screen.getByText('Switch to Dark Mode')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('clicking a navigation item calls history.push with correct route', async () => {
|
||||
it('clicking a navigation item calls history.push with correct route', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
@@ -170,14 +170,14 @@ describe('CmdKPalette', () => {
|
||||
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
});
|
||||
|
||||
test('role-based filtering (basic smoke)', () => {
|
||||
it('role-based filtering (basic smoke)', () => {
|
||||
render(<CmdKPalette userRole="VIEWER" />);
|
||||
|
||||
// VIEWER still sees basic navigation items
|
||||
expect(screen.getByText(HOME_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('keyboard shortcut opens palette via setOpen', () => {
|
||||
it('keyboard shortcut opens palette via setOpen', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const event = new KeyboardEvent('keydown', { key: 'k', ctrlKey: true });
|
||||
@@ -186,7 +186,7 @@ describe('CmdKPalette', () => {
|
||||
expect(mockSetOpen).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('items render with icons when provided', () => {
|
||||
it('items render with icons when provided', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const iconHolders = document.querySelectorAll('.cmd-item-icon');
|
||||
@@ -194,7 +194,7 @@ describe('CmdKPalette', () => {
|
||||
expect(screen.getByText(HOME_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('closing the palette via handleInvoke sets open to false', async () => {
|
||||
it('closing the palette via handleInvoke sets open to false', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ export enum Events {
|
||||
UPDATE_GRAPH_MANAGER_TABLE = 'UPDATE_GRAPH_MANAGER_TABLE',
|
||||
TABLE_COLUMNS_DATA = 'TABLE_COLUMNS_DATA',
|
||||
SLOW_API_WARNING = 'SLOW_API_WARNING',
|
||||
TOOLTIP_PINNED = 'TOOLTIP_PINNED',
|
||||
TOOLTIP_UNPINNED = 'TOOLTIP_UNPINNED',
|
||||
TOOLTIP_CONTENT_SCROLLED = 'TOOLTIP_CONTENT_SCROLLED',
|
||||
}
|
||||
|
||||
export enum InfraMonitoringEvents {
|
||||
|
||||
@@ -9,4 +9,5 @@ export enum FeatureKeys {
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
ONBOARDING_V3 = 'onboarding_v3',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
BODY_JSON_ENABLED = 'body_json_enabled',
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ const ROUTES = {
|
||||
HOME_PAGE: '/',
|
||||
PUBLIC_DASHBOARD: '/public/dashboard/:dashboardId',
|
||||
SERVICE_ACCOUNTS_SETTINGS: '/settings/service-accounts',
|
||||
MCP_SERVER: '/settings/mcp-server',
|
||||
} as const;
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -157,9 +157,3 @@
|
||||
.view-all-drawer {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.ant-table {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ describe('Alert Channels Settings List page', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
describe('Should display the Alert Channels page properly', () => {
|
||||
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
|
||||
it('Should check if "The alerts will be sent to all the configured channels." is visible', () => {
|
||||
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if "New Alert Channel" Button is visble ', () => {
|
||||
it('Should check if "New Alert Channel" Button is visble', () => {
|
||||
expect(screen.getByText('button_new_channel')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
|
||||
it('Should check if the help icon is visible and displays "tooltip_notification_channels', async () => {
|
||||
const helpIcon = screen.getByLabelText('question-circle');
|
||||
|
||||
fireEvent.mouseOver(helpIcon);
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('Alert Channels Settings List page (Normal User)', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
describe('Should display the Alert Channels page properly', () => {
|
||||
it('Should check if "The alerts will be sent to all the configured channels." is visible ', async () => {
|
||||
it('Should check if "The alerts will be sent to all the configured channels." is visible', async () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('sending_channels_note')).toBeInTheDocument(),
|
||||
);
|
||||
@@ -51,7 +51,7 @@ describe('Alert Channels Settings List page (Normal User)', () => {
|
||||
await waitFor(() => expect(newAlertButton).toBeInTheDocument());
|
||||
expect(newAlertButton).toBeDisabled();
|
||||
});
|
||||
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
|
||||
it('Should check if the help icon is visible and displays "tooltip_notification_channels', async () => {
|
||||
const helpIcon = screen.getByLabelText('question-circle');
|
||||
fireEvent.mouseOver(helpIcon);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('Create Alert Channel', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
|
||||
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel', () => {
|
||||
beforeEach(() => {
|
||||
render(<CreateAlertChannels preType={ChannelType.Slack} />);
|
||||
});
|
||||
@@ -54,13 +54,13 @@ describe('Create Alert Channel', () => {
|
||||
it('Should check if the title is "New Notification Channels"', () => {
|
||||
expect(screen.getByText('page_title_create')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||
it('Should check if the name label and textbox are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_channel_name',
|
||||
testId: 'channel-name-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_send_resolved',
|
||||
testId: 'field-send-resolved-checkbox',
|
||||
@@ -76,13 +76,13 @@ describe('Create Alert Channel', () => {
|
||||
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
|
||||
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||
it('Should check if Recepient label, input, and help text are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_recipient',
|
||||
testId: 'slack-channel-textbox',
|
||||
@@ -90,7 +90,7 @@ describe('Create Alert Channel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Title label and text area are displayed properly ', () => {
|
||||
it('Should check if Title label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_title',
|
||||
testId: 'title-textarea',
|
||||
@@ -101,7 +101,7 @@ describe('Create Alert Channel', () => {
|
||||
|
||||
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||
});
|
||||
it('Should check if Description label and text area are displayed properly ', () => {
|
||||
it('Should check if Description label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_description',
|
||||
testId: 'description-textarea',
|
||||
@@ -177,13 +177,13 @@ describe('Create Alert Channel', () => {
|
||||
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
|
||||
expect(screen.getByText('Webhook')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
|
||||
it('Should check if Webhook User Name label, input, and help text are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_username',
|
||||
testId: 'webhook-username-textbox',
|
||||
@@ -321,7 +321,7 @@ describe('Create Alert Channel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Message contains the default template ', () => {
|
||||
it('Should check if Message contains the default template', () => {
|
||||
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
|
||||
|
||||
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
|
||||
@@ -387,14 +387,14 @@ describe('Create Alert Channel', () => {
|
||||
expect(screen.getByText('Microsoft Teams')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Title label and text area are displayed properly ', () => {
|
||||
it('Should check if Title label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_title',
|
||||
testId: 'title-textarea',
|
||||
@@ -406,7 +406,7 @@ describe('Create Alert Channel', () => {
|
||||
|
||||
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||
});
|
||||
it('Should check if Description label and text area are displayed properly ', () => {
|
||||
it('Should check if Description label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_description',
|
||||
testId: 'description-textarea',
|
||||
|
||||
@@ -23,20 +23,20 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
|
||||
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel', () => {
|
||||
beforeEach(() => {
|
||||
render(<CreateAlertChannels preType={ChannelType.Slack} />);
|
||||
});
|
||||
it('Should check if the title is "New Notification Channels"', () => {
|
||||
expect(screen.getByText('page_title_create')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||
it('Should check if the name label and textbox are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_channel_name',
|
||||
testId: 'channel-name-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_send_resolved',
|
||||
testId: 'field-send-resolved-checkbox',
|
||||
@@ -52,13 +52,13 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
|
||||
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||
it('Should check if Recepient label, input, and help text are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_recipient',
|
||||
testId: 'slack-channel-textbox',
|
||||
@@ -66,7 +66,7 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Title label and text area are displayed properly ', () => {
|
||||
it('Should check if Title label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_title',
|
||||
testId: 'title-textarea',
|
||||
@@ -77,7 +77,7 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
|
||||
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||
});
|
||||
it('Should check if Description label and text area are displayed properly ', () => {
|
||||
it('Should check if Description label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_description',
|
||||
testId: 'description-textarea',
|
||||
@@ -103,13 +103,13 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
|
||||
expect(screen.getByText('Webhook')).toBeInTheDocument();
|
||||
});
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
});
|
||||
});
|
||||
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
|
||||
it('Should check if Webhook User Name label, input, and help text are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_username',
|
||||
testId: 'webhook-username-textbox',
|
||||
@@ -247,7 +247,7 @@ describe('Create Alert Channel (Normal User)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Message contains the default template ', () => {
|
||||
it('Should check if Message contains the default template', () => {
|
||||
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
|
||||
|
||||
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
|
||||
|
||||
@@ -24,7 +24,7 @@ jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
|
||||
}));
|
||||
|
||||
describe('Should check if the edit alert channel is properly displayed ', () => {
|
||||
describe('Should check if the edit alert channel is properly displayed', () => {
|
||||
beforeEach(() => {
|
||||
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);
|
||||
});
|
||||
@@ -35,14 +35,14 @@ describe('Should check if the edit alert channel is properly displayed ', () =>
|
||||
expect(screen.getByText('page_title_edit')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should check if the name label and textbox are displayed properly ', () => {
|
||||
it('Should check if the name label and textbox are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_channel_name',
|
||||
testId: 'channel-name-textbox',
|
||||
value: 'Dummy-Channel',
|
||||
});
|
||||
});
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly and the checkbox is checked ', () => {
|
||||
it('Should check if Send resolved alerts label and checkbox are displayed properly and the checkbox is checked', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_send_resolved',
|
||||
testId: 'field-send-resolved-checkbox',
|
||||
@@ -61,7 +61,7 @@ describe('Should check if the edit alert channel is properly displayed ', () =>
|
||||
expect(screen.getByText('Slack')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should check if Webhook URL label and input are displayed properly ', () => {
|
||||
it('Should check if Webhook URL label and input are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_webhook_url',
|
||||
testId: 'webhook-url-textbox',
|
||||
@@ -70,7 +70,7 @@ describe('Should check if the edit alert channel is properly displayed ', () =>
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
|
||||
it('Should check if Recepient label, input, and help text are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_recipient',
|
||||
testId: 'slack-channel-textbox',
|
||||
@@ -79,7 +79,7 @@ describe('Should check if the edit alert channel is properly displayed ', () =>
|
||||
});
|
||||
});
|
||||
|
||||
it('Should check if Title label and text area are displayed properly ', () => {
|
||||
it('Should check if Title label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_title',
|
||||
testId: 'title-textarea',
|
||||
@@ -92,7 +92,7 @@ describe('Should check if the edit alert channel is properly displayed ', () =>
|
||||
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
|
||||
});
|
||||
|
||||
it('Should check if Description label and text area are displayed properly ', () => {
|
||||
it('Should check if Description label and text area are displayed properly', () => {
|
||||
testLabelInputAndHelpValue({
|
||||
labelText: 'field_slack_description',
|
||||
testId: 'description-textarea',
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from './utils';
|
||||
|
||||
describe('Error utils', () => {
|
||||
test('Valid OrderBy Params', () => {
|
||||
it('Valid OrderBy Params', () => {
|
||||
expect(isOrderParams('serviceName')).toBe(true);
|
||||
expect(isOrderParams('exceptionCount')).toBe(true);
|
||||
expect(isOrderParams('lastSeen')).toBe(true);
|
||||
@@ -20,24 +20,24 @@ describe('Error utils', () => {
|
||||
expect(isOrderParams('exceptionType')).toBe(true);
|
||||
});
|
||||
|
||||
test('Invalid OrderBy Params', () => {
|
||||
it('Invalid OrderBy Params', () => {
|
||||
expect(isOrderParams('invalid')).toBe(false);
|
||||
expect(isOrderParams(null)).toBe(false);
|
||||
expect(isOrderParams('')).toBe(false);
|
||||
});
|
||||
|
||||
test('Valid Order', () => {
|
||||
it('Valid Order', () => {
|
||||
expect(isOrder('ascending')).toBe(true);
|
||||
expect(isOrder('descending')).toBe(true);
|
||||
});
|
||||
|
||||
test('Invalid Order', () => {
|
||||
it('Invalid Order', () => {
|
||||
expect(isOrder('invalid')).toBe(false);
|
||||
expect(isOrder(null)).toBe(false);
|
||||
expect(isOrder('')).toBe(false);
|
||||
});
|
||||
|
||||
test('Default Order', () => {
|
||||
it('Default Order', () => {
|
||||
const OrderBy: OrderBy[] = [
|
||||
'exceptionCount',
|
||||
'exceptionType',
|
||||
@@ -57,7 +57,7 @@ describe('Error utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Limit', () => {
|
||||
it('Limit', () => {
|
||||
expect(getLimit(null)).toBe(10);
|
||||
expect(getLimit('')).toBe(10);
|
||||
expect(getLimit('0')).toBe(0);
|
||||
@@ -68,7 +68,7 @@ describe('Error utils', () => {
|
||||
expect(getLimit('101')).toBe(101);
|
||||
});
|
||||
|
||||
test('Update Page Size', () => {
|
||||
it('Update Page Size', () => {
|
||||
expect(getUpdatePageSize(null)).toBe(10);
|
||||
expect(getUpdatePageSize('')).toBe(10);
|
||||
expect(getUpdatePageSize('0')).toBe(0);
|
||||
@@ -79,7 +79,7 @@ describe('Error utils', () => {
|
||||
expect(getUpdatePageSize('101')).toBe(101);
|
||||
});
|
||||
|
||||
test('Order Params', () => {
|
||||
it('Order Params', () => {
|
||||
expect(getOrderParams(null)).toBe('serviceName');
|
||||
expect(getOrderParams('')).toBe('serviceName');
|
||||
expect(getOrderParams('serviceName')).toBe('serviceName');
|
||||
@@ -89,7 +89,7 @@ describe('Error utils', () => {
|
||||
expect(getOrderParams('exceptionType')).toBe('exceptionType');
|
||||
});
|
||||
|
||||
test('OffSet', () => {
|
||||
it('OffSet', () => {
|
||||
expect(getOffSet(null)).toBe(0);
|
||||
expect(getOffSet('')).toBe(0);
|
||||
expect(getOffSet('0')).toBe(0);
|
||||
@@ -100,7 +100,7 @@ describe('Error utils', () => {
|
||||
expect(getOffSet('101')).toBe(101);
|
||||
});
|
||||
|
||||
test('Order', () => {
|
||||
it('Order', () => {
|
||||
expect(getOrder(null)).toBe('ascending');
|
||||
expect(getOrder('')).toBe('ascending');
|
||||
expect(getOrder('ascending')).toBe('ascending');
|
||||
|
||||
@@ -30,18 +30,18 @@ function UplotChart({
|
||||
|
||||
useEffect(() => {
|
||||
if (plotInstance.current) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
plotInstance.current.destroy();
|
||||
}
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
plotInstance.current = new uPlot(options, data, chartRef.current);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
if (plotInstance.current) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
plotInstance.current.destroy();
|
||||
}
|
||||
};
|
||||
@@ -275,7 +275,7 @@ function AnomalyAlertEvaluationView({
|
||||
};
|
||||
|
||||
const handleSearchValueChange = useDebouncedFn((event): void => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const value = event?.target?.value || '';
|
||||
|
||||
handleSearch(value);
|
||||
|
||||
@@ -233,7 +233,7 @@ describe('DomainMetrics - V5 Query Payload Tests', () => {
|
||||
// Wait for skeletons to disappear
|
||||
await waitFor(() => {
|
||||
const skeletons = document.querySelectorAll('.ant-skeleton-button');
|
||||
expect(skeletons.length).toBe(0);
|
||||
expect(skeletons).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Verify all metric labels are displayed
|
||||
@@ -272,7 +272,7 @@ describe('DomainMetrics - V5 Query Payload Tests', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const skeletons = document.querySelectorAll('.ant-skeleton-button');
|
||||
expect(skeletons.length).toBe(0);
|
||||
expect(skeletons).toHaveLength(0);
|
||||
});
|
||||
|
||||
// When no data, all values should show "-"
|
||||
|
||||
@@ -303,7 +303,7 @@ describe('EndPointMetrics - V5 Query Payload Tests', () => {
|
||||
// Wait for skeletons to disappear
|
||||
await waitFor(() => {
|
||||
const skeletons = document.querySelectorAll('.ant-skeleton-button');
|
||||
expect(skeletons.length).toBe(0);
|
||||
expect(skeletons).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Verify all metric labels are displayed
|
||||
@@ -342,7 +342,7 @@ describe('EndPointMetrics - V5 Query Payload Tests', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const skeletons = document.querySelectorAll('.ant-skeleton-button');
|
||||
expect(skeletons.length).toBe(0);
|
||||
expect(skeletons).toHaveLength(0);
|
||||
});
|
||||
|
||||
// When no data, all values should show "-"
|
||||
|
||||
@@ -226,54 +226,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.api-quick-filter-left-section {
|
||||
.api-quick-filters-header {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.api-module-right-section {
|
||||
.toolbar {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.no-filtered-domains-message-container {
|
||||
.no-filtered-domains-message-content {
|
||||
.no-filtered-domains-message {
|
||||
.no-domain-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.no-domain-subtitle {
|
||||
color: var(--l2-foreground);
|
||||
|
||||
.attribute {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.api-monitoring-domain-list-table {
|
||||
.ant-table {
|
||||
.ant-table-cell {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.table-row-light {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.table-row-dark {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.round-metric-tag {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ describe('API Monitoring Utils', () => {
|
||||
const result = extractPortAndEndpoint(url);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
port: '8080',
|
||||
endpoint: '/api/endpoint?param=value',
|
||||
});
|
||||
@@ -317,7 +317,7 @@ describe('API Monitoring Utils', () => {
|
||||
const result = extractPortAndEndpoint(url);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
port: '-',
|
||||
endpoint: '/api/endpoint',
|
||||
});
|
||||
@@ -331,7 +331,7 @@ describe('API Monitoring Utils', () => {
|
||||
const result = extractPortAndEndpoint(nonUrl);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
expect(result).toStrictEqual({
|
||||
port: '-',
|
||||
endpoint: nonUrl,
|
||||
});
|
||||
@@ -379,7 +379,7 @@ describe('API Monitoring Utils', () => {
|
||||
const result = getFormattedEndPointDropDownData([]);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle undefined input', () => {
|
||||
@@ -392,7 +392,7 @@ describe('API Monitoring Utils', () => {
|
||||
// Assert
|
||||
// If the implementation doesn't handle undefined, just check that it returns something predictable
|
||||
// Based on the error, it seems the function returns undefined for undefined input
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle items without URL path', () => {
|
||||
@@ -460,7 +460,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result).toHaveLength(2);
|
||||
|
||||
// Check first item
|
||||
expect(result[0].statusCode).toBe('200');
|
||||
@@ -493,7 +493,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(1);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].statusCode).toBe('-');
|
||||
expect(result[0].count).toBe('-');
|
||||
expect(result[0].p99Latency).toBe('-');
|
||||
@@ -506,7 +506,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle undefined input', () => {
|
||||
@@ -518,7 +518,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle mixed status code formats and preserve order', () => {
|
||||
@@ -555,7 +555,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(3);
|
||||
expect(result).toHaveLength(3);
|
||||
|
||||
// Check order preservation - should maintain the same order as input
|
||||
expect(result[0].statusCode).toBe('404');
|
||||
@@ -675,7 +675,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.data.result).toEqual([]);
|
||||
expect(result.data.result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty result array', () => {
|
||||
@@ -695,7 +695,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.data.result).toEqual([]);
|
||||
expect(result.data.result).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -779,7 +779,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result).toHaveLength(2);
|
||||
|
||||
// Should have two filters, one for >= start code and one for <= end code
|
||||
const startRangeFilter = result.find((item) => item.op === '>=');
|
||||
@@ -811,7 +811,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result).toHaveLength(2);
|
||||
|
||||
const startRangeFilter = result.find((item) => item.op === '>=');
|
||||
const endRangeFilter = result.find((item) => item.op === '<=');
|
||||
@@ -832,7 +832,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty metric object', () => {
|
||||
@@ -841,7 +841,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle metric without response_status_code', () => {
|
||||
@@ -855,7 +855,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('should handle unsupported status code range', () => {
|
||||
@@ -869,7 +869,7 @@ describe('API Monitoring Utils', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBe(2);
|
||||
expect(result).toHaveLength(2);
|
||||
|
||||
// Should still have two filters
|
||||
const startRangeFilter = result.find((item) => item.op === '>=');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user